From dafac54a408fa1af120c682794415b46b1afd414 Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Mon, 29 Apr 2019 18:07:21 +0900 Subject: [PATCH] Media: Add ClientRecord in MediaRouter2Service ClientRecord is a record for media router client. Save the client info on MediaRouter2Service to make setControlCategories work and MediaRouter2Manager get the callback onControlCategoriesChanged. Test: atest mediaroutertest (after atest mediarouteprovider) Change-Id: Iaf79242e18a699e339e91c8b0baceb767dc36bd4 --- .../MediaRouterManagerTest.java | 6 +- .../server/media/MediaRouter2ServiceImpl.java | 261 +++++++++++++----- .../server/media/MediaRouterService.java | 14 +- 3 files changed, 204 insertions(+), 77 deletions(-) diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index de30e6c91da7e..65d9d0638f098 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -18,8 +18,6 @@ package com.android.mediaroutertest; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.after; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -100,8 +98,8 @@ public class MediaRouterManagerTest { MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class); mManager.addCallback(mExecutor, mockCallback); - verify(mockCallback, after(AWAIT_MS).never()).onControlCategoriesChanged(eq(uid), - any(List.class)); + verify(mockCallback, after(AWAIT_MS).never()).onControlCategoriesChanged(uid, + TEST_CONTROL_CATEGORIES); mRouter.setControlCategories(TEST_CONTROL_CATEGORIES); verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce()) diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 8f6a6a97e082d..2ecc082c24c5b 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -16,8 +16,11 @@ package com.android.server.media; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; +import android.content.pm.PackageManager; import android.media.IMediaRouter2Manager; import android.media.IMediaRouterClient; import android.media.MediaRoute2ProviderInfo; @@ -33,10 +36,14 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; + import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; /** * TODO: Merge this to MediaRouterService once it's finished. @@ -47,20 +54,57 @@ class MediaRouter2ServiceImpl { private final Context mContext; private final Object mLock = new Object(); - private final SparseArray mUserRecords = new SparseArray<>(); + @GuardedBy("mLock") + private final SparseArray mUserRecords = new SparseArray<>(); + @GuardedBy("mLock") + private final ArrayMap mAllClientRecords = new ArrayMap<>(); + @GuardedBy("mLock") private final ArrayMap mAllManagerRecords = new ArrayMap<>(); + @GuardedBy("mLock") private int mCurrentUserId = -1; MediaRouter2ServiceImpl(Context context) { mContext = context; } - public void registerManagerAsUser(IMediaRouter2Manager client, - String packageName, int userId) { - if (client == null) { - throw new IllegalArgumentException("client must not be null"); + public void registerClientAsUser(@NonNull IMediaRouterClient client, + @NonNull String packageName, int userId) { + Objects.requireNonNull(client, "client must not be null"); + + final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); + final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, + false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName); + final boolean trusted = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + == PackageManager.PERMISSION_GRANTED; + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted); + } + } finally { + Binder.restoreCallingIdentity(token); } + } + + public void unregisterClient(@NonNull IMediaRouterClient client) { + Objects.requireNonNull(client, "client must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + unregisterClientLocked(client, false); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public void registerManagerAsUser(@NonNull IMediaRouter2Manager manager, + @NonNull String packageName, int userId) { + Objects.requireNonNull(manager, "manager must not be null"); //TODO: should check permission final boolean trusted = true; @@ -71,32 +115,30 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - registerManagerLocked(client, uid, pid, packageName, resolvedUserId, trusted); + registerManagerLocked(manager, uid, pid, packageName, resolvedUserId, trusted); } } finally { Binder.restoreCallingIdentity(token); } } - public void unregisterManager(IMediaRouter2Manager client) { - if (client == null) { - throw new IllegalArgumentException("client must not be null"); - } + public void unregisterManager(@NonNull IMediaRouter2Manager manager) { + Objects.requireNonNull(manager, "manager must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - unregisterManagerLocked(client, false); + unregisterManagerLocked(manager, false); } } finally { Binder.restoreCallingIdentity(token); } } - public void setControlCategories(IMediaRouterClient client, List categories) { - if (client == null) { - throw new IllegalArgumentException("client must not be null"); - } + public void setControlCategories(@NonNull IMediaRouterClient client, + @Nullable List categories) { + Objects.requireNonNull(client, "client must not be null"); + final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -107,12 +149,12 @@ class MediaRouter2ServiceImpl { } } - public void setRemoteRoute(IMediaRouter2Manager client, - int uid, String routeId, boolean explicit) { + public void setRemoteRoute(@NonNull IMediaRouter2Manager manager, + int uid, @Nullable String routeId, boolean explicit) { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - setRemoteRouteLocked(client, uid, routeId, explicit); + setRemoteRouteLocked(manager, uid, routeId, explicit); } } finally { Binder.restoreCallingIdentity(token); @@ -141,15 +183,60 @@ class MediaRouter2ServiceImpl { } } - void clientDied(ManagerRecord managerRecord) { + void clientDied(ClientRecord clientRecord) { synchronized (mLock) { - unregisterManagerLocked(managerRecord.mClient, true); + unregisterClientLocked(clientRecord.mClient, true); } } - private void registerManagerLocked(IMediaRouter2Manager client, + void managerDied(ManagerRecord managerRecord) { + synchronized (mLock) { + unregisterManagerLocked(managerRecord.mManager, true); + } + } + + private void registerClientLocked(IMediaRouterClient client, int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord == null) { + boolean newUser = false; + UserRecord userRecord = mUserRecords.get(userId); + if (userRecord == null) { + userRecord = new UserRecord(userId); + newUser = true; + } + clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted); + try { + binder.linkToDeath(clientRecord, 0); + } catch (RemoteException ex) { + throw new RuntimeException("Media router client died prematurely.", ex); + } + + if (newUser) { + mUserRecords.put(userId, userRecord); + initializeUserLocked(userRecord); + } + + userRecord.mClientRecords.add(clientRecord); + mAllClientRecords.put(binder, clientRecord); + } + } + + private void unregisterClientLocked(IMediaRouterClient client, boolean died) { + ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); + if (clientRecord != null) { + UserRecord userRecord = clientRecord.mUserRecord; + userRecord.mClientRecords.remove(clientRecord); + //TODO: update discovery request + clientRecord.dispose(); + disposeUserIfNeededLocked(userRecord); // since client removed from user + } + } + + private void registerManagerLocked(IMediaRouter2Manager manager, + int uid, int pid, String packageName, int userId, boolean trusted) { + final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); if (managerRecord == null) { boolean newUser = false; @@ -158,11 +245,11 @@ class MediaRouter2ServiceImpl { userRecord = new UserRecord(userId); newUser = true; } - managerRecord = new ManagerRecord(userRecord, client, uid, pid, packageName, trusted); + managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName, trusted); try { binder.linkToDeath(managerRecord, 0); } catch (RemoteException ex) { - throw new RuntimeException("Media router client died prematurely.", ex); + throw new RuntimeException("Media router manager died prematurely.", ex); } if (newUser) { @@ -176,22 +263,29 @@ class MediaRouter2ServiceImpl { //TODO: remove this when it's unnecessary // Sends published routes to newly added manager userRecord.mHandler.scheduleUpdateManagerState(); + + final int count = userRecord.mClientRecords.size(); + for (int i = 0; i < count; ++i) { + ClientRecord clientRecord = userRecord.mClientRecords.get(i); + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_UPDATE_CLIENT_USAGE, clientRecord).sendToTarget(); + } } } - private void unregisterManagerLocked(IMediaRouter2Manager client, boolean died) { - ManagerRecord clientRecord = mAllManagerRecords.remove(client.asBinder()); - if (clientRecord != null) { - UserRecord userRecord = clientRecord.mUserRecord; - userRecord.mManagerRecords.remove(clientRecord); - clientRecord.dispose(); - disposeUserIfNeededLocked(userRecord); // since client removed from user + private void unregisterManagerLocked(IMediaRouter2Manager manager, boolean died) { + ManagerRecord managerRecord = mAllManagerRecords.remove(manager.asBinder()); + if (managerRecord != null) { + UserRecord userRecord = managerRecord.mUserRecord; + userRecord.mManagerRecords.remove(managerRecord); + managerRecord.dispose(); + disposeUserIfNeededLocked(userRecord); // since manager removed from user } } - private void setRemoteRouteLocked(IMediaRouter2Manager client, + private void setRemoteRouteLocked(IMediaRouter2Manager manager, int uid, String routeId, boolean explicit) { - ManagerRecord managerRecord = mAllManagerRecords.get(client.asBinder()); + ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder()); if (managerRecord != null) { if (explicit && managerRecord.mTrusted) { Pair obj = new Pair<>(uid, routeId); @@ -202,15 +296,14 @@ class MediaRouter2ServiceImpl { } private void setControlCategoriesLocked(IMediaRouterClient client, List categories) { - //TODO: implement this when we have client record (MediaRouter2?) -// final IBinder binder = client.asBinder(); -// ClientRecord clientRecord = mAllClientRecords.get(binder); -// -// if (clientRecord != null) { -// clientRecord.mControlCategories = categories; -// clientRecord.mUserRecord.mHandler.obtainMessage( -// UserHandler.MSG_UPDATE_CLIENT_USAGE, clientRecord).sendToTarget(); -// } + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + + if (clientRecord != null) { + clientRecord.mControlCategories = categories; + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_UPDATE_CLIENT_USAGE, clientRecord).sendToTarget(); + } } private void initializeUserLocked(UserRecord userRecord) { @@ -228,6 +321,7 @@ class MediaRouter2ServiceImpl { // then leave it alone since we might be connected to a route or want to query // the same route information again soon. if (userRecord.mUserId != mCurrentUserId + && userRecord.mClientRecords.isEmpty() && userRecord.mManagerRecords.isEmpty()) { if (DEBUG) { Slog.d(TAG, userRecord + ": Disposed"); @@ -239,6 +333,7 @@ class MediaRouter2ServiceImpl { final class UserRecord { public final int mUserId; + final ArrayList mClientRecords = new ArrayList<>(); final ArrayList mManagerRecords = new ArrayList<>(); final UserHandler mHandler; @@ -248,15 +343,16 @@ class MediaRouter2ServiceImpl { } } - final class ManagerRecord implements IBinder.DeathRecipient { + final class ClientRecord implements IBinder.DeathRecipient { public final UserRecord mUserRecord; - public final IMediaRouter2Manager mClient; + public final IMediaRouterClient mClient; public final int mUid; public final int mPid; public final String mPackageName; public final boolean mTrusted; + public List mControlCategories; - ManagerRecord(UserRecord userRecord, IMediaRouter2Manager client, + ClientRecord(UserRecord userRecord, IMediaRouterClient client, int uid, int pid, String packageName, boolean trusted) { mUserRecord = userRecord; mClient = client; @@ -264,6 +360,7 @@ class MediaRouter2ServiceImpl { mPid = pid; mPackageName = packageName; mTrusted = trusted; + mControlCategories = Collections.emptyList(); } public void dispose() { @@ -274,6 +371,34 @@ class MediaRouter2ServiceImpl { public void binderDied() { clientDied(this); } + } + + final class ManagerRecord implements IBinder.DeathRecipient { + public final UserRecord mUserRecord; + public final IMediaRouter2Manager mManager; + public final int mUid; + public final int mPid; + public final String mPackageName; + public final boolean mTrusted; + + ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager, + int uid, int pid, String packageName, boolean trusted) { + mUserRecord = userRecord; + mManager = manager; + mUid = uid; + mPid = pid; + mPackageName = packageName; + mTrusted = trusted; + } + + public void dispose() { + mManager.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + managerDied(this); + } public void dump(PrintWriter pw, String prefix) { pw.println(prefix + this); @@ -284,7 +409,7 @@ class MediaRouter2ServiceImpl { @Override public String toString() { - return "Client " + mPackageName + " (pid " + mPid + ")"; + return "Manager " + mPackageName + " (pid " + mPid + ")"; } } @@ -336,7 +461,7 @@ class MediaRouter2ServiceImpl { break; } case MSG_UPDATE_CLIENT_USAGE: { - updateClientUsage(); + updateClientUsage((ClientRecord) msg.obj); break; } case MSG_UPDATE_MANAGER_STATE: { @@ -424,7 +549,7 @@ class MediaRouter2ServiceImpl { synchronized (service.mLock) { final int count = mUserRecord.mManagerRecords.size(); for (int i = 0; i < count; i++) { - mTempManagers.add(mUserRecord.mManagerRecords.get(i).mClient); + mTempManagers.add(mUserRecord.mManagerRecords.get(i).mManager); } } //TODO: Call !proper callbacks when provider descriptor is implemented. @@ -445,26 +570,28 @@ class MediaRouter2ServiceImpl { } } - private void updateClientUsage() { - //TODO: merge these code to updateClientState() - -// List managers = new ArrayList<>(); -// synchronized (mService.mLock) { -// final int count = mUserRecord.mManagerRecords.size(); -// for (int i = 0; i < count; i++) { -// managers.add(mUserRecord.mManagerRecords.get(i).mClient); -// } -// } -// final int count = managers.size(); -// for (int i = 0; i < count; i++) { -// try { -// managers.get(i).onControlCategoriesChanged(clientRecord.mUid, -// clientRecord.mControlCategories); -// } catch (RemoteException ex) { -// Slog.w(TAG, "Failed to call onControlCategoriesChanged. " -// + "Manager probably died.", ex); -// } -// } + private void updateClientUsage(ClientRecord clientRecord) { + MediaRouter2ServiceImpl service = mServiceRef.get(); + if (service == null) { + return; + } + List managers = new ArrayList<>(); + synchronized (service.mLock) { + final int count = mUserRecord.mManagerRecords.size(); + for (int i = 0; i < count; i++) { + managers.add(mUserRecord.mManagerRecords.get(i).mManager); + } + } + final int count = managers.size(); + for (int i = 0; i < count; i++) { + try { + managers.get(i).notifyControlCategoriesChanged(clientRecord.mUid, + clientRecord.mControlCategories); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to call onControlCategoriesChanged. " + + "Manager probably died.", ex); + } + } } } } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index fcfa7835509de..fade906df7c0c 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -250,6 +250,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub } finally { Binder.restoreCallingIdentity(token); } + mService2.registerClientAsUser(client, packageName, userId); } // Binder call @@ -267,6 +268,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub } finally { Binder.restoreCallingIdentity(token); } + mService2.unregisterClient(client); } // Binder call @@ -416,26 +418,26 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void registerManagerAsUser(IMediaRouter2Manager client, + public void registerManagerAsUser(IMediaRouter2Manager manager, String packageName, int userId) { final int uid = Binder.getCallingUid(); if (!validatePackageName(uid, packageName)) { throw new SecurityException("packageName must match the calling uid"); } - mService2.registerManagerAsUser(client, packageName, userId); + mService2.registerManagerAsUser(manager, packageName, userId); } // Binder call @Override - public void unregisterManager(IMediaRouter2Manager client) { - mService2.unregisterManager(client); + public void unregisterManager(IMediaRouter2Manager manager) { + mService2.unregisterManager(manager); } // Binder call @Override - public void setRemoteRoute(IMediaRouter2Manager client, + public void setRemoteRoute(IMediaRouter2Manager manager, int uid, String routeId, boolean explicit) { - mService2.setRemoteRoute(client, uid, routeId, explicit); + mService2.setRemoteRoute(manager, uid, routeId, explicit); } void restoreBluetoothA2dp() {