Merge "Media: Introduce MediaRouter2"

This commit is contained in:
Kyunglyul Hyun
2019-05-24 06:00:13 +00:00
committed by Android (Google) Code Review
15 changed files with 495 additions and 77 deletions

View File

@@ -484,8 +484,9 @@ java_defaults {
"media/java/android/media/IMediaResourceMonitor.aidl",
"media/java/android/media/IMediaRoute2Provider.aidl",
"media/java/android/media/IMediaRoute2ProviderClient.aidl",
"media/java/android/media/IMediaRouterClient.aidl",
"media/java/android/media/IMediaRouter2Client.aidl",
"media/java/android/media/IMediaRouter2Manager.aidl",
"media/java/android/media/IMediaRouterClient.aidl",
"media/java/android/media/IMediaRouterService.aidl",
"media/java/android/media/IMediaScannerListener.aidl",
"media/java/android/media/IMediaScannerService.aidl",

View File

@@ -16,6 +16,7 @@
package android.media;
import android.content.Intent;
import android.media.IMediaRoute2ProviderClient;
/**
@@ -23,5 +24,6 @@ import android.media.IMediaRoute2ProviderClient;
*/
oneway interface IMediaRoute2Provider {
void registerClient(IMediaRoute2ProviderClient client);
void selectRoute(int uid, String id);
void selectRoute(IMediaRoute2ProviderClient client, int uid, String id);
void notifyControlRequestSent(IMediaRoute2ProviderClient client, String id, in Intent request);
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
/**
* @hide
*/
oneway interface IMediaRouter2Client {
void notifyStateChanged();
void notifyRestoreRoute();
}

View File

@@ -16,26 +16,35 @@
package android.media;
import android.media.IMediaRouterClient;
import android.content.Intent;
import android.media.IMediaRouter2Client;
import android.media.IMediaRouter2Manager;
import android.media.IMediaRouterClient;
import android.media.MediaRoute2Info;
import android.media.MediaRouterClientState;
/**
* {@hide}
*/
interface IMediaRouterService {
//TODO: Merge or remove methods when media router 2 is done.
void registerClientAsUser(IMediaRouterClient client, String packageName, int userId);
void unregisterClient(IMediaRouterClient client);
MediaRouterClientState getState(IMediaRouterClient client);
boolean isPlaybackActive(IMediaRouterClient client);
void setControlCategories(IMediaRouterClient client, in List<String> categories);
void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
void requestSetVolume(IMediaRouterClient client, String routeId, int volume);
void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction);
// Methods for media router 2
void registerClient2AsUser(IMediaRouter2Client client, String packageName, int userId);
void unregisterClient2(IMediaRouter2Client client);
void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request);
void setControlCategories(IMediaRouter2Client client, in List<String> categories);
void registerManagerAsUser(IMediaRouter2Manager manager,
String packageName, int userId);
void unregisterManager(IMediaRouter2Manager manager);

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
parcelable MediaRoute2Info;

View File

@@ -137,9 +137,8 @@ public final class MediaRoute2ProviderInfo implements Parcelable {
}
public Builder(@NonNull MediaRoute2ProviderInfo descriptor) {
if (descriptor == null) {
throw new IllegalArgumentException("descriptor must not be null");
}
Objects.requireNonNull(descriptor, "descriptor must not be null");
mRoutes = new ArrayMap<>(descriptor.mRoutes);
}

View File

@@ -64,7 +64,16 @@ public abstract class MediaRoute2ProviderService extends Service {
public abstract void onSelect(int uid, String routeId);
/**
* Updates provider info from selected route and appliation.
* Called when sendControlRequest is called on a route of the provider.
*
* @param routeId The id of the target route
* @param request The media control request intent
*/
//TODO: Discuss what to use for request (e.g., Intent? Request class?)
public abstract void onControlRequest(String routeId, Intent request);
/**
* Updates provider info from selected route and application.
*
* TODO: When provider descriptor is defined, this should update the descriptor correctly.
*
@@ -117,9 +126,16 @@ public abstract class MediaRoute2ProviderService extends Service {
}
@Override
public void selectRoute(int uid, String id) {
public void selectRoute(IMediaRoute2ProviderClient client, int uid, String id) {
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelect,
MediaRoute2ProviderService.this, uid, id));
}
@Override
public void notifyControlRequestSent(IMediaRoute2ProviderClient client, String id,
Intent request) {
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onControlRequest,
MediaRoute2ProviderService.this, id, request));
}
}
}

View File

@@ -347,17 +347,6 @@ public class MediaRouter {
return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
}
void setControlCategories(List<String> categories) {
if (mClient != null) {
try {
mMediaRouterService.setControlCategories(mClient,
categories);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to set control categories.", ex);
}
}
}
private void updatePresentationDisplays(int changedDisplayId) {
final int count = mRoutes.size();
for (int i = 0; i < count; i++) {
@@ -930,25 +919,6 @@ public class MediaRouter {
return -1;
}
//TODO: Remove @hide when it is ready.
//TODO: Provide pre-defined categories for app developers.
/**
* Sets control categories of the client application.
* Control categories can be used to filter out media routes
* that don't correspond with the client application.
* The only routes that match any of the categories will be shown on other applications.
*
* @hide
* @param categories Categories to set
*/
public void setControlCategories(@NonNull List<String> categories) {
if (categories == null) {
throw new IllegalArgumentException("Categories must not be null");
}
sStatic.setControlCategories(categories);
}
/**
* Select the specified route to use for output of the given media types.
* <p class="note">

View File

@@ -0,0 +1,237 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* A new Media Router
* @hide
*/
public class MediaRouter2 {
private static final String TAG = "MediaRouter";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static MediaRouter2 sInstance;
private Context mContext;
private final IMediaRouterService mMediaRouterService;
private List<CallbackRecord> mCallbackRecords = new ArrayList<>();
final String mPackageName;
IMediaRouter2Client mClient;
/**
* Gets an instance of the media router associated with the context.
*/
public static MediaRouter2 getInstance(@NonNull Context context) {
synchronized (sLock) {
if (sInstance == null) {
sInstance = new MediaRouter2(context);
}
return sInstance;
}
}
private MediaRouter2(Context context) {
mContext = Objects.requireNonNull(context, "context must not be null");
mMediaRouterService = IMediaRouterService.Stub.asInterface(
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
mPackageName = mContext.getPackageName();
}
/**
* Registers a callback to discover routes that match the selector and to receive events
* when they change.
*/
@MainThread
public void addCallback(@NonNull List<String> controlCategories, @NonNull Executor executor,
@NonNull Callback callback) {
addCallback(controlCategories, executor, callback, 0);
}
/**
* Registers a callback to discover routes that match the selector and to receive events
* when they change.
* <p>
* If you add the same callback twice or more, the previous arguments will be overwritten
* with the new arguments.
* </p>
*/
@MainThread
public void addCallback(@NonNull List<String> controlCategories, @NonNull Executor executor,
@NonNull Callback callback, int flags) {
checkMainThread();
Objects.requireNonNull(controlCategories, "control categories must not be null");
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(callback, "callback must not be null");
if (mCallbackRecords.size() == 0) {
Client client = new Client();
try {
mMediaRouterService.registerClient2AsUser(client, mPackageName,
UserHandle.myUserId());
//TODO: We should merge control categories of callbacks.
mMediaRouterService.setControlCategories(client, controlCategories);
mClient = client;
} catch (RemoteException ex) {
Log.e(TAG, "Unable to register media router.", ex);
}
}
final int index = findCallbackRecordIndex(callback);
CallbackRecord record;
if (index < 0) {
record = new CallbackRecord(callback);
mCallbackRecords.add(record);
} else {
record = mCallbackRecords.get(index);
}
record.mExecutor = executor;
record.mControlCategories = controlCategories;
record.mFlags = flags;
//TODO: Check if we need an update.
}
/**
* Removes the given callback. The callback will no longer receive events.
* If the callback has not been added or been removed already, it is ignored.
* @param callback the callback to remove.
* @see #addCallback
*/
@MainThread
public void removeCallback(@NonNull Callback callback) {
checkMainThread();
Objects.requireNonNull(callback, "callback must not be null");
final int index = findCallbackRecordIndex(callback);
if (index < 0) {
Log.w(TAG, "Ignoring to remove unknown callback. " + callback);
return;
}
mCallbackRecords.remove(index);
if (mCallbackRecords.size() == 0 && mClient != null) {
try {
mMediaRouterService.unregisterClient2(mClient);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to unregister media router.", ex);
}
mClient = null;
}
}
private int findCallbackRecordIndex(Callback callback) {
final int count = mCallbackRecords.size();
for (int i = 0; i < count; i++) {
CallbackRecord callbackRecord = mCallbackRecords.get(i);
if (callbackRecord.mCallback == callback) {
return i;
}
}
return -1;
}
/**
* Sends a media control request to be performed asynchronously by the route's destination.
* @param route the route that will receive the control request
* @param request the media control request
*/
//TODO: Discuss what to use for request (e.g., Intent? Request class?)
//TODO: Provide a way to obtain the result
public void sendControlRequest(@NonNull MediaRoute2Info route, @NonNull Intent request) {
Objects.requireNonNull(route, "route must not be null");
Objects.requireNonNull(request, "request must not be null");
if (mClient != null) {
try {
mMediaRouterService.sendControlRequest(mClient, route, request);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to send control request.", ex);
}
}
}
void checkMainThread() {
Looper looper = Looper.myLooper();
if (looper == null || looper != Looper.getMainLooper()) {
throw new IllegalStateException("the method must be called on the main thread");
}
}
/**
* Interface for receiving events about media routing changes.
*/
public static class Callback {
/**
* Called when a route is added.
*/
public void onRouteAdded(MediaRoute2Info routeInfo) {}
/**
* Called when a route is changed.
*/
public void onRouteChanged(MediaRoute2Info routeInfo) {}
/**
* Called when a route is removed.
*/
public void onRouteRemoved(MediaRoute2Info routeInfo) {}
}
final class CallbackRecord {
public final Callback mCallback;
public Executor mExecutor;
public List<String> mControlCategories;
public int mFlags;
CallbackRecord(@NonNull Callback callback) {
mCallback = Objects.requireNonNull(callback, "callback must not be null");
mControlCategories = Collections.emptyList();
}
}
class Client extends IMediaRouter2Client.Stub {
@Override
public void notifyStateChanged() throws RemoteException {}
@Override
public void notifyRestoreRoute() throws RemoteException {}
}
}

View File

@@ -35,6 +35,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -62,14 +63,11 @@ public class MediaRouter2Manager {
private List<MediaRoute2ProviderInfo> mProviders = Collections.emptyList();
/**
* Gets an instance of media router manager that controls media route of other apps.
* @param context
* @return
* Gets an instance of media router manager that controls media route of other applications.
* @return The media router manager instance for the context.
*/
public static MediaRouter2Manager getInstance(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("context must not be null");
}
Objects.requireNonNull(context, "context must not be null");
synchronized (sLock) {
if (sInstance == null) {
sInstance = new MediaRouter2Manager(context);

View File

@@ -28,16 +28,23 @@ import java.util.Map;
public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService {
private static final String TAG = "SampleMediaRoute2Serv";
public static final String ROUTE_ID1 = "route_id";
public static final String ROUTE_NAME1 = "route_name";
public static final String ROUTE_ID1 = "route_id1";
public static final String ROUTE_NAME1 = "Sample Route 1";
public static final String ROUTE_ID2 = "route_id2";
public static final String ROUTE_NAME2 = "Sample Route 2";
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
private void initializeRoutes() {
MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
.build();
MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
.build();
mRoutes.put(route1.getId(), route1);
mRoutes.put(route2.getId(), route2);
}
@Override
@@ -57,6 +64,18 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
publishRoutes();
}
@Override
public void onControlRequest(String routeId, Intent request) {
if (ACTION_REMOVE_ROUTE.equals(request.getAction())) {
MediaRoute2Info route = mRoutes.get(routeId);
if (route != null) {
mRoutes.remove(routeId);
publishRoutes();
mRoutes.put(routeId, route);
}
}
}
void publishRoutes() {
MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
.addRoutes(mRoutes.values())

View File

@@ -23,8 +23,9 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent;
import android.media.MediaRoute2Info;
import android.media.MediaRouter;
import android.media.MediaRouter2;
import android.media.MediaRouter2Manager;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -49,15 +50,19 @@ public class MediaRouterManagerTest {
private static final int TARGET_UID = 109992;
// Must be the same as SampleMediaRoute2ProviderService
public static final String ROUTE_ID1 = "route_id";
public static final String ROUTE_NAME1 = "route_name";
public static final String ROUTE_ID1 = "route_id1";
public static final String ROUTE_NAME1 = "Sample Route 1";
public static final String ROUTE_ID2 = "route_id2";
public static final String ROUTE_NAME2 = "Sample Route 2";
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
private static final int AWAIT_MS = 1000;
private static final int TIMEOUT_MS = 5000;
private Context mContext;
private MediaRouter2Manager mManager;
private MediaRouter mRouter;
private MediaRouter2 mRouter;
private Executor mExecutor;
private static final List<String> TEST_CONTROL_CATEGORIES = new ArrayList();
@@ -72,15 +77,15 @@ public class MediaRouterManagerTest {
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mManager = MediaRouter2Manager.getInstance(mContext);
mRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mRouter = MediaRouter2.getInstance(mContext);
mExecutor = new ThreadPoolExecutor(
1, 20, 3, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//TODO: onRouteChanged, onRouteRemoved must be tested
//TODO: Test onRouteChanged when it's properly implemented.
@Test
public void testRouteAddedOnce() {
public void testRouteAdded() {
MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
mManager.addCallback(mExecutor, mockCallback);
@@ -91,6 +96,30 @@ public class MediaRouterManagerTest {
mManager.removeCallback(mockCallback);
}
@Test
public void testRouteRemoved() {
MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
mManager.addCallback(mExecutor, mockCallback);
MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
//TODO: Figure out a more proper way to test.
// (Control requests shouldn't be used in this way.)
InstrumentationRegistry.getInstrumentation().runOnMainSync(
(Runnable) () -> {
mRouter.addCallback(TEST_CONTROL_CATEGORIES, mExecutor, mockRouterCallback);
mRouter.sendControlRequest(
new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2).build(),
new Intent(ACTION_REMOVE_ROUTE));
mRouter.removeCallback(mockRouterCallback);
}
);
verify(mockCallback, timeout(TIMEOUT_MS)).onRouteRemoved(argThat(
(MediaRoute2Info info) ->
info.getId().equals(ROUTE_ID2) && info.getName().equals(ROUTE_NAME2)));
mManager.removeCallback(mockCallback);
}
@Test
public void controlCategoryTest() throws Exception {
final int uid = android.os.Process.myUid();
@@ -98,12 +127,19 @@ public class MediaRouterManagerTest {
MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
mManager.addCallback(mExecutor, mockCallback);
MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
verify(mockCallback, after(AWAIT_MS).never()).onControlCategoriesChanged(uid,
TEST_CONTROL_CATEGORIES);
mRouter.setControlCategories(TEST_CONTROL_CATEGORIES);
InstrumentationRegistry.getInstrumentation().runOnMainSync(
(Runnable) () -> {
mRouter.addCallback(TEST_CONTROL_CATEGORIES, mExecutor, mockRouterCallback);
mRouter.removeCallback(mockRouterCallback);
}
);
verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
.onControlCategoriesChanged(uid, TEST_CONTROL_CATEGORIES);
.onControlCategoriesChanged(uid, TEST_CONTROL_CATEGORIES);
mManager.removeCallback(mockCallback);
}

View File

@@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.media.IMediaRoute2Provider;
import android.media.IMediaRoute2ProviderClient;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
import android.os.Handler;
@@ -90,6 +91,13 @@ final class MediaRoute2ProviderProxy implements ServiceConnection {
}
}
public void sendControlRequest(MediaRoute2Info route, Intent request) {
if (mConnectionReady) {
mActiveConnection.sendControlRequest(route.getId(), request);
updateBinding();
}
}
public MediaRoute2ProviderInfo getProviderInfo() {
return mProviderInfo;
}
@@ -303,13 +311,27 @@ final class MediaRoute2ProviderProxy implements ServiceConnection {
}
public void selectRoute(int uid, String id) {
if (mClient == null) {
return;
}
try {
mProvider.selectRoute(uid, id);
mProvider.selectRoute(mClient, uid, id);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
}
}
public void sendControlRequest(String id, Intent request) {
if (mClient == null) {
return;
}
try {
mProvider.notifyControlRequestSent(mClient, id, request);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to send control request.", ex);
}
}
@Override
public void binderDied() {
mHandler.post(() -> onConnectionDied(Connection.this));

View File

@@ -20,9 +20,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.IMediaRouter2Client;
import android.media.IMediaRouter2Manager;
import android.media.IMediaRouterClient;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.os.Binder;
import android.os.Handler;
@@ -68,7 +70,7 @@ class MediaRouter2ServiceImpl {
mContext = context;
}
public void registerClientAsUser(@NonNull IMediaRouterClient client,
public void registerClientAsUser(@NonNull IMediaRouter2Client client,
@NonNull String packageName, int userId) {
Objects.requireNonNull(client, "client must not be null");
@@ -89,7 +91,7 @@ class MediaRouter2ServiceImpl {
}
}
public void unregisterClient(@NonNull IMediaRouterClient client) {
public void unregisterClient(@NonNull IMediaRouter2Client client) {
Objects.requireNonNull(client, "client must not be null");
final long token = Binder.clearCallingIdentity();
@@ -135,7 +137,23 @@ class MediaRouter2ServiceImpl {
}
}
public void setControlCategories(@NonNull IMediaRouterClient client,
public void sendControlRequest(@NonNull IMediaRouter2Client client,
@NonNull MediaRoute2Info route, @NonNull Intent request) {
Objects.requireNonNull(client, "client must not be null");
Objects.requireNonNull(route, "route must not be null");
Objects.requireNonNull(request, "request must not be null");
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
sendControlRequestLocked(client, route, request);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
public void setControlCategories(@NonNull IMediaRouter2Client client,
@Nullable List<String> categories) {
Objects.requireNonNull(client, "client must not be null");
@@ -195,7 +213,7 @@ class MediaRouter2ServiceImpl {
}
}
private void registerClientLocked(IMediaRouterClient client,
private void registerClientLocked(IMediaRouter2Client client,
int uid, int pid, String packageName, int userId, boolean trusted) {
final IBinder binder = client.asBinder();
ClientRecord clientRecord = mAllClientRecords.get(binder);
@@ -223,7 +241,7 @@ class MediaRouter2ServiceImpl {
}
}
private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
private void unregisterClientLocked(IMediaRouter2Client client, boolean died) {
ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
if (clientRecord != null) {
UserRecord userRecord = clientRecord.mUserRecord;
@@ -261,11 +279,11 @@ class MediaRouter2ServiceImpl {
mAllManagerRecords.put(binder, managerRecord);
//TODO: remove this when it's unnecessary
// Sends published routes to newly added manager
// Sends published routes to newly added manager.
userRecord.mHandler.scheduleUpdateManagerState();
final int count = userRecord.mClientRecords.size();
for (int i = 0; i < count; ++i) {
for (int i = 0; i < count; i++) {
ClientRecord clientRecord = userRecord.mClientRecords.get(i);
clientRecord.mUserRecord.mHandler.obtainMessage(
UserHandler.MSG_UPDATE_CLIENT_USAGE, clientRecord).sendToTarget();
@@ -295,7 +313,7 @@ class MediaRouter2ServiceImpl {
}
}
private void setControlCategoriesLocked(IMediaRouterClient client, List<String> categories) {
private void setControlCategoriesLocked(IMediaRouter2Client client, List<String> categories) {
final IBinder binder = client.asBinder();
ClientRecord clientRecord = mAllClientRecords.get(binder);
@@ -306,6 +324,18 @@ class MediaRouter2ServiceImpl {
}
}
private void sendControlRequestLocked(IMediaRouter2Client client, MediaRoute2Info route,
Intent request) {
final IBinder binder = client.asBinder();
ClientRecord clientRecord = mAllClientRecords.get(binder);
if (clientRecord != null) {
Pair<MediaRoute2Info, Intent> obj = new Pair<>(route, request);
clientRecord.mUserRecord.mHandler.obtainMessage(
UserHandler.MSG_SEND_CONTROL_REQUEST, obj).sendToTarget();
}
}
private void initializeUserLocked(UserRecord userRecord) {
if (DEBUG) {
Slog.d(TAG, userRecord + ": Initialized");
@@ -345,14 +375,14 @@ class MediaRouter2ServiceImpl {
final class ClientRecord implements IBinder.DeathRecipient {
public final UserRecord mUserRecord;
public final IMediaRouterClient mClient;
public final IMediaRouter2Client mClient;
public final int mUid;
public final int mPid;
public final String mPackageName;
public final boolean mTrusted;
public List<String> mControlCategories;
ClientRecord(UserRecord userRecord, IMediaRouterClient client,
ClientRecord(UserRecord userRecord, IMediaRouter2Client client,
int uid, int pid, String packageName, boolean trusted) {
mUserRecord = userRecord;
mClient = client;
@@ -424,6 +454,7 @@ class MediaRouter2ServiceImpl {
private static final int MSG_SELECT_REMOTE_ROUTE = 10;
private static final int MSG_UPDATE_CLIENT_USAGE = 11;
private static final int MSG_UPDATE_MANAGER_STATE = 12;
private static final int MSG_SEND_CONTROL_REQUEST = 13;
private final WeakReference<MediaRouter2ServiceImpl> mServiceRef;
private final UserRecord mUserRecord;
@@ -468,6 +499,10 @@ class MediaRouter2ServiceImpl {
updateManagerState();
break;
}
case MSG_SEND_CONTROL_REQUEST: {
Pair<MediaRoute2Info, Intent> obj = (Pair<MediaRoute2Info, Intent>) msg.obj;
sendControlRequest(obj.first, obj.second);
}
}
}
@@ -512,12 +547,19 @@ class MediaRouter2ServiceImpl {
final int providerCount = mMediaProviders.size();
//TODO: should find proper provider (currently assumes a single provider)
for (int i = 0; i < providerCount; ++i) {
for (int i = 0; i < providerCount; i++) {
mMediaProviders.get(i).setSelectedRoute(uid, routeId);
}
}
}
private void sendControlRequest(MediaRoute2Info route, Intent request) {
final int providerCount = mMediaProviders.size();
for (int i = 0; i < providerCount; i++) {
mMediaProviders.get(i).sendControlRequest(route, request);
}
}
private void scheduleUpdateManagerState() {
if (!mManagerStateUpdateScheduled) {
mManagerStateUpdateScheduled = true;

View File

@@ -30,9 +30,11 @@ import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
import android.media.IAudioService;
import android.media.IMediaRouter2Client;
import android.media.IMediaRouter2Manager;
import android.media.IMediaRouterClient;
import android.media.IMediaRouterService;
import android.media.MediaRoute2Info;
import android.media.MediaRouter;
import android.media.MediaRouterClientState;
import android.media.RemoteDisplayState;
@@ -250,7 +252,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub
} finally {
Binder.restoreCallingIdentity(token);
}
mService2.registerClientAsUser(client, packageName, userId);
}
// Binder call
@@ -268,7 +269,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub
} finally {
Binder.restoreCallingIdentity(token);
}
mService2.unregisterClient(client);
}
// Binder call
@@ -310,12 +310,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub
}
}
// Binder call
@Override
public void setControlCategories(IMediaRouterClient client, List<String> categories) {
mService2.setControlCategories(client, categories);
}
// Binder call
@Override
public void setDiscoveryRequest(IMediaRouterClient client,
@@ -416,6 +410,29 @@ public final class MediaRouterService extends IMediaRouterService.Stub
}
}
// Binder call
@Override
public void registerClient2AsUser(IMediaRouter2Client client, String packageName, int userId) {
final int uid = Binder.getCallingUid();
if (!validatePackageName(uid, packageName)) {
throw new SecurityException("packageName must match the calling uid");
}
mService2.registerClientAsUser(client, packageName, userId);
}
// Binder call
@Override
public void unregisterClient2(IMediaRouter2Client client) {
mService2.unregisterClient(client);
}
// Binder call
@Override
public void sendControlRequest(IMediaRouter2Client client, MediaRoute2Info route,
Intent request) {
mService2.sendControlRequest(client, route, request);
}
// Binder call
@Override
public void registerManagerAsUser(IMediaRouter2Manager manager,
@@ -440,6 +457,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub
mService2.setRemoteRoute(manager, uid, routeId, explicit);
}
// Binder call
@Override
public void setControlCategories(IMediaRouter2Client client, List<String> categories) {
mService2.setControlCategories(client, categories);
}
void restoreBluetoothA2dp() {
try {
boolean a2dpOn;