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
This commit is contained in:
Kyunglyul Hyun
2019-04-29 18:07:21 +09:00
parent ff783371d9
commit dafac54a40
3 changed files with 204 additions and 77 deletions

View File

@@ -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())

View File

@@ -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<UserRecord> mUserRecords = new SparseArray<>();
@GuardedBy("mLock")
private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
@GuardedBy("mLock")
private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
@GuardedBy("mLock")
private final ArrayMap<IBinder, ManagerRecord> 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<String> categories) {
if (client == null) {
throw new IllegalArgumentException("client must not be null");
}
public void setControlCategories(@NonNull IMediaRouterClient client,
@Nullable List<String> 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<Integer, String> obj = new Pair<>(uid, routeId);
@@ -202,15 +296,14 @@ class MediaRouter2ServiceImpl {
}
private void setControlCategoriesLocked(IMediaRouterClient client, List<String> 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<ClientRecord> mClientRecords = new ArrayList<>();
final ArrayList<ManagerRecord> 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<String> 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<IMediaRouter2Manager> 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<IMediaRouter2Manager> 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);
}
}
}
}
}

View File

@@ -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() {