summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--media/java/android/media/IMediaRoute2Provider.aidl2
-rw-r--r--media/java/android/media/IMediaRoute2ProviderClient.aidl6
-rw-r--r--media/java/android/media/IMediaRouter2Client.aidl4
-rw-r--r--media/java/android/media/IMediaRouterService.aidl10
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java44
-rw-r--r--media/java/android/media/MediaRouter2.java236
-rw-r--r--media/java/android/media/RouteSessionController.java28
-rw-r--r--media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java23
-rw-r--r--media/tests/MediaRouter/Android.bp1
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java132
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java75
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2Provider.java6
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java43
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java194
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java5
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java6
16 files changed, 619 insertions, 196 deletions
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index d8fd1ff22c89..a03aa868fd84 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -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);
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index f4fb7f450fb8..8e63c4ae2b27 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -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);
}
diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl
index b04af7d1d28d..f4c6b6a7c5c4 100644
--- a/media/java/android/media/IMediaRouter2Client.aidl
+++ b/media/java/android/media/IMediaRouter2Client.aidl
@@ -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);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index d803f04839b1..faf25631a167 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -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);
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 8d6f2551f9f6..6e9f3d0efe87 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -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.
@@ -339,6 +351,14 @@ public abstract class MediaRoute2ProviderService extends Service {
}
@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
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f9dabcf8c90f..161a75a9679d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -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
- */
- @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 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
*
- * @param route the route to select
+ * @see SessionCreationCallback#onSessionCreated(RouteSessionController, Bundle)
+ * @see SessionCreationCallback#onSessionCreationFailed()
*/
- public void requestSelectRoute(@NonNull MediaRoute2Info route) {
+ @NonNull
+ 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) {
- record.mExecutor.execute(
- () -> record.mCallback.onRoutesChanged(routes));
- }
- }
-
- private void notifyRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
- for (CallbackRecord record: mCallbackRecords) {
+ for (RouteCallbackRecord record: mRouteCallbackRecords) {
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) {}
+ }
+ /**
+ * Callback for receiving a result of session creation.
+ */
+ public static class SessionCreationCallback {
/**
- * Called when a route is selected. Exactly one route can be selected at a time.
+ * Called when the route session is created by the route provider.
*
- * @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()
+ * @param controller the controller to control the created session
*/
- public void onRouteSelected(@NonNull MediaRoute2Info route, @SelectReason int reason,
- @NonNull Bundle controlHints) {}
+ public void onSessionCreated(RouteSessionController controller, Bundle controlHints) {}
+
+ /**
+ * Called when the session creation request failed.
+ */
+ public void onSessionCreationFailed() {}
}
- final class CallbackRecord {
- public final Callback mCallback;
- public Executor mExecutor;
- public int mFlags;
+ final class RouteCallbackRecord {
+ public final Executor mExecutor;
+ public final RouteCallback mRouteCallback;
+ public final int mFlags;
- CallbackRecord(@NonNull Callback callback, @Nullable Executor executor, int flags) {
- mCallback = callback;
+ 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));
}
}
}
diff --git a/media/java/android/media/RouteSessionController.java b/media/java/android/media/RouteSessionController.java
index 5ff721837573..b6e8bb55aa4c 100644
--- a/media/java/android/media/RouteSessionController.java
+++ b/media/java/android/media/RouteSessionController.java
@@ -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) {
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index 524be8c014c2..98efeb82cea3 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -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
diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp
index 611b25a2f128..5a0a50c2ae38 100644
--- a/media/tests/MediaRouter/Android.bp
+++ b/media/tests/MediaRouter/Android.bp
@@ -11,6 +11,7 @@ android_test {
static_libs: [
"android-support-test",
"mockito-target-minus-junit4",
+ "testng"
],
platform_apis: true,
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 6f1a070d6f7c..e093599e5a0a 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -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);
}
}
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index c5d8a96c7e08..4b3dc0d184a4 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -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();
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 9a49c166e2b2..85509f7d1ee0 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -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);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index a5abb1835e7b..5685ce982ace 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -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;
@@ -74,6 +76,16 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv
}
@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) {
mActiveConnection.requestSelectRoute(packageName, routeId, seq);
@@ -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);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e7b88604db32..8ee5f66e5358 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -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,44 +168,50 @@ class MediaRouter2ServiceImpl {
}
}
- public void sendControlRequest(@NonNull IMediaRouter2Client client,
- @NonNull MediaRoute2Info route, @NonNull Intent request) {
+ 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");
- Objects.requireNonNull(request, "request must not be null");
+ if (TextUtils.isEmpty(controlCategory)) {
+ throw new IllegalArgumentException("controlCategory must not be empty");
+ }
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- sendControlRequestLocked(client, route, request);
+ requestCreateSessionLocked(client, route, controlCategory, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
- public void setControlCategories(@NonNull IMediaRouter2Client client,
- @NonNull List<String> categories) {
+ public void sendControlRequest(@NonNull IMediaRouter2Client client,
+ @NonNull MediaRoute2Info route, @NonNull Intent request) {
Objects.requireNonNull(client, "client must not be null");
- Objects.requireNonNull(categories, "categories 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) {
- Client2Record clientRecord = mAllClientRecords.get(client.asBinder());
- setControlCategoriesLocked(clientRecord, categories);
+ sendControlRequestLocked(client, route, request);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
- public void requestSelectRoute2(@NonNull IMediaRouter2Client client,
- @Nullable MediaRoute2Info route) {
+ public void setControlCategories(@NonNull IMediaRouter2Client client,
+ @NonNull List<String> categories) {
+ Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(categories, "categories must not be null");
+
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), false, route);
+ Client2Record clientRecord = mAllClientRecords.get(client.asBinder());
+ setControlCategoriesLocked(clientRecord, categories);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -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;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 9c99e8f43639..a280f9171725 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -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
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 4f64177ad135..6c4c8d50b41e 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -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) {