Merge "MediaRouter2: Add requestCreateSession"

This commit is contained in:
TreeHugger Robot
2019-12-26 09:14:42 +00:00
committed by Android (Google) Code Review
16 changed files with 623 additions and 200 deletions

View File

@@ -24,6 +24,8 @@ import android.media.IMediaRoute2ProviderClient;
*/
oneway interface IMediaRoute2Provider {
void setClient(IMediaRoute2ProviderClient client);
void requestCreateSession(String packageName, String routeId, String controlCategory,
int requestId);
void requestSelectRoute(String packageName, String id, int seq);
void unselectRoute(String packageName, String id);
void notifyControlRequestSent(String id, in Intent request);

View File

@@ -18,6 +18,7 @@ package android.media;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2Info;
import android.media.RouteSessionInfo;
import android.os.Bundle;
/**
@@ -25,5 +26,8 @@ import android.os.Bundle;
*/
oneway interface IMediaRoute2ProviderClient {
void updateProviderInfo(in MediaRoute2ProviderInfo info);
void notifyRouteSelected(String packageName, String routeId, in Bundle controlHints, int seq);
void notifyRouteSelected(String packageName, String routeId, in @nullable Bundle controlHints,
int seq);
void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo,
in @nullable Bundle controlHints, int requestId);
}

View File

@@ -17,6 +17,7 @@
package android.media;
import android.media.MediaRoute2Info;
import android.media.RouteSessionInfo;
import android.os.Bundle;
/**
@@ -27,5 +28,6 @@ oneway interface IMediaRouter2Client {
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
void notifyRouteSelected(in MediaRoute2Info route, int reason, in Bundle controlHints);
void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo,
in @nullable Bundle controlHints, int requestId);
}

View File

@@ -48,13 +48,9 @@ interface IMediaRouterService {
void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request);
void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume);
void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
/**
* Changes the selected route of the client.
*
* @param client the client that changes it's selected route
* @param route the route to be selected
*/
void requestSelectRoute2(IMediaRouter2Client client, in @nullable MediaRoute2Info route);
void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route,
String controlCategory, int requestId);
void setControlCategories(IMediaRouter2Client client, in List<String> categories);
void registerManager(IMediaRouter2Manager manager, String packageName);

View File

@@ -47,6 +47,7 @@ public abstract class MediaRoute2ProviderService extends Service {
private final Handler mHandler;
private final Object mSessionLock = new Object();
private ProviderStub mStub;
// TODO: Rename this to mService (and accordingly IMediaRoute2ProviderClient to something else)
private IMediaRoute2ProviderClient mClient;
private MediaRoute2ProviderInfo mProviderInfo;
@@ -166,19 +167,29 @@ public abstract class MediaRoute2ProviderService extends Service {
* Notifies clients of that the session is created and ready for use. If the session can be
* controlled, pass a {@link Bundle} that contains how to control it.
*
* @param sessionId id of the session
* @param sessionInfo information of the new session.
* Pass {@code null} to reject the request or inform clients that
* session creation has failed.
* @param controlHints a {@link Bundle} that contains how to control the session.
* @param requestId id of the previous request to create this session
*/
//TODO: fail reason?
public final void notifySessionCreated(int sessionId, @Nullable RouteSessionInfo sessionInfo,
@Nullable Bundle controlHints) {
//TODO: validate sessionId (it must be in "waiting list")
synchronized (mSessionLock) {
mSessionInfo.put(sessionId, sessionInfo);
//TODO: notify media router service of session creation.
public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo,
@Nullable Bundle controlHints, int requestId) {
//TODO: validate sessionInfo.getSessionId() (it must be in "waiting list")
if (sessionInfo != null) {
synchronized (mSessionLock) {
mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo);
}
}
if (mClient == null) {
return;
}
try {
mClient.notifySessionCreated(sessionInfo, controlHints, requestId);
} catch (RemoteException ex) {
Log.w(TAG, "Failed to notify session created.");
}
}
@@ -203,18 +214,19 @@ public abstract class MediaRoute2ProviderService extends Service {
/**
* Called when a session should be created.
* You should create and maintain your own session and notifies the client of
* session info. Call {@link #notifySessionCreated(int, RouteSessionInfo, Bundle)}
* to notify the information of a new session.
* session info. Call {@link #notifySessionCreated(RouteSessionInfo, Bundle, int)}
* with the given {@code requestId} to notify the information of a new session.
* If you can't create the session or want to reject the request, pass {@code null}
* as session info in {@link #notifySessionCreated(int, RouteSessionInfo, Bundle)}.
* as session info in {@link #notifySessionCreated(RouteSessionInfo, Bundle, int)}
* with the given {@code requestId}.
*
* @param packageName the package name of the application that selected the route
* @param routeId the id of the route initially being connected
* @param controlCategory the control category of the new session
* @param sessionId the id of a new session
* @param requestId the id of this session creation request
*/
public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
@NonNull String controlCategory, int sessionId);
@NonNull String controlCategory, int requestId);
/**
* Called when a session is about to be destroyed.
@@ -338,6 +350,14 @@ public abstract class MediaRoute2ProviderService extends Service {
MediaRoute2ProviderService.this, client));
}
@Override
public void requestCreateSession(String packageName, String routeId, String controlCategory,
int requestId) {
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
MediaRoute2ProviderService.this, packageName, routeId, controlCategory,
requestId));
}
@Override
public void requestSelectRoute(String packageName, String routeId, int seq) {
//TODO: call onCreateSession instead

View File

@@ -29,6 +29,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
@@ -46,6 +47,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A new Media Router
@@ -98,7 +100,10 @@ public class MediaRouter2 {
private final Context mContext;
private final IMediaRouterService mMediaRouterService;
private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords =
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
new CopyOnWriteArrayList<>();
private final String mPackageName;
@@ -108,12 +113,12 @@ public class MediaRouter2 {
@GuardedBy("sLock")
private List<String> mControlCategories = Collections.emptyList();
private MediaRoute2Info mSelectedRoute;
@GuardedBy("sLock")
private MediaRoute2Info mSelectingRoute;
// TODO: Make MediaRouter2 is always connected to the MediaRouterService.
@GuardedBy("sLock")
private Client2 mClient;
private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
final Handler mHandler;
@GuardedBy("sLock")
private boolean mShouldUpdateRoutes;
@@ -154,18 +159,14 @@ public class MediaRouter2 {
for (MediaRoute2Info route : currentSystemRoutes) {
mRoutes.put(route.getId(), route);
}
// The first route is the currently selected system route.
// For example, if there are two system routes (BT and device speaker),
// BT will be the first route in the list.
mSelectedRoute = currentSystemRoutes.get(0);
}
/**
* Registers a callback to discover routes and to receive events when they change.
*/
public void registerCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback) {
registerCallback(executor, callback, 0);
@NonNull RouteCallback routeCallback) {
registerCallback(executor, routeCallback, 0);
}
/**
@@ -175,12 +176,12 @@ public class MediaRouter2 {
* </p>
*/
public void registerCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback, int flags) {
@NonNull RouteCallback routeCallback, int flags) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(callback, "callback must not be null");
Objects.requireNonNull(routeCallback, "callback must not be null");
CallbackRecord record = new CallbackRecord(callback, executor, flags);
if (!mCallbackRecords.addIfAbsent(record)) {
RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, flags);
if (!mRouteCallbackRecords.addIfAbsent(record)) {
Log.w(TAG, "Ignoring the same callback");
return;
}
@@ -205,19 +206,20 @@ public class MediaRouter2 {
* Unregisters 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 unregister
* @param routeCallback the callback to unregister
* @see #registerCallback
*/
public void unregisterCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "callback must not be null");
public void unregisterCallback(@NonNull RouteCallback routeCallback) {
Objects.requireNonNull(routeCallback, "callback must not be null");
if (!mCallbackRecords.remove(new CallbackRecord(callback, null, 0))) {
if (!mRouteCallbackRecords.remove(
new RouteCallbackRecord(null, routeCallback, 0))) {
Log.w(TAG, "Ignoring unknown callback");
return;
}
synchronized (sLock) {
if (mCallbackRecords.size() == 0 && mClient != null) {
if (mRouteCallbackRecords.size() == 0 && mClient != null) {
try {
mMediaRouterService.unregisterClient2(mClient);
} catch (RemoteException ex) {
@@ -282,38 +284,51 @@ public class MediaRouter2 {
}
/**
* Gets the currently selected route.
* Requests the media route provider service to create a session with the given route.
*
* @return the selected route
* @param route the route you want to create a session with.
* @param controlCategory the control category of the session. Should not be empty
* @param executor the executor to get the result of the session creation
* @param callback the callback to get the result of the session creation
*
* @see SessionCreationCallback#onSessionCreated(RouteSessionController, Bundle)
* @see SessionCreationCallback#onSessionCreationFailed()
*/
@NonNull
public MediaRoute2Info getSelectedRoute() {
return mSelectedRoute;
}
/**
* Request to select the specified route. When the route is selected,
* {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} will be called.
*
* @param route the route to select
*/
public void requestSelectRoute(@NonNull MediaRoute2Info route) {
public void requestCreateSession(@NonNull MediaRoute2Info route,
@NonNull String controlCategory,
@CallbackExecutor Executor executor, @NonNull SessionCreationCallback callback) {
Objects.requireNonNull(route, "route must not be null");
if (TextUtils.isEmpty(controlCategory)) {
throw new IllegalArgumentException("controlCategory must not be empty");
}
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(callback, "callback must not be null");
// TODO: Check the given route exists
// TODO: Check the route supports the given controlCategory
final int requestId;
// TODO: This does not ensure the uniqueness of the request ID.
// Find the way to ensure it. (e.g. have mapping inside MediaRouterService)
requestId = Process.myPid() * 10000 + mSessionCreationRequestCnt.getAndIncrement();
SessionCreationRequest request = new SessionCreationRequest(
requestId, route, controlCategory, executor, callback);
mSessionCreationRequests.add(request);
Client2 client;
synchronized (sLock) {
if (mSelectingRoute == route) {
Log.w(TAG, "The route selection request is already sent.");
return;
}
mSelectingRoute = route;
client = mClient;
}
if (client != null) {
try {
mMediaRouterService.requestSelectRoute2(client, route);
mMediaRouterService.requestCreateSession(
client, route, controlCategory, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to request to select route.", ex);
Log.e(TAG, "Unable to request to create session.", ex);
mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
MediaRouter2.this, null, null, requestId));
}
}
}
@@ -472,57 +487,71 @@ public class MediaRouter2 {
}
}
void selectRouteOnHandler(MediaRoute2Info route, int reason, Bundle controlHints) {
synchronized (sLock) {
if (reason == SELECT_REASON_USER_SELECTED) {
if (mSelectingRoute == null
|| !TextUtils.equals(mSelectingRoute.getUniqueId(), route.getUniqueId())) {
Log.w(TAG, "Ignoring invalid or outdated notifyRouteSelected call. "
+ "selectingRoute=" + mSelectingRoute + " route=" + route);
return;
}
/**
* Creates a controller and calls the {@link SessionCreationCallback#onSessionCreated}.
* If session creation has failed, then it calls
* {@link SessionCreationCallback#onSessionCreationFailed()}.
* <p>
* Pass {@code null} to sessionInfo for the failure case.
*/
void createControllerOnHandler(@Nullable RouteSessionInfo sessionInfo,
@Nullable Bundle controlHints, int requestId) {
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
if (request.mRequestId == requestId) {
matchingRequest = request;
break;
}
mSelectingRoute = null;
}
if (reason == SELECT_REASON_SYSTEM_SELECTED) {
reason = SELECT_REASON_USER_SELECTED;
if (matchingRequest == null) {
Log.w(TAG, "Ignoring session creation result for unknown request."
+ " requestId=" + requestId + ", sessionInfo=" + sessionInfo);
return;
}
mSessionCreationRequests.remove(matchingRequest);
final Executor executor = matchingRequest.mExecutor;
final SessionCreationCallback callback = matchingRequest.mSessionCreationCallback;
if (sessionInfo == null) {
// TODO: We may need to distinguish between failure and rejection.
// One way can be introducing 'reason'.
executor.execute(callback::onSessionCreationFailed);
} else {
// TODO: RouteSessionController should be created with full info (e.g. routes)
// from RouteSessionInfo.
RouteSessionController controller = new RouteSessionController(sessionInfo);
executor.execute(() -> callback.onSessionCreated(controller, controlHints));
}
mSelectedRoute = route;
notifyRouteSelected(route, reason, controlHints);
}
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
record.mExecutor.execute(
() -> record.mCallback.onRoutesAdded(routes));
() -> record.mRouteCallback.onRoutesAdded(routes));
}
}
private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
record.mExecutor.execute(
() -> record.mCallback.onRoutesRemoved(routes));
() -> record.mRouteCallback.onRoutesRemoved(routes));
}
}
private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
record.mExecutor.execute(
() -> record.mCallback.onRoutesChanged(routes));
}
}
private void notifyRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
() -> record.mCallback.onRouteSelected(route, reason, controlHints));
() -> record.mRouteCallback.onRoutesChanged(routes));
}
}
/**
* Interface for receiving events about media routing changes.
* Callback for receiving events about media route discovery.
*/
public static class Callback {
public static class RouteCallback {
/**
* Called when routes are added. Whenever you registers a callback, this will
* be invoked with known routes.
@@ -549,30 +578,33 @@ public class MediaRouter2 {
* @param routes the list of routes that have been changed. It's never empty.
*/
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
/**
* Called when a route is selected. Exactly one route can be selected at a time.
*
* @param route the selected route.
* @param reason the reason why the route is selected.
* @param controlHints An optional bundle of provider-specific arguments which may be
* used to control the selected route. Can be empty.
* @see #SELECT_REASON_UNKNOWN
* @see #SELECT_REASON_USER_SELECTED
* @see #SELECT_REASON_FALLBACK
* @see #getSelectedRoute()
*/
public void onRouteSelected(@NonNull MediaRoute2Info route, @SelectReason int reason,
@NonNull Bundle controlHints) {}
}
final class CallbackRecord {
public final Callback mCallback;
public Executor mExecutor;
public int mFlags;
/**
* Callback for receiving a result of session creation.
*/
public static class SessionCreationCallback {
/**
* Called when the route session is created by the route provider.
*
* @param controller the controller to control the created session
*/
public void onSessionCreated(RouteSessionController controller, Bundle controlHints) {}
CallbackRecord(@NonNull Callback callback, @Nullable Executor executor, int flags) {
mCallback = callback;
/**
* Called when the session creation request failed.
*/
public void onSessionCreationFailed() {}
}
final class RouteCallbackRecord {
public final Executor mExecutor;
public final RouteCallback mRouteCallback;
public final int mFlags;
RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback,
int flags) {
mRouteCallback = routeCallback;
mExecutor = executor;
mFlags = flags;
}
@@ -582,15 +614,35 @@ public class MediaRouter2 {
if (this == obj) {
return true;
}
if (!(obj instanceof CallbackRecord)) {
if (!(obj instanceof RouteCallbackRecord)) {
return false;
}
return mCallback == ((CallbackRecord) obj).mCallback;
return mRouteCallback
== ((RouteCallbackRecord) obj).mRouteCallback;
}
@Override
public int hashCode() {
return mCallback.hashCode();
return mRouteCallback.hashCode();
}
}
final class SessionCreationRequest {
public final MediaRoute2Info mRoute;
public final String mControlCategory;
public final Executor mExecutor;
public final SessionCreationCallback mSessionCreationCallback;
public final int mRequestId;
SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route,
@NonNull String controlCategory,
@Nullable Executor executor,
@NonNull SessionCreationCallback sessionCreationCallback) {
mRoute = route;
mControlCategory = controlCategory;
mExecutor = executor;
mSessionCreationCallback = sessionCreationCallback;
mRequestId = requestId;
}
}
@@ -617,10 +669,10 @@ public class MediaRouter2 {
}
@Override
public void notifyRouteSelected(MediaRoute2Info route, int reason,
Bundle controlHints) {
mHandler.sendMessage(obtainMessage(MediaRouter2::selectRouteOnHandler,
MediaRouter2.this, route, reason, controlHints));
public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo,
@Nullable Bundle controlHints, int requestId) {
mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
MediaRouter2.this, sessionInfo, controlHints, requestId));
}
}
}

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import com.android.internal.annotations.GuardedBy;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -39,6 +40,8 @@ public class RouteSessionController {
private final String mCategory;
private final Object mLock = new Object();
private List<String> mSelectedRoutes;
@GuardedBy("mLock")
private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
new CopyOnWriteArrayList<>();
@@ -46,12 +49,13 @@ public class RouteSessionController {
private volatile boolean mIsReleased;
/**
* @param sessionId the ID of the session.
* @param category The category of media routes that the session includes.
* @param sessionInfo
*/
RouteSessionController(int sessionId, @NonNull String category) {
mSessionId = sessionId;
mCategory = category;
RouteSessionController(@NonNull RouteSessionInfo sessionInfo) {
mSessionId = sessionInfo.getSessionId();
mCategory = sessionInfo.getControlCategory();
mSelectedRoutes = sessionInfo.getSelectedRoutes();
// TODO: Create getters for all other types of routes
}
/**
@@ -70,12 +74,11 @@ public class RouteSessionController {
}
/**
* @return the list of currently connected routes
* @return the list of currently selected routes
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
// TODO: Implement this when SessionInfo is introduced.
return null;
public List<String> getSelectedRoutes() {
return Collections.unmodifiableList(mSelectedRoutes);
}
/**
@@ -93,7 +96,7 @@ public class RouteSessionController {
/**
* Add routes to the remote session.
*
* @see #getRoutes()
* @see #getSelectedRoutes()
* @see Callback#onSessionInfoChanged
*/
public void addRoutes(List<MediaRoute2Info> routes) {
@@ -102,9 +105,10 @@ public class RouteSessionController {
/**
* Remove routes from this session. Media may be stopped on those devices.
* Route removal requests that are not currently in {@link #getRoutes()} will be ignored.
* Route removal requests that are not currently in {@link #getSelectedRoutes()} will be
* ignored.
*
* @see #getRoutes()
* @see #getSelectedRoutes()
* @see Callback#onSessionInfoChanged
*/
public void removeRoutes(List<MediaRoute2Info> routes) {

View File

@@ -26,6 +26,7 @@ import android.media.MediaRoute2ProviderService;
import android.media.RouteSessionInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import java.util.HashMap;
import java.util.Map;
@@ -37,6 +38,9 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
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 ROUTE_ID3_SESSION_CREATION_FAILED =
"route_id3_session_creation_failed";
public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
@@ -55,6 +59,8 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
public static final String CATEGORY_SPECIAL =
"com.android.mediarouteprovider.CATEGORY_SPECIAL";
public static final int SESSION_ID_1 = 1000;
Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
private void initializeRoutes() {
@@ -66,6 +72,10 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
.addSupportedCategory(CATEGORY_SAMPLE)
.setDeviceType(DEVICE_TYPE_SPEAKER)
.build();
MediaRoute2Info route3 = new MediaRoute2Info.Builder(
ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
.addSupportedCategory(CATEGORY_SAMPLE)
.build();
MediaRoute2Info routeSpecial =
new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_CATEGORY, ROUTE_NAME_SPECIAL_CATEGORY)
.addSupportedCategory(CATEGORY_SAMPLE)
@@ -85,6 +95,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
mRoutes.put(route1.getId(), route1);
mRoutes.put(route2.getId(), route2);
mRoutes.put(route3.getId(), route3);
mRoutes.put(routeSpecial.getId(), routeSpecial);
mRoutes.put(fixedVolumeRoute.getId(), fixedVolumeRoute);
mRoutes.put(variableVolumeRoute.getId(), variableVolumeRoute);
@@ -168,12 +179,18 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService
@Override
public void onCreateSession(String packageName, String routeId, String controlCategory,
int sessionId) {
int requestId) {
if (TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
// Tell the router that session cannot be created by passing null as sessionInfo.
notifySessionCreated(null /* sessionInfo */, null /* controlHitns */, requestId);
return;
}
RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
sessionId, packageName, controlCategory)
SESSION_ID_1, packageName, controlCategory)
.addSelectedRoute(routeId)
.build();
notifySessionCreated(sessionId, sessionInfo, null);
notifySessionCreated(sessionInfo, null /* controlHints */, requestId);
}
@Override

View File

@@ -11,6 +11,7 @@ android_test {
static_libs: [
"android-support-test",
"mockito-target-minus-junit4",
"testng"
],
platform_apis: true,

View File

@@ -27,19 +27,26 @@ import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_ALL;
import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_SPECIAL;
import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORY_SAMPLE;
import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORY_SPECIAL;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertThrows;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.SessionCreationCallback;
import android.media.RouteSessionController;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -51,6 +58,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -81,13 +89,6 @@ public class MediaRouter2Test {
public void tearDown() throws Exception {
}
@Test
public void testGetSelectedRoute_afterCreation() throws Exception {
MediaRouter2 router = MediaRouter2.getInstance(mContext);
MediaRoute2Info initiallySelectedRoute = router.getSelectedRoute();
assertNotNull(initiallySelectedRoute);
}
/**
* Tests if we get proper routes for application that has special control category.
*/
@@ -204,6 +205,109 @@ public class MediaRouter2Test {
(route -> route.getVolume() == originalVolume));
}
@Test
public void testRequestCreateSessionWithInvalidArguments() {
MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
String controlCategory = "controlCategory";
Executor executor = mExecutor;
MediaRouter2.SessionCreationCallback callback = new MediaRouter2.SessionCreationCallback();
// Tests null route
assertThrows(NullPointerException.class,
() -> mRouter2.requestCreateSession(null, controlCategory, executor, callback));
// Tests null or empty control category
assertThrows(IllegalArgumentException.class,
() -> mRouter2.requestCreateSession(route, null, executor, callback));
assertThrows(IllegalArgumentException.class,
() -> mRouter2.requestCreateSession(route, "", executor, callback));
// Tests null executor
assertThrows(NullPointerException.class,
() -> mRouter2.requestCreateSession(route, controlCategory, null, callback));
// Tests null callback
assertThrows(NullPointerException.class,
() -> mRouter2.requestCreateSession(route, controlCategory, executor, null));
}
@Test
public void testRequestCreateSessionSuccess() throws Exception {
final List<String> sampleControlCategory = new ArrayList<>();
sampleControlCategory.add(CATEGORY_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
MediaRoute2Info route = routes.get(ROUTE_ID1);
assertNotNull(route);
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
// Create session with this route
SessionCreationCallback callback = new SessionCreationCallback() {
@Override
public void onSessionCreated(RouteSessionController controller, Bundle controlHints) {
assertNotNull(controller);
assertTrue(controller.getSelectedRoutes().contains(ROUTE_ID1));
assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getCategory()));
successLatch.countDown();
}
@Override
public void onSessionCreationFailed() {
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
mRouter2.registerCallback(mExecutor, new MediaRouter2.RouteCallback());
mRouter2.requestCreateSession(route, CATEGORY_SAMPLE, mExecutor, callback);
assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreationFailed should not be called.
assertFalse(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@Test
public void testRequestCreateSessionFailure() throws Exception {
final List<String> sampleControlCategory = new ArrayList<>();
sampleControlCategory.add(CATEGORY_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
assertNotNull(route);
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
// Create session with this route
SessionCreationCallback callback = new SessionCreationCallback() {
@Override
public void onSessionCreated(RouteSessionController controller, Bundle controlHints) {
successLatch.countDown();
}
@Override
public void onSessionCreationFailed() {
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
mRouter2.registerCallback(mExecutor, new MediaRouter2.RouteCallback());
mRouter2.requestCreateSession(route, CATEGORY_SAMPLE, mExecutor, callback);
assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreated should not be called.
assertFalse(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@Test
public void testRequestCreateSessionMultipleSessions() throws Exception {
// TODO: Test creating multiple sessions (Check the ID of each controller)
}
// Helper for getting routes easily
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
@@ -220,7 +324,8 @@ public class MediaRouter2Test {
CountDownLatch latch = new CountDownLatch(1);
// A dummy callback is required to send control category info.
MediaRouter2.Callback routerCallback = new MediaRouter2.Callback() {
MediaRouter2.RouteCallback
routeCallback = new MediaRouter2.RouteCallback() {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
for (int i = 0; i < routes.size(); i++) {
@@ -233,19 +338,20 @@ public class MediaRouter2Test {
};
mRouter2.setControlCategories(controlCategories);
mRouter2.registerCallback(mExecutor, routerCallback);
mRouter2.registerCallback(mExecutor, routeCallback);
try {
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mRouter2.getRoutes());
} finally {
mRouter2.unregisterCallback(routerCallback);
mRouter2.unregisterCallback(routeCallback);
}
}
void awaitOnRouteChanged(Runnable task, String routeId,
Predicate<MediaRoute2Info> predicate) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
MediaRouter2.Callback callback = new MediaRouter2.Callback() {
MediaRouter2.RouteCallback
routeCallback = new MediaRouter2.RouteCallback() {
@Override
public void onRoutesChanged(List<MediaRoute2Info> changed) {
MediaRoute2Info route = createRouteMap(changed).get(routeId);
@@ -254,12 +360,12 @@ public class MediaRouter2Test {
}
}
};
mRouter2.registerCallback(mExecutor, callback);
mRouter2.registerCallback(mExecutor, routeCallback);
try {
task.run();
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
mRouter2.unregisterCallback(callback);
mRouter2.unregisterCallback(routeCallback);
}
}
}

View File

@@ -20,6 +20,7 @@ import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -27,8 +28,8 @@ import android.content.Context;
import android.content.Intent;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.RouteCallback;
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;
@@ -59,6 +60,9 @@ public class MediaRouterManagerTest {
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 ROUTE_ID3_SESSION_CREATION_FAILED =
"route_id3_session_creation_failed";
public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
@@ -91,7 +95,8 @@ public class MediaRouterManagerTest {
private String mPackageName;
private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
private final List<MediaRouter2.Callback> mRouterCallbacks = new ArrayList<>();
private final List<RouteCallback> mRouteCallbacks =
new ArrayList<>();
public static final List<String> CATEGORIES_ALL = new ArrayList();
public static final List<String> CATEGORIES_SPECIAL = new ArrayList();
@@ -123,7 +128,21 @@ public class MediaRouterManagerTest {
clearCallbacks();
}
/**
//TODO: Move to a separate file
@Test
public void testMediaRoute2Info() {
MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder("id", "name")
.build();
MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build();
MediaRoute2Info routeInfo3 = new MediaRoute2Info.Builder(routeInfo1)
.setClientPackageName(mPackageName).build();
assertEquals(routeInfo1, routeInfo2);
assertNotEquals(routeInfo1, routeInfo3);
}
/**
* Tests if routes are added correctly when a new callback is registered.
*/
@Test
@@ -149,7 +168,7 @@ public class MediaRouterManagerTest {
CountDownLatch latch = new CountDownLatch(1);
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
addRouterCallback(new MediaRouter2.Callback());
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
public void onRoutesRemoved(List<MediaRoute2Info> routes) {
@@ -162,6 +181,8 @@ public class MediaRouterManagerTest {
}
});
//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));
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -179,29 +200,30 @@ public class MediaRouterManagerTest {
/**
* Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager.
*
* TODO: Change this test so that this test check whether the route is added in a session.
* Until then, temporailiy removing @Test annotation.
*/
@Test
public void testRouterOnRouteSelected() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
CountDownLatch latch = new CountDownLatch(1);
addManagerCallback(new MediaRouter2Manager.Callback());
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();
}
}
});
// addRouterCallback(new RouteDiscoveryCallback() {
// @Override
// public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
// if (route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
// latch.countDown();
// }
// }
// });
MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertNotNull(routeToSelect);
try {
mManager.selectRoute(mPackageName, routeToSelect);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
mManager.unselectRoute(mPackageName);
@@ -217,7 +239,7 @@ public class MediaRouterManagerTest {
CountDownLatch latch = new CountDownLatch(1);
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
addRouterCallback(new MediaRouter2.Callback());
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
public void onRouteSelected(String packageName, MediaRoute2Info route) {
@@ -245,7 +267,7 @@ public class MediaRouterManagerTest {
CountDownLatch latch2 = new CountDownLatch(1);
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
addRouterCallback(new MediaRouter2.Callback());
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
public void onRouteSelected(String packageName, MediaRoute2Info route) {
@@ -276,7 +298,7 @@ public class MediaRouterManagerTest {
@Test
public void testSingleProviderSelect() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
addRouterCallback(new MediaRouter2.Callback());
addRouterCallback(new RouteCallback());
awaitOnRouteChangedManager(
() -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
@@ -330,7 +352,8 @@ public class MediaRouterManagerTest {
CountDownLatch latch = new CountDownLatch(2);
// A dummy callback is required to send control category info.
MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
RouteCallback
routeCallback = new RouteCallback();
MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
@@ -352,12 +375,12 @@ public class MediaRouterManagerTest {
};
mManager.registerCallback(mExecutor, managerCallback);
mRouter2.setControlCategories(controlCategories);
mRouter2.registerCallback(mExecutor, routerCallback);
mRouter2.registerCallback(mExecutor, routeCallback);
try {
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mManager.getAvailableRoutes(mPackageName));
} finally {
mRouter2.unregisterCallback(routerCallback);
mRouter2.unregisterCallback(routeCallback);
mManager.unregisterCallback(managerCallback);
}
}
@@ -398,9 +421,9 @@ public class MediaRouterManagerTest {
mManager.registerCallback(mExecutor, callback);
}
private void addRouterCallback(MediaRouter2.Callback callback) {
mRouterCallbacks.add(callback);
mRouter2.registerCallback(mExecutor, callback);
private void addRouterCallback(RouteCallback routeCallback) {
mRouteCallbacks.add(routeCallback);
mRouter2.registerCallback(mExecutor, routeCallback);
}
private void clearCallbacks() {
@@ -409,9 +432,9 @@ public class MediaRouterManagerTest {
}
mManagerCallbacks.clear();
for (MediaRouter2.Callback callback : mRouterCallbacks) {
mRouter2.unregisterCallback(callback);
for (RouteCallback routeCallback : mRouteCallbacks) {
mRouter2.unregisterCallback(routeCallback);
}
mRouterCallbacks.clear();
mRouteCallbacks.clear();
}
}

View File

@@ -22,6 +22,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.RouteSessionInfo;
import android.os.Bundle;
import java.util.Objects;
@@ -42,6 +43,8 @@ abstract class MediaRoute2Provider {
mCallback = callback;
}
public abstract void requestCreateSession(String packageName, String routeId,
String controlCategory, int requestId);
public abstract void requestSelectRoute(String packageName, String routeId, int seq);
public abstract void unselectRoute(String packageName, String routeId);
public abstract void sendControlRequest(MediaRoute2Info route, Intent request);
@@ -82,5 +85,8 @@ abstract class MediaRoute2Provider {
void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider,
@NonNull String clientPackageName, @NonNull MediaRoute2Info route,
@Nullable Bundle controlHints, int seq);
void onSessionCreated(@NonNull MediaRoute2Provider provider,
@Nullable RouteSessionInfo sessionInfo, @Nullable Bundle controlHints,
int requestId);
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.media;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -26,6 +27,7 @@ import android.media.IMediaRoute2ProviderClient;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
import android.media.RouteSessionInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -73,6 +75,16 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
pw.println(prefix + " mConnectionReady=" + mConnectionReady);
}
@Override
public void requestCreateSession(String packageName, String routeId, String controlCategory,
int requestId) {
if (mConnectionReady) {
mActiveConnection.requestCreateSession(packageName, routeId, controlCategory,
requestId);
updateBinding();
}
}
@Override
public void requestSelectRoute(String packageName, String routeId, int seq) {
if (mConnectionReady) {
@@ -268,6 +280,14 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
mCallback.onRouteSelected(this, packageName, route, controlHints, seq);
}
private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo,
@Nullable Bundle controlHints, int requestId) {
if (mActiveConnection != connection) {
return;
}
mCallback.onSessionCreated(this, sessionInfo, controlHints, requestId);
}
private void disconnect() {
if (mActiveConnection != null) {
mConnectionReady = false;
@@ -308,6 +328,15 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
mClient.dispose();
}
public void requestCreateSession(String packageName, String routeId, String controlCategory,
int requestId) {
try {
mProvider.requestCreateSession(packageName, routeId, controlCategory, requestId);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to create a session.", ex);
}
}
public void requestSelectRoute(String packageName, String routeId, int seq) {
try {
mProvider.requestSelectRoute(packageName, routeId, seq);
@@ -361,6 +390,12 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
mHandler.post(() -> onRouteSelected(Connection.this,
packageName, routeId, controlHints, seq));
}
void postSessionCreated(@Nullable RouteSessionInfo sessionInfo,
@Nullable Bundle controlHints, int requestId) {
mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo, controlHints,
requestId));
}
}
private static final class ProviderClient extends IMediaRoute2ProviderClient.Stub {
@@ -391,5 +426,13 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
}
}
@Override
public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo,
@Nullable Bundle controlHints, int requestId) {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.postSessionCreated(sessionInfo, controlHints, requestId);
}
}
}
}

View File

@@ -29,6 +29,7 @@ import android.media.IMediaRouter2Manager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRouter2;
import android.media.RouteSessionInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -55,6 +56,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* TODO: Merge this to MediaRouterService once it's finished.
@@ -166,6 +168,24 @@ class MediaRouter2ServiceImpl {
}
}
public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
String controlCategory, int requestId) {
Objects.requireNonNull(client, "client must not be null");
Objects.requireNonNull(route, "route must not be null");
if (TextUtils.isEmpty(controlCategory)) {
throw new IllegalArgumentException("controlCategory must not be empty");
}
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
requestCreateSessionLocked(client, route, controlCategory, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
public void sendControlRequest(@NonNull IMediaRouter2Client client,
@NonNull MediaRoute2Info route, @NonNull Intent request) {
Objects.requireNonNull(client, "client must not be null");
@@ -198,18 +218,6 @@ class MediaRouter2ServiceImpl {
}
}
public void requestSelectRoute2(@NonNull IMediaRouter2Client client,
@Nullable MediaRoute2Info route) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), false, route);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
Objects.requireNonNull(client, "client must not be null");
Objects.requireNonNull(route, "route must not be null");
@@ -353,6 +361,19 @@ class MediaRouter2ServiceImpl {
}
}
private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client,
@NonNull MediaRoute2Info route, @NonNull String controlCategory, int requestId) {
final IBinder binder = client.asBinder();
final Client2Record clientRecord = mAllClientRecords.get(binder);
if (clientRecord != null) {
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::requestCreateSessionOnHandler,
clientRecord.mUserRecord.mHandler,
clientRecord, route, controlCategory, requestId));
}
}
private void requestSelectRoute2Locked(Client2Record clientRecord, boolean selectedByManager,
MediaRoute2Info route) {
if (clientRecord != null) {
@@ -569,6 +590,7 @@ class MediaRouter2ServiceImpl {
mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
}
// TODO: This assumes that only one client exists in a package. Is it true?
Client2Record findClientRecordLocked(String packageName) {
for (Client2Record clientRecord : mClientRecords) {
if (TextUtils.equals(clientRecord.mPackageName, packageName)) {
@@ -667,10 +689,12 @@ class MediaRouter2ServiceImpl {
private final SystemMediaRoute2Provider mSystemProvider;
private final ArrayList<MediaRoute2Provider> mMediaProviders =
new ArrayList<>();
private final List<MediaRoute2ProviderInfo> mProviderInfos = new ArrayList<>();
private final List<MediaRoute2ProviderInfo> mLastProviderInfos = new ArrayList<>();
private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
new CopyOnWriteArrayList<>();
private boolean mRunning;
private boolean mProviderInfosUpdateScheduled;
UserHandler(MediaRouter2ServiceImpl service, UserRecord userRecord) {
super(Looper.getMainLooper(), null, true);
@@ -721,23 +745,31 @@ class MediaRouter2ServiceImpl {
controlHints, seq));
}
@Override
public void onSessionCreated(@NonNull MediaRoute2Provider provider,
@Nullable RouteSessionInfo sessionInfo, @Nullable Bundle controlHints,
int requestId) {
sendMessage(PooledLambda.obtainMessage(UserHandler::handleCreateSessionResultOnHandler,
this, provider, sessionInfo, controlHints, requestId));
}
private void updateProvider(MediaRoute2Provider provider) {
int providerIndex = getProviderInfoIndex(provider.getUniqueId());
MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
MediaRoute2ProviderInfo prevInfo =
(providerIndex < 0) ? null : mProviderInfos.get(providerIndex);
(providerIndex < 0) ? null : mLastProviderInfos.get(providerIndex);
if (Objects.equals(prevInfo, providerInfo)) return;
if (prevInfo == null) {
mProviderInfos.add(providerInfo);
mLastProviderInfos.add(providerInfo);
Collection<MediaRoute2Info> addedRoutes = providerInfo.getRoutes();
if (addedRoutes.size() > 0) {
sendMessage(PooledLambda.obtainMessage(UserHandler::notifyRoutesAddedToClients,
this, getClients(), new ArrayList<>(addedRoutes)));
}
} else if (providerInfo == null) {
mProviderInfos.remove(prevInfo);
mLastProviderInfos.remove(prevInfo);
Collection<MediaRoute2Info> removedRoutes = prevInfo.getRoutes();
if (removedRoutes.size() > 0) {
sendMessage(PooledLambda.obtainMessage(
@@ -745,7 +777,7 @@ class MediaRouter2ServiceImpl {
this, getClients(), new ArrayList<>(removedRoutes)));
}
} else {
mProviderInfos.set(providerIndex, providerInfo);
mLastProviderInfos.set(providerIndex, providerInfo);
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
@@ -794,8 +826,8 @@ class MediaRouter2ServiceImpl {
}
private int getProviderInfoIndex(String providerId) {
for (int i = 0; i < mProviderInfos.size(); i++) {
MediaRoute2ProviderInfo providerInfo = mProviderInfos.get(i);
for (int i = 0; i < mLastProviderInfos.size(); i++) {
MediaRoute2ProviderInfo providerInfo = mLastProviderInfos.get(i);
if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) {
return i;
}
@@ -803,6 +835,96 @@ class MediaRouter2ServiceImpl {
return -1;
}
private void requestCreateSessionOnHandler(Client2Record clientRecord,
MediaRoute2Info route, String controlCategory, int requestId) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider == null) {
Slog.w(TAG, "Ignoring session creation request since no provider found for"
+ " given route=" + route);
notifySessionCreationFailed(clientRecord, requestId);
return;
}
if (!route.getSupportedCategories().contains(controlCategory)) {
Slog.w(TAG, "Ignoring session creation request since the given route=" + route
+ " doesn't support the given category=" + controlCategory);
notifySessionCreationFailed(clientRecord, requestId);
return;
}
// TODO: Apply timeout for each request (How many seconds should we wait?)
SessionCreationRequest request = new SessionCreationRequest(
clientRecord, route, controlCategory, requestId);
mSessionCreationRequests.add(request);
provider.requestCreateSession(clientRecord.mPackageName, route.getId(),
controlCategory, requestId);
}
private void handleCreateSessionResultOnHandler(
@NonNull MediaRoute2Provider provider, @Nullable RouteSessionInfo sessionInfo,
@Nullable Bundle controlHints, int requestId) {
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
if (request.mRequestId == requestId
&& TextUtils.equals(
request.mRoute.getProviderId(), provider.getUniqueId())) {
matchingRequest = request;
break;
}
}
if (matchingRequest == null) {
Slog.w(TAG, "Ignoring session creation result for unknown request. "
+ "requestId=" + requestId + ", sessionInfo=" + sessionInfo);
return;
}
if (sessionInfo == null) {
// Failed
notifySessionCreationFailed(matchingRequest.mClientRecord, requestId);
return;
}
String originalRouteId = matchingRequest.mRoute.getId();
String originalCategory = matchingRequest.mControlCategory;
if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
|| !TextUtils.equals(originalCategory, sessionInfo.getControlCategory())) {
Slog.w(TAG, "Created session doesn't match the original request."
+ " originalRouteId=" + originalRouteId
+ ", originalCategory=" + originalCategory
+ ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
notifySessionCreationFailed(matchingRequest.mClientRecord, requestId);
return;
}
// Succeeded
notifySessionCreated(matchingRequest.mClientRecord, sessionInfo, controlHints,
requestId);
// TODO: Tell managers for the session creation
}
private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo,
Bundle controlHints, int requestId) {
try {
clientRecord.mClient.notifySessionCreated(sessionInfo, controlHints, requestId);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify client of the session creation."
+ " Client probably died.", ex);
}
}
private void notifySessionCreationFailed(Client2Record clientRecord, int requestId) {
try {
clientRecord.mClient.notifySessionCreated(
null /* sessionInfo */, null /* controlHints */, requestId);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify client of the session creation failure."
+ " Client probably died.", ex);
}
}
private void updateSelectedRoute(MediaRoute2ProviderProxy provider,
String clientPackageName, MediaRoute2Info selectedRoute, Bundle controlHints,
int seq) {
@@ -858,6 +980,12 @@ class MediaRouter2ServiceImpl {
clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
}
if (clientRecord == null) {
Log.w(TAG, "The client has gone. packageName=" + clientPackageName
+ " selectingRoute=" + selectingRoute);
return;
}
if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
clientRecord.mSelectingRoute.getUniqueId(), selectingRoute.getUniqueId())) {
Log.w(TAG, "Ignoring invalid selectFallbackRoute call. "
@@ -951,7 +1079,7 @@ class MediaRouter2ServiceImpl {
private void notifyRoutesToClient(IMediaRouter2Client client) {
List<MediaRoute2Info> routes = new ArrayList<>();
for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) {
for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
routes.addAll(providerInfo.getRoutes());
}
if (routes.size() == 0) {
@@ -964,13 +1092,9 @@ class MediaRouter2ServiceImpl {
}
}
// TODO: Remove notifyRouteSelected* methods
private void notifyRouteSelectedToClient(IMediaRouter2Client client,
MediaRoute2Info route, int reason, Bundle controlHints) {
try {
client.notifyRouteSelected(route, reason, controlHints);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify routes selected. Client probably died.", ex);
}
}
private void notifyRoutesAddedToClients(List<IMediaRouter2Client> clients,
@@ -1008,7 +1132,7 @@ class MediaRouter2ServiceImpl {
private void notifyRoutesToManager(IMediaRouter2Manager manager) {
List<MediaRoute2Info> routes = new ArrayList<>();
for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) {
for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
routes.addAll(providerInfo.getRoutes());
}
if (routes.size() == 0) {
@@ -1085,5 +1209,21 @@ class MediaRouter2ServiceImpl {
}
return null;
}
final class SessionCreationRequest {
public final Client2Record mClientRecord;
public final MediaRoute2Info mRoute;
public final String mControlCategory;
public final int mRequestId;
SessionCreationRequest(@NonNull Client2Record clientRecord,
@NonNull MediaRoute2Info route,
@NonNull String controlCategory, int requestId) {
mClientRecord = clientRecord;
mRoute = route;
mControlCategory = controlCategory;
mRequestId = requestId;
}
}
}
}

View File

@@ -457,8 +457,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
public void requestSelectRoute2(IMediaRouter2Client client, MediaRoute2Info route) {
mService2.requestSelectRoute2(client, route);
public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
String controlCategory, int requestId) {
mService2.requestCreateSession(client, route, controlCategory, requestId);
}
// Binder call

View File

@@ -88,6 +88,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
initializeRoutes();
}
@Override
public void requestCreateSession(String packageName, String routeId, String controlCategory,
int requestId) {
// Do nothing
}
//TODO: implement method
@Override
public void requestSelectRoute(@NonNull String packageName, @NonNull String routeId, int seq) {