diff options
| author | 2023-05-19 15:09:38 -0500 | |
|---|---|---|
| committer | 2023-05-22 15:15:24 -0500 | |
| commit | 0ea0f35dce5e3ecdddbe2a93caaecb2b1c394b78 (patch) | |
| tree | 78f65402dd22cd0d751fb5f19a45446247a37a66 | |
| parent | 6c0a668d51984eec8d718b5169732269e2b7ea99 (diff) | |
Always use placeholder for blank media titles
Removes the behavior where apps targeting U would have their
notification cancelled if the media title was blank, and instead uses
the placeholder title for all apps. Also log these instances with the compat
framework to observe impact on apps.
Bug: 274775190
Test: atest MediaDataManagerTest
Change-Id: I1cb6010f19623bd7d5e5bbb5d829b9e4936894b8
4 files changed, 38 insertions, 181 deletions
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 776e34bb4792..385fd509757b 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -24,9 +24,11 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; +import android.compat.annotation.LoggingOnly; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -49,6 +51,7 @@ import android.util.Slog; import android.view.KeyEvent; import android.view.View; +import com.android.internal.compat.IPlatformCompat; import com.android.internal.statusbar.AppClipsServiceConnector; import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBarService; @@ -170,6 +173,8 @@ public class StatusBarManager { public @interface Disable2Flags {} // LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt) + private static final String TAG = "StatusBarManager"; + /** * Default disable flags for setup * @@ -572,13 +577,13 @@ public class StatusBarManager { private static final long MEDIA_CONTROL_SESSION_ACTIONS = 203800354L; /** - * Media controls based on {@link android.app.Notification.MediaStyle} notifications will be - * required to include a non-empty title, either in the {@link android.media.MediaMetadata} or + * Media controls based on {@link android.app.Notification.MediaStyle} notifications should + * include a non-empty title, either in the {@link android.media.MediaMetadata} or * notification title. */ @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - private static final long MEDIA_CONTROL_REQUIRES_TITLE = 274775190L; + @LoggingOnly + private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L; @UnsupportedAppUsage private Context mContext; @@ -586,6 +591,9 @@ public class StatusBarManager { @UnsupportedAppUsage private IBinder mToken = new Binder(); + private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + @UnsupportedAppUsage StatusBarManager(Context context) { mContext = context; @@ -597,7 +605,7 @@ public class StatusBarManager { mService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); if (mService == null) { - Slog.w("StatusBarManager", "warning: no STATUS_BAR_SERVICE"); + Slog.w(TAG, "warning: no STATUS_BAR_SERVICE"); } } return mService; @@ -1226,18 +1234,22 @@ public class StatusBarManager { } /** - * Checks whether the given package must include a non-empty title for its media controls. + * Log that the given package has posted media controls with a blank title * * @param packageName App posting media controls - * @param user Current user handle - * @return true if the app is required to provide a non-empty title + * @param userId Current user ID + * @throws RuntimeException if there is an error reporting the change * * @hide */ - @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG, - android.Manifest.permission.LOG_COMPAT_CHANGE}) - public static boolean isMediaTitleRequiredForApp(String packageName, UserHandle user) { - return CompatChanges.isChangeEnabled(MEDIA_CONTROL_REQUIRES_TITLE, packageName, user); + public void logBlankMediaTitle(String packageName, @UserIdInt int userId) + throws RuntimeException { + try { + mPlatformCompat.reportChangeByPackageName(MEDIA_CONTROL_BLANK_TITLE, packageName, + userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 1469d96dd3e8..bce334610f28 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -16,10 +16,12 @@ package com.android.systemui.media.controls.pipeline +import android.annotation.SuppressLint import android.app.BroadcastOptions import android.app.Notification import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME import android.app.PendingIntent +import android.app.StatusBarManager import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceSession @@ -43,7 +45,6 @@ import android.media.session.PlaybackState import android.net.Uri import android.os.Parcelable import android.os.Process -import android.os.RemoteException import android.os.UserHandle import android.provider.Settings import android.service.notification.StatusBarNotification @@ -53,7 +54,6 @@ import android.util.Log import android.util.Pair as APair import androidx.media.utils.MediaConstants import com.android.internal.logging.InstanceId -import com.android.internal.statusbar.IStatusBarService import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Dumpable import com.android.systemui.R @@ -185,7 +185,6 @@ class MediaDataManager( private val logger: MediaUiEventLogger, private val smartspaceManager: SmartspaceManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val statusBarService: IStatusBarService, ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -230,6 +229,10 @@ class MediaDataManager( private val artworkHeight = context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded) + @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE + private val statusBarManager = + context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager + /** Check whether this notification is an RCN */ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE) @@ -257,7 +260,6 @@ class MediaDataManager( mediaFlags: MediaFlags, logger: MediaUiEventLogger, smartspaceManager: SmartspaceManager, - statusBarService: IStatusBarService, keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : this( context, @@ -283,7 +285,6 @@ class MediaDataManager( logger, smartspaceManager, keyguardUpdateMonitor, - statusBarService, ) private val appChangeReceiver = @@ -793,27 +794,12 @@ class MediaDataManager( song = HybridGroupManager.resolveTitle(notif) } if (song.isNullOrBlank()) { - if (mediaFlags.isMediaTitleRequired(sbn.packageName, sbn.user)) { - // App is required to provide a title: cancel the underlying notification - try { - statusBarService.onNotificationError( - sbn.packageName, - sbn.tag, - sbn.id, - sbn.uid, - sbn.initialPid, - MEDIA_TITLE_ERROR_MESSAGE, - sbn.user.identifier - ) - } catch (e: RemoteException) { - Log.e(TAG, "cancelNotification failed: $e") - } - // Only add log for media removed if active media is updated with invalid title. - foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) } - return - } else { - // For apps that don't have the title requirement yet, add a placeholder - song = context.getString(R.string.controls_media_empty_title, appName) + // For apps that don't include a title, log and add a placeholder + song = context.getString(R.string.controls_media_empty_title, appName) + try { + statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier) + } catch (e: RuntimeException) { + Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}") } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 3751c60b06d5..9bc66f6c98d0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -64,9 +64,4 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** Check whether we allow remote media to generate resume controls */ fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) - - /** Check whether app is required to provide a non-empty media title */ - fun isMediaTitleRequired(packageName: String, user: UserHandle): Boolean { - return StatusBarManager.isMediaTitleRequiredForApp(packageName, user) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 3bcefcf6ffff..56698e0ec41c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -41,7 +41,6 @@ import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId -import com.android.internal.statusbar.IStatusBarService import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.InstanceIdSequenceFake import com.android.systemui.R @@ -133,7 +132,6 @@ class MediaDataManagerTest : SysuiTestCase() { @Mock lateinit var activityStarter: ActivityStarter @Mock lateinit var smartspaceManager: SmartspaceManager @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock lateinit var statusBarService: IStatusBarService lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget @Mock private lateinit var mediaRecommendationItem: SmartspaceAction @@ -197,7 +195,6 @@ class MediaDataManagerTest : SysuiTestCase() { logger = logger, smartspaceManager = smartspaceManager, keyguardUpdateMonitor = keyguardUpdateMonitor, - statusBarService = statusBarService, ) verify(tunerService) .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) @@ -522,143 +519,12 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test - fun testOnNotificationAdded_emptyTitle_isRequired_notLoaded() { - // When the manager has a notification with an empty title, and the app is required - // to include a non-empty title - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - // Then the media control is not added and we report a notification error - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any()) - verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any()) - } - - @Test - fun testOnNotificationAdded_blankTitle_isRequired_notLoaded() { - // When the manager has a notification with a blank title, and the app is required - // to include a non-empty title - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - // Then the media control is not added and we report a notification error - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any()) - verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any()) - } - - @Test - fun testOnNotificationUpdated_invalidTitle_isRequired_logMediaRemoved() { - // When the app is required to provide a non-blank title, and updates a previously valid - // title to an empty one - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) - addNotificationAndLoad() - val data = mediaDataCaptor.value - - verify(listener) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - - reset(listener) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - // Then the media control is removed - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) - } - - @Test - fun testOnNotificationAdded_emptyTitle_notRequired_hasPlaceholder() { + fun testOnNotificationAdded_emptyTitle_hasPlaceholder() { // When the manager has a notification with an empty title, and the app is not // required to include a non-empty title val mockPackageManager = mock(PackageManager::class.java) context.setMockPackageManager(mockPackageManager) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false) whenever(controller.metadata) .thenReturn( metadataBuilder @@ -684,13 +550,12 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test - fun testOnNotificationAdded_blankTitle_notRequired_hasPlaceholder() { + fun testOnNotificationAdded_blankTitle_hasPlaceholder() { // GIVEN that the manager has a notification with a blank title, and the app is not // required to include a non-empty title val mockPackageManager = mock(PackageManager::class.java) context.setMockPackageManager(mockPackageManager) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false) whenever(controller.metadata) .thenReturn( metadataBuilder @@ -722,7 +587,6 @@ class MediaDataManagerTest : SysuiTestCase() { val mockPackageManager = mock(PackageManager::class.java) context.setMockPackageManager(mockPackageManager) whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) - whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true) whenever(controller.metadata) .thenReturn( metadataBuilder |