MediaRouterService binds services when necessary

MediaRouterService maintained bindings to provider services
once those are established.

With this CL, it only binds services when there is a non-empty
discovery preference set by a foreground app or an app is casting.

This change may break output switcher, which enables transfer media
of background apps.

To alleviate that MediaRouter2Manager#startScan and #stopScan are added
so that system UI can force the service bind to the services and find
remote devices to cast.

Bug: 169575701
Bug: 172920557

Test: manually and CTS
Change-Id: I4a47fdb1c9fe05a04d26950485833c9cbfb91a69
This commit is contained in:
Kyunglyul Hyun
2020-10-15 06:20:45 +00:00
parent 0ee3a89e21
commit 9f889ca4e6
8 changed files with 183 additions and 9 deletions

View File

@@ -73,6 +73,8 @@ interface IMediaRouterService {
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);

View File

@@ -146,6 +146,36 @@ public final class MediaRouter2Manager {
}
}
/**
* 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.

View File

@@ -153,6 +153,7 @@ public final class RouteDiscoveryPreference implements Parcelable {
return false;
}
RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
//TODO: Make this order-free
return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
&& mShouldPerformActiveScan == other.mShouldPerformActiveScan;
}

View File

@@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable;
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 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
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 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
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 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
mLocalMediaManager.stopScan();
mLocalMediaManager.registerCallback(this);
mLocalMediaManager.startScan();
mRouterManager.startScan();
}
void stop() {
@@ -144,6 +148,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
mLocalMediaManager.unregisterCallback(this);
mLocalMediaManager.stopScan();
}
if (mRouterManager != null) {
mRouterManager.stopScan();
}
mMediaDevices.clear();
}

View File

@@ -77,7 +77,7 @@ abstract class MediaRoute2Provider {
@NonNull
public List<RoutingSessionInfo> getSessionInfos() {
synchronized (mLock) {
return mSessionInfos;
return new ArrayList<>(mSessionInfos);
}
}

View File

@@ -108,8 +108,8 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
mLastDiscoveryPreference = discoveryPreference;
if (mConnectionReady) {
mActiveConnection.updateDiscoveryPreference(discoveryPreference);
updateBinding();
}
updateBinding();
}
@Override
@@ -205,9 +205,11 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
}
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;
}

View File

@@ -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 @@ class MediaRouter2ServiceImpl {
// 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 @@ class MediaRouter2ServiceImpl {
@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 @@ class MediaRouter2ServiceImpl {
}
}
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 @@ class MediaRouter2ServiceImpl {
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 @@ class MediaRouter2ServiceImpl {
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 @@ class MediaRouter2ServiceImpl {
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 @@ class MediaRouter2ServiceImpl {
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 @@ class MediaRouter2ServiceImpl {
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 @@ class MediaRouter2ServiceImpl {
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);

View File

@@ -542,6 +542,18 @@ public final class MediaRouterService extends IMediaRouterService.Stub
mService2.unregisterManager(manager);
}
// 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,