Merge "Media: Introduce MediaRouter2"
This commit is contained in:
committed by
Android (Google) Code Review
commit
c9b19fcaf9
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
25
media/java/android/media/IMediaRouter2Client.aidl
Normal file
25
media/java/android/media/IMediaRouter2Client.aidl
Normal 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();
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
19
media/java/android/media/MediaRoute2Info.aidl
Normal file
19
media/java/android/media/MediaRoute2Info.aidl
Normal 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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
237
media/java/android/media/MediaRouter2.java
Normal file
237
media/java/android/media/MediaRouter2.java
Normal 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 {}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user