diff options
| author | 2020-02-05 18:45:35 +0900 | |
|---|---|---|
| committer | 2020-02-11 16:12:12 +0900 | |
| commit | 5161b37e6201a2f26c3130d6f68f9ea9ebf84352 (patch) | |
| tree | ce1a0dec23eacb843fe19c120eb81bbc137080cc | |
| parent | a00008934115e91bb0a41b67c16268763f35ee6e (diff) | |
MediaRouter2: Add group volume
Add session volume or group volume info into RoutingSessionInfo.
Group volume can be controlled by MediaRouter2 or MediaRouter2Manager.
This CL includes minor clean up for CTS and MediaRouter2Manager.
Bug: 148994658
Bug: 149200358
Test: atest android.media.cts.MediaRoute2InfoTest
&& atest android.media.cts.MediaRouter2Test
&& atest android.media.cts.RouteDiscoveryPreferenceTest
&& atest android.media.cts.RoutingSessionInfoTest
&& atest android.media.cts.MediaRoute2ProviderServiceTest
&& atest mediaroutertest
Change-Id: If1d3aaae604a3c23d504e620ddb2bd2ffb260602
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()); } } |