From cd18ace5718db9c68fb35cbc3b6476f84ebc62a0 Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Wed, 22 May 2019 21:32:57 +0900 Subject: [PATCH] Media: Add group id for media router to sync This CL adds MediaRouter.setRouterGroupId() methods to set the "group id" of a media router. Media routers that have the same group id synchronize their selected route. For example, if System UI and Settings use the group id, you can see "connected" status from System UI even if the cast begins from Settings , and vice versa. Bug: 112826114 Bug: 131385091 Bug: 130345243 Test: manually w/ setting the same group id for Settings and System UI. Change-Id: I9d4e061b57f52d7b2bec622b5f02068f3d11c133 --- .../android/media/IMediaRouterClient.aidl | 1 + .../android/media/IMediaRouterService.aidl | 2 + media/java/android/media/MediaRouter.java | 61 +++++++++ .../server/media/MediaRouterService.java | 123 +++++++++++++++++- 4 files changed, 186 insertions(+), 1 deletion(-) diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl index 08344f188098f..240ae796f9574 100644 --- a/media/java/android/media/IMediaRouterClient.aidl +++ b/media/java/android/media/IMediaRouterClient.aidl @@ -22,4 +22,5 @@ package android.media; oneway interface IMediaRouterClient { void onStateChanged(); void onRestoreRoute(); + void onSelectedRouteChanged(String routeId); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 3308fc929b035..04c2d074722d4 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -26,6 +26,8 @@ interface IMediaRouterService { void registerClientAsUser(IMediaRouterClient client, String packageName, int userId); void unregisterClient(IMediaRouterClient client); + void registerClientGroupId(IMediaRouterClient client, String groupId); + MediaRouterClientState getState(IMediaRouterClient client); boolean isPlaybackActive(IMediaRouterClient client); diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 3444e92779490..d72231f40dcf8 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemService; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; @@ -343,6 +344,16 @@ public class MediaRouter { updatePresentationDisplays(displayId); } + public void setRouterGroupId(String groupId) { + if (mClient != null) { + try { + mMediaRouterService.registerClientGroupId(mClient, groupId); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to register group ID of the client.", ex); + } + } + } + public Display[] getAllPresentationDisplays() { return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); } @@ -358,6 +369,21 @@ public class MediaRouter { } } + void updateSelectedRouteForId(String routeId) { + RouteInfo selectedRoute = isBluetoothA2dpOn() + ? mBluetoothA2dpRoute : mDefaultAudioVideo; + final int count = mRoutes.size(); + for (int i = 0; i < count; i++) { + final RouteInfo route = mRoutes.get(i); + if (TextUtils.equals(route.mGlobalRouteId, routeId)) { + selectedRoute = route; + } + } + if (selectedRoute != mSelectedRoute) { + selectRouteStatic(selectedRoute.mSupportedTypes, selectedRoute, false); + } + } + void setSelectedRoute(RouteInfo info, boolean explicit) { // Must be non-reentrant. mSelectedRoute = info; @@ -619,6 +645,15 @@ public class MediaRouter { } }); } + + @Override + public void onSelectedRouteChanged(String routeId) { + mHandler.post(() -> { + if (Client.this == mClient) { + updateSelectedRouteForId(routeId); + } + }); + } } } @@ -728,6 +763,13 @@ public class MediaRouter { */ public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0; + /** + * The route group id used for sharing the selected mirroring device. + * System UI and Settings use this to synchronize their mirroring status. + * @hide + */ + public static final String MIRRORING_GROUP_ID = "android.media.mirroring_group"; + // Maps application contexts static final HashMap sRouters = new HashMap(); @@ -847,6 +889,25 @@ public class MediaRouter { return false; } + /** + * Sets the group ID of the router. + * Media routers with the same ID acts as if they were a single media router. + * For example, if a media router selects a route, the selected route of routers + * with the same group ID will be changed automatically. + * + * Two routers in a group are supposed to use the same route types. + * + * System UI and Settings use this to synchronize their mirroring status. + * Do not set the router group id unless it's necessary. + * + * {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY} permission is required to + * call this method. + * @hide + */ + public void setRouterGroupId(@Nullable String groupId) { + sStatic.setRouterGroupId(groupId); + } + /** * Add a callback to listen to events about specific kinds of media routes. * If the specified callback is already registered, its registration will be updated for any diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 23d3ce063a3ef..7b9fff55b6546 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -246,6 +246,29 @@ public final class MediaRouterService extends IMediaRouterService.Stub } } + // Binder call + @Override + public void registerClientGroupId(IMediaRouterClient client, String groupId) { + if (client == null) { + throw new NullPointerException("client must not be null"); + } + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "Ignoring client group request because " + + "the client doesn't have the CONFIGURE_WIFI_DISPLAY permission."); + return; + } + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + registerClientGroupIdLocked(client, groupId); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + // Binder call @Override public void unregisterClient(IMediaRouterClient client) { @@ -502,11 +525,37 @@ public final class MediaRouterService extends IMediaRouterService.Stub } } + private void registerClientGroupIdLocked(IMediaRouterClient client, String groupId) { + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord == null) { + Log.w(TAG, "Ignoring group id register request of a unregistered client."); + return; + } + if (TextUtils.equals(clientRecord.mGroupId, groupId)) { + return; + } + UserRecord userRecord = clientRecord.mUserRecord; + if (clientRecord.mGroupId != null) { + userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord); + } + clientRecord.mGroupId = groupId; + if (groupId != null) { + userRecord.addToGroup(groupId, clientRecord); + userRecord.mHandler.obtainMessage(UserHandler.MSG_UPDATE_SELECTED_ROUTE, groupId) + .sendToTarget(); + } + } + private void unregisterClientLocked(IMediaRouterClient client, boolean died) { ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); if (clientRecord != null) { UserRecord userRecord = clientRecord.mUserRecord; userRecord.mClientRecords.remove(clientRecord); + if (clientRecord.mGroupId != null) { + userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord); + clientRecord.mGroupId = null; + } disposeClientLocked(clientRecord, died); disposeUserIfNeededLocked(userRecord); // since client removed from user } @@ -568,6 +617,16 @@ public final class MediaRouterService extends IMediaRouterService.Stub clientRecord.mUserRecord.mHandler.obtainMessage( UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget(); } + if (clientRecord.mGroupId != null) { + ClientGroup group = + clientRecord.mUserRecord.mClientGroupMap.get(clientRecord.mGroupId); + if (group != null) { + group.mSelectedRouteId = routeId; + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_UPDATE_SELECTED_ROUTE, clientRecord.mGroupId) + .sendToTarget(); + } + } } } } @@ -680,6 +739,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub public int mRouteTypes; public boolean mActiveScan; public String mSelectedRouteId; + public String mGroupId; public ClientRecord(UserRecord userRecord, IMediaRouterClient client, int uid, int pid, String packageName, boolean trusted) { @@ -720,6 +780,11 @@ public final class MediaRouterService extends IMediaRouterService.Stub } } + final class ClientGroup { + public String mSelectedRouteId; + public final List mClientRecords = new ArrayList<>(); + } + /** * Information about a particular user. * The contents of this object is guarded by mLock. @@ -729,6 +794,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub public final ArrayList mClientRecords = new ArrayList(); public final UserHandler mHandler; public MediaRouterClientState mRouterState; + private final ArrayMap mClientGroupMap = new ArrayMap<>(); public UserRecord(int userId) { mUserId = userId; @@ -759,7 +825,26 @@ public final class MediaRouterService extends IMediaRouterService.Stub }, 1000)) { pw.println(indent + ""); } - } + } + + public void addToGroup(String groupId, ClientRecord clientRecord) { + ClientGroup group = mClientGroupMap.get(groupId); + if (group == null) { + group = new ClientGroup(); + mClientGroupMap.put(groupId, group); + } + group.mClientRecords.add(clientRecord); + } + + public void removeFromGroup(String groupId, ClientRecord clientRecord) { + ClientGroup group = mClientGroupMap.get(groupId); + if (group != null) { + group.mClientRecords.remove(clientRecord); + if (group.mClientRecords.size() == 0) { + mClientGroupMap.remove(groupId); + } + } + } @Override public String toString() { @@ -791,6 +876,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub public static final int MSG_REQUEST_UPDATE_VOLUME = 7; private static final int MSG_UPDATE_CLIENT_STATE = 8; private static final int MSG_CONNECTION_TIMED_OUT = 9; + private static final int MSG_UPDATE_SELECTED_ROUTE = 10; private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1; private static final int TIMEOUT_REASON_CONNECTION_LOST = 2; @@ -867,6 +953,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub connectionTimedOut(); break; } + case MSG_UPDATE_SELECTED_ROUTE: { + updateSelectedRoute((String) msg.obj); + break; + } } } @@ -1191,6 +1281,37 @@ public final class MediaRouterService extends IMediaRouterService.Stub } } + private void updateSelectedRoute(String groupId) { + try { + String selectedRouteId = null; + synchronized (mService.mLock) { + ClientGroup group = mUserRecord.mClientGroupMap.get(groupId); + if (group == null) { + return; + } + selectedRouteId = group.mSelectedRouteId; + final int count = group.mClientRecords.size(); + for (int i = 0; i < count; i++) { + ClientRecord clientRecord = group.mClientRecords.get(i); + if (!TextUtils.equals(selectedRouteId, clientRecord.mSelectedRouteId)) { + mTempClients.add(clientRecord.mClient); + } + } + } + + final int count = mTempClients.size(); + for (int i = 0; i < count; i++) { + try { + mTempClients.get(i).onSelectedRouteChanged(selectedRouteId); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to call onSelectedRouteChanged. Client probably died."); + } + } + } finally { + mTempClients.clear(); + } + } + private int findProviderRecord(RemoteDisplayProviderProxy provider) { final int count = mProviderRecords.size(); for (int i = 0; i < count; i++) {