From bd2a1d5574a393b57e23d53ba12eadce33091080 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 28 Apr 2021 15:22:13 -0400 Subject: [PATCH] Add support for lower APIs to new notification system. --- app/build.gradle | 4 + .../NotificationCancellationHelper.java | 2 +- .../notifications/v2/MessageNotifierV2.kt | 93 ++++++--- .../notifications/v2/NotificationBuilder.kt | 17 +- .../v2/NotificationConversation.kt | 22 +-- .../notifications/v2/NotificationFactory.kt | 182 +++++++++++++----- .../notifications/v2/NotificationStateV2.kt | 4 + .../securesms/util/FeatureFlags.java | 2 +- .../org/signal/lint/SignalLogDetector.java | 3 +- .../java/org/signal/lint/LogDetectorTest.java | 21 ++ 10 files changed, 238 insertions(+), 112 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a01b2fdc32..9b58d11006 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,6 +80,10 @@ android { flavorDimensions 'distribution', 'environment' useLibrary 'org.apache.http.legacy' + kotlinOptions { + freeCompilerArgs = ["-Xallow-result-return-type"] + } + dexOptions { javaMaxHeapSize "4g" } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java index 802efdd41d..37902569cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java @@ -79,7 +79,7 @@ public final class NotificationCancellationHelper { } public static void cancelMessageSummaryIfSoleNotification(@NonNull Context context) { - if (Build.VERSION.SDK_INT >= 23) { + if (Build.VERSION.SDK_INT > 23) { try { NotificationManager notifications = ServiceUtil.getNotificationManager(context); StatusBarNotification[] activeNotifications = notifications.getActiveNotifications(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt index c15ff264d1..a50ff86a8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt @@ -14,6 +14,7 @@ import me.leolin.shortcutbadger.ShortcutBadger import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.database.MessageDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.messages.IncomingMessageObserver import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier @@ -29,6 +30,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder import org.whispersystems.signalservice.internal.util.Util +import java.lang.UnsupportedOperationException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor import java.util.concurrent.Executors @@ -125,9 +127,26 @@ class MessageNotifierV2(context: Application) : MessageNotifier { } Log.internal().i(TAG, "sticky thread: $stickyThreads") - val state: NotificationStateV2 = NotificationStateProvider.constructNotificationState(context, stickyThreads) + var state: NotificationStateV2 = NotificationStateProvider.constructNotificationState(context, stickyThreads) Log.internal().i(TAG, "state: $state") + val displayedNotifications: Set? = ServiceUtil.getNotificationManager(context).getDisplayedNotificationIds().getOrNull() + if (displayedNotifications != null) { + val cleanedUpThreadIds: MutableSet = mutableSetOf() + state.conversations.filterNot { it.hasNewNotifications() || displayedNotifications.contains(it.notificationId) } + .forEach { conversation -> + cleanedUpThreadIds += conversation.threadId + conversation.notificationItems.forEach { item -> + val messageDatabase: MessageDatabase = if (item.isMms) DatabaseFactory.getMmsDatabase(context) else DatabaseFactory.getSmsDatabase(context) + messageDatabase.markAsNotified(item.id) + } + } + if (cleanedUpThreadIds.isNotEmpty()) { + Log.i(TAG, "Cleaned up ${cleanedUpThreadIds.size} thread(s) with dangling notifications") + state = NotificationStateV2(state.conversations.filterNot { cleanedUpThreadIds.contains(it.threadId) }) + } + } + val retainStickyThreadIds: Set = state.getThreadsWithMostRecentNotificationFromSelf() stickyThreads.keys.retainAll { retainStickyThreadIds.contains(it) } @@ -148,7 +167,6 @@ class MessageNotifierV2(context: Application) : MessageNotifier { targetThreadId = threadId, defaultBubbleState = defaultBubbleState, lastAudibleNotification = lastAudibleNotification, - notificationConfigurationChanged = notificationConfigurationChanged, alertOverrides = alertOverrides ) @@ -238,8 +256,8 @@ class MessageNotifierV2(context: Application) : MessageNotifier { } companion object { - private val TAG = Log.tag(MessageNotifierV2::class.java) - private val REMINDER_TIMEOUT = TimeUnit.MINUTES.toMillis(2) + val TAG: String = Log.tag(MessageNotifierV2::class.java) + private val REMINDER_TIMEOUT: Long = TimeUnit.MINUTES.toMillis(2) private fun updateBadge(context: Context, count: Int) { try { @@ -250,36 +268,51 @@ class MessageNotifierV2(context: Application) : MessageNotifier { } } - private fun NotificationManager.cancelOrphanedNotifications(context: Context, state: NotificationStateV2, stickyNotifications: Set) { - if (Build.VERSION.SDK_INT < 23) { - return - } - - try { - for (notification: StatusBarNotification in activeNotifications) { - if (notification.id != NotificationIds.MESSAGE_SUMMARY && - notification.id != KeyCachingService.SERVICE_RUNNING_ID && - notification.id != IncomingMessageObserver.FOREGROUND_ID && - notification.id != NotificationIds.PENDING_MESSAGES && - !CallNotificationBuilder.isWebRtcNotification(notification.id) && - !stickyNotifications.contains(notification.id) - ) { - if (!state.notificationIds.contains(notification.id)) { - Log.d(TAG, "Cancelling orphaned notification: ${notification.id}") - NotificationCancellationHelper.cancel(context, notification.id) - } - } - } - NotificationCancellationHelper.cancelMessageSummaryIfSoleNotification(context) - } catch (e: Throwable) { - Log.w(TAG, e) - } - } - data class StickyThread(val threadId: Long, val notificationId: Int, val earliestTimestamp: Long) private data class Reminder(val lastNotified: Long, val count: Int = 0) } +private fun StatusBarNotification.isMessageNotification(): Boolean { + return id != NotificationIds.MESSAGE_SUMMARY && + id != KeyCachingService.SERVICE_RUNNING_ID && + id != IncomingMessageObserver.FOREGROUND_ID && + id != NotificationIds.PENDING_MESSAGES && + !CallNotificationBuilder.isWebRtcNotification(id) +} + +private fun NotificationManager.getDisplayedNotificationIds(): Result> { + if (Build.VERSION.SDK_INT < 23) { + return Result.failure(UnsupportedOperationException("SDK level too low")) + } + + return try { + Result.success(activeNotifications.filter { it.isMessageNotification() }.map { it.id }.toSet()) + } catch (e: Throwable) { + Log.w(MessageNotifierV2.TAG, e) + Result.failure(e) + } +} + +private fun NotificationManager.cancelOrphanedNotifications(context: Context, state: NotificationStateV2, stickyNotifications: Set) { + if (Build.VERSION.SDK_INT < 23) { + return + } + + try { + activeNotifications.filter { it.isMessageNotification() && !stickyNotifications.contains(it.id) } + .map { it.id } + .filterNot { state.notificationIds.contains(it) } + .forEach { id -> + Log.d(MessageNotifierV2.TAG, "Cancelling orphaned notification: $id") + NotificationCancellationHelper.cancel(context, id) + } + + NotificationCancellationHelper.cancelMessageSummaryIfSoleNotification(context) + } catch (e: Throwable) { + Log.w(MessageNotifierV2.TAG, e) + } +} + private class CancelableExecutor { private val executor: Executor = Executors.newSingleThreadExecutor() private val tasks: MutableSet = mutableSetOf() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt index a1d82c5c8f..99ad3f2b32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt @@ -332,10 +332,18 @@ sealed class NotificationBuilder(protected val context: Context) { } override fun setGroup(group: String) { + if (Build.VERSION.SDK_INT < 23) { + return + } + builder.setGroup(group) } override fun setGroupAlertBehavior(behavior: Int) { + if (Build.VERSION.SDK_INT < 23) { + return + } + builder.setGroupAlertBehavior(behavior) } @@ -479,15 +487,6 @@ sealed class NotificationBuilder(protected val context: Context) { } override fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean) { - val bigPictureUri: Uri? = conversation.getSlideBigPictureUri(context) - if (bigPictureUri != null) { - builder.style = Notification.BigPictureStyle() - .bigPicture(bigPictureUri.toBitmap(context, BIG_PICTURE_DIMEN)) - .setSummaryText(conversation.getContentText(context)) - .bigLargeIcon(null as Bitmap?) - return - } - val self: Person = Person.Builder() .setBot(false) .setName(Recipient.self().getDisplayName(context)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt index 3e07b5d645..b2768af151 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.notifications.v2 import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.net.Uri import android.text.SpannableStringBuilder @@ -25,13 +24,11 @@ import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.Util -private const val LARGE_ICON_DIMEN = 250 - /** * Encapsulate all the notifications for a given conversation (thread) and the top * level information about said conversation. */ -class NotificationConversation( +data class NotificationConversation( val recipient: Recipient, val threadId: Long, val notificationItems: List @@ -51,18 +48,7 @@ class NotificationConversation( } } - fun getLargeIcon(context: Context): Bitmap? { - if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayMessage) { - val largeIconUri: Uri? = getSlideLargeIcon() - if (largeIconUri != null) { - return largeIconUri.toBitmap(context, LARGE_ICON_DIMEN) - } - } - - return getContactLargeIcon(context).toLargeBitmap(context) - } - - private fun getContactLargeIcon(context: Context): Drawable? { + fun getContactLargeIcon(context: Context): Drawable? { return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) { recipient.getContactDrawable(context) } else { @@ -78,10 +64,6 @@ class NotificationConversation( } } - private fun getSlideLargeIcon(): Uri? { - return if (notificationItems.size == 1) mostRecentNotification.getLargeIconUri() else null - } - fun getSlideBigPictureUri(context: Context): Uri? { return if (notificationItems.size == 1 && TextSecurePreferences.getNotificationPrivacy(context).isDisplayMessage && !KeyCachingService.isLocked(context)) { mostRecentNotification.getBigPictureUri() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt index 9b3298f280..e28abf0c24 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.notifications.v2 +import android.annotation.TargetApi import android.app.Notification import android.app.NotificationManager import android.app.PendingIntent @@ -44,67 +45,114 @@ object NotificationFactory { targetThreadId: Long, defaultBubbleState: BubbleUtil.BubbleState, lastAudibleNotification: Long, - notificationConfigurationChanged: Boolean, alertOverrides: Set ): Set { if (state.isEmpty) { Log.d(TAG, "State is empty, bailing") return emptySet() } + + val nonVisibleThreadCount = state.conversations.count { it.threadId != visibleThreadId } + return if (Build.VERSION.SDK_INT < 23) { + notify19( + context = context, + state = state, + visibleThreadId = visibleThreadId, + targetThreadId = targetThreadId, + defaultBubbleState = defaultBubbleState, + lastAudibleNotification = lastAudibleNotification, + alertOverrides = alertOverrides, + nonVisibleThreadCount = nonVisibleThreadCount + ) + } else { + notify23( + context = context, + state = state, + visibleThreadId = visibleThreadId, + targetThreadId = targetThreadId, + defaultBubbleState = defaultBubbleState, + lastAudibleNotification = lastAudibleNotification, + alertOverrides = alertOverrides, + nonVisibleThreadCount = nonVisibleThreadCount + ) + } + } + + private fun notify19( + context: Context, + state: NotificationStateV2, + visibleThreadId: Long, + targetThreadId: Long, + defaultBubbleState: BubbleUtil.BubbleState, + lastAudibleNotification: Long, + alertOverrides: Set, + nonVisibleThreadCount: Int + ): Set { val threadsThatNewlyAlerted: MutableSet = mutableSetOf() - if (Build.VERSION.SDK_INT >= 23 || state.conversations.size == 1) { - state.conversations.forEach { conversation -> - if (conversation.threadId == visibleThreadId && conversation.hasNewNotifications()) { - Log.internal().i(TAG, "Thread is visible, notifying in thread. notificationId: ${conversation.notificationId}") - notifyInThread(context, conversation.recipient, lastAudibleNotification) - } else if (notificationConfigurationChanged || conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId)) { - - if (conversation.hasNewNotifications()) { - threadsThatNewlyAlerted += conversation.threadId - } - - notifyForConversation( - context = context, - conversation = conversation, - targetThreadId = targetThreadId, - defaultBubbleState = defaultBubbleState, - shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId)) && !conversation.mostRecentNotification.individualRecipient.isSelf - ) - } + state.conversations.find { it.threadId == visibleThreadId }?.let { conversation -> + if (conversation.hasNewNotifications()) { + Log.internal().i(TAG, "Thread is visible, notifying in thread. notificationId: ${conversation.notificationId}") + notifyInThread(context, conversation.recipient, lastAudibleNotification) } } - if ((state.conversations.size > 1 && threadsThatNewlyAlerted.isNotEmpty()) || ServiceUtil.getNotificationManager(context).isDisplayingSummaryNotification()) { - val builder: NotificationBuilder = NotificationBuilder.create(context) - - builder.apply { - setSmallIcon(R.drawable.ic_notification) - setColor(ContextCompat.getColor(context, R.color.core_ultramarine)) - setCategory(NotificationCompat.CATEGORY_MESSAGE) - setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP) - setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) - setChannelId(NotificationChannels.getMessagesChannel(context)) - setContentTitle(context.getString(R.string.app_name)) - setContentIntent(PendingIntent.getActivity(context, 0, MainActivity.clearTop(context), 0)) - setGroupSummary(true) - setSubText(context.getString(R.string.MessageNotifier_d_new_messages_in_d_conversations, state.messageCount, state.threadCount)) - setContentInfo(state.messageCount.toString()) - setNumber(state.messageCount) - setSummaryContentText(state.mostRecentSender) - setDeleteIntent(state.getDeleteIntent(context)) - setWhen(state.mostRecentNotification) - addMarkAsReadAction(state) - addMessages(state) - setOnlyAlertOnce(!state.notificationItems.any { it.isNewNotification }) - setPriority(TextSecurePreferences.getNotificationPriority(context)) - setLights() - setAlarms(state.mostRecentSender) - setTicker(state.mostRecentNotification.getStyledPrimaryText(context, true)) + if (nonVisibleThreadCount == 1) { + state.conversations.first { it.threadId != visibleThreadId }.let { conversation -> + notifyForConversation( + context = context, + conversation = conversation, + targetThreadId = targetThreadId, + defaultBubbleState = defaultBubbleState, + shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId)) && !conversation.mostRecentNotification.individualRecipient.isSelf + ) + if (conversation.hasNewNotifications()) { + threadsThatNewlyAlerted += conversation.threadId + } } + } else if (nonVisibleThreadCount > 1) { + val nonVisibleConversations: List = state.getNonVisibleConversation(visibleThreadId) + threadsThatNewlyAlerted += nonVisibleConversations.filter { it.hasNewNotifications() }.map { it.threadId } + notifySummary(context = context, state = state.copy(conversations = nonVisibleConversations)) + } - Log.d(TAG, "showing summary notification") - NotificationManagerCompat.from(context).safelyNotify(context, null, NotificationIds.MESSAGE_SUMMARY, builder.build()) + return threadsThatNewlyAlerted + } + + @TargetApi(23) + private fun notify23( + context: Context, + state: NotificationStateV2, + visibleThreadId: Long, + targetThreadId: Long, + defaultBubbleState: BubbleUtil.BubbleState, + lastAudibleNotification: Long, + alertOverrides: Set, + nonVisibleThreadCount: Int + ): Set { + val threadsThatNewlyAlerted: MutableSet = mutableSetOf() + + state.conversations.forEach { conversation -> + if (conversation.threadId == visibleThreadId && conversation.hasNewNotifications()) { + Log.internal().i(TAG, "Thread is visible, notifying in thread. notificationId: ${conversation.notificationId}") + notifyInThread(context, conversation.recipient, lastAudibleNotification) + } else { + if (conversation.hasNewNotifications()) { + threadsThatNewlyAlerted += conversation.threadId + } + + notifyForConversation( + context = context, + conversation = conversation, + targetThreadId = targetThreadId, + defaultBubbleState = defaultBubbleState, + shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId)) && !conversation.mostRecentNotification.individualRecipient.isSelf + ) + } + } + + if (nonVisibleThreadCount > 1 || ServiceUtil.getNotificationManager(context).isDisplayingSummaryNotification()) { + notifySummary(context = context, state = state.copy(conversations = state.getNonVisibleConversation(visibleThreadId))) } return threadsThatNewlyAlerted @@ -127,7 +175,7 @@ object NotificationFactory { setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) setChannelId(conversation.getChannelId(context)) setContentTitle(conversation.getContentTitle(context)) - setLargeIcon(conversation.getLargeIcon(context)) + setLargeIcon(conversation.getContactLargeIcon(context).toLargeBitmap(context)) addPerson(conversation.recipient) setShortcutId(ConversationUtil.getShortcutId(conversation.recipient)) setContentInfo(conversation.messageCount.toString()) @@ -151,7 +199,41 @@ object NotificationFactory { builder.addTurnOffJoinedNotificationsAction(conversation.getTurnOffJoinedNotificationsIntent(context)) } - NotificationManagerCompat.from(context).safelyNotify(context, conversation.recipient, conversation.notificationId, builder.build()) + val notificationId: Int = if (Build.VERSION.SDK_INT < 23) NotificationIds.MESSAGE_SUMMARY else conversation.notificationId + + NotificationManagerCompat.from(context).safelyNotify(context, conversation.recipient, notificationId, builder.build()) + } + + private fun notifySummary(context: Context, state: NotificationStateV2) { + val builder: NotificationBuilder = NotificationBuilder.create(context) + + builder.apply { + setSmallIcon(R.drawable.ic_notification) + setColor(ContextCompat.getColor(context, R.color.core_ultramarine)) + setCategory(NotificationCompat.CATEGORY_MESSAGE) + setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP) + setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) + setChannelId(NotificationChannels.getMessagesChannel(context)) + setContentTitle(context.getString(R.string.app_name)) + setContentIntent(PendingIntent.getActivity(context, 0, MainActivity.clearTop(context), 0)) + setGroupSummary(true) + setSubText(context.getString(R.string.MessageNotifier_d_new_messages_in_d_conversations, state.messageCount, state.threadCount)) + setContentInfo(state.messageCount.toString()) + setNumber(state.messageCount) + setSummaryContentText(state.mostRecentSender) + setDeleteIntent(state.getDeleteIntent(context)) + setWhen(state.mostRecentNotification) + addMarkAsReadAction(state) + addMessages(state) + setOnlyAlertOnce(!state.notificationItems.any { it.isNewNotification }) + setPriority(TextSecurePreferences.getNotificationPriority(context)) + setLights() + setAlarms(state.mostRecentSender) + setTicker(state.mostRecentNotification.getStyledPrimaryText(context, true)) + } + + Log.d(TAG, "showing summary notification") + NotificationManagerCompat.from(context).safelyNotify(context, null, NotificationIds.MESSAGE_SUMMARY, builder.build()) } private fun notifyInThread(context: Context, recipient: Recipient, lastAudibleNotification: Long) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt index 1469a23d9e..e969b01192 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt @@ -39,6 +39,10 @@ data class NotificationStateV2(val conversations: List val mostRecentSender: Recipient get() = mostRecentNotification.individualRecipient + fun getNonVisibleConversation(visibleThreadId: Long): List { + return conversations.filterNot { it.threadId == visibleThreadId } + } + fun getDeleteIntent(context: Context): PendingIntent? { val ids = LongArray(messageCount) val mms = BooleanArray(ids.size) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index e702f7191a..2a70344ea5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -343,7 +343,7 @@ public final class FeatureFlags { /** Whether or not to use the new notification system. */ public static boolean useNewNotificationSystem() { - return getBoolean(NOTIFICATION_REWRITE, false) && Build.VERSION.SDK_INT >= 26; + return Build.VERSION.SDK_INT >= 26 || getBoolean(NOTIFICATION_REWRITE, false); } public static boolean mp4GifSendSupport() { diff --git a/lintchecks/src/main/java/org/signal/lint/SignalLogDetector.java b/lintchecks/src/main/java/org/signal/lint/SignalLogDetector.java index 8c62733673..b45796ddda 100644 --- a/lintchecks/src/main/java/org/signal/lint/SignalLogDetector.java +++ b/lintchecks/src/main/java/org/signal/lint/SignalLogDetector.java @@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.uast.UCallExpression; import org.jetbrains.uast.UExpression; import org.jetbrains.uast.java.JavaUSimpleNameReferenceExpression; +import org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression; import org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression; import java.util.Arrays; @@ -74,7 +75,7 @@ public final class SignalLogDetector extends Detector implements Detector.UastSc if (evaluator.isMemberInClass(method, "org.signal.core.util.logging.Log")) { List arguments = call.getValueArguments(); UExpression tag = arguments.get(0); - if (!(tag instanceof JavaUSimpleNameReferenceExpression || tag instanceof KotlinUSimpleReferenceExpression)) { + if (!(tag instanceof JavaUSimpleNameReferenceExpression || tag instanceof KotlinUSimpleReferenceExpression || tag instanceof KotlinUQualifiedReferenceExpression)) { context.report(INLINE_TAG, call, context.getLocation(call), "Not using a tag constant"); } } diff --git a/lintchecks/src/test/java/org/signal/lint/LogDetectorTest.java b/lintchecks/src/test/java/org/signal/lint/LogDetectorTest.java index 0a6b9c87c5..d91a19bc2b 100644 --- a/lintchecks/src/test/java/org/signal/lint/LogDetectorTest.java +++ b/lintchecks/src/test/java/org/signal/lint/LogDetectorTest.java @@ -152,6 +152,27 @@ public final class LogDetectorTest { .expectClean(); } + @Test + public void log_uses_tag_companion_kotlin() { + lint() + .files(appLogStub, + kotlin("package foo\n" + + "import org.signal.core.util.logging.Log\n" + + "class Example {\n" + + " companion object { val TAG: String = Log.tag(Example::class.java) }\n" + + " fun log() {\n" + + " Log.d(TAG, \"msg\")\n" + + " }\n" + + "}\n"+ + "fun logOutsie() {\n" + + " Log.d(Example.TAG, \"msg\")\n" + + "}\n") + ) + .issues(SignalLogDetector.INLINE_TAG) + .run() + .expectClean(); + } + @Test public void log_uses_inline_tag() { lint()