summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Beth Thibodeau <ethibodeau@google.com> 2023-05-19 15:09:38 -0500
committer Beth Thibodeau <ethibodeau@google.com> 2023-05-22 15:15:24 -0500
commit0ea0f35dce5e3ecdddbe2a93caaecb2b1c394b78 (patch)
tree78f65402dd22cd0d751fb5f19a45446247a37a66
parent6c0a668d51984eec8d718b5169732269e2b7ea99 (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
-rw-r--r--core/java/android/app/StatusBarManager.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt140
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