Tweak volume adjustment rules

Test: manual
Bug: 197586676
Bug: 202500642
Merged-In: If531a23f5e7b242d3d637966b74b67acfd303199
Change-Id: If531a23f5e7b242d3d637966b74b67acfd303199
(cherry picked from commit 022081595bf4a082f8354b852eae6d5a015be3fe)
Merged-In:If531a23f5e7b242d3d637966b74b67acfd303199
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index db43b5b..89fb933 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1894,8 +1894,9 @@
          STREAM_MUSIC as if it's on TV platform. -->
     <bool name="config_single_volume">false</bool>
 
-    <!-- Flag indicating whether the volume panel should show remote sessions. -->
-    <bool name="config_volumeShowRemoteSessions">true</bool>
+    <!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions
+         on grouped devices. -->
+    <bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
 
     <!-- Flag indicating that an outbound call must have a call capable phone account
          that has declared it can process the call's handle. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index adb046e..85034a8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4422,7 +4422,7 @@
 
   <java-symbol type="dimen" name="config_wallpaperDimAmount" />
 
-  <java-symbol type="bool" name="config_volumeShowRemoteSessions" />
+  <java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" />
 
   <java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index e570598..cd6a778 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -33,7 +33,11 @@
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.IVolumeController;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
+import android.media.RoutingSessionInfo;
 import android.media.VolumePolicy;
+import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.MediaSession.Token;
 import android.net.Uri;
@@ -71,6 +75,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -118,6 +123,7 @@
     private final Context mContext;
     private final Looper mWorkerLooper;
     private final PackageManager mPackageManager;
+    private final MediaRouter2Manager mRouter2Manager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private AudioManager mAudio;
     private IAudioService mAudioService;
@@ -179,6 +185,7 @@
         mWorkerLooper = theadFactory.buildLooperOnNewThread(
                 VolumeDialogControllerImpl.class.getSimpleName());
         mWorker = new W(mWorkerLooper);
+        mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
         mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext);
         mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW);
         mAudio = audioManager;
@@ -1149,16 +1156,16 @@
         private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
 
         private int mNextStream = DYNAMIC_STREAM_START_INDEX;
-        private final boolean mShowRemoteSessions;
+        private final boolean mVolumeAdjustmentForRemoteGroupSessions;
 
         public MediaSessionsCallbacks(Context context) {
-            mShowRemoteSessions = context.getResources().getBoolean(
-                    com.android.internal.R.bool.config_volumeShowRemoteSessions);
+            mVolumeAdjustmentForRemoteGroupSessions = context.getResources().getBoolean(
+                    com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
         }
 
         @Override
         public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
-            if (mShowRemoteSessions) {
+            if (showForSession(token)) {
                 addStream(token, "onRemoteUpdate");
 
                 int stream = 0;
@@ -1190,7 +1197,7 @@
 
         @Override
         public void onRemoteVolumeChanged(Token token, int flags) {
-            if (mShowRemoteSessions) {
+            if (showForSession(token)) {
                 addStream(token, "onRemoteVolumeChanged");
                 int stream = 0;
                 synchronized (mRemoteStreams) {
@@ -1214,7 +1221,7 @@
 
         @Override
         public void onRemoteRemoved(Token token) {
-            if (mShowRemoteSessions) {
+            if (showForSession(token)) {
                 int stream = 0;
                 synchronized (mRemoteStreams) {
                     if (!mRemoteStreams.containsKey(token)) {
@@ -1233,14 +1240,41 @@
         }
 
         public void setStreamVolume(int stream, int level) {
-            if (mShowRemoteSessions) {
-                final Token t = findToken(stream);
-                if (t == null) {
-                    Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
-                    return;
-                }
-                mMediaSessions.setVolume(t, level);
+            final Token token = findToken(stream);
+            if (token == null) {
+                Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
+                return;
             }
+            if (showForSession(token)) {
+                mMediaSessions.setVolume(token, level);
+            }
+        }
+
+        private boolean showForSession(Token token) {
+            if (mVolumeAdjustmentForRemoteGroupSessions) {
+                return true;
+            }
+            MediaController ctr = new MediaController(mContext, token);
+            String packageName = ctr.getPackageName();
+            List<RoutingSessionInfo> sessions =
+                    mRouter2Manager.getRoutingSessions(packageName);
+            boolean foundNonSystemSession = false;
+            boolean isGroup = false;
+            for (RoutingSessionInfo session : sessions) {
+                if (!session.isSystemSession()) {
+                    foundNonSystemSession = true;
+                    int selectedRouteCount = session.getSelectedRoutes().size();
+                    if (selectedRouteCount > 1) {
+                        isGroup = true;
+                        break;
+                    }
+                }
+            }
+            if (!foundNonSystemSession) {
+                Log.d(TAG, "No routing session for " + packageName);
+                return false;
+            }
+            return !isGroup;
         }
 
         private Token findToken(int stream) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 5c0efd3..c9462d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -101,6 +101,11 @@
         // Initial non-set value
         when(mRingerModeLiveData.getValue()).thenReturn(-1);
         when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
+        // Enable group volume adjustments
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
+                true);
+
         mCallback = mock(VolumeDialogControllerImpl.C.class);
         mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
         mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 607218e..b424c20 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -146,6 +146,12 @@
     }
 
     @Override
+    public boolean canHandleVolumeKey() {
+        // TODO: Implement when MediaSession2 starts to get key events.
+        return false;
+    }
+
+    @Override
     public int getSessionPolicies() {
         synchronized (mLock) {
             return mPolicies;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 1525cd4..e4ed0e5 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -26,7 +26,9 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.MediaMetadata;
+import android.media.MediaRouter2Manager;
 import android.media.Rating;
+import android.media.RoutingSessionInfo;
 import android.media.VolumeProvider;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
@@ -50,6 +52,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -121,6 +124,7 @@
     private final SessionCb mSessionCb;
     private final MediaSessionService mService;
     private final Context mContext;
+    private final boolean mVolumeAdjustmentForRemoteGroupSessions;
 
     private final Object mLock = new Object();
     private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
@@ -180,6 +184,8 @@
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mAudioAttrs = DEFAULT_ATTRIBUTES;
         mPolicies = policies;
+        mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
 
         // May throw RemoteException if the session app is killed.
         mSessionCb.mCb.asBinder().linkToDeath(this, 0);
@@ -449,6 +455,33 @@
     }
 
     @Override
+    public boolean canHandleVolumeKey() {
+        if (isPlaybackTypeLocal() || mVolumeAdjustmentForRemoteGroupSessions) {
+            return true;
+        }
+        MediaRouter2Manager mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
+        List<RoutingSessionInfo> sessions =
+                mRouter2Manager.getRoutingSessions(mPackageName);
+        boolean foundNonSystemSession = false;
+        boolean isGroup = false;
+        for (RoutingSessionInfo session : sessions) {
+            if (!session.isSystemSession()) {
+                foundNonSystemSession = true;
+                int selectedRouteCount = session.getSelectedRoutes().size();
+                if (selectedRouteCount > 1) {
+                    isGroup = true;
+                    break;
+                }
+            }
+        }
+        if (!foundNonSystemSession) {
+            Log.d(TAG, "No routing session for " + mPackageName);
+            return false;
+        }
+        return !isGroup;
+    }
+
+    @Override
     public int getSessionPolicies() {
         synchronized (mLock) {
             return mPolicies;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 3c50597..8f01f02 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -131,6 +131,13 @@
             KeyEvent ke, int sequenceId, ResultReceiver cb);
 
     /**
+     * Returns whether the media session can handle volume key events.
+     *
+     * @return True if this media session can handle volume key events, false otherwise.
+     */
+    boolean canHandleVolumeKey();
+
+    /**
      * Get session policies from custom policy provider set when MediaSessionRecord is instantiated.
      * If custom policy does not exist, will return null.
      */
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index c4c21df..b75ba75 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -325,8 +325,7 @@
         int size = records.size();
         for (int i = 0; i < size; i++) {
             MediaSessionRecord record = records.get(i);
-            // Do not send the volume key events to remote sessions.
-            if (record.checkPlaybackActiveState(true) && record.isPlaybackTypeLocal()) {
+            if (record.checkPlaybackActiveState(true) && record.canHandleVolumeKey()) {
                 mCachedVolumeDefault = record;
                 return record;
             }