summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--media/java/android/media/IMediaRouter2.aidl5
-rw-r--r--media/java/android/media/IMediaRouterService.aidl11
-rw-r--r--media/java/android/media/MediaRouter2.java347
-rw-r--r--media/java/android/media/MediaRouter2Manager.java9
-rw-r--r--media/java/android/media/RoutingSessionInfo.java2
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java74
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java20
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2Provider.java1
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java105
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java316
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java16
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java5
12 files changed, 541 insertions, 370 deletions
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index ca14052c964f..fe15f0e67b1d 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -34,7 +34,8 @@ oneway interface IMediaRouter2 {
void notifySessionReleased(in RoutingSessionInfo sessionInfo);
/**
* Gets hints of the new session for the given route.
- * Call MediaRouterService#notifySessionHintsForCreatingSession to pass the result.
+ * Call MediaRouterService#requestCreateSessionWithRouter2 to pass the result.
*/
- void getSessionHintsForCreatingSession(long uniqueRequestId, in MediaRoute2Info route);
+ void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession,
+ in MediaRoute2Info route);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 52bac671cc6f..068f9689d06f 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -44,7 +44,7 @@ interface IMediaRouterService {
void requestSetVolume(IMediaRouterClient client, String routeId, int volume);
void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction);
- // Note: When changing this file, match the order of methods below with
+ // Note: When changing this file, match the order of methods below with
// MediaRouterService.java for readability.
// Methods for MediaRouter2
@@ -57,10 +57,9 @@ interface IMediaRouterService {
in RouteDiscoveryPreference preference);
void setRouteVolumeWithRouter2(IMediaRouter2 router, in MediaRoute2Info route, int volume);
- void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
- in MediaRoute2Info route, in @nullable Bundle sessionHints);
- void notifySessionHintsForCreatingSession(IMediaRouter2 router, long uniqueRequestId,
- in MediaRoute2Info route, in @nullable Bundle sessionHints);
+ void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId,
+ in RoutingSessionInfo oldSession, in MediaRoute2Info route,
+ in @nullable Bundle sessionHints);
void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
@@ -76,7 +75,7 @@ interface IMediaRouterService {
in MediaRoute2Info route, int volume);
void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
- String packageName, in @nullable MediaRoute2Info route);
+ in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route);
void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
String sessionId, in MediaRoute2Info route);
void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8e95239a73f8..f22222d10ad8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -36,7 +36,6 @@ import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -62,6 +61,11 @@ public final class MediaRouter2 {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final Object sRouterLock = new Object();
+ // The maximum time for the old routing controller available after transfer.
+ private static final int TRANSFER_TIMEOUT_MS = 30_000;
+ // The manager request ID representing that no manager is involved.
+ private static final long MANAGER_REQUEST_ID_NONE = MediaRoute2ProviderService.REQUEST_ID_NONE;
+
@GuardedBy("sRouterLock")
private static MediaRouter2 sInstance;
@@ -80,7 +84,7 @@ public final class MediaRouter2 {
private final String mPackageName;
@GuardedBy("sRouterLock")
- final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
+ final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>();
final RoutingController mSystemController;
@@ -94,7 +98,7 @@ public final class MediaRouter2 {
@GuardedBy("sRouterLock")
private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
- private final AtomicInteger mControllerCreationRequestCnt = new AtomicInteger(1);
+ private final AtomicInteger mNextRequestId = new AtomicInteger(1);
final Handler mHandler;
@GuardedBy("sRouterLock")
@@ -412,9 +416,16 @@ public final class MediaRouter2 {
return;
}
- final int requestId = mControllerCreationRequestCnt.getAndIncrement();
+ requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
+ }
+
+ void requestCreateController(@NonNull RoutingController controller,
+ @NonNull MediaRoute2Info route, long managerRequestId) {
- ControllerCreationRequest request = new ControllerCreationRequest(requestId, route);
+ final int requestId = mNextRequestId.getAndIncrement();
+
+ ControllerCreationRequest request = new ControllerCreationRequest(requestId,
+ managerRequestId, route, controller);
mControllerCreationRequests.add(request);
OnGetControllerHintsListener listener = mOnGetControllerHintsListener;
@@ -433,11 +444,15 @@ public final class MediaRouter2 {
if (stub != null) {
try {
mMediaRouterService.requestCreateSessionWithRouter2(
- stub, requestId, route, controllerHints);
+ stub, requestId, managerRequestId,
+ controller.getRoutingSessionInfo(), route, controllerHints);
} catch (RemoteException ex) {
- Log.e(TAG, "transfer: Unable to request to create controller.", ex);
- mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
- MediaRouter2.this, requestId, null));
+ Log.e(TAG, "createControllerForTransfer: "
+ + "Failed to request for creating a controller.", ex);
+ mControllerCreationRequests.remove(request);
+ if (managerRequestId == MANAGER_REQUEST_ID_NONE) {
+ notifyTransferFailure(route);
+ }
}
}
}
@@ -463,7 +478,8 @@ public final class MediaRouter2 {
}
/**
- * Gets the list of currently non-released {@link RoutingController routing controllers}.
+ * Gets the list of currently active {@link RoutingController routing controllers} on which
+ * media can be played.
* <p>
* Note: The list returned here will never be empty. The first element in the list is
* always the {@link #getSystemController() system controller}.
@@ -554,13 +570,13 @@ public final class MediaRouter2 {
mShouldUpdateRoutes = true;
}
- if (addedRoutes.size() > 0) {
+ if (!addedRoutes.isEmpty()) {
notifyRoutesAdded(addedRoutes);
}
- if (removedRoutes.size() > 0) {
+ if (!removedRoutes.isEmpty()) {
notifyRoutesRemoved(removedRoutes);
}
- if (changedRoutes.size() > 0) {
+ if (!changedRoutes.isEmpty()) {
notifyRoutesChanged(changedRoutes);
}
@@ -582,7 +598,7 @@ public final class MediaRouter2 {
}
mShouldUpdateRoutes = true;
}
- if (addedRoutes.size() > 0) {
+ if (!addedRoutes.isEmpty()) {
notifyRoutesAdded(addedRoutes);
}
}
@@ -598,7 +614,7 @@ public final class MediaRouter2 {
}
mShouldUpdateRoutes = true;
}
- if (removedRoutes.size() > 0) {
+ if (!removedRoutes.isEmpty()) {
notifyRoutesRemoved(removedRoutes);
}
}
@@ -614,7 +630,7 @@ public final class MediaRouter2 {
}
mShouldUpdateRoutes = true;
}
- if (changedRoutes.size() > 0) {
+ if (!changedRoutes.isEmpty()) {
notifyRoutesChanged(changedRoutes);
}
}
@@ -635,44 +651,47 @@ public final class MediaRouter2 {
}
}
- if (matchingRequest != null) {
- mControllerCreationRequests.remove(matchingRequest);
-
- MediaRoute2Info requestedRoute = matchingRequest.mRoute;
-
- if (sessionInfo == null) {
- // TODO: We may need to distinguish between failure and rejection.
- // One way can be introducing 'reason'.
- notifyTransferFailure(requestedRoute);
- return;
- } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
- Log.w(TAG, "The session does not contain the requested route. "
- + "(requestedRouteId=" + requestedRoute.getId()
- + ", actualRoutes=" + sessionInfo.getSelectedRoutes()
- + ")");
- notifyTransferFailure(requestedRoute);
- return;
- } else if (!TextUtils.equals(requestedRoute.getProviderId(),
- sessionInfo.getProviderId())) {
- Log.w(TAG, "The session's provider ID does not match the requested route's. "
- + "(requested route's providerId=" + requestedRoute.getProviderId()
- + ", actual providerId=" + sessionInfo.getProviderId()
- + ")");
- notifyTransferFailure(requestedRoute);
- return;
- }
+ if (matchingRequest == null) {
+ Log.w(TAG, "createControllerOnHandler: Ignoring an unknown request.");
+ return;
}
+ mControllerCreationRequests.remove(matchingRequest);
+ MediaRoute2Info requestedRoute = matchingRequest.mRoute;
+
+ // TODO: Notify the reason for failure.
if (sessionInfo == null) {
+ notifyTransferFailure(requestedRoute);
+ return;
+ } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
+ Log.w(TAG, "The session does not contain the requested route. "
+ + "(requestedRouteId=" + requestedRoute.getId()
+ + ", actualRoutes=" + sessionInfo.getSelectedRoutes()
+ + ")");
+ notifyTransferFailure(requestedRoute);
+ return;
+ } else if (!TextUtils.equals(requestedRoute.getProviderId(),
+ sessionInfo.getProviderId())) {
+ Log.w(TAG, "The session's provider ID does not match the requested route's. "
+ + "(requested route's providerId=" + requestedRoute.getProviderId()
+ + ", actual providerId=" + sessionInfo.getProviderId()
+ + ")");
+ notifyTransferFailure(requestedRoute);
return;
}
- RoutingController oldController = getCurrentController();
- if (!oldController.releaseInternal(
- /* shouldReleaseSession= */ matchingRequest != null,
- /* shouldNotifyStop= */ false)) {
- // Could not release the controller since it was just released by other thread.
- oldController = getSystemController();
+ RoutingController oldController = matchingRequest.mOldController;
+ // When the old controller is released before transferred, treat it as a failure.
+ // This could also happen when transfer is requested twice or more.
+ if (!oldController.scheduleRelease()) {
+ Log.w(TAG, "createControllerOnHandler: "
+ + "Ignoring controller creation for released old controller. "
+ + "oldController=" + oldController);
+ if (!sessionInfo.isSystemSession()) {
+ new RoutingController(sessionInfo).release();
+ }
+ notifyTransferFailure(requestedRoute);
+ return;
}
RoutingController newController;
@@ -686,12 +705,7 @@ public final class MediaRouter2 {
}
}
- // Two controller can be same if stop() is called before the result of Cast -> Phone comes.
- if (oldController != newController) {
- notifyTransfer(oldController, newController);
- } else if (matchingRequest != null) {
- notifyTransferFailure(matchingRequest.mRoute);
- }
+ notifyTransfer(oldController, newController);
}
void updateControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -736,10 +750,9 @@ public final class MediaRouter2 {
return;
}
- final String uniqueSessionId = sessionInfo.getId();
RoutingController matchingController;
synchronized (sRouterLock) {
- matchingController = mNonSystemRoutingControllers.get(uniqueSessionId);
+ matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
}
if (matchingController == null) {
@@ -757,34 +770,23 @@ public final class MediaRouter2 {
return;
}
- matchingController.releaseInternal(
- /* shouldReleaseSession= */ false, /* shouldNotifyStop= */ true);
+ matchingController.releaseInternal(/* shouldReleaseSession= */ false);
}
- void onGetControllerHintsForCreatingSessionOnHandler(long uniqueRequestId,
- MediaRoute2Info route) {
- OnGetControllerHintsListener listener = mOnGetControllerHintsListener;
- Bundle controllerHints = null;
- if (listener != null) {
- controllerHints = listener.onGetControllerHints(route);
- if (controllerHints != null) {
- controllerHints = new Bundle(controllerHints);
+ void onRequestCreateControllerByManagerOnHandler(RoutingSessionInfo oldSession,
+ MediaRoute2Info route, long managerRequestId) {
+ RoutingController controller;
+ if (oldSession.isSystemSession()) {
+ controller = getSystemController();
+ } else {
+ synchronized (sRouterLock) {
+ controller = mNonSystemRoutingControllers.get(oldSession.getId());
}
}
-
- MediaRouter2Stub stub;
- synchronized (sRouterLock) {
- stub = mStub;
- }
- if (stub != null) {
- try {
- mMediaRouterService.notifySessionHintsForCreatingSession(
- stub, uniqueRequestId, route, controllerHints);
- } catch (RemoteException ex) {
- Log.e(TAG, "onGetControllerHintsForCreatingSessionOnHandler: Unable to notify "
- + " session hints for creating session.", ex);
- }
+ if (controller == null) {
+ return;
}
+ requestCreateController(controller, route, managerRequestId);
}
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
@@ -886,8 +888,13 @@ public final class MediaRouter2 {
/**
* Called when a media is transferred between two different routing controllers.
* This can happen by calling {@link #transferTo(MediaRoute2Info)}.
- * The {@code oldController} is released before this method is called, except for the
- * {@link #getSystemController() system controller}.
+ * <p> Override this to start playback with {@code newController}. You may want to get
+ * the status of the media that is being played with {@code oldController} and resume it
+ * continuously with {@code newController}.
+ * After this is called, any callbacks with {@code oldController} will not be invoked
+ * unless {@code oldController} is the {@link #getSystemController() system controller}.
+ * You need to {@link RoutingController#release() release} {@code oldController} before
+ * playing the media with {@code newController}.
*
* @param oldController the previous controller that controlled routing
* @param newController the new controller to control routing
@@ -906,16 +913,15 @@ public final class MediaRouter2 {
/**
* Called when a media routing stops. It can be stopped by a user or a provider.
* App should not continue playing media locally when this method is called.
- * The {@code oldController} is released before this method is called, except for the
- * {@link #getSystemController() system controller}.
+ * The {@code controller} is released before this method is called.
*
- * @param controller the controller that controlled the stopped media routing.
+ * @param controller the controller that controlled the stopped media routing
*/
public void onStop(@NonNull RoutingController controller) { }
}
/**
- * A listener interface to send an optional app-specific hints when creating the
+ * A listener interface to send optional app-specific hints when creating a
* {@link RoutingController}.
*/
public interface OnGetControllerHintsListener {
@@ -929,9 +935,9 @@ public final class MediaRouter2 {
* The method will be called on the same thread that calls
* {@link #transferTo(MediaRoute2Info)} or the main thread if it is requested by the system.
*
- * @param route The route to create controller with
+ * @param route the route to create a controller with
* @return An optional bundle of app-specific arguments to send to the provider,
- * or null if none. The contents of this bundle may affect the result of
+ * or {@code null} if none. The contents of this bundle may affect the result of
* controller creation.
* @see MediaRoute2ProviderService#onCreateSession(long, String, String, Bundle)
*/
@@ -944,10 +950,11 @@ public final class MediaRouter2 {
*/
public abstract static class ControllerCallback {
/**
- * Called when a controller is updated. (e.g., the selected routes of the
- * controller is changed or the volume of the controller is changed.)
+ * Called when a controller is updated. (e.g., when the selected routes of the
+ * controller is changed or when the volume of the controller is changed.)
*
- * @param controller the updated controller. Can be the system controller.
+ * @param controller the updated controller. It may be the
+ * {@link #getSystemController() system controller}.
* @see #getSystemController()
*/
public void onControllerUpdated(@NonNull RoutingController controller) { }
@@ -955,20 +962,28 @@ public final class MediaRouter2 {
/**
* A class to control media routing session in media route provider.
- * For example, selecting/deselecting/transferring routes to a session can be done through this
- * class. Instances are created by {@link #transferTo(MediaRoute2Info)}.
+ * For example, selecting/deselecting/transferring to routes of a session can be done through
+ * this. Instances are created when
+ * {@link TransferCallback#onTransfer(RoutingController, RoutingController)} is called,
+ * which is invoked after {@link #transferTo(MediaRoute2Info)} is called.
*/
public class RoutingController {
private final Object mControllerLock = new Object();
+ private static final int CONTROLLER_STATE_UNKNOWN = 0;
+ private static final int CONTROLLER_STATE_ACTIVE = 1;
+ private static final int CONTROLLER_STATE_RELEASING = 2;
+ private static final int CONTROLLER_STATE_RELEASED = 3;
+
@GuardedBy("mControllerLock")
private RoutingSessionInfo mSessionInfo;
@GuardedBy("mControllerLock")
- private volatile boolean mIsReleased;
+ private int mState;
RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
mSessionInfo = sessionInfo;
+ mState = CONTROLLER_STATE_ACTIVE;
}
/**
@@ -982,7 +997,7 @@ public final class MediaRouter2 {
}
/**
- * Gets the original session id set by
+ * Gets the original session ID set by
* {@link RoutingSessionInfo.Builder#Builder(String, String)}.
*
* @hide
@@ -996,7 +1011,8 @@ public final class MediaRouter2 {
}
/**
- * @return the control hints used to control routing session if available.
+ * Gets the control hints used to control routing session if available.
+ * It is set by the media route provider.
*/
@Nullable
public Bundle getControlHints() {
@@ -1042,7 +1058,9 @@ public final class MediaRouter2 {
}
/**
- * Gets information about how volume is handled on the session.
+ * Gets the information about how volume is handled on the session.
+ * <p>Please note that you may not control the volume of the session even when
+ * you can control the volume of each selected route in the session.
*
* @return {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or
* {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}
@@ -1067,8 +1085,8 @@ public final class MediaRouter2 {
* Gets the current volume of the session.
* <p>
* When it's available, it represents the volume of routing session, which is a group
- * of selected routes. To get the volume of a route,
- * use {@link MediaRoute2Info#getVolume()}.
+ * of selected routes. Use {@link MediaRoute2Info#getVolume()}
+ * to get the volume of a route,
* </p>
* @see MediaRoute2Info#getVolume()
*/
@@ -1087,7 +1105,7 @@ public final class MediaRouter2 {
*/
public boolean isReleased() {
synchronized (mControllerLock) {
- return mIsReleased;
+ return mState == CONTROLLER_STATE_RELEASED;
}
}
@@ -1099,8 +1117,8 @@ public final class MediaRouter2 {
* <p>
* The given route must satisfy all of the following conditions:
* <ul>
- * <li>ID should not be included in {@link #getSelectedRoutes()}</li>
- * <li>ID should be included in {@link #getSelectableRoutes()}</li>
+ * <li>It should not be included in {@link #getSelectedRoutes()}</li>
+ * <li>It should be included in {@link #getSelectableRoutes()}</li>
* </ul>
* If the route doesn't meet any of above conditions, it will be ignored.
*
@@ -1111,11 +1129,9 @@ public final class MediaRouter2 {
*/
public void selectRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
- synchronized (mControllerLock) {
- if (mIsReleased) {
- Log.w(TAG, "selectRoute: Called on released controller. Ignoring.");
- return;
- }
+ if (isReleased()) {
+ Log.w(TAG, "selectRoute: Called on released controller. Ignoring.");
+ return;
}
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
@@ -1145,12 +1161,12 @@ public final class MediaRouter2 {
/**
* Deselects a route from the remote session. After a route is deselected, the media is
- * expected to be stopped on the deselected routes.
+ * expected to be stopped on the deselected route.
* <p>
* The given route must satisfy all of the following conditions:
* <ul>
- * <li>ID should be included in {@link #getSelectedRoutes()}</li>
- * <li>ID should be included in {@link #getDeselectableRoutes()}</li>
+ * <li>It should be included in {@link #getSelectedRoutes()}</li>
+ * <li>It should be included in {@link #getDeselectableRoutes()}</li>
* </ul>
* If the route doesn't meet any of above conditions, it will be ignored.
*
@@ -1160,11 +1176,9 @@ public final class MediaRouter2 {
*/
public void deselectRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
- synchronized (mControllerLock) {
- if (mIsReleased) {
- Log.w(TAG, "deselectRoute: called on released controller. Ignoring.");
- return;
- }
+ if (isReleased()) {
+ Log.w(TAG, "deselectRoute: called on released controller. Ignoring.");
+ return;
}
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
@@ -1193,13 +1207,8 @@ public final class MediaRouter2 {
}
/**
- * Transfers to a given route for the remote session. The given route must satisfy
- * all of the following conditions:
- * <ul>
- * <li>ID should not be included in {@link RoutingSessionInfo#getSelectedRoutes()}</li>
- * <li>ID should be included in {@link RoutingSessionInfo#getTransferableRoutes()}</li>
- * </ul>
- * If the route doesn't meet any of above conditions, it will be ignored.
+ * Transfers to a given route for the remote session. The given route must be included
+ * in {@link RoutingSessionInfo#getTransferableRoutes()}.
*
* @see RoutingSessionInfo#getSelectedRoutes()
* @see RoutingSessionInfo#getTransferableRoutes()
@@ -1208,19 +1217,13 @@ public final class MediaRouter2 {
void transferToRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
synchronized (mControllerLock) {
- if (mIsReleased) {
+ if (isReleased()) {
Log.w(TAG, "transferToRoute: Called on released controller. Ignoring.");
return;
}
- if (mSessionInfo.getSelectedRoutes().contains(route.getId())) {
- Log.w(TAG, "Ignoring transferring to a route that is already added. "
- + "route=" + route);
- return;
- }
-
if (!mSessionInfo.getTransferableRoutes().contains(route.getId())) {
- Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
+ Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route);
return;
}
}
@@ -1255,11 +1258,9 @@ public final class MediaRouter2 {
return;
}
- synchronized (mControllerLock) {
- if (mIsReleased) {
- Log.w(TAG, "setVolume: Called on released controller. Ignoring.");
- return;
- }
+ if (isReleased()) {
+ Log.w(TAG, "setVolume: Called on released controller. Ignoring.");
+ return;
}
MediaRouter2Stub stub;
synchronized (sRouterLock) {
@@ -1275,33 +1276,58 @@ public final class MediaRouter2 {
}
/**
- * Release this controller and corresponding session.
+ * Releases this controller and the corresponding session.
* Any operations on this controller after calling this method will be ignored.
* The devices that are playing media will stop playing it.
*/
- // TODO(b/157872573): Add tests using {@link MediaRouter2Manager#getActiveSessions()}.
public void release() {
- releaseInternal(/* shouldReleaseSession= */ true, /* shouldNotifyStop= */ true);
+ releaseInternal(/* shouldReleaseSession= */ true);
}
/**
- * Returns {@code true} when succeeded to release, {@code false} if the controller is
- * already released.
+ * Schedules release of the controller.
+ * @return {@code true} if it's successfully scheduled, {@code false} if it's already
+ * scheduled to be released or released.
*/
- boolean releaseInternal(boolean shouldReleaseSession, boolean shouldNotifyStop) {
+ boolean scheduleRelease() {
synchronized (mControllerLock) {
- if (mIsReleased) {
- Log.w(TAG, "releaseInternal: Called on released controller. Ignoring.");
+ if (mState != CONTROLLER_STATE_ACTIVE) {
return false;
}
- mIsReleased = true;
+ mState = CONTROLLER_STATE_RELEASING;
}
synchronized (sRouterLock) {
+ // It could happen if the controller is released by the another thread
+ // in between two locks
if (!mNonSystemRoutingControllers.remove(getId(), this)) {
- Log.w(TAG, "releaseInternal: Ignoring unknown controller.");
- return false;
+ // In that case, onStop isn't called so we return true to call onTransfer.
+ // It's also consistent with that the another thread acquires the lock later.
+ return true;
}
+ }
+
+ mHandler.postDelayed(this::release, TRANSFER_TIMEOUT_MS);
+
+ return true;
+ }
+
+ void releaseInternal(boolean shouldReleaseSession) {
+ boolean shouldNotifyStop;
+
+ synchronized (mControllerLock) {
+ if (mState == CONTROLLER_STATE_RELEASED) {
+ if (DEBUG) {
+ Log.d(TAG, "releaseInternal: Called on released controller. Ignoring.");
+ }
+ return;
+ }
+ shouldNotifyStop = (mState == CONTROLLER_STATE_ACTIVE);
+ mState = CONTROLLER_STATE_RELEASED;
+ }
+
+ synchronized (sRouterLock) {
+ mNonSystemRoutingControllers.remove(getId(), this);
if (shouldReleaseSession && mStub != null) {
try {
@@ -1326,7 +1352,6 @@ public final class MediaRouter2 {
mStub = null;
}
}
- return true;
}
@Override
@@ -1389,9 +1414,14 @@ public final class MediaRouter2 {
}
@Override
- boolean releaseInternal(boolean shouldReleaseSession, boolean shouldNotifyStop) {
+ boolean scheduleRelease() {
+ // SystemRoutingController can be always transferred
+ return true;
+ }
+
+ @Override
+ void releaseInternal(boolean shouldReleaseSession) {
// Do nothing. SystemRoutingController will never be released
- return false;
}
}
@@ -1442,8 +1472,7 @@ public final class MediaRouter2 {
if (!(obj instanceof TransferCallbackRecord)) {
return false;
}
- return mTransferCallback
- == ((TransferCallbackRecord) obj).mTransferCallback;
+ return mTransferCallback == ((TransferCallbackRecord) obj).mTransferCallback;
}
@Override
@@ -1481,11 +1510,17 @@ public final class MediaRouter2 {
static final class ControllerCreationRequest {
public final int mRequestId;
+ public final long mManagerRequestId;
public final MediaRoute2Info mRoute;
+ public final RoutingController mOldController;
- ControllerCreationRequest(int requestId, @NonNull MediaRoute2Info route) {
+ ControllerCreationRequest(int requestId, long managerRequestId,
+ @NonNull MediaRoute2Info route, @NonNull RoutingController oldController) {
mRequestId = requestId;
- mRoute = route;
+ mManagerRequestId = managerRequestId;
+ mRoute = Objects.requireNonNull(route, "route must not be null");
+ mOldController = Objects.requireNonNull(oldController,
+ "oldController must not be null");
}
}
@@ -1534,11 +1569,11 @@ public final class MediaRouter2 {
}
@Override
- public void getSessionHintsForCreatingSession(long uniqueRequestId,
- @NonNull MediaRoute2Info route) {
+ public void requestCreateSessionByManager(long managerRequestId,
+ RoutingSessionInfo oldSession, MediaRoute2Info route) {
mHandler.sendMessage(obtainMessage(
- MediaRouter2::onGetControllerHintsForCreatingSessionOnHandler,
- MediaRouter2.this, uniqueRequestId, route));
+ MediaRouter2::onRequestCreateControllerByManagerOnHandler,
+ MediaRouter2.this, oldSession, route, managerRequestId));
}
}
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index dad7859db622..4b09a5f19fb0 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -54,6 +54,12 @@ import java.util.stream.Collectors;
public final class MediaRouter2Manager {
private static final String TAG = "MR2Manager";
private static final Object sLock = new Object();
+ /**
+ * The request ID for requests not asked by this instance.
+ * Shouldn't be used for a valid request.
+ * @hide
+ */
+ public static final int REQUEST_ID_NONE = 0;
/** @hide */
@VisibleForTesting
public static final int TRANSFER_TIMEOUT_MS = 30_000;
@@ -480,7 +486,6 @@ public final class MediaRouter2Manager {
notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
return;
}
- releaseSession(matchingRequest.mOldSessionInfo);
notifyTransferred(matchingRequest.mOldSessionInfo, sessionInfo);
}
@@ -777,7 +782,7 @@ public final class MediaRouter2Manager {
if (client != null) {
try {
mMediaRouterService.requestCreateSessionWithManager(
- client, requestId, oldSession.getClientPackageName(), route);
+ client, requestId, oldSession, route);
} catch (RemoteException ex) {
Log.e(TAG, "requestCreateSession: Failed to send a request", ex);
}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index edf1fc58ecf5..a5d25e0771fd 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -220,7 +220,7 @@ public final class RoutingSessionInfo implements Parcelable {
}
/**
- * Gets information about how volume is handled on the session.
+ * Gets the information about how volume is handled on the session.
*
* @return {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or
* {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}.
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 0979627e5e8d..ddefe266d897 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -126,6 +126,7 @@ public class MediaRouter2ManagerTest {
StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
if (instance != null) {
instance.setProxy(null);
+ instance.setSpy(null);
}
}
@@ -425,6 +426,79 @@ public class MediaRouter2ManagerTest {
@Test
@LargeTest
+ public void testTransferTwice() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+ addRouterCallback(new RouteCallback() { });
+
+ CountDownLatch successLatch1 = new CountDownLatch(1);
+ CountDownLatch successLatch2 = new CountDownLatch(1);
+ CountDownLatch failureLatch = new CountDownLatch(1);
+ CountDownLatch managerOnSessionReleasedLatch = new CountDownLatch(1);
+ CountDownLatch serviceOnReleaseSessionLatch = new CountDownLatch(1);
+ List<RoutingSessionInfo> sessions = new ArrayList<>();
+
+ StubMediaRoute2ProviderService instance = StubMediaRoute2ProviderService.getInstance();
+ assertNotNull(instance);
+ instance.setSpy(new StubMediaRoute2ProviderService.Spy() {
+ @Override
+ public void onReleaseSession(long requestId, String sessionId) {
+ serviceOnReleaseSessionLatch.countDown();
+ }
+ });
+
+ addManagerCallback(new MediaRouter2Manager.Callback() {
+ @Override
+ public void onTransferred(RoutingSessionInfo oldSession,
+ RoutingSessionInfo newSession) {
+ sessions.add(newSession);
+ if (successLatch1.getCount() > 0) {
+ successLatch1.countDown();
+ } else {
+ successLatch2.countDown();
+ }
+ }
+
+ @Override
+ public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
+ failureLatch.countDown();
+ }
+
+ @Override
+ public void onSessionReleased(RoutingSessionInfo session) {
+ managerOnSessionReleasedLatch.countDown();
+ }
+ });
+
+ MediaRoute2Info route1 = routes.get(ROUTE_ID1);
+ MediaRoute2Info route2 = routes.get(ROUTE_ID2);
+ assertNotNull(route1);
+ assertNotNull(route2);
+
+ mManager.selectRoute(mPackageName, route1);
+ assertTrue(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ mManager.selectRoute(mPackageName, route2);
+ assertTrue(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ // onTransferFailed/onSessionReleased should not be called.
+ assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertFalse(managerOnSessionReleasedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+
+ assertEquals(2, sessions.size());
+ List<String> activeSessionIds = mManager.getActiveSessions().stream()
+ .map(RoutingSessionInfo::getId)
+ .collect(Collectors.toList());
+ // The old session shouldn't appear on the active session list.
+ assertFalse(activeSessionIds.contains(sessions.get(0).getId()));
+ assertTrue(activeSessionIds.contains(sessions.get(1).getId()));
+
+ assertFalse(serviceOnReleaseSessionLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ mManager.releaseSession(sessions.get(0));
+ assertTrue(serviceOnReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertFalse(managerOnSessionReleasedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ @LargeTest
public void testTransfer_ignored_fails() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback() {});
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index 4551876d774a..a51e3714b6f7 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -79,6 +79,7 @@ public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService {
@GuardedBy("sLock")
private static StubMediaRoute2ProviderService sInstance;
private Proxy mProxy;
+ private Spy mSpy;
private void initializeRoutes() {
MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
@@ -256,6 +257,11 @@ public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService {
@Override
public void onReleaseSession(long requestId, String sessionId) {
+ Spy spy = mSpy;
+ if (spy != null) {
+ spy.onReleaseSession(requestId, sessionId);
+ }
+
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
return;
@@ -375,7 +381,21 @@ public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService {
mProxy = proxy;
}
+ public void setSpy(@Nullable Spy spy) {
+ mSpy = spy;
+ }
+
+ /**
+ * It overrides the original service
+ */
public static class Proxy {
public void onSetRouteVolume(String routeId, int volume, long requestId) {}
}
+
+ /**
+ * It gets notified but doesn't prevent the original methods to be called.
+ */
+ public static class Spy {
+ public void onReleaseSession(long requestId, String sessionId) {}
+ }
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 27216783d0d2..f882c57e49ba 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -62,6 +62,7 @@ abstract class MediaRoute2Provider {
public abstract void setRouteVolume(long requestId, String routeId, int volume);
public abstract void setSessionVolume(long requestId, String sessionId, int volume);
+ public abstract void prepareReleaseSession(@NonNull String sessionId);
@NonNull
public String getUniqueId() {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index d6b98e2de901..85af346aa88a 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -34,11 +34,16 @@ import android.os.IBinder.DeathRecipient;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -61,6 +66,9 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
private RouteDiscoveryPreference mLastDiscoveryPreference = null;
+ @GuardedBy("mLock")
+ final List<RoutingSessionInfo> mReleasingSessions = new ArrayList<>();
+
MediaRoute2ProviderServiceProxy(@NonNull Context context, @NonNull ComponentName componentName,
int userId) {
super(componentName);
@@ -141,6 +149,19 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
}
}
+ @Override
+ public void prepareReleaseSession(@NonNull String sessionId) {
+ synchronized (mLock) {
+ for (RoutingSessionInfo session : mSessionInfos) {
+ if (TextUtils.equals(session.getId(), sessionId)) {
+ mSessionInfos.remove(session);
+ mReleasingSessions.add(session);
+ break;
+ }
+ }
+ }
+ }
+
public boolean hasComponentName(String packageName, String className) {
return mComponentName.getPackageName().equals(packageName)
&& mComponentName.getClassName().equals(className);
@@ -300,88 +321,97 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
}
private void onSessionCreated(Connection connection, long requestId,
- RoutingSessionInfo sessionInfo) {
+ RoutingSessionInfo newSession) {
if (mActiveConnection != connection) {
return;
}
- if (sessionInfo == null) {
- Slog.w(TAG, "onSessionCreated: Ignoring null sessionInfo sent from " + mComponentName);
+ if (newSession == null) {
+ Slog.w(TAG, "onSessionCreated: Ignoring null session sent from " + mComponentName);
return;
}
- sessionInfo = updateSessionInfo(sessionInfo);
+ newSession = assignProviderIdForSession(newSession);
+ String newSessionId = newSession.getId();
- boolean duplicateSessionAlreadyExists = false;
synchronized (mLock) {
- for (int i = 0; i < mSessionInfos.size(); i++) {
- if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
- duplicateSessionAlreadyExists = true;
- break;
- }
+ if (mSessionInfos.stream()
+ .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId))
+ || mReleasingSessions.stream()
+ .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId))) {
+ Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring.");
+ return;
}
- mSessionInfos.add(sessionInfo);
+ mSessionInfos.add(newSession);
}
- if (duplicateSessionAlreadyExists) {
- Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring.");
- return;
- }
-
- mCallback.onSessionCreated(this, requestId, sessionInfo);
+ mCallback.onSessionCreated(this, requestId, newSession);
}
- private void onSessionUpdated(Connection connection, RoutingSessionInfo sessionInfo) {
+ private void onSessionUpdated(Connection connection, RoutingSessionInfo updatedSession) {
if (mActiveConnection != connection) {
return;
}
- if (sessionInfo == null) {
- Slog.w(TAG, "onSessionUpdated: Ignoring null sessionInfo sent from "
+ if (updatedSession == null) {
+ Slog.w(TAG, "onSessionUpdated: Ignoring null session sent from "
+ mComponentName);
return;
}
- sessionInfo = updateSessionInfo(sessionInfo);
+ updatedSession = assignProviderIdForSession(updatedSession);
boolean found = false;
synchronized (mLock) {
for (int i = 0; i < mSessionInfos.size(); i++) {
- if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
- mSessionInfos.set(i, sessionInfo);
+ if (mSessionInfos.get(i).getId().equals(updatedSession.getId())) {
+ mSessionInfos.set(i, updatedSession);
found = true;
break;
}
}
- }
- if (!found) {
- Slog.w(TAG, "onSessionUpdated: Matching session info not found");
- return;
+ if (!found) {
+ for (RoutingSessionInfo releasingSession : mReleasingSessions) {
+ if (TextUtils.equals(releasingSession.getId(), updatedSession.getId())) {
+ return;
+ }
+ }
+ Slog.w(TAG, "onSessionUpdated: Matching session info not found");
+ return;
+ }
}
- mCallback.onSessionUpdated(this, sessionInfo);
+ mCallback.onSessionUpdated(this, updatedSession);
}
- private void onSessionReleased(Connection connection, RoutingSessionInfo sessionInfo) {
+ private void onSessionReleased(Connection connection, RoutingSessionInfo releaedSession) {
if (mActiveConnection != connection) {
return;
}
- if (sessionInfo == null) {
- Slog.w(TAG, "onSessionReleased: Ignoring null sessionInfo sent from " + mComponentName);
+ if (releaedSession == null) {
+ Slog.w(TAG, "onSessionReleased: Ignoring null session sent from " + mComponentName);
return;
}
- sessionInfo = updateSessionInfo(sessionInfo);
+ releaedSession = assignProviderIdForSession(releaedSession);
boolean found = false;
synchronized (mLock) {
- for (int i = 0; i < mSessionInfos.size(); i++) {
- if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
- mSessionInfos.remove(i);
+ for (RoutingSessionInfo session : mSessionInfos) {
+ if (TextUtils.equals(session.getId(), releaedSession.getId())) {
+ mSessionInfos.remove(session);
found = true;
break;
}
}
+ if (!found) {
+ for (RoutingSessionInfo session : mReleasingSessions) {
+ if (TextUtils.equals(session.getId(), releaedSession.getId())) {
+ mReleasingSessions.remove(session);
+ return;
+ }
+ }
+ }
}
if (!found) {
@@ -389,10 +419,10 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
return;
}
- mCallback.onSessionReleased(this, sessionInfo);
+ mCallback.onSessionReleased(this, releaedSession);
}
- private RoutingSessionInfo updateSessionInfo(RoutingSessionInfo sessionInfo) {
+ private RoutingSessionInfo assignProviderIdForSession(RoutingSessionInfo sessionInfo) {
return new RoutingSessionInfo.Builder(sessionInfo)
.setOwnerPackageName(mComponentName.getPackageName())
.setProviderId(getUniqueId())
@@ -423,6 +453,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
mCallback.onSessionReleased(this, sessionInfo);
}
mSessionInfos.clear();
+ mReleasingSessions.clear();
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 72d296fc5f6b..cc9503995ad9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,9 +16,7 @@
package com.android.server.media;
-import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
-import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
import static android.media.MediaRouter2Utils.getOriginalId;
import static android.media.MediaRouter2Utils.getProviderId;
@@ -33,6 +31,8 @@ import android.media.IMediaRouter2;
import android.media.IMediaRouter2Manager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
+import android.media.MediaRoute2ProviderService;
+import android.media.MediaRouter2Manager;
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Binder;
@@ -235,30 +235,17 @@ class MediaRouter2ServiceImpl {
}
public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
+ long managerRequestId, RoutingSessionInfo oldSession,
MediaRoute2Info route, Bundle sessionHints) {
Objects.requireNonNull(router, "router must not be null");
+ Objects.requireNonNull(oldSession, "oldSession must not be null");
Objects.requireNonNull(route, "route must not be null");
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionWithRouter2Locked(requestId, router, route, sessionHints);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
- long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
- Objects.requireNonNull(router, "router must not be null");
- Objects.requireNonNull(route, "route must not be null");
-
- final long token = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- notifySessionHintsForCreatingSessionLocked(uniqueRequestId,
- router, route, sessionHints);
+ requestCreateSessionWithRouter2Locked(requestId, managerRequestId,
+ router, oldSession, route, sessionHints);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -417,16 +404,14 @@ class MediaRouter2ServiceImpl {
}
public void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
- String packageName, MediaRoute2Info route) {
+ RoutingSessionInfo oldSession, MediaRoute2Info route) {
Objects.requireNonNull(manager, "manager must not be null");
- if (TextUtils.isEmpty(packageName)) {
- throw new IllegalArgumentException("packageName must not be empty");
- }
+ Objects.requireNonNull(oldSession, "oldSession must not be null");
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionWithManagerLocked(requestId, manager, packageName, route);
+ requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -638,7 +623,8 @@ class MediaRouter2ServiceImpl {
}
}
- private void requestCreateSessionWithRouter2Locked(int requestId, @NonNull IMediaRouter2 router,
+ private void requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId,
+ @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession,
@NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
final IBinder binder = router.asBinder();
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
@@ -647,41 +633,60 @@ class MediaRouter2ServiceImpl {
return;
}
- if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
- && !TextUtils.equals(route.getId(),
- routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId())) {
- Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
- + route);
- routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
- routerRecord, requestId);
- return;
+ if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
+ ManagerRecord manager = routerRecord.mUserRecord.mHandler.findManagerWithId(
+ toRequesterId(managerRequestId));
+ if (manager == null || manager.mLastSessionCreationRequest == null) {
+ Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
+ + "Ignoring unknown request.");
+ routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
+ routerRecord, requestId);
+ return;
+ }
+ if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(),
+ oldSession.getId())) {
+ Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
+ + "Ignoring unmatched routing session.");
+ routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
+ routerRecord, requestId);
+ return;
+ }
+ if (!TextUtils.equals(manager.mLastSessionCreationRequest.mRoute.getId(),
+ route.getId())) {
+ // When media router has no permission
+ if (!routerRecord.mHasModifyAudioRoutingPermission
+ && manager.mLastSessionCreationRequest.mRoute.isSystemRoute()
+ && route.isSystemRoute()) {
+ route = manager.mLastSessionCreationRequest.mRoute;
+ } else {
+ Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
+ + "Ignoring unmatched route.");
+ routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
+ routerRecord, requestId);
+ return;
+ }
+ }
+ manager.mLastSessionCreationRequest = null;
+ } else {
+ if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
+ && !TextUtils.equals(route.getId(),
+ routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId())) {
+ Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
+ + route);
+ routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
+ routerRecord, requestId);
+ return;
+ }
}
long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
routerRecord.mUserRecord.mHandler,
- uniqueRequestId, routerRecord, route,
+ uniqueRequestId, managerRequestId, routerRecord, oldSession, route,
sessionHints));
}
- private void notifySessionHintsForCreatingSessionLocked(long uniqueRequestId,
- @NonNull IMediaRouter2 router,
- @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
- final IBinder binder = router.asBinder();
- final RouterRecord routerRecord = mAllRouterRecords.get(binder);
-
- if (routerRecord == null) {
- Slog.w(TAG, "notifySessionHintsForCreatingSessionLocked: Ignoring unknown router.");
- return;
- }
-
- routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::requestCreateSessionWithManagerOnHandler,
- routerRecord.mUserRecord.mHandler,
- uniqueRequestId, routerRecord, route, sessionHints));
- }
-
private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
final IBinder binder = router.asBinder();
@@ -853,27 +858,46 @@ class MediaRouter2ServiceImpl {
private void requestCreateSessionWithManagerLocked(int requestId,
@NonNull IMediaRouter2Manager manager,
- @NonNull String packageName, @NonNull MediaRoute2Info route) {
+ @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
if (managerRecord == null) {
return;
}
+ String packageName = oldSession.getClientPackageName();
+
RouterRecord routerRecord = managerRecord.mUserRecord.findRouterRecordLocked(packageName);
if (routerRecord == null) {
Slog.w(TAG, "requestCreateSessionWithManagerLocked: Ignoring session creation for "
+ "unknown router.");
+ try {
+ managerRecord.mManager.notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "requestCreateSessionWithManagerLocked: Failed to notify failure. "
+ + "Manager probably died.");
+ }
return;
}
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
+ if (managerRecord.mLastSessionCreationRequest != null) {
+ managerRecord.mUserRecord.mHandler.notifyRequestFailedToManager(
+ managerRecord.mManager,
+ toOriginalRequestId(managerRecord.mLastSessionCreationRequest
+ .mManagerRequestId),
+ REASON_UNKNOWN_ERROR);
+ managerRecord.mLastSessionCreationRequest = null;
+ }
+ managerRecord.mLastSessionCreationRequest = new SessionCreationRequest(routerRecord,
+ MediaRoute2ProviderService.REQUEST_ID_NONE, uniqueRequestId,
+ oldSession, route);
// Before requesting to the provider, get session hints from the media router.
// As a return, media router will request to create a session.
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::getSessionHintsForCreatingSessionOnHandler,
+ obtainMessage(UserHandler::requestRouterCreateSessionOnHandler,
routerRecord.mUserRecord.mHandler,
- uniqueRequestId, routerRecord, managerRecord, route));
+ uniqueRequestId, routerRecord, managerRecord, oldSession, route));
}
private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager,
@@ -887,7 +911,7 @@ class MediaRouter2ServiceImpl {
// Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
- .findRouterforSessionLocked(uniqueSessionId);
+ .findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
@@ -908,7 +932,7 @@ class MediaRouter2ServiceImpl {
// Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
- .findRouterforSessionLocked(uniqueSessionId);
+ .findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
@@ -929,7 +953,7 @@ class MediaRouter2ServiceImpl {
// Can be null if the session is system's or RCN.
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
- .findRouterforSessionLocked(uniqueSessionId);
+ .findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
@@ -966,7 +990,7 @@ class MediaRouter2ServiceImpl {
}
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
- .findRouterforSessionLocked(uniqueSessionId);
+ .findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
@@ -1097,6 +1121,7 @@ class MediaRouter2ServiceImpl {
public final int mPid;
public final String mPackageName;
public final int mManagerId;
+ public SessionCreationRequest mLastSessionCreationRequest;
ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager,
int uid, int pid, String packageName) {
@@ -1222,10 +1247,20 @@ class MediaRouter2ServiceImpl {
}
@Nullable
- public RouterRecord findRouterforSessionLocked(@NonNull String uniqueSessionId) {
+ public RouterRecord findRouterWithSessionLocked(@NonNull String uniqueSessionId) {
return mSessionToRouterMap.get(uniqueSessionId);
}
+ @Nullable
+ public ManagerRecord findManagerWithId(int managerId) {
+ for (ManagerRecord manager : getManagerRecords()) {
+ if (manager.mManagerId == managerId) {
+ return manager;
+ }
+ }
+ return null;
+ }
+
private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
@@ -1318,26 +1353,28 @@ class MediaRouter2ServiceImpl {
return -1;
}
- private void getSessionHintsForCreatingSessionOnHandler(long uniqueRequestId,
+ private void requestRouterCreateSessionOnHandler(long uniqueRequestId,
@NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
- @NonNull MediaRoute2Info route) {
- SessionCreationRequest request =
- new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
- mSessionCreationRequests.add(request);
-
+ @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
try {
- routerRecord.mRouter.getSessionHintsForCreatingSession(uniqueRequestId, route);
+ if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission) {
+ routerRecord.mRouter.requestCreateSessionByManager(uniqueRequestId,
+ oldSession, mSystemProvider.getDefaultRoute());
+ } else {
+ routerRecord.mRouter.requestCreateSessionByManager(uniqueRequestId,
+ oldSession, route);
+ }
} catch (RemoteException ex) {
Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
+ "Failed to request. Router probably died.", ex);
- mSessionCreationRequests.remove(request);
notifyRequestFailedToManager(managerRecord.mManager,
toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
}
}
private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
- @NonNull RouterRecord routerRecord,
+ long managerRequestId, @NonNull RouterRecord routerRecord,
+ @NonNull RoutingSessionInfo oldSession,
@NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
@@ -1350,49 +1387,14 @@ class MediaRouter2ServiceImpl {
}
SessionCreationRequest request =
- new SessionCreationRequest(routerRecord, uniqueRequestId, route, null);
+ new SessionCreationRequest(routerRecord, uniqueRequestId,
+ managerRequestId, oldSession, route);
mSessionCreationRequests.add(request);
provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
route.getOriginalId(), sessionHints);
}
- private void requestCreateSessionWithManagerOnHandler(long uniqueRequestId,
- @NonNull RouterRecord routerRecord,
- @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
- SessionCreationRequest matchingRequest = null;
- for (SessionCreationRequest request : mSessionCreationRequests) {
- if (request.mUniqueRequestId == uniqueRequestId) {
- matchingRequest = request;
- break;
- }
- }
- if (matchingRequest == null) {
- Slog.w(TAG, "requestCreateSessionWithManagerOnHandler: "
- + "Ignoring an unknown session creation request.");
- return;
- }
-
- if (!TextUtils.equals(matchingRequest.mRoute.getId(), route.getId())) {
- Slog.w(TAG, "requestCreateSessionWithManagerOnHandler: "
- + "The given route is different from the requested route.");
- return;
- }
-
- final MediaRoute2Provider provider = findProvider(route.getProviderId());
- if (provider == null) {
- Slog.w(TAG, "requestCreateSessionWithManagerOnHandler: Ignoring session "
- + "creation request since no provider found for given route=" + route);
- mSessionCreationRequests.remove(matchingRequest);
- notifyRequestFailedToManager(matchingRequest.mRequestedManagerRecord.mManager,
- toOriginalRequestId(uniqueRequestId), REASON_ROUTE_NOT_AVAILABLE);
- return;
- }
-
- provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
- route.getOriginalId(), sessionHints);
- }
-
// routerRecord can be null if the session is system's or RCN.
private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
@@ -1539,25 +1541,23 @@ class MediaRouter2ServiceImpl {
private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
- notifySessionCreatedToManagers(getManagers(),
- toOriginalRequestId(uniqueRequestId), sessionInfo);
-
- if (uniqueRequestId == REQUEST_ID_NONE) {
- // The session is created without any matching request.
- return;
- }
-
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
if (request.mUniqueRequestId == uniqueRequestId
&& TextUtils.equals(
- request.mRoute.getProviderId(), provider.getUniqueId())) {
+ request.mRoute.getProviderId(), provider.getUniqueId())) {
matchingRequest = request;
break;
}
}
+ long managerRequestId = (matchingRequest == null)
+ ? MediaRoute2ProviderService.REQUEST_ID_NONE
+ : matchingRequest.mManagerRequestId;
+ // Managers should know created session even if it's not requested.
+ notifySessionCreatedToManagers(managerRequestId, sessionInfo);
+
if (matchingRequest == null) {
Slog.w(TAG, "Ignoring session creation result for unknown request. "
+ "uniqueRequestId=" + uniqueRequestId + ", sessionInfo=" + sessionInfo);
@@ -1565,12 +1565,14 @@ class MediaRouter2ServiceImpl {
}
mSessionCreationRequests.remove(matchingRequest);
-
- if (sessionInfo == null) {
- // Failed
- notifySessionCreationFailedToRouter(matchingRequest.mRouterRecord,
- toOriginalRequestId(uniqueRequestId));
- return;
+ // Not to show old session
+ MediaRoute2Provider oldProvider =
+ findProvider(matchingRequest.mOldSession.getProviderId());
+ if (oldProvider != null) {
+ oldProvider.prepareReleaseSession(matchingRequest.mOldSession.getId());
+ } else {
+ Slog.w(TAG, "onSessionCreatedOnHandler: Can't find provider for an old session. "
+ + "session=" + matchingRequest.mOldSession);
}
String originalRouteId = matchingRequest.mRoute.getId();
@@ -1645,23 +1647,17 @@ class MediaRouter2ServiceImpl {
}
final int requesterId = toRequesterId(uniqueRequestId);
- for (ManagerRecord manager : getManagerRecords()) {
- if (manager.mManagerId == requesterId) {
- notifyRequestFailedToManager(
- manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
- return;
- }
+ ManagerRecord manager = findManagerWithId(requesterId);
+ if (manager != null) {
+ notifyRequestFailedToManager(
+ manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
+ return;
}
// Currently, only the manager can get notified of failures.
// TODO: Notify router too when the related callback is introduced.
}
- // TODO(b/157873556): Find a way to prevent providers from notifying error on random reqID.
- // Possible solutions can be:
- // 1) Record the other type of requests too (not only session creation request)
- // 2) Throw exception on providers when they try to notify error on
- // random uniqueRequestId.
private boolean handleSessionCreationRequestFailed(@NonNull MediaRoute2Provider provider,
long uniqueRequestId, int reason) {
// Check whether the failure is about creating a session
@@ -1683,12 +1679,16 @@ class MediaRouter2ServiceImpl {
// Notify the requester about the failure.
// The call should be made by either MediaRouter2 or MediaRouter2Manager.
- if (matchingRequest.mRequestedManagerRecord == null) {
+ if (matchingRequest.mManagerRequestId == MediaRouter2Manager.REQUEST_ID_NONE) {
notifySessionCreationFailedToRouter(
matchingRequest.mRouterRecord, toOriginalRequestId(uniqueRequestId));
} else {
- notifyRequestFailedToManager(matchingRequest.mRequestedManagerRecord.mManager,
- toOriginalRequestId(uniqueRequestId), reason);
+ final int requesterId = toRequesterId(matchingRequest.mManagerRequestId);
+ ManagerRecord manager = findManagerWithId(requesterId);
+ if (manager != null) {
+ notifyRequestFailedToManager(manager.mManager,
+ toOriginalRequestId(matchingRequest.mManagerRequestId), reason);
+ }
}
return true;
}
@@ -1921,14 +1921,19 @@ class MediaRouter2ServiceImpl {
}
}
- private void notifySessionCreatedToManagers(@NonNull List<IMediaRouter2Manager> managers,
- int requestId, @NonNull RoutingSessionInfo sessionInfo) {
- for (IMediaRouter2Manager manager : managers) {
+ private void notifySessionCreatedToManagers(long managerRequestId,
+ @NonNull RoutingSessionInfo session) {
+ int requesterId = toRequesterId(managerRequestId);
+ int originalRequestId = toOriginalRequestId(managerRequestId);
+
+ for (ManagerRecord manager : getManagerRecords()) {
try {
- manager.notifySessionCreated(requestId, sessionInfo);
+ manager.mManager.notifySessionCreated(
+ ((manager.mManagerId == requesterId) ? originalRequestId :
+ MediaRouter2Manager.REQUEST_ID_NONE), session);
} catch (RemoteException ex) {
Slog.w(TAG, "notifySessionCreatedToManagers: "
- + "failed to notify. Manager probably died.", ex);
+ + "Failed to notify. Manager probably died.", ex);
}
}
}
@@ -2014,7 +2019,7 @@ class MediaRouter2ServiceImpl {
}
mUserRecord.mCompositeDiscoveryPreference =
new RouteDiscoveryPreference.Builder(discoveryPreferences)
- .build();
+ .build();
}
for (MediaRoute2Provider provider : mRouteProviders) {
provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
@@ -2030,21 +2035,22 @@ class MediaRouter2ServiceImpl {
return null;
}
- final class SessionCreationRequest {
- public final RouterRecord mRouterRecord;
- public final long mUniqueRequestId;
- public final MediaRoute2Info mRoute;
- public final ManagerRecord mRequestedManagerRecord;
-
- // requestedManagerRecord is not null only when the request is made by manager.
- SessionCreationRequest(@NonNull RouterRecord routerRecord, long uniqueRequestId,
- @NonNull MediaRoute2Info route,
- @Nullable ManagerRecord requestedManagerRecord) {
- mRouterRecord = routerRecord;
- mUniqueRequestId = uniqueRequestId;
- mRoute = route;
- mRequestedManagerRecord = requestedManagerRecord;
- }
+ }
+ static final class SessionCreationRequest {
+ public final RouterRecord mRouterRecord;
+ public final long mUniqueRequestId;
+ public final long mManagerRequestId;
+ public final RoutingSessionInfo mOldSession;
+ public final MediaRoute2Info mRoute;
+
+ SessionCreationRequest(@NonNull RouterRecord routerRecord, long uniqueRequestId,
+ long managerRequestId, @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route) {
+ mRouterRecord = routerRecord;
+ mUniqueRequestId = uniqueRequestId;
+ mManagerRequestId = managerRequestId;
+ mOldSession = oldSession;
+ mRoute = route;
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 3337b480d6a8..0e52a67c8d39 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -481,16 +481,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
+ long managerRequestId, RoutingSessionInfo oldSession,
MediaRoute2Info route, Bundle sessionHints) {
- mService2.requestCreateSessionWithRouter2(router, requestId, route, sessionHints);
- }
-
- // Binder call
- @Override
- public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
- long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
- mService2.notifySessionHintsForCreatingSession(router,
- uniqueRequestId, route, sessionHints);
+ mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId,
+ oldSession, route, sessionHints);
}
// Binder call
@@ -558,8 +552,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
public void requestCreateSessionWithManager(IMediaRouter2Manager manager,
- int requestId, String packageName, MediaRoute2Info route) {
- mService2.requestCreateSessionWithManager(manager, requestId, packageName, route);
+ int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
+ mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
}
// 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 42d4c88959bd..2c089ca8300e 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -222,6 +222,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
// Do nothing since we don't support grouping volume yet.
}
+ @Override
+ public void prepareReleaseSession(String sessionId) {
+ // Do nothing since the system session persists.
+ }
+
public MediaRoute2Info getDefaultRoute() {
return mDefaultRoute;
}