From 547f613a80f80be6a2155acb956bec45d09bb605 Mon Sep 17 00:00:00 2001 From: shaoweishen Date: Wed, 4 May 2022 12:19:53 +0000 Subject: [PATCH] [Output Switcher] Sets TemporaryAllowList when transfer from local to remote (or oppsite way) app will create new UMO when transfer between local and remote, and it will produce Exception if app is background, so set it to TemporaryAllowList before start transferring. Bug: 235339353 Test: atest atest MediaOutputAdapterTest MediaOutputControllerTest MediaOutputBaseDialogTest MediaOutputDialogTest MediaOutputGroupAdapterTest MediaOutputGroupDialogTest Change-Id: I32a823919b48fd784d354858c7e3b1070a723e28 Merged-In: I32a823919b48fd784d354858c7e3b1070a723e28 --- .../android/os/PowerExemptionManager.java | 9 ++++++ .../dagger/FrameworkServicesModule.java | 8 +++++ .../media/dialog/MediaOutputAdapter.java | 1 + .../MediaOutputBroadcastDialogFactory.kt | 7 +++-- .../media/dialog/MediaOutputController.java | 22 ++++++++++++-- .../media/dialog/MediaOutputDialogFactory.kt | 12 +++++--- .../dialog/MediaOutputBaseDialogTest.java | 4 ++- .../dialog/MediaOutputControllerTest.java | 30 ++++++++++++++++--- .../media/dialog/MediaOutputDialogTest.java | 4 ++- .../dialog/MediaOutputGroupDialogTest.java | 4 ++- 10 files changed, 86 insertions(+), 15 deletions(-) diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index 2fcab59cdec76..78214dc27a9fe 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -385,6 +385,12 @@ public class PowerExemptionManager { */ public static final int REASON_ACTIVE_DEVICE_ADMIN = 324; + /** + * Media notification re-generate during transferring. + * @hide + */ + public static final int REASON_MEDIA_NOTIFICATION_TRANSFER = 325; + /** @hide The app requests out-out. */ public static final int REASON_OPT_OUT_REQUESTED = 1000; @@ -465,6 +471,7 @@ public class PowerExemptionManager { REASON_DPO_PROTECTED_APP, REASON_DISALLOW_APPS_CONTROL, REASON_ACTIVE_DEVICE_ADMIN, + REASON_MEDIA_NOTIFICATION_TRANSFER, }) @Retention(RetentionPolicy.SOURCE) public @interface ReasonCode {} @@ -830,6 +837,8 @@ public class PowerExemptionManager { return "ACTIVE_DEVICE_ADMIN"; case REASON_OPT_OUT_REQUESTED: return "REASON_OPT_OUT_REQUESTED"; + case REASON_MEDIA_NOTIFICATION_TRANSFER: + return "REASON_MEDIA_NOTIFICATION_TRANSFER"; default: return "(unknown:" + reasonCode + ")"; } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index aeda20f165156..cd8ca0553add8 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -60,6 +60,7 @@ import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.BatteryStats; +import android.os.PowerExemptionManager; import android.os.PowerManager; import android.os.ServiceManager; import android.os.UserManager; @@ -375,6 +376,13 @@ public class FrameworkServicesModule { return context.getSystemService(PowerManager.class); } + /** */ + @Provides + @Singleton + static PowerExemptionManager providePowerExemptionManager(Context context) { + return context.getSystemService(PowerExemptionManager.class); + } + /** */ @Provides @Main diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index f0ce30d2dc66b..f2f275323d58d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -285,6 +285,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { Log.d(TAG, "This device is already connected! : " + device.getName()); return; } + mController.setTemporaryAllowListExceptionIfNeeded(device); mCurrentActivePosition = -1; mController.connectDevice(device); device.setState(MediaDeviceState.STATE_CONNECTING); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt index 38005db28cf61..0fa326573c9cb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.dialog import android.content.Context import android.media.AudioManager import android.media.session.MediaSessionManager +import android.os.PowerExemptionManager import android.view.View import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.LocalBluetoothManager @@ -43,7 +44,8 @@ class MediaOutputBroadcastDialogFactory @Inject constructor( private val uiEventLogger: UiEventLogger, private val dialogLaunchAnimator: DialogLaunchAnimator, private val nearbyMediaDevicesManagerOptional: Optional, - private val audioManager: AudioManager + private val audioManager: AudioManager, + private val powerExemptionManager: PowerExemptionManager ) { var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null @@ -54,7 +56,8 @@ class MediaOutputBroadcastDialogFactory @Inject constructor( val controller = MediaOutputController(context, packageName, mediaSessionManager, lbm, starter, notifCollection, - dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager) + dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager, + powerExemptionManager) val dialog = MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller) mediaOutputBroadcastDialog = dialog diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 42e9af846322e..f8b5edcbd11fa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -44,6 +44,7 @@ import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.IBinder; +import android.os.PowerExemptionManager; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -101,6 +102,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String PAGE_CONNECTED_DEVICES_KEY = "top_level_connected_devices"; + private static final long ALLOWLIST_DURATION_MS = 20000; + private static final String ALLOWLIST_REASON = "mediaoutput:remote_transfer"; + private final String mPackageName; private final Context mContext; private final MediaSessionManager mMediaSessionManager; @@ -114,6 +118,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, final List mMediaDevices = new CopyOnWriteArrayList<>(); final List mCachedMediaDevices = new CopyOnWriteArrayList<>(); private final AudioManager mAudioManager; + private final PowerExemptionManager mPowerExemptionManager; private final NearbyMediaDevicesManager mNearbyMediaDevicesManager; private final Map mNearbyDeviceInfoMap = new ConcurrentHashMap<>(); @@ -147,7 +152,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, CommonNotifCollection notifCollection, DialogLaunchAnimator dialogLaunchAnimator, Optional nearbyMediaDevicesManagerOptional, - AudioManager audioManager) { + AudioManager audioManager, + PowerExemptionManager powerExemptionManager) { mContext = context; mPackageName = packageName; mMediaSessionManager = mediaSessionManager; @@ -155,6 +161,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mActivityStarter = starter; mNotifCollection = notifCollection; mAudioManager = audioManager; + mPowerExemptionManager = powerExemptionManager; InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm); mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); @@ -776,7 +783,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, MediaOutputController controller = new MediaOutputController(mContext, mPackageName, mMediaSessionManager, mLocalBluetoothManager, mActivityStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), - mAudioManager); + mAudioManager, mPowerExemptionManager); MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true, broadcastSender, controller); mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog); @@ -822,6 +829,17 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, broadcast.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8)); } + void setTemporaryAllowListExceptionIfNeeded(MediaDevice targetDevice) { + if (mPowerExemptionManager == null || mPackageName == null) { + Log.w(TAG, "powerExemptionManager or package name is null"); + return; + } + mPowerExemptionManager.addToTemporaryAllowList(mPackageName, + PowerExemptionManager.REASON_MEDIA_NOTIFICATION_TRANSFER, + ALLOWLIST_REASON, + ALLOWLIST_DURATION_MS); + } + String getBroadcastMetadata() { LocalBluetoothLeBroadcast broadcast = mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt index 36a46f067fb05..5581fb861e6a8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.dialog import android.content.Context import android.media.AudioManager import android.media.session.MediaSessionManager +import android.os.PowerExemptionManager import android.view.View import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.LocalBluetoothManager @@ -43,7 +44,8 @@ class MediaOutputDialogFactory @Inject constructor( private val uiEventLogger: UiEventLogger, private val dialogLaunchAnimator: DialogLaunchAnimator, private val nearbyMediaDevicesManagerOptional: Optional, - private val audioManager: AudioManager + private val audioManager: AudioManager, + private val powerExemptionManager: PowerExemptionManager ) { companion object { var mediaOutputDialog: MediaOutputDialog? = null @@ -54,9 +56,11 @@ class MediaOutputDialogFactory @Inject constructor( // Dismiss the previous dialog, if any. mediaOutputDialog?.dismiss() - val controller = MediaOutputController(context, packageName, - mediaSessionManager, lbm, starter, notifCollection, - dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager) + val controller = MediaOutputController( + context, packageName, + mediaSessionManager, lbm, starter, notifCollection, + dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager, + powerExemptionManager) val dialog = MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger) mediaOutputDialog = dialog diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index e3b5059131fab..9eaa20c2afedb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -32,6 +32,7 @@ import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Bundle; +import android.os.PowerExemptionManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -86,6 +87,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { NearbyMediaDevicesManager.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private final AudioManager mAudioManager = mock(AudioManager.class); + private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private List mMediaControllers = new ArrayList<>(); private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl; @@ -110,7 +112,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotificationEntryManager, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender, mMediaOutputController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index feed3347f3e22..2bf5f0fcbfcb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -19,6 +19,9 @@ package com.android.systemui.media.dialog; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -34,10 +37,12 @@ import android.graphics.drawable.Icon; import android.media.AudioManager; import android.media.MediaDescription; import android.media.MediaMetadata; +import android.media.MediaRoute2Info; import android.media.NearbyDevice; import android.media.RoutingSessionInfo; import android.media.session.MediaController; import android.media.session.MediaSessionManager; +import android.os.PowerExemptionManager; import android.os.RemoteException; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; @@ -97,6 +102,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { private RoutingSessionInfo mRemoteSessionInfo = mock(RoutingSessionInfo.class); private ActivityStarter mStarter = mock(ActivityStarter.class); private AudioManager mAudioManager = mock(AudioManager.class); + private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( @@ -125,7 +131,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; MediaDescription.Builder builder = new MediaDescription.Builder(); @@ -177,7 +183,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, null, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputController.start(mCb); @@ -206,7 +212,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, null, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputController.start(mCb); @@ -511,7 +517,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, null, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); assertThat(mMediaOutputController.getNotificationIcon()).isNull(); } @@ -591,4 +597,20 @@ public class MediaOutputControllerTest extends SysuiTestCase { assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isTrue(); } + + @Test + public void setTemporaryAllowListExceptionIfNeeded_fromRemoteToBluetooth_addsAllowList() { + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); + when(mMediaDevice1.getDeviceType()).thenReturn( + MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); + when(mMediaDevice1.getFeatures()).thenReturn( + ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK)); + when(mMediaDevice2.getDeviceType()).thenReturn( + MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE); + + mMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); + + verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(), + anyLong()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 6786ad0116ea7..ef0fc95f95b29 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -29,6 +29,7 @@ import android.media.MediaRoute2Info; import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; +import android.os.PowerExemptionManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -84,6 +85,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); private final AudioManager mAudioManager = mock(AudioManager.class); + private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private List mMediaControllers = new ArrayList<>(); private MediaOutputDialog mMediaOutputDialog; @@ -103,7 +105,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotificationEntryManager, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, mMediaOutputController, mUiEventLogger); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java index 379bb4fd63366..4534ae6448ec3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import android.media.AudioManager; import android.media.session.MediaSessionManager; +import android.os.PowerExemptionManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -70,6 +71,7 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); private final AudioManager mAudioManager = mock(AudioManager.class); + private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private MediaOutputGroupDialog mMediaOutputGroupDialog; private MediaOutputController mMediaOutputController; @@ -80,7 +82,7 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotificationEntryManager, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false, mBroadcastSender, mMediaOutputController);