From e2dbbf79f97921a6bb9b2e4af13070b79d949f35 Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Wed, 29 Apr 2020 22:40:59 +0900 Subject: [PATCH] Introduce APIs to deal with RCN case This CL does following: - MR2Manager#getRoutingSessionForMediaController is added - MR2Manager#getAvailableRoutesForRoutingSession is added - Add a workaround for provider to provide a routing session not requested by the user (RCN case) Using the added methods, we exepect System UI to implement UX easily. Bug: 154780833 Bug: 152582294 Test: It is tested manually with two demo APKs. MediaRoute2ProviderDemo app is updated to enable the user to create a routing session w/o MediaRouter2 clients and Sample Output Switcher is updated to use the new APIs. With updated APKs, transfer from cast to cast (Variable Volume1 -> Variable Volume2) is enabled. Change-Id: I03c261c1779725f43933bacad429e1a22f602818 --- .../android/media/MediaRouter2Manager.java | 82 ++++++++++++++++++- .../android/media/RoutingSessionInfo.java | 28 ++++++- .../MediaRoute2ProviderServiceProxy.java | 19 +++-- 3 files changed, 117 insertions(+), 12 deletions(-) diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 5d61dd06c7920..6c5ea8085c3be 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -171,8 +171,7 @@ public final class MediaRouter2Manager { public MediaController getMediaControllerForRoutingSession( @NonNull RoutingSessionInfo sessionInfo) { for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) { - String volumeControlId = controller.getPlaybackInfo().getVolumeControlId(); - if (TextUtils.equals(sessionInfo.getId(), volumeControlId)) { + if (areSessionsMatched(controller, sessionInfo)) { return controller; } } @@ -206,6 +205,37 @@ public final class MediaRouter2Manager { return routes; } + /** + * Gets available routes for the given routing session. + * The returned routes can be passed to + * {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} for transferring the routing session. + * + * @param sessionInfo the routing session that would be transferred + */ + @NonNull + public List getAvailableRoutesForRoutingSession( + @NonNull RoutingSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + List routes = new ArrayList<>(); + + String packageName = sessionInfo.getClientPackageName(); + List preferredFeatures = mPreferredFeaturesMap.get(packageName); + if (preferredFeatures == null) { + preferredFeatures = Collections.emptyList(); + } + synchronized (mRoutesLock) { + for (MediaRoute2Info route : mRoutes.values()) { + if (route.isSystemRoute() || route.hasAnyFeatures(preferredFeatures) + || sessionInfo.getSelectedRoutes().contains(route.getId()) + || sessionInfo.getTransferableRoutes().contains(route.getId())) { + routes.add(route); + } + } + } + return routes; + } + /** * Gets the system routing session associated with no specific application. */ @@ -219,6 +249,33 @@ public final class MediaRouter2Manager { throw new IllegalStateException("No system routing session"); } + /** + * Gets the routing session of a media session. + * If the session is using {#link PlaybackInfo#PLAYBACK_TYPE_LOCAL local playback}, + * the system routing session is returned. + * If the session is using {#link PlaybackInfo#PLAYBACK_TYPE_REMOTE remote playback}, + * it returns the corresponding routing session or {@code null} if it's unavailable. + */ + @Nullable + public RoutingSessionInfo getRoutingSessionForMediaController(MediaController mediaController) { + MediaController.PlaybackInfo playbackInfo = mediaController.getPlaybackInfo(); + if (playbackInfo == null) { + return null; + } + if (playbackInfo.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) { + return new RoutingSessionInfo.Builder(getSystemRoutingSession()) + .setClientPackageName(mediaController.getPackageName()) + .build(); + } + for (RoutingSessionInfo sessionInfo : getActiveSessions()) { + if (!sessionInfo.isSystemSession() + && areSessionsMatched(mediaController, sessionInfo)) { + return sessionInfo; + } + } + return null; + } + /** * Gets routing sessions of an application with the given package name. * The first element of the returned list is the system routing session. @@ -782,6 +839,27 @@ public final class MediaRouter2Manager { } } + private boolean areSessionsMatched(MediaController mediaController, + RoutingSessionInfo sessionInfo) { + MediaController.PlaybackInfo playbackInfo = mediaController.getPlaybackInfo(); + if (playbackInfo == null) { + return false; + } + + String volumeControlId = playbackInfo.getVolumeControlId(); + if (volumeControlId == null) { + return false; + } + + if (TextUtils.equals(volumeControlId, sessionInfo.getId())) { + return true; + } + // Workaround for provider not being able to know the unique session ID. + return TextUtils.equals(volumeControlId, sessionInfo.getOriginalId()) + && TextUtils.equals(mediaController.getPackageName(), + sessionInfo.getOwnerPackageName()); + } + private List getRoutesWithIds(List routeIds) { synchronized (sLock) { return routeIds.stream().map(mRoutes::get) diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index 608e29a7a6ca3..edf1fc58ecf56 100644 --- a/media/java/android/media/RoutingSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -50,6 +50,7 @@ public final class RoutingSessionInfo implements Parcelable { final String mId; final CharSequence mName; + final String mOwnerPackageName; final String mClientPackageName; @Nullable final String mProviderId; @@ -71,6 +72,7 @@ public final class RoutingSessionInfo implements Parcelable { mId = builder.mId; mName = builder.mName; + mOwnerPackageName = builder.mOwnerPackageName; mClientPackageName = builder.mClientPackageName; mProviderId = builder.mProviderId; @@ -96,6 +98,7 @@ public final class RoutingSessionInfo implements Parcelable { mId = ensureString(src.readString()); mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src); + mOwnerPackageName = src.readString(); mClientPackageName = ensureString(src.readString()); mProviderId = src.readString(); @@ -158,6 +161,15 @@ public final class RoutingSessionInfo implements Parcelable { return mId; } + /** + * Gets the package name of the session owner. + * @hide + */ + @Nullable + public String getOwnerPackageName() { + return mOwnerPackageName; + } + /** * Gets the client package name of the session */ @@ -263,6 +275,7 @@ public final class RoutingSessionInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mId); dest.writeCharSequence(mName); + dest.writeString(mOwnerPackageName); dest.writeString(mClientPackageName); dest.writeString(mProviderId); dest.writeStringList(mSelectedRoutes); @@ -288,6 +301,7 @@ public final class RoutingSessionInfo implements Parcelable { RoutingSessionInfo other = (RoutingSessionInfo) obj; return Objects.equals(mId, other.mId) && Objects.equals(mName, other.mName) + && Objects.equals(mOwnerPackageName, other.mOwnerPackageName) && Objects.equals(mClientPackageName, other.mClientPackageName) && Objects.equals(mProviderId, other.mProviderId) && Objects.equals(mSelectedRoutes, other.mSelectedRoutes) @@ -301,7 +315,7 @@ public final class RoutingSessionInfo implements Parcelable { @Override public int hashCode() { - return Objects.hash(mId, mName, mClientPackageName, mProviderId, + return Objects.hash(mId, mName, mOwnerPackageName, mClientPackageName, mProviderId, mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes, mVolumeMax, mVolumeHandling, mVolume); } @@ -356,6 +370,7 @@ public final class RoutingSessionInfo implements Parcelable { // TODO: Reorder these (important ones first) final String mId; CharSequence mName; + String mOwnerPackageName; String mClientPackageName; String mProviderId; final List mSelectedRoutes; @@ -439,6 +454,17 @@ public final class RoutingSessionInfo implements Parcelable { return this; } + /** + * Sets the package name of the session owner. It is expected to be called by the system. + * + * @hide + */ + @NonNull + public Builder setOwnerPackageName(@Nullable String packageName) { + mOwnerPackageName = packageName; + return this; + } + /** * Sets the client package name of the session. * diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index a5de90c93aab4..a435f1e16b804 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -315,9 +315,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider return; } - sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) - .setProviderId(getUniqueId()) - .build(); + sessionInfo = updateSessionInfo(sessionInfo); boolean duplicateSessionAlreadyExists = false; synchronized (mLock) { @@ -348,9 +346,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider return; } - sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) - .setProviderId(getUniqueId()) - .build(); + sessionInfo = updateSessionInfo(sessionInfo); boolean found = false; synchronized (mLock) { @@ -380,9 +376,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider return; } - sessionInfo = new RoutingSessionInfo.Builder(sessionInfo) - .setProviderId(getUniqueId()) - .build(); + sessionInfo = updateSessionInfo(sessionInfo); boolean found = false; synchronized (mLock) { @@ -403,6 +397,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider mCallback.onSessionReleased(this, sessionInfo); } + private RoutingSessionInfo updateSessionInfo(RoutingSessionInfo sessionInfo) { + return new RoutingSessionInfo.Builder(sessionInfo) + .setOwnerPackageName(mComponentName.getPackageName()) + .setProviderId(getUniqueId()) + .build(); + } + private void onRequestFailed(Connection connection, long requestId, int reason) { if (mActiveConnection != connection) { return;