Merge "MediaRouterService binds services when necessary"
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 068f968..4b8a8ad 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -73,6 +73,8 @@
     void unregisterManager(IMediaRouter2Manager manager);
     void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
             in MediaRoute2Info route, int volume);
+    void startScan(IMediaRouter2Manager manager);
+    void stopScan(IMediaRouter2Manager manager);
 
     void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
             in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route);
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 4b09a5f..68237de 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -147,6 +147,36 @@
     }
 
     /**
+     * Starts scanning remote routes.
+     * @see #stopScan(String)
+     */
+    public void startScan() {
+        Client client = getOrCreateClient();
+        if (client != null) {
+            try {
+                mMediaRouterService.startScan(client);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Unable to get sessions. Service probably died.", ex);
+            }
+        }
+    }
+
+    /**
+     * Stops scanning remote routes to reduce resource consumption.
+     * @see #startScan(String)
+     */
+    public void stopScan() {
+        Client client = getOrCreateClient();
+        if (client != null) {
+            try {
+                mMediaRouterService.stopScan(client);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Unable to get sessions. Service probably died.", ex);
+            }
+        }
+    }
+
+    /**
      * Gets a {@link android.media.session.MediaController} associated with the
      * given routing session.
      * If there is no matching media session, {@code null} is returned.
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 68f2964..2f95247 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -153,6 +153,7 @@
             return false;
         }
         RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
+        //TODO: Make this order-free
         return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
                 && mShouldPerformActiveScan == other.mShouldPerformActiveScan;
     }
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 eb86128..9e25389 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.Icon;
 import android.media.MediaMetadata;
 import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
 import android.media.RoutingSessionInfo;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
@@ -76,6 +77,7 @@
     private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
     private final boolean mAboveStatusbar;
     private final NotificationEntryManager mNotificationEntryManager;
+    private final MediaRouter2Manager mRouterManager;
     @VisibleForTesting
     final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
 
@@ -104,6 +106,7 @@
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mUiEventLogger = uiEventLogger;
+        mRouterManager = MediaRouter2Manager.getInstance(mContext);
     }
 
     void start(@NonNull Callback cb) {
@@ -134,6 +137,7 @@
         mLocalMediaManager.stopScan();
         mLocalMediaManager.registerCallback(this);
         mLocalMediaManager.startScan();
+        mRouterManager.startScan();
     }
 
     void stop() {
@@ -144,6 +148,9 @@
             mLocalMediaManager.unregisterCallback(this);
             mLocalMediaManager.stopScan();
         }
+        if (mRouterManager != null) {
+            mRouterManager.stopScan();
+        }
         mMediaDevices.clear();
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index f882c57..edc9d7c 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -77,7 +77,7 @@
     @NonNull
     public List<RoutingSessionInfo> getSessionInfos() {
         synchronized (mLock) {
-            return mSessionInfos;
+            return new ArrayList<>(mSessionInfos);
         }
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 85af346..ab38dca 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -108,8 +108,8 @@
         mLastDiscoveryPreference = discoveryPreference;
         if (mConnectionReady) {
             mActiveConnection.updateDiscoveryPreference(discoveryPreference);
-            updateBinding();
         }
+        updateBinding();
     }
 
     @Override
@@ -205,9 +205,11 @@
     }
 
     private boolean shouldBind() {
-        //TODO: Binding could be delayed until it's necessary.
         if (mRunning) {
-            return true;
+            // Bind when there is a discovery preference or an active route session.
+            return (mLastDiscoveryPreference != null
+                    && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty())
+                    || !getSessionInfos().isEmpty();
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1114fe0..31edf43 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
 import static android.media.MediaRouter2Utils.getOriginalId;
 import static android.media.MediaRouter2Utils.getProviderId;
@@ -73,10 +74,12 @@
     // TODO: (In Android S or later) if we add callback methods for generic failures
     //       in MediaRouter2, remove this constant and replace the usages with the real request IDs.
     private static final long DUMMY_REQUEST_ID = -1;
+    private static final int PACKAGE_IMPORTANCE_FOR_DISCOVERY = IMPORTANCE_FOREGROUND;
 
     private final Context mContext;
     private final Object mLock = new Object();
     final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1);
+    final ActivityManager mActivityManager;
 
     @GuardedBy("mLock")
     private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
@@ -87,8 +90,21 @@
     @GuardedBy("mLock")
     private int mCurrentUserId = -1;
 
+    private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
+            (uid, importance) -> {
+                synchronized (mLock) {
+                    final int count = mUserRecords.size();
+                    for (int i = 0; i < count; i++) {
+                        mUserRecords.valueAt(i).mHandler.maybeUpdateDiscoveryPreferenceForUid(uid);
+                    }
+                }
+            };
+
     MediaRouter2ServiceImpl(Context context) {
         mContext = context;
+        mActivityManager = mContext.getSystemService(ActivityManager.class);
+        mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
+                PACKAGE_IMPORTANCE_FOR_DISCOVERY);
     }
 
     ////////////////////////////////////////////////////////////////
@@ -388,6 +404,30 @@
         }
     }
 
+    public void startScan(IMediaRouter2Manager manager) {
+        Objects.requireNonNull(manager, "manager must not be null");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                startScanLocked(manager);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public void stopScan(IMediaRouter2Manager manager) {
+        Objects.requireNonNull(manager, "manager must not be null");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                stopScanLocked(manager);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
             MediaRoute2Info route, int volume) {
         Objects.requireNonNull(manager, "manager must not be null");
@@ -839,6 +879,24 @@
         disposeUserIfNeededLocked(userRecord); // since manager removed from user
     }
 
+    private void startScanLocked(@NonNull IMediaRouter2Manager manager) {
+        final IBinder binder = manager.asBinder();
+        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+        if (managerRecord == null) {
+            return;
+        }
+        managerRecord.startScan();
+    }
+
+    private void stopScanLocked(@NonNull IMediaRouter2Manager manager) {
+        final IBinder binder = manager.asBinder();
+        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+        if (managerRecord == null) {
+            return;
+        }
+        managerRecord.stopScan();
+    }
+
     private void setRouteVolumeWithManagerLocked(int requestId,
             @NonNull IMediaRouter2Manager manager,
             @NonNull MediaRoute2Info route, int volume) {
@@ -1122,6 +1180,7 @@
         public final String mPackageName;
         public final int mManagerId;
         public SessionCreationRequest mLastSessionCreationRequest;
+        public boolean mIsScanning;
 
         ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager,
                 int uid, int pid, String packageName) {
@@ -1146,6 +1205,24 @@
             pw.println(prefix + this);
         }
 
+        public void startScan() {
+            if (mIsScanning) {
+                return;
+            }
+            mIsScanning = true;
+            mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
+                    UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
+        }
+
+        public void stopScan() {
+            if (!mIsScanning) {
+                return;
+            }
+            mIsScanning = false;
+            mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
+                    UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
+        }
+
         @Override
         public String toString() {
             return "Manager " + mPackageName + " (pid " + mPid + ")";
@@ -1262,6 +1339,24 @@
             return null;
         }
 
+        public void maybeUpdateDiscoveryPreferenceForUid(int uid) {
+            MediaRouter2ServiceImpl service = mServiceRef.get();
+            if (service == null) {
+                return;
+            }
+            boolean isUidRelevant;
+            synchronized (service.mLock) {
+                isUidRelevant = mUserRecord.mRouterRecords.stream().anyMatch(
+                        router -> router.mUid == uid)
+                        | mUserRecord.mManagerRecords.stream().anyMatch(
+                            manager -> manager.mUid == uid);
+            }
+            if (isUidRelevant) {
+                sendMessage(PooledLambda.obtainMessage(
+                        UserHandler::updateDiscoveryPreferenceOnHandler, this));
+            }
+        }
+
         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
             int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
             MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
@@ -1767,6 +1862,16 @@
             return managers;
         }
 
+        private List<RouterRecord> getRouterRecords() {
+            MediaRouter2ServiceImpl service = mServiceRef.get();
+            if (service == null) {
+                return Collections.emptyList();
+            }
+            synchronized (service.mLock) {
+                return new ArrayList<>(mUserRecord.mRouterRecords);
+            }
+        }
+
         private List<ManagerRecord> getManagerRecords() {
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
@@ -2001,13 +2106,28 @@
                 return;
             }
             List<RouteDiscoveryPreference> discoveryPreferences = new ArrayList<>();
-            synchronized (service.mLock) {
-                for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
+            List<RouterRecord> routerRecords = getRouterRecords();
+            List<ManagerRecord> managerRecords = getManagerRecords();
+            boolean isAnyManagerScanning =
+                    managerRecords.stream().anyMatch(manager -> manager.mIsScanning
+                    && service.mActivityManager.getPackageImportance(manager.mPackageName)
+                    <= PACKAGE_IMPORTANCE_FOR_DISCOVERY);
+
+            for (RouterRecord routerRecord : routerRecords) {
+                if (isAnyManagerScanning
+                        || service.mActivityManager.getPackageImportance(routerRecord.mPackageName)
+                        <= PACKAGE_IMPORTANCE_FOR_DISCOVERY) {
                     discoveryPreferences.add(routerRecord.mDiscoveryPreference);
                 }
-                mUserRecord.mCompositeDiscoveryPreference =
-                        new RouteDiscoveryPreference.Builder(discoveryPreferences)
-                                .build();
+            }
+
+            synchronized (service.mLock) {
+                RouteDiscoveryPreference newPreference =
+                        new RouteDiscoveryPreference.Builder(discoveryPreferences).build();
+                if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)) {
+                    return;
+                }
+                mUserRecord.mCompositeDiscoveryPreference = newPreference;
             }
             for (MediaRoute2Provider provider : mRouteProviders) {
                 provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 0e52a67..b6d6cc4 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -544,6 +544,18 @@
 
     // Binder call
     @Override
+    public void startScan(IMediaRouter2Manager manager) {
+        mService2.startScan(manager);
+    }
+
+    // Binder call
+    @Override
+    public void stopScan(IMediaRouter2Manager manager) {
+        mService2.stopScan(manager);
+    }
+
+    // Binder call
+    @Override
     public void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
             MediaRoute2Info route, int volume) {
         mService2.setRouteVolumeWithManager(manager, requestId, route, volume);