Merge "[Output Switcher] Sets TemporaryAllowList when transfer from local to remote (or oppsite way)" into tm-dev am: 3a4731d949

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18156225

Change-Id: I0d19de0171dbe78d07716d26d8110b08716895da
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 2fcab59..78214dc 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -385,6 +385,12 @@
      */
     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 @@
             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 @@
                 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 aeda20f..cd8ca05 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.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;
@@ -377,6 +378,13 @@
 
     /** */
     @Provides
+    @Singleton
+    static PowerExemptionManager providePowerExemptionManager(Context context) {
+        return context.getSystemService(PowerExemptionManager.class);
+    }
+
+    /** */
+    @Provides
     @Main
     public SharedPreferences provideSharePreferences(Context context) {
         return Prefs.get(context);
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 f0ce30d..f2f2753 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 @@
                 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 38005db..0fa3265 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 @@
 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 @@
     private val uiEventLogger: UiEventLogger,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
-    private val audioManager: AudioManager
+    private val audioManager: AudioManager,
+    private val powerExemptionManager: PowerExemptionManager
 ) {
     var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
 
@@ -54,7 +56,8 @@
 
         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 42e9af8..f8b5edc 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.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 @@
     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 @@
     final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
     final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
     private final AudioManager mAudioManager;
+    private final PowerExemptionManager mPowerExemptionManager;
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
     private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
 
@@ -147,7 +152,8 @@
             CommonNotifCollection notifCollection,
             DialogLaunchAnimator dialogLaunchAnimator,
             Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
-            AudioManager audioManager) {
+            AudioManager audioManager,
+            PowerExemptionManager powerExemptionManager) {
         mContext = context;
         mPackageName = packageName;
         mMediaSessionManager = mediaSessionManager;
@@ -155,6 +161,7 @@
         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 @@
         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 @@
         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 36a46f0..5581fb8 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 @@
 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 @@
     private val uiEventLogger: UiEventLogger,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
-    private val audioManager: AudioManager
+    private val audioManager: AudioManager,
+    private val powerExemptionManager: PowerExemptionManager
 ) {
     companion object {
         var mediaOutputDialog: MediaOutputDialog? = null
@@ -54,9 +56,11 @@
         // 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 e3b5059..9eaa20c 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.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 @@
             NearbyMediaDevicesManager.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
     private final AudioManager mAudioManager = mock(AudioManager.class);
+    private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
 
     private List<MediaController> mMediaControllers = new ArrayList<>();
     private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -110,7 +112,7 @@
         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 feed334..2bf5f0f 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 @@
 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.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 @@
     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 @@
         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 @@
         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 @@
         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 @@
         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 @@
 
         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 6786ad0..ef0fc95 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.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 @@
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
             NearbyMediaDevicesManager.class);
     private final AudioManager mAudioManager = mock(AudioManager.class);
+    private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
 
     private List<MediaController> mMediaControllers = new ArrayList<>();
     private MediaOutputDialog mMediaOutputDialog;
@@ -103,7 +105,7 @@
         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 379bb4f..4534ae6 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 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 @@
     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 @@
         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);