diff options
14 files changed, 560 insertions, 77 deletions
diff --git a/api/current.txt b/api/current.txt index 475506a317bc..26f6951672ac 100644 --- a/api/current.txt +++ b/api/current.txt @@ -26816,7 +26816,8 @@ package android.media { method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference); method public abstract void onReleaseSession(@NonNull String); method public abstract void onSelectRoute(@NonNull String, @NonNull String); - method public abstract void onSetVolume(@NonNull String, int); + method public abstract void onSetRouteVolume(@NonNull String, int); + method public abstract void onSetSessionVolume(@NonNull String, int); method public abstract void onTransferToRoute(@NonNull String, @NonNull String); field public static final long REQUEST_ID_UNKNOWN = 0L; // 0x0L field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; @@ -26977,9 +26978,13 @@ package android.media { method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectableRoutes(); method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectedRoutes(); method @NonNull public java.util.List<android.media.MediaRoute2Info> getTransferrableRoutes(); + method public int getVolume(); + method public int getVolumeHandling(); + method public int getVolumeMax(); method public boolean isReleased(); method public void release(); method public void selectRoute(@NonNull android.media.MediaRoute2Info); + method public void setVolume(int); method public void transferToRoute(@NonNull android.media.MediaRoute2Info); } @@ -27374,6 +27379,9 @@ package android.media { method @NonNull public java.util.List<java.lang.String> getSelectableRoutes(); method @NonNull public java.util.List<java.lang.String> getSelectedRoutes(); method @NonNull public java.util.List<java.lang.String> getTransferrableRoutes(); + method public int getVolume(); + method public int getVolumeHandling(); + method public int getVolumeMax(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.RoutingSessionInfo> CREATOR; } @@ -27395,6 +27403,9 @@ package android.media { method @NonNull public android.media.RoutingSessionInfo.Builder removeSelectedRoute(@NonNull String); method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferrableRoute(@NonNull String); method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle); + method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int); + method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int); + method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeMax(int); } public final class Session2Command implements android.os.Parcelable { diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index 9131f3bc960d..0c645641ee37 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -36,5 +36,6 @@ oneway interface IMediaRoute2Provider { void transferToRoute(String sessionId, String routeId); void notifyControlRequestSent(String id, in Intent request); - void requestSetVolume(String id, int volume); + void setRouteVolume(String routeId, int volume); + void setSessionVolume(String sessionId, int volume); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 6fef46889742..f919dce17b2f 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -51,7 +51,8 @@ interface IMediaRouterService { void unregisterClient2(IMediaRouter2Client client); void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request); - void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume); + void setRouteVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume); + void setSessionVolume2(IMediaRouter2Client client, String sessionId, int volume); void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, int requestId, in @nullable Bundle sessionHints); @@ -67,8 +68,10 @@ interface IMediaRouterService { void requestCreateClientSession(IMediaRouter2Manager manager, String packageName, in @nullable MediaRoute2Info route, int requestId); - void requestSetVolume2Manager(IMediaRouter2Manager manager, + void setRouteVolume2Manager(IMediaRouter2Manager manager, in MediaRoute2Info route, int volume); + void setSessionVolume2Manager(IMediaRouter2Manager manager, + String sessionId, int volume); List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager); void selectClientRoute(IMediaRouter2Manager manager, diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 20a59bba54c6..aac195dc76d5 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -122,16 +122,25 @@ public abstract class MediaRoute2ProviderService extends Service { * @hide */ //TODO: Discuss what to use for request (e.g., Intent? Request class?) - public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request); + public void onControlRequest(@NonNull String routeId, @NonNull Intent request) {} /** - * Called when requestSetVolume is called on a route of the provider. + * Called when a volume setting is requested on a route of the provider * * @param routeId the id of the route * @param volume the target volume * @see MediaRoute2Info#getVolumeMax() */ - public abstract void onSetVolume(@NonNull String routeId, int volume); + public abstract void onSetRouteVolume(@NonNull String routeId, int volume); + + /** + * Called when {@link MediaRouter2.RoutingController#setVolume(int)} is called on + * a routing session of the provider + * + * @param sessionId the id of the routing session + * @param volume the target volume + */ + public abstract void onSetSessionVolume(@NonNull String sessionId, int volume); /** * Gets information of the session with the given id. @@ -513,12 +522,21 @@ public abstract class MediaRoute2ProviderService extends Service { } @Override - public void requestSetVolume(String routeId, int volume) { + public void setRouteVolume(String routeId, int volume) { if (!checkCallerisSystem()) { return; } - mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume, + mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume, MediaRoute2ProviderService.this, routeId, volume)); } + + @Override + public void setSessionVolume(String sessionId, int volume) { + if (!checkCallerisSystem()) { + return; + } + mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume, + MediaRoute2ProviderService.this, sessionId, volume)); + } } } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 64186106d2c8..0e6ade539e35 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -446,7 +446,7 @@ public class MediaRouter2 { * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}. * @hide */ - public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) { + public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) { Objects.requireNonNull(route, "route must not be null"); Client2 client; @@ -455,7 +455,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.requestSetVolume2(client, route, volume); + mMediaRouterService.setRouteVolume2(client, route, volume); } catch (RemoteException ex) { Log.e(TAG, "Unable to send control request.", ex); } @@ -885,6 +885,43 @@ public class MediaRouter2 { } /** + * Gets information about how volume is handled on the session. + * + * @return {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or + * {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE} + */ + @MediaRoute2Info.PlaybackVolume + public int getVolumeHandling() { + synchronized (mControllerLock) { + return mSessionInfo.getVolumeHandling(); + } + } + + /** + * Gets the maximum volume of the session. + */ + public int getVolumeMax() { + synchronized (mControllerLock) { + return mSessionInfo.getVolumeMax(); + } + } + + /** + * 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()}. + * </p> + * @see MediaRoute2Info#getVolume() + */ + public int getVolume() { + synchronized (mControllerLock) { + return mSessionInfo.getVolume(); + } + } + + /** * Returns true if this controller is released, false otherwise. * If it is released, then all other getters from this instance may return invalid values. * Also, any operations to this instance will be ignored once released. @@ -1040,6 +1077,42 @@ public class MediaRouter2 { } /** + * Requests a volume change for the remote session asynchronously. + * + * @param volume The new volume value between 0 and {@link RoutingController#getVolumeMax} + * (inclusive). + * @see #getVolume() + */ + public void setVolume(int volume) { + if (getVolumeHandling() == MediaRoute2Info.PLAYBACK_VOLUME_FIXED) { + Log.w(TAG, "setVolume: the routing session has fixed volume. Ignoring."); + return; + } + if (volume < 0 || volume > getVolumeMax()) { + Log.w(TAG, "setVolume: the target volume is out of range. Ignoring"); + return; + } + + synchronized (mControllerLock) { + if (mIsReleased) { + Log.w(TAG, "setVolume is called on released controller. Ignoring."); + return; + } + } + Client2 client; + synchronized (sRouterLock) { + client = mClient; + } + if (client != null) { + try { + mMediaRouterService.setSessionVolume2(client, getId(), volume); + } catch (RemoteException ex) { + Log.e(TAG, "setVolume: Failed to deliver request.", ex); + } + } + } + + /** * Release this controller and corresponding session. * Any operations on this controller after calling this method will be ignored. * The devices that are playing media will stop playing it. diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 2c1fdab9da01..5ce291c06ade 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -22,6 +22,8 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.media.session.MediaController; +import android.media.session.MediaSessionManager; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; @@ -53,6 +55,8 @@ public class MediaRouter2Manager { @GuardedBy("sLock") private static MediaRouter2Manager sInstance; + private final MediaSessionManager mMediaSessionManager; + final String mPackageName; private Context mContext; @@ -89,6 +93,8 @@ public class MediaRouter2Manager { mContext = context.getApplicationContext(); mMediaRouterService = IMediaRouterService.Stub.asInterface( ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE)); + mMediaSessionManager = (MediaSessionManager) context + .getSystemService(Context.MEDIA_SESSION_SERVICE); mPackageName = mContext.getPackageName(); mHandler = new Handler(context.getMainLooper()); } @@ -150,6 +156,23 @@ public class MediaRouter2Manager { } } + /** + * Gets a {@link android.media.session.MediaController} associated with the + * given routing session. + * If there is no matching media session, {@code null} is returned. + */ + @Nullable + public MediaController getMediaControllerForRoutingSession( + @NonNull RoutingSessionInfo sessionInfo) { + for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) { + String volumeControlId = controller.getPlaybackInfo().getVolumeControlId(); + if (TextUtils.equals(sessionInfo.getId(), volumeControlId)) { + return controller; + } + } + return null; + } + //TODO: Use cache not to create array. For now, it's unclear when to purge the cache. //Do this when we finalize how to set control categories. /** @@ -178,24 +201,24 @@ public class MediaRouter2Manager { } /** - * Gets routing controllers of an application with the given package name. + * Gets routing sessions of an application with the given package name. * The first element of the returned list is the system routing controller. * * @see MediaRouter2#getSystemController() */ @NonNull - public List<RoutingController> getRoutingControllers(@NonNull String packageName) { + public List<RoutingSessionInfo> getRoutingSessions(@NonNull String packageName) { Objects.requireNonNull(packageName, "packageName must not be null"); - List<RoutingController> controllers = new ArrayList<>(); + List<RoutingSessionInfo> sessions = new ArrayList<>(); for (RoutingSessionInfo sessionInfo : getActiveSessions()) { if (sessionInfo.isSystemSession() || TextUtils.equals(sessionInfo.getClientPackageName(), packageName)) { - controllers.add(new RoutingController(sessionInfo)); + sessions.add(sessionInfo); } } - return controllers; + return sessions; } /** @@ -250,13 +273,12 @@ public class MediaRouter2Manager { boolean transferred = false; //TODO: instead of release all controllers, add an API to specify controllers that // should be released (or is the system controller). - for (RoutingController controller : getRoutingControllers(packageName)) { - if (!transferred && controller.getSessionInfo().getTransferrableRoutes() - .contains(route.getId())) { - controller.transferToRoute(route); + for (RoutingSessionInfo sessionInfo : getRoutingSessions(packageName)) { + if (!transferred && sessionInfo.getTransferrableRoutes().contains(route.getId())) { + new RoutingController(sessionInfo).transferToRoute(route); transferred = true; - } else if (!controller.getSessionInfo().isSystemSession()) { - controller.release(); + } else if (!sessionInfo.isSystemSession()) { + new RoutingController(sessionInfo).release(); } } @@ -282,22 +304,72 @@ public class MediaRouter2Manager { /** * Requests a volume change for a route asynchronously. + */ + //TODO: remove this. + public void requestSetVolume(MediaRoute2Info route, int volume) { + setRouteVolume(route, volume); + } + + /** + * Requests a volume change for a route asynchronously. * <p> * It may have no effect if the route is currently not selected. * </p> * - * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}. + * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax} + * (inclusive). */ - public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) { + public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) { Objects.requireNonNull(route, "route must not be null"); + if (route.getVolumeHandling() == MediaRoute2Info.PLAYBACK_VOLUME_FIXED) { + Log.w(TAG, "setRouteVolume: the route has fixed volume. Ignoring."); + return; + } + if (volume < 0 || volume > route.getVolumeMax()) { + Log.w(TAG, "setRouteVolume: the target volume is out of range. Ignoring"); + return; + } + + Client client; + synchronized (sLock) { + client = mClient; + } + if (client != null) { + try { + mMediaRouterService.setRouteVolume2Manager(client, route, volume); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to send control request.", ex); + } + } + } + + /** + * Requests a volume change for a routing session asynchronously. + * + * @param volume The new volume value between 0 and {@link RoutingSessionInfo#getVolumeMax} + * (inclusive). + */ + public void setSessionVolume(@NonNull RoutingSessionInfo sessionInfo, int volume) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + if (sessionInfo.getVolumeHandling() == MediaRoute2Info.PLAYBACK_VOLUME_FIXED) { + Log.w(TAG, "setSessionVolume: the route has fixed volume. Ignoring."); + return; + } + if (volume < 0 || volume > sessionInfo.getVolumeMax()) { + Log.w(TAG, "setSessionVolume: the target volume is out of range. Ignoring"); + return; + } + Client client; synchronized (sLock) { client = mClient; } if (client != null) { try { - mMediaRouterService.requestSetVolume2Manager(client, route, volume); + mMediaRouterService.setSessionVolume2Manager( + client, sessionInfo.getId(), volume); } catch (RemoteException ex) { Log.e(TAG, "Unable to send control request.", ex); } diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index 5383ea219c57..0d4e666de51b 100644 --- a/media/java/android/media/RoutingSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -56,6 +56,11 @@ public final class RoutingSessionInfo implements Parcelable { final List<String> mSelectableRoutes; final List<String> mDeselectableRoutes; final List<String> mTransferrableRoutes; + + final int mVolumeHandling; + final int mVolumeMax; + final int mVolume; + @Nullable final Bundle mControlHints; final boolean mIsSystemSession; @@ -77,6 +82,10 @@ public final class RoutingSessionInfo implements Parcelable { mTransferrableRoutes = Collections.unmodifiableList( convertToUniqueRouteIds(builder.mTransferrableRoutes)); + mVolumeHandling = builder.mVolumeHandling; + mVolumeMax = builder.mVolumeMax; + mVolume = builder.mVolume; + mControlHints = builder.mControlHints; mIsSystemSession = builder.mIsSystemSession; } @@ -93,6 +102,10 @@ public final class RoutingSessionInfo implements Parcelable { mDeselectableRoutes = ensureList(src.createStringArrayList()); mTransferrableRoutes = ensureList(src.createStringArrayList()); + mVolumeHandling = src.readInt(); + mVolumeMax = src.readInt(); + mVolume = src.readInt(); + mControlHints = src.readBundle(); mIsSystemSession = src.readBoolean(); } @@ -188,6 +201,36 @@ public final class RoutingSessionInfo implements Parcelable { } /** + * Gets information about how volume is handled on the session. + * + * @return {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or + * {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}. + */ + @MediaRoute2Info.PlaybackVolume + public int getVolumeHandling() { + return mVolumeHandling; + } + + /** + * Gets the maximum volume of the session. + */ + public int getVolumeMax() { + return mVolumeMax; + } + + /** + * 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 each route, use {@link MediaRoute2Info#getVolume()}. + * </p> + * @see MediaRoute2Info#getVolume() + */ + public int getVolume() { + return mVolume; + } + + /** * Gets the control hints */ @Nullable @@ -218,6 +261,9 @@ public final class RoutingSessionInfo implements Parcelable { dest.writeStringList(mSelectableRoutes); dest.writeStringList(mDeselectableRoutes); dest.writeStringList(mTransferrableRoutes); + dest.writeInt(mVolumeHandling); + dest.writeInt(mVolumeMax); + dest.writeInt(mVolume); dest.writeBundle(mControlHints); dest.writeBoolean(mIsSystemSession); } @@ -238,13 +284,17 @@ public final class RoutingSessionInfo implements Parcelable { && Objects.equals(mSelectedRoutes, other.mSelectedRoutes) && Objects.equals(mSelectableRoutes, other.mSelectableRoutes) && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes) - && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes); + && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes) + && (mVolumeHandling == other.mVolumeHandling) + && (mVolumeMax == other.mVolumeMax) + && (mVolume == other.mVolume); } @Override public int hashCode() { return Objects.hash(mId, mClientPackageName, mProviderId, - mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes); + mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes, + mVolumeMax, mVolumeHandling, mVolume); } @Override @@ -264,6 +314,9 @@ public final class RoutingSessionInfo implements Parcelable { .append(", transferrableRoutes={") .append(String.join(",", mTransferrableRoutes)) .append("}") + .append(", volumeHandling=").append(getVolumeHandling()) + .append(", volumeMax=").append(getVolumeMax()) + .append(", volume=").append(getVolume()) .append(" }"); return result.toString(); } @@ -298,6 +351,9 @@ public final class RoutingSessionInfo implements Parcelable { final List<String> mSelectableRoutes; final List<String> mDeselectableRoutes; final List<String> mTransferrableRoutes; + int mVolumeHandling = MediaRoute2Info.PLAYBACK_VOLUME_FIXED; + int mVolumeMax; + int mVolume; Bundle mControlHints; boolean mIsSystemSession; @@ -346,6 +402,10 @@ public final class RoutingSessionInfo implements Parcelable { mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes); mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes); + mVolumeHandling = sessionInfo.mVolumeHandling; + mVolumeMax = sessionInfo.mVolumeMax; + mVolume = sessionInfo.mVolume; + mControlHints = sessionInfo.mControlHints; mIsSystemSession = sessionInfo.mIsSystemSession; } @@ -497,6 +557,36 @@ public final class RoutingSessionInfo implements Parcelable { } /** + * Sets the session's volume handling. + * {@link MediaRoute2Info#PLAYBACK_VOLUME_FIXED} or + * {@link MediaRoute2Info#PLAYBACK_VOLUME_VARIABLE}. + */ + @NonNull + public RoutingSessionInfo.Builder setVolumeHandling( + @MediaRoute2Info.PlaybackVolume int volumeHandling) { + mVolumeHandling = volumeHandling; + return this; + } + + /** + * Sets the session's maximum volume, or 0 if unknown. + */ + @NonNull + public RoutingSessionInfo.Builder setVolumeMax(int volumeMax) { + mVolumeMax = volumeMax; + return this; + } + + /** + * Sets the session's current volume, or 0 if unknown. + */ + @NonNull + public RoutingSessionInfo.Builder setVolume(int volume) { + mVolume = volume; + return this; + } + + /** * Sets control hints. */ @NonNull diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index f10e5ebb5d3e..3ffb9514a98b 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -44,6 +44,7 @@ import android.media.MediaRouter2; import android.media.MediaRouter2.RouteCallback; import android.media.MediaRouter2.RoutingControllerCallback; import android.media.MediaRouter2Manager; +import android.media.MediaRouter2Utils; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.support.test.InstrumentationRegistry; @@ -57,6 +58,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -225,22 +227,22 @@ public class MediaRouterManagerTest { } }); - assertEquals(1, mManager.getRoutingControllers(mPackageName).size()); + assertEquals(1, mManager.getRoutingSessions(mPackageName).size()); mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)); latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); - List<MediaRouter2Manager.RoutingController> controllers = - mManager.getRoutingControllers(mPackageName); + List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); - assertEquals(2, controllers.size()); + assertEquals(2, sessions.size()); - MediaRouter2Manager.RoutingController routingController = controllers.get(1); + MediaRouter2Manager.RoutingController routingController = + mManager.getControllerForSession(sessions.get(1)); awaitOnRouteChangedManager( () -> routingController.release(), ROUTE_ID1, route -> TextUtils.equals(route.getClientPackageName(), null)); - assertEquals(1, mManager.getRoutingControllers(mPackageName).size()); + assertEquals(1, mManager.getRoutingSessions(mPackageName).size()); } /** @@ -266,11 +268,11 @@ public class MediaRouterManagerTest { route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - List<MediaRouter2Manager.RoutingController> controllers = - mManager.getRoutingControllers(mPackageName); + List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); - assertEquals(2, controllers.size()); - MediaRouter2Manager.RoutingController routingController = controllers.get(1); + assertEquals(2, sessions.size()); + MediaRouter2Manager.RoutingController routingController = + mManager.getControllerForSession(sessions.get(1)); awaitOnRouteChangedManager( () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)), @@ -284,7 +286,33 @@ public class MediaRouterManagerTest { } @Test - public void testControlVolumeWithManager() throws Exception { + public void testSetSystemRouteVolume() throws Exception { + // ensure client + addManagerCallback(new MediaRouter2Manager.Callback()); + String selectedSystemRouteId = + MediaRouter2Utils.getOriginalId( + mManager.getActiveSessions().get(0).getSelectedRoutes().get(0)); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(Collections.emptyList()); + MediaRoute2Info volRoute = routes.get(selectedSystemRouteId); + assertNotNull(volRoute); + + int originalVolume = volRoute.getVolume(); + int targetVolume = originalVolume == volRoute.getVolumeMax() + ? originalVolume - 1 : originalVolume + 1; + + awaitOnRouteChangedManager( + () -> mManager.setRouteVolume(volRoute, targetVolume), + selectedSystemRouteId, + (route -> route.getVolume() == targetVolume)); + + awaitOnRouteChangedManager( + () -> mManager.setRouteVolume(volRoute, originalVolume), + selectedSystemRouteId, + (route -> route.getVolume() == originalVolume)); + } + + @Test + public void testSetRouteVolume() throws Exception { Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); @@ -293,17 +321,81 @@ public class MediaRouterManagerTest { ? originalVolume - 1 : originalVolume + 1; awaitOnRouteChangedManager( - () -> mManager.requestSetVolume(volRoute, targetVolume), + () -> mManager.setRouteVolume(volRoute, targetVolume), ROUTE_ID_VARIABLE_VOLUME, (route -> route.getVolume() == targetVolume)); awaitOnRouteChangedManager( - () -> mManager.requestSetVolume(volRoute, originalVolume), + () -> mManager.setRouteVolume(volRoute, originalVolume), ROUTE_ID_VARIABLE_VOLUME, (route -> route.getVolume() == originalVolume)); } @Test + public void testSetSessionVolume() throws Exception { + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); + addRouterCallback(new RouteCallback()); + + CountDownLatch onSessionCreatedLatch = new CountDownLatch(1); + CountDownLatch volumeChangedLatch = new CountDownLatch(2); + + // create a controller + addManagerCallback(new MediaRouter2Manager.Callback() { + @Override + public void onSessionCreated(MediaRouter2Manager.RoutingController controller) { + assertNotNull(controller); + onSessionCreatedLatch.countDown(); + } + }); + + mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)); + assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + + List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); + assertEquals(2, sessions.size()); + + // test setSessionVolume + RoutingSessionInfo sessionInfo = sessions.get(1); + int currentVolume = sessionInfo.getVolume(); + int targetVolume = (currentVolume == 0) ? 1 : (currentVolume - 1); + + RoutingControllerCallback routingControllerCallback = new RoutingControllerCallback() { + @Override + public void onControllerUpdated(MediaRouter2.RoutingController controller) { + if (!TextUtils.equals(sessionInfo.getId(), controller.getId())) { + return; + } + if (controller.getVolume() == targetVolume) { + volumeChangedLatch.countDown(); + } + } + }; + mRouter2.registerControllerCallback(mExecutor, routingControllerCallback); + + addManagerCallback(new MediaRouter2Manager.Callback() { + @Override + public void onSessionsUpdated() { + List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); + if (sessions.size() != 2) { + return; + } + + if (sessions.get(1).getVolume() == targetVolume) { + volumeChangedLatch.countDown(); + } + } + }); + + mManager.setSessionVolume(sessionInfo, targetVolume); + + try { + assertTrue(volumeChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } finally { + mRouter2.unregisterControllerCallback(routingControllerCallback); + } + } + + @Test public void testVolumeHandling() throws Exception { Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java index 1a866cafff90..267927ff4a6e 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java @@ -18,6 +18,7 @@ package com.android.mediaroutertest; import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER; import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV; +import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE; import android.annotation.Nullable; import android.content.Intent; @@ -51,6 +52,8 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route"; public static final int VOLUME_MAX = 100; + public static final int SESSION_VOLUME_MAX = 50; + public static final int SESSION_VOLUME_INITIAL = 20; public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume"; public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route"; public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume"; @@ -141,12 +144,12 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onSetVolume(String routeId, int volume) { + public void onSetRouteVolume(String routeId, int volume) { MediaRoute2Info route = mRoutes.get(routeId); if (route == null) { return; } - volume = Math.min(volume, Math.max(0, route.getVolumeMax())); + volume = Math.max(0, Math.min(volume, route.getVolumeMax())); mRoutes.put(routeId, new MediaRoute2Info.Builder(route) .setVolume(volume) .build()); @@ -154,6 +157,19 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override + public void onSetSessionVolume(String sessionId, int volume) { + RoutingSessionInfo sessionInfo = getSessionInfo(sessionId); + if (sessionInfo == null) { + return; + } + volume = Math.max(0, Math.min(volume, sessionInfo.getVolumeMax())); + RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo) + .setVolume(volume) + .build(); + notifySessionUpdated(newSessionInfo); + } + + @Override public void onCreateSession(String packageName, String routeId, long requestId, @Nullable Bundle sessionHints) { MediaRoute2Info route = mRoutes.get(routeId); @@ -176,6 +192,9 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .addSelectedRoute(routeId) .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT) .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO) + .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) + .setVolumeMax(SESSION_VOLUME_MAX) + .setVolume(SESSION_VOLUME_INITIAL) // Set control hints with given sessionHints .setControlHints(sessionHints) .build(); diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 83588846ade8..3de5cf1ceaff 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -62,7 +62,8 @@ abstract class MediaRoute2Provider { public abstract void transferToRoute(String sessionId, String routeId); public abstract void sendControlRequest(String routeId, Intent request); - public abstract void requestSetVolume(String routeId, int volume); + public abstract void setRouteVolume(String routeId, int volume); + public abstract void setSessionVolume(String sessionId, int volume); @NonNull public String getUniqueId() { diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index dd536ecd19fa..c1ea697d8a2d 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -131,9 +131,17 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void requestSetVolume(String routeId, int volume) { + public void setRouteVolume(String routeId, int volume) { if (mConnectionReady) { - mActiveConnection.requestSetVolume(routeId, volume); + mActiveConnection.setRouteVolume(routeId, volume); + updateBinding(); + } + } + + @Override + public void setSessionVolume(String sessionId, int volume) { + if (mConnectionReady) { + mActiveConnection.setSessionVolume(sessionId, volume); updateBinding(); } } @@ -456,7 +464,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv try { mProvider.requestCreateSession(packageName, routeId, requestId, sessionHints); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to deliver request to create a session.", ex); + Slog.e(TAG, "requestCreateSession: Failed to deliver request."); } } @@ -464,7 +472,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv try { mProvider.releaseSession(sessionId); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to deliver request to release a session.", ex); + Slog.e(TAG, "releaseSession: Failed to deliver request."); } } @@ -472,7 +480,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv try { mProvider.updateDiscoveryPreference(discoveryPreference); } catch (RemoteException ex) { - Slog.e(TAG, "updateDiscoveryPreference(): Failed to deliver request."); + Slog.e(TAG, "updateDiscoveryPreference: Failed to deliver request."); } } @@ -480,7 +488,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv try { mProvider.selectRoute(sessionId, routeId); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to deliver request to select a route for a session.", ex); + Slog.e(TAG, "selectRoute: Failed to deliver request.", ex); } } @@ -488,7 +496,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv try { mProvider.deselectRoute(sessionId, routeId); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to deliver request to deselect a route from a session.", ex); + Slog.e(TAG, "deselectRoute: Failed to deliver request.", ex); } } @@ -496,7 +504,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv try { mProvider.transferToRoute(sessionId, routeId); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to deliver request to transfer a session to a route.", ex); + Slog.e(TAG, "transferToRoute: Failed to deliver request.", ex); } } @@ -504,15 +512,23 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv try { mProvider.notifyControlRequestSent(routeId, request); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to deliver request to send control request.", ex); + Slog.e(TAG, "sendControlRequest: Failed to deliver request.", ex); + } + } + + public void setRouteVolume(String routeId, int volume) { + try { + mProvider.setRouteVolume(routeId, volume); + } catch (RemoteException ex) { + Slog.e(TAG, "setRouteVolume: Failed to deliver request.", ex); } } - public void requestSetVolume(String routeId, int volume) { + public void setSessionVolume(String sessionId, int volume) { try { - mProvider.requestSetVolume(routeId, volume); + mProvider.setSessionVolume(sessionId, volume); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to deliver request to request set volume.", ex); + Slog.e(TAG, "setSessionVolume: Failed to deliver request.", ex); } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index b1133223e59a..2096531e7611 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -320,14 +320,29 @@ class MediaRouter2ServiceImpl { } } - public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) { + public void setRouteVolume2(IMediaRouter2Client client, + MediaRoute2Info route, int volume) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestSetVolumeLocked(client, route, volume); + setRouteVolumeLocked(client, route, volume); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public void setSessionVolume2(IMediaRouter2Client client, String sessionId, int volume) { + Objects.requireNonNull(client, "client must not be null"); + Objects.requireNonNull(sessionId, "sessionId must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setSessionVolumeLocked(client, sessionId, volume); } } finally { Binder.restoreCallingIdentity(token); @@ -346,7 +361,7 @@ class MediaRouter2ServiceImpl { } } - public void requestSetVolume2Manager(IMediaRouter2Manager manager, + public void setRouteVolume2Manager(IMediaRouter2Manager manager, MediaRoute2Info route, int volume) { Objects.requireNonNull(manager, "manager must not be null"); Objects.requireNonNull(route, "route must not be null"); @@ -354,7 +369,22 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestSetVolumeLocked(manager, route, volume); + setRouteVolumeLocked(manager, route, volume); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public void setSessionVolume2Manager(IMediaRouter2Manager manager, + String sessionId, int volume) { + Objects.requireNonNull(manager, "manager must not be null"); + Objects.requireNonNull(sessionId, "sessionId must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setSessionVolumeLocked(manager, sessionId, volume); } } finally { Binder.restoreCallingIdentity(token); @@ -587,18 +617,30 @@ class MediaRouter2ServiceImpl { } } - private void requestSetVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route, + private void setRouteVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route, int volume) { final IBinder binder = client.asBinder(); Client2Record clientRecord = mAllClientRecords.get(binder); if (clientRecord != null) { clientRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestSetVolume, + obtainMessage(UserHandler::setRouteVolume, clientRecord.mUserRecord.mHandler, route, volume)); } } + private void setSessionVolumeLocked(IMediaRouter2Client client, String sessionId, + int volume) { + final IBinder binder = client.asBinder(); + Client2Record clientRecord = mAllClientRecords.get(binder); + + if (clientRecord != null) { + clientRecord.mUserRecord.mHandler.sendMessage( + obtainMessage(UserHandler::setSessionVolume, + clientRecord.mUserRecord.mHandler, sessionId, volume)); + } + } + private void registerManagerLocked(IMediaRouter2Manager manager, int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = manager.asBinder(); @@ -660,18 +702,30 @@ class MediaRouter2ServiceImpl { } } - private void requestSetVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route, + private void setRouteVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route, int volume) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); if (managerRecord != null) { managerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestSetVolume, + obtainMessage(UserHandler::setRouteVolume, managerRecord.mUserRecord.mHandler, route, volume)); } } + private void setSessionVolumeLocked(IMediaRouter2Manager manager, String sessionId, + int volume) { + final IBinder binder = manager.asBinder(); + ManagerRecord managerRecord = mAllManagerRecords.get(binder); + + if (managerRecord != null) { + managerRecord.mUserRecord.mHandler.sendMessage( + obtainMessage(UserHandler::setSessionVolume, + managerRecord.mUserRecord.mHandler, sessionId, volume)); + } + } + private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); @@ -1399,11 +1453,21 @@ class MediaRouter2ServiceImpl { } } - private void requestSetVolume(MediaRoute2Info route, int volume) { + private void setRouteVolume(MediaRoute2Info route, int volume) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider != null) { - provider.requestSetVolume(route.getOriginalId(), volume); + provider.setRouteVolume(route.getOriginalId(), volume); + } + } + + private void setSessionVolume(String sessionId, int volume) { + final MediaRoute2Provider provider = findProvider(getProviderId(sessionId)); + if (provider == null) { + Slog.w(TAG, "setSessionVolume: couldn't find provider for session " + + "id=" + sessionId); + return; } + provider.setSessionVolume(getOriginalId(sessionId), volume); } private List<IMediaRouter2Client> getClients() { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 57f0328f1c00..b38e47a1c25e 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -533,15 +533,29 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) { - mService2.requestSetVolume2(client, route, volume); + public void setRouteVolume2(IMediaRouter2Client client, + MediaRoute2Info route, int volume) { + mService2.setRouteVolume2(client, route, volume); + } + + // Binder call + @Override + public void setSessionVolume2(IMediaRouter2Client client, String sessionId, int volume) { + mService2.setSessionVolume2(client, sessionId, volume); } // Binder call @Override - public void requestSetVolume2Manager(IMediaRouter2Manager manager, + public void setRouteVolume2Manager(IMediaRouter2Manager manager, MediaRoute2Info route, int volume) { - mService2.requestSetVolume2Manager(manager, route, volume); + mService2.setRouteVolume2Manager(manager, route, volume); + } + + // Binder call + @Override + public void setSessionVolume2Manager(IMediaRouter2Manager manager, + String sessionId, int volume) { + mService2.setSessionVolume2Manager(manager, sessionId, volume); } // 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 b5dcea8bb51e..18383c431479 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -42,6 +42,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import java.util.Collections; import java.util.List; @@ -67,6 +68,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { SystemMediaRoute2Provider.class.getPackageName$(), SystemMediaRoute2Provider.class.getName()); + @GuardedBy("mLock") + private String mSelectedRouteId; MediaRoute2Info mDefaultRoute; @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST; final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo(); @@ -153,9 +156,17 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { public void sendControlRequest(@NonNull String routeId, @NonNull Intent request) { } - //TODO: implement method @Override - public void requestSetVolume(String routeId, int volume) { + public void setRouteVolume(String routeId, int volume) { + if (!TextUtils.equals(routeId, mSelectedRouteId)) { + return; + } + mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); + } + + @Override + public void setSessionVolume(String sessionId, int volume) { + // Do nothing since we don't support grouping volume yet. } private void initializeDefaultRoute() { @@ -241,18 +252,16 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { SYSTEM_SESSION_ID, "" /* clientPackageName */) .setSystemSession(true); String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress(); + mSelectedRouteId = TextUtils.isEmpty(activeBtDeviceAddress) ? mDefaultRoute.getId() + : activeBtDeviceAddress; + builder.addSelectedRoute(mSelectedRouteId); if (!TextUtils.isEmpty(activeBtDeviceAddress)) { - // Bluetooth route. Set the route ID with the device's address. - builder.addSelectedRoute(activeBtDeviceAddress); builder.addTransferrableRoute(mDefaultRoute.getId()); - } else { - // Default device - builder.addSelectedRoute(mDefaultRoute.getId()); } for (MediaRoute2Info route : mBluetoothRoutes) { - if (!TextUtils.equals(activeBtDeviceAddress, route.getId())) { + if (!TextUtils.equals(mSelectedRouteId, route.getId())) { builder.addTransferrableRoute(route.getId()); } } |