MediaRouter: send control hints from provider to client
This CL enables providers to send control hints to the clients so connections can be established between clients and providers. This CL also adds a hidden flag such that MR2 can notice whether the route is selected by itself or MR2Manager. Uses of Mockito is completely removed from the test for better test environment. Callbacks will be unregistered automatically when a test is ended so there will be no unregistered callbacks remaining after test failures. Test: atest mediaroutertest Change-Id: I1411bad3ddd7890fbf76020107f870daa2413077
This commit is contained in:
@@ -17,10 +17,13 @@
|
||||
package android.media;
|
||||
|
||||
import android.media.MediaRoute2ProviderInfo;
|
||||
import android.media.MediaRoute2Info;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IMediaRoute2ProviderClient {
|
||||
void updateProviderInfo(in MediaRoute2ProviderInfo info);
|
||||
void notifyRouteSelected(String packageName, String routeId, in Bundle controlHints, int seq);
|
||||
}
|
||||
|
||||
@@ -18,14 +18,19 @@ package android.media;
|
||||
|
||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -44,7 +49,7 @@ public abstract class MediaRoute2ProviderService extends Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
public IBinder onBind(@NonNull Intent intent) {
|
||||
//TODO: Allow binding from media router service only?
|
||||
if (SERVICE_INTERFACE.equals(intent.getAction())) {
|
||||
if (mStub == null) {
|
||||
@@ -57,11 +62,17 @@ public abstract class MediaRoute2ProviderService extends Service {
|
||||
|
||||
/**
|
||||
* Called when selectRoute is called on a route of the provider.
|
||||
* Once the route is ready to be used , call {@link #notifyRouteSelected(SelectToken, Bundle)}
|
||||
* to notify that.
|
||||
*
|
||||
* @param packageName the package name of the application that selected the route
|
||||
* @param routeId the id of the route being selected
|
||||
* @param token token that contains select info
|
||||
*
|
||||
* @see #notifyRouteSelected
|
||||
*/
|
||||
public abstract void onSelectRoute(String packageName, String routeId);
|
||||
public abstract void onSelectRoute(@NonNull String packageName, @NonNull String routeId,
|
||||
@NonNull SelectToken token);
|
||||
|
||||
/**
|
||||
* Called when unselectRoute is called on a route of the provider.
|
||||
@@ -69,7 +80,7 @@ public abstract class MediaRoute2ProviderService extends Service {
|
||||
* @param packageName the package name of the application that has selected the route.
|
||||
* @param routeId the id of the route being unselected
|
||||
*/
|
||||
public abstract void onUnselectRoute(String packageName, String routeId);
|
||||
public abstract void onUnselectRoute(@NonNull String packageName, @NonNull String routeId);
|
||||
|
||||
/**
|
||||
* Called when sendControlRequest is called on a route of the provider
|
||||
@@ -78,21 +89,21 @@ public abstract class MediaRoute2ProviderService extends Service {
|
||||
* @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);
|
||||
public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request);
|
||||
|
||||
/**
|
||||
* Called when requestSetVolume is called on a route of the provider
|
||||
* @param routeId the id of the route
|
||||
* @param volume the target volume
|
||||
*/
|
||||
public abstract void onSetVolume(String routeId, int volume);
|
||||
public abstract void onSetVolume(@NonNull String routeId, int volume);
|
||||
|
||||
/**
|
||||
* Called when requestUpdateVolume is called on a route of the provider
|
||||
* @param routeId id of the route
|
||||
* @param delta the delta to add to the current volume
|
||||
*/
|
||||
public abstract void onUpdateVolume(String routeId, int delta);
|
||||
public abstract void onUpdateVolume(@NonNull String routeId, int delta);
|
||||
|
||||
/**
|
||||
* Updates provider info and publishes routes
|
||||
@@ -102,6 +113,29 @@ public abstract class MediaRoute2ProviderService extends Service {
|
||||
publishState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the client of that the selected route is ready for use. If the selected route can be
|
||||
* controlled, pass a {@link Bundle} that contains how to control it.
|
||||
*
|
||||
* @param token token passed in {@link #onSelectRoute}
|
||||
* @param controlHints a {@link Bundle} that contains how to control the given route.
|
||||
* Pass {@code null} if the route is not available.
|
||||
*/
|
||||
public final void notifyRouteSelected(@NonNull SelectToken token,
|
||||
@Nullable Bundle controlHints) {
|
||||
Objects.requireNonNull(token, "token must not be null");
|
||||
|
||||
if (mClient == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mClient.notifyRouteSelected(token.mPackageName, token.mRouteId,
|
||||
controlHints, token.mSeq);
|
||||
} catch (RemoteException ex) {
|
||||
Log.w(TAG, "Failed to notify route selected");
|
||||
}
|
||||
}
|
||||
|
||||
void setClient(IMediaRoute2ProviderClient client) {
|
||||
mClient = client;
|
||||
publishState();
|
||||
@@ -118,6 +152,23 @@ public abstract class MediaRoute2ProviderService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route selection information.
|
||||
*
|
||||
* @see #notifyRouteSelected
|
||||
*/
|
||||
public final class SelectToken {
|
||||
final String mPackageName;
|
||||
final String mRouteId;
|
||||
final int mSeq;
|
||||
|
||||
SelectToken(String packageName, String routeId, int seq) {
|
||||
mPackageName = packageName;
|
||||
mRouteId = routeId;
|
||||
mSeq = seq;
|
||||
}
|
||||
}
|
||||
|
||||
final class ProviderStub extends IMediaRoute2Provider.Stub {
|
||||
ProviderStub() { }
|
||||
|
||||
@@ -129,10 +180,10 @@ public abstract class MediaRoute2ProviderService extends Service {
|
||||
|
||||
@Override
|
||||
public void requestSelectRoute(String packageName, String id, int seq) {
|
||||
// TODO: When introducing MediaRoute2ProviderService#sendConnectionHints(),
|
||||
// use the sequence number here properly.
|
||||
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
|
||||
MediaRoute2ProviderService.this, packageName, id));
|
||||
MediaRoute2ProviderService.this, packageName, id,
|
||||
new SelectToken(packageName, id, seq)));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1318,6 +1318,7 @@ public class MediaRouter {
|
||||
sStatic.rebindAsUser(userId);
|
||||
}
|
||||
|
||||
//TODO: remove this and Client1Record in MediaRouter2ServiceImpl.
|
||||
/**
|
||||
* Sets the control categories of the application.
|
||||
* Routes that support at least one of the given control categories only exists and are handled
|
||||
|
||||
@@ -57,7 +57,8 @@ public class MediaRouter2 {
|
||||
@IntDef(value = {
|
||||
SELECT_REASON_UNKNOWN,
|
||||
SELECT_REASON_USER_SELECTED,
|
||||
SELECT_REASON_FALLBACK})
|
||||
SELECT_REASON_FALLBACK,
|
||||
SELECT_REASON_SYSTEM_SELECTED})
|
||||
public @interface SelectReason {}
|
||||
|
||||
/**
|
||||
@@ -80,6 +81,13 @@ public class MediaRouter2 {
|
||||
*/
|
||||
public static final int SELECT_REASON_FALLBACK = 2;
|
||||
|
||||
/**
|
||||
* This is passed from {@link com.android.server.media.MediaRouterService} when the route
|
||||
* is selected in response to a request from other apps (e.g. System UI).
|
||||
* @hide
|
||||
*/
|
||||
public static final int SELECT_REASON_SYSTEM_SELECTED = 3;
|
||||
|
||||
private static final String TAG = "MR2";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
private static final Object sLock = new Object();
|
||||
@@ -485,6 +493,9 @@ public class MediaRouter2 {
|
||||
}
|
||||
mSelectingRoute = null;
|
||||
}
|
||||
if (reason == SELECT_REASON_SYSTEM_SELECTED) {
|
||||
reason = SELECT_REASON_USER_SELECTED;
|
||||
}
|
||||
mSelectedRoute = route;
|
||||
notifyRouteSelected(route, reason, controlHints);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Intent;
|
||||
import android.media.MediaRoute2Info;
|
||||
import android.media.MediaRoute2ProviderInfo;
|
||||
import android.media.MediaRoute2ProviderService;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -95,7 +96,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectRoute(String packageName, String routeId) {
|
||||
public void onSelectRoute(String packageName, String routeId, SelectToken token) {
|
||||
MediaRoute2Info route = mRoutes.get(routeId);
|
||||
if (route == null) {
|
||||
return;
|
||||
@@ -104,6 +105,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
|
||||
.setClientPackageName(packageName)
|
||||
.build());
|
||||
publishRoutes();
|
||||
notifyRouteSelected(token, Bundle.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,23 +23,19 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
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.MediaRouter2;
|
||||
import android.media.MediaRouter2Manager;
|
||||
import android.os.Bundle;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -95,9 +91,14 @@ public class MediaRouterManagerTest {
|
||||
private Executor mExecutor;
|
||||
private String mPackageName;
|
||||
|
||||
private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
|
||||
private final List<MediaRouter2.Callback> mRouterCallbacks = new ArrayList<>();
|
||||
private Map<String, MediaRoute2Info> mRoutes;
|
||||
|
||||
private static final List<String> CATEGORIES_ALL = new ArrayList();
|
||||
private static final List<String> CATEGORIES_SPECIAL = new ArrayList();
|
||||
private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>();
|
||||
|
||||
static {
|
||||
CATEGORIES_ALL.add(CATEGORY_SAMPLE);
|
||||
CATEGORIES_ALL.add(CATEGORY_SPECIAL);
|
||||
@@ -108,6 +109,7 @@ public class MediaRouterManagerTest {
|
||||
CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO);
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mContext = InstrumentationRegistry.getTargetContext();
|
||||
@@ -116,6 +118,16 @@ public class MediaRouterManagerTest {
|
||||
//TODO: If we need to support thread pool executors, change this to thread pool executor.
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
mPackageName = mContext.getPackageName();
|
||||
|
||||
// ensure media router 2 client
|
||||
addRouterCallback(new MediaRouter2.Callback());
|
||||
mRoutes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// unregister callbacks
|
||||
clearCallbacks();
|
||||
}
|
||||
|
||||
//TODO: Move to a separate file
|
||||
@@ -132,10 +144,13 @@ public class MediaRouterManagerTest {
|
||||
assertNotEquals(routeInfo1, routeInfo3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if routes are added correctly when a new callback is registered.
|
||||
*/
|
||||
@Test
|
||||
public void testOnRoutesAdded() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
|
||||
addManagerCallback(new MediaRouter2Manager.Callback() {
|
||||
@Override
|
||||
public void onRoutesAdded(List<MediaRoute2Info> routes) {
|
||||
assertTrue(routes.size() > 0);
|
||||
@@ -145,27 +160,15 @@ public class MediaRouterManagerTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
mManager.registerCallback(mExecutor, callback);
|
||||
});
|
||||
|
||||
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
|
||||
mManager.unregisterCallback(callback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnRoutesRemoved() throws Exception {
|
||||
MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
|
||||
mManager.registerCallback(mExecutor, mockCallback);
|
||||
|
||||
MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
|
||||
mRouter2.registerCallback(mExecutor, routerCallback);
|
||||
|
||||
Map<String, MediaRoute2Info> routes =
|
||||
waitAndGetRoutesWithManager(CATEGORIES_ALL);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
|
||||
addManagerCallback(new MediaRouter2Manager.Callback() {
|
||||
@Override
|
||||
public void onRoutesRemoved(List<MediaRoute2Info> routes) {
|
||||
assertTrue(routes.size() > 0);
|
||||
@@ -175,16 +178,12 @@ public class MediaRouterManagerTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
mManager.registerCallback(mExecutor, callback);
|
||||
});
|
||||
|
||||
//TODO: Figure out a more proper way to test.
|
||||
// (Control requests shouldn't be used in this way.)
|
||||
mRouter2.sendControlRequest(routes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
|
||||
mRouter2.sendControlRequest(mRoutes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
|
||||
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
|
||||
mRouter2.unregisterCallback(routerCallback);
|
||||
mManager.unregisterCallback(mockCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,16 +191,10 @@ public class MediaRouterManagerTest {
|
||||
*/
|
||||
@Test
|
||||
public void testControlCategory() throws Exception {
|
||||
MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
|
||||
mManager.registerCallback(mExecutor, mockCallback);
|
||||
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_SPECIAL);
|
||||
|
||||
Map<String, MediaRoute2Info> routes =
|
||||
waitAndGetRoutesWithManager(CATEGORIES_SPECIAL);
|
||||
|
||||
Assert.assertEquals(1, routes.size());
|
||||
Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
|
||||
|
||||
mManager.unregisterCallback(mockCallback);
|
||||
assertEquals(1, routes.size());
|
||||
assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,37 +202,60 @@ public class MediaRouterManagerTest {
|
||||
*/
|
||||
@Test
|
||||
public void testGetRoutes() throws Exception {
|
||||
MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
|
||||
mRouter2.registerCallback(mExecutor, mockCallback);
|
||||
|
||||
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
|
||||
|
||||
Assert.assertEquals(1, routes.size());
|
||||
Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
|
||||
|
||||
mRouter2.unregisterCallback(mockCallback);
|
||||
assertEquals(1, routes.size());
|
||||
assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager.
|
||||
*/
|
||||
@Test
|
||||
public void testOnRouteSelected() throws Exception {
|
||||
MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
|
||||
MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
|
||||
public void testRouterOnRouteSelected() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
mManager.registerCallback(mExecutor, managerCallback);
|
||||
mRouter2.registerCallback(mExecutor, routerCallback);
|
||||
addRouterCallback(new MediaRouter2.Callback() {
|
||||
@Override
|
||||
public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
|
||||
if (route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
|
||||
|
||||
MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
|
||||
MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
|
||||
assertNotNull(routeToSelect);
|
||||
|
||||
mManager.selectRoute(mPackageName, routeToSelect);
|
||||
verify(managerCallback, timeout(TIMEOUT_MS))
|
||||
.onRouteSelected(eq(mPackageName),
|
||||
argThat(route -> route != null && route.equals(routeToSelect)));
|
||||
|
||||
mRouter2.unregisterCallback(routerCallback);
|
||||
mManager.unregisterCallback(managerCallback);
|
||||
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if MR2Manager.Callback.onRouteSelected is called
|
||||
* when a route is selected by MR2Manager.
|
||||
*/
|
||||
@Test
|
||||
public void testManagerOnRouteSelected() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
addManagerCallback(new MediaRouter2Manager.Callback() {
|
||||
@Override
|
||||
public void onRouteSelected(String packageName, MediaRoute2Info route) {
|
||||
if (TextUtils.equals(mPackageName, packageName)
|
||||
&& route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
|
||||
assertNotNull(routeToSelect);
|
||||
|
||||
mManager.selectRoute(mPackageName, routeToSelect);
|
||||
|
||||
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,19 +263,13 @@ public class MediaRouterManagerTest {
|
||||
*/
|
||||
@Test
|
||||
public void testSingleProviderSelect() throws Exception {
|
||||
MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class);
|
||||
|
||||
mRouter2.registerCallback(mExecutor, routerCallback);
|
||||
|
||||
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
|
||||
|
||||
awaitOnRouteChangedManager(
|
||||
() -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
|
||||
() -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID1)),
|
||||
ROUTE_ID1,
|
||||
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
|
||||
|
||||
awaitOnRouteChangedManager(
|
||||
() -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)),
|
||||
() -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID2)),
|
||||
ROUTE_ID2,
|
||||
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
|
||||
|
||||
@@ -267,8 +277,6 @@ public class MediaRouterManagerTest {
|
||||
() -> mManager.unselectRoute(mPackageName),
|
||||
ROUTE_ID2,
|
||||
route -> TextUtils.equals(route.getClientPackageName(), null));
|
||||
|
||||
mRouter2.unregisterCallback(routerCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -292,12 +300,7 @@ public class MediaRouterManagerTest {
|
||||
|
||||
@Test
|
||||
public void testControlVolumeWithManager() throws Exception {
|
||||
MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
|
||||
|
||||
mRouter2.registerCallback(mExecutor, mockCallback);
|
||||
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
|
||||
|
||||
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
|
||||
MediaRoute2Info volRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
|
||||
int originalVolume = volRoute.getVolume();
|
||||
int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
|
||||
|
||||
@@ -310,24 +313,16 @@ public class MediaRouterManagerTest {
|
||||
() -> mManager.requestSetVolume(volRoute, originalVolume),
|
||||
ROUTE_ID_VARIABLE_VOLUME,
|
||||
(route -> route.getVolume() == originalVolume));
|
||||
|
||||
mRouter2.unregisterCallback(mockCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeHandling() throws Exception {
|
||||
MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
|
||||
mRouter2.registerCallback(mExecutor, mockCallback);
|
||||
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
|
||||
|
||||
MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
|
||||
MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
|
||||
MediaRoute2Info fixedVolumeRoute = mRoutes.get(ROUTE_ID_FIXED_VOLUME);
|
||||
MediaRoute2Info variableVolumeRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
|
||||
|
||||
assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling());
|
||||
assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling());
|
||||
assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
|
||||
|
||||
mRouter2.unregisterCallback(mockCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -368,6 +363,7 @@ public class MediaRouterManagerTest {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onControlCategoriesChanged(String packageName) {
|
||||
if (TextUtils.equals(mPackageName, packageName)) {
|
||||
@@ -401,7 +397,7 @@ public class MediaRouterManagerTest {
|
||||
};
|
||||
mRouter2.registerCallback(mExecutor, callback);
|
||||
try {
|
||||
new Thread(task).start();
|
||||
task.run();
|
||||
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
} finally {
|
||||
mRouter2.unregisterCallback(callback);
|
||||
@@ -422,7 +418,7 @@ public class MediaRouterManagerTest {
|
||||
};
|
||||
mManager.registerCallback(mExecutor, callback);
|
||||
try {
|
||||
new Thread(task).start();
|
||||
task.run();
|
||||
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
} finally {
|
||||
mManager.unregisterCallback(callback);
|
||||
@@ -433,9 +429,31 @@ public class MediaRouterManagerTest {
|
||||
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
|
||||
Map<String, MediaRoute2Info> routeMap = new HashMap<>();
|
||||
for (MediaRoute2Info route : routes) {
|
||||
// intentionally not route.getUniqueId() for convenience.
|
||||
// intentionally not using route.getUniqueId() for convenience.
|
||||
routeMap.put(route.getId(), route);
|
||||
}
|
||||
return routeMap;
|
||||
}
|
||||
|
||||
private void addManagerCallback(MediaRouter2Manager.Callback callback) {
|
||||
mManagerCallbacks.add(callback);
|
||||
mManager.registerCallback(mExecutor, callback);
|
||||
}
|
||||
|
||||
private void addRouterCallback(MediaRouter2.Callback callback) {
|
||||
mRouterCallbacks.add(callback);
|
||||
mRouter2.registerCallback(mExecutor, callback);
|
||||
}
|
||||
|
||||
private void clearCallbacks() {
|
||||
for (MediaRouter2Manager.Callback callback : mManagerCallbacks) {
|
||||
mManager.unregisterCallback(callback);
|
||||
}
|
||||
mManagerCallbacks.clear();
|
||||
|
||||
for (MediaRouter2.Callback callback : mRouterCallbacks) {
|
||||
mRouter2.unregisterCallback(callback);
|
||||
}
|
||||
mRouterCallbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.media.MediaRoute2Info;
|
||||
import android.media.MediaRoute2ProviderInfo;
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -29,7 +30,7 @@ abstract class MediaRoute2Provider {
|
||||
final ComponentName mComponentName;
|
||||
final String mUniqueId;
|
||||
|
||||
private Callback mCallback;
|
||||
Callback mCallback;
|
||||
private MediaRoute2ProviderInfo mProviderInfo;
|
||||
|
||||
MediaRoute2Provider(@NonNull ComponentName componentName) {
|
||||
@@ -77,6 +78,9 @@ abstract class MediaRoute2Provider {
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onProviderStateChanged(MediaRoute2Provider provider);
|
||||
void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
|
||||
void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider,
|
||||
@NonNull String clientPackageName, @NonNull MediaRoute2Info route,
|
||||
@Nullable Bundle controlHints, int seq);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.media.IMediaRoute2ProviderClient;
|
||||
import android.media.MediaRoute2Info;
|
||||
import android.media.MediaRoute2ProviderInfo;
|
||||
import android.media.MediaRoute2ProviderService;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.IBinder.DeathRecipient;
|
||||
@@ -253,6 +254,20 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
|
||||
setAndNotifyProviderInfo(info);
|
||||
}
|
||||
|
||||
private void onRouteSelected(Connection connection,
|
||||
String packageName, String routeId, Bundle controlHints, int seq) {
|
||||
if (mActiveConnection != connection) {
|
||||
return;
|
||||
}
|
||||
MediaRoute2ProviderInfo providerInfo = getProviderInfo();
|
||||
MediaRoute2Info route = (providerInfo == null) ? null : providerInfo.getRoute(routeId);
|
||||
if (route == null) {
|
||||
Slog.w(TAG, this + ": Unknown route " + routeId + " is selected from remove provider");
|
||||
return;
|
||||
}
|
||||
mCallback.onRouteSelected(this, packageName, route, controlHints, seq);
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
if (mActiveConnection != null) {
|
||||
mConnectionReady = false;
|
||||
@@ -341,6 +356,11 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
|
||||
void postProviderInfoUpdated(MediaRoute2ProviderInfo info) {
|
||||
mHandler.post(() -> onProviderInfoUpdated(Connection.this, info));
|
||||
}
|
||||
|
||||
void postRouteSelected(String packageName, String routeId, Bundle controlHints, int seq) {
|
||||
mHandler.post(() -> onRouteSelected(Connection.this,
|
||||
packageName, routeId, controlHints, seq));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProviderClient extends IMediaRoute2ProviderClient.Stub {
|
||||
@@ -361,5 +381,15 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
|
||||
connection.postProviderInfoUpdated(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyRouteSelected(String packageName, String routeId,
|
||||
Bundle controlHints, int seq) {
|
||||
Connection connection = mConnectionRef.get();
|
||||
if (connection != null) {
|
||||
connection.postRouteSelected(packageName, routeId, controlHints, seq);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class MediaRouter2ServiceImpl {
|
||||
@GuardedBy("mLock")
|
||||
private int mCurrentUserId = -1;
|
||||
@GuardedBy("mLock")
|
||||
private int mSelectRouteRequestSequenceNumber = 0;
|
||||
private int mSelectRouteRequestSequenceNumber = 1;
|
||||
|
||||
MediaRouter2ServiceImpl(Context context) {
|
||||
mContext = context;
|
||||
@@ -218,7 +218,7 @@ class MediaRouter2ServiceImpl {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), route);
|
||||
requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), false, route);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
@@ -399,10 +399,12 @@ class MediaRouter2ServiceImpl {
|
||||
}
|
||||
}
|
||||
|
||||
private void requestSelectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) {
|
||||
private void requestSelectRoute2Locked(ClientRecord clientRecord, boolean selectedByManager,
|
||||
MediaRoute2Info route) {
|
||||
if (clientRecord != null) {
|
||||
MediaRoute2Info oldRoute = clientRecord.mSelectedRoute;
|
||||
clientRecord.mSelectingRoute = route;
|
||||
clientRecord.mIsManagerSelecting = selectedByManager;
|
||||
|
||||
UserHandler handler = clientRecord.mUserRecord.mHandler;
|
||||
//TODO: Handle transfer instead of unselect and select
|
||||
@@ -417,7 +419,6 @@ class MediaRouter2ServiceImpl {
|
||||
handler.sendMessage(obtainMessage(
|
||||
UserHandler::requestSelectRoute, handler, clientRecord.mPackageName,
|
||||
route, seq));
|
||||
|
||||
// Remove all previous timeout messages
|
||||
for (int previousSeq : clientRecord.mSelectRouteSequenceNumbers) {
|
||||
clientRecord.mUserRecord.mHandler.removeMessages(previousSeq);
|
||||
@@ -543,7 +544,7 @@ class MediaRouter2ServiceImpl {
|
||||
Slog.w(TAG, "Ignoring route selection for unknown client.");
|
||||
}
|
||||
if (clientRecord != null && managerRecord.mTrusted) {
|
||||
requestSelectRoute2Locked(clientRecord, route);
|
||||
requestSelectRoute2Locked(clientRecord, true, route);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -656,7 +657,9 @@ class MediaRouter2ServiceImpl {
|
||||
public final UserRecord mUserRecord;
|
||||
public final String mPackageName;
|
||||
public final List<Integer> mSelectRouteSequenceNumbers;
|
||||
|
||||
public List<String> mControlCategories;
|
||||
public boolean mIsManagerSelecting;
|
||||
public MediaRoute2Info mSelectingRoute;
|
||||
public MediaRoute2Info mSelectedRoute;
|
||||
|
||||
@@ -802,9 +805,8 @@ class MediaRouter2ServiceImpl {
|
||||
sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider));
|
||||
}
|
||||
|
||||
// TODO: When introducing MediaRoute2ProviderService#sendControlHints(),
|
||||
// Make this method to be called.
|
||||
public void onRouteSelectionRequestHandled(@NonNull MediaRoute2ProviderProxy provider,
|
||||
@Override
|
||||
public void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider,
|
||||
String clientPackageName, MediaRoute2Info route, Bundle controlHints, int seq) {
|
||||
sendMessage(PooledLambda.obtainMessage(
|
||||
UserHandler::updateSelectedRoute, this, provider, clientPackageName, route,
|
||||
@@ -917,6 +919,8 @@ class MediaRouter2ServiceImpl {
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO: handle a case such that controlHints is null. (How should we notify MR2?)
|
||||
|
||||
if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
|
||||
clientRecord.mSelectingRoute.getUniqueId(), selectedRoute.getUniqueId())) {
|
||||
Log.w(TAG, "Ignoring invalid updateSelectedRoute call. selectingRoute="
|
||||
@@ -929,7 +933,9 @@ class MediaRouter2ServiceImpl {
|
||||
|
||||
notifyRouteSelectedToClient(((Client2Record) clientRecord).mClient,
|
||||
selectedRoute,
|
||||
MediaRouter2.SELECT_REASON_USER_SELECTED,
|
||||
clientRecord.mIsManagerSelecting
|
||||
? MediaRouter2.SELECT_REASON_SYSTEM_SELECTED :
|
||||
MediaRouter2.SELECT_REASON_USER_SELECTED,
|
||||
controlHints);
|
||||
updateClientUsage(clientRecord);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user