From efe4374eb40246e8b4fd08858a67c6d332fa2869 Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Tue, 31 Dec 2019 18:32:28 +0900 Subject: [PATCH] MediaRouter: save sessions in media router service Each MediaRoute2Provider instance saves session infos published by MediaRoute2ProviderService. For that, MediaRouter2ProviderService notifies provider info and session infos simultaneously and to minimize the number of binder calls, schedulePublishState is added into MediaRoute2ProviderService. The saved sessions can be obtained by calling MRM.getActiveSessions(). Test: atest mediaroutertest && manually call MRM.getActiveSessions() Change-Id: I6938105f8d154ad9542f7e7c6210a451ee4ae90d --- .../media/IMediaRoute2ProviderClient.aidl | 3 +- .../android/media/IMediaRouterService.aidl | 3 ++ .../media/MediaRoute2ProviderService.java | 46 +++++++++++++------ .../android/media/MediaRouter2Manager.java | 13 +++++- .../MediaRouterManagerTest.java | 4 +- .../server/media/MediaRoute2Provider.java | 28 +++++++++-- .../media/MediaRoute2ProviderProxy.java | 20 +++++--- .../server/media/MediaRouter2ServiceImpl.java | 28 +++++++++++ .../server/media/MediaRouterService.java | 7 +++ .../media/SystemMediaRoute2Provider.java | 4 +- 10 files changed, 126 insertions(+), 30 deletions(-) diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl index 6a763c5110ceb..b1f93a3ac82df 100644 --- a/media/java/android/media/IMediaRoute2ProviderClient.aidl +++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl @@ -25,6 +25,7 @@ import android.os.Bundle; * @hide */ oneway interface IMediaRoute2ProviderClient { - void updateProviderInfo(in MediaRoute2ProviderInfo info); + void updateState(in MediaRoute2ProviderInfo providerInfo, + in List sessionInfos); void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 86fa20f1cca97..e89fc1e6e3725 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -22,6 +22,7 @@ import android.media.IMediaRouter2Manager; import android.media.IMediaRouterClient; import android.media.MediaRoute2Info; import android.media.MediaRouterClientState; +import android.media.RouteSessionInfo; /** * {@hide} @@ -63,4 +64,6 @@ interface IMediaRouterService { in MediaRoute2Info route, int volume); void requestUpdateVolume2Manager(IMediaRouter2Manager manager, in MediaRoute2Info route, int direction); + + List getActiveSessions(IMediaRouter2Manager manager); } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index d3f47e95380bc..9b44c6e583a2b 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -37,6 +37,7 @@ import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; /** * @hide @@ -48,6 +49,7 @@ public abstract class MediaRoute2ProviderService extends Service { private final Handler mHandler; private final Object mSessionLock = new Object(); + private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false); private ProviderStub mStub; private IMediaRoute2ProviderClient mClient; private MediaRoute2ProviderInfo mProviderInfo; @@ -132,20 +134,22 @@ public abstract class MediaRoute2ProviderService extends Service { */ public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - int sessionId = sessionInfo.getSessionId(); - //TODO: notify updated session info + int sessionId = sessionInfo.getSessionId(); + if (sessionInfo.getSelectedRoutes().isEmpty()) { + releaseSession(sessionId); + return; + } synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { mSessionInfo.put(sessionId, sessionInfo); + schedulePublishState(); } else { Log.w(TAG, "Ignoring unknown session info."); + return; } } - if (sessionInfo.getSelectedRoutes().isEmpty()) { - releaseSession(sessionId); - } } /** @@ -169,6 +173,7 @@ public abstract class MediaRoute2ProviderService extends Service { } mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo); } + schedulePublishState(); } if (mClient == null) { @@ -192,11 +197,12 @@ public abstract class MediaRoute2ProviderService extends Service { //TODO: notify media router service of release. RouteSessionInfo sessionInfo; synchronized (mSessionLock) { - sessionInfo = mSessionInfo.put(sessionId, null); + sessionInfo = mSessionInfo.remove(sessionId); } if (sessionInfo != null) { mHandler.sendMessage(obtainMessage( MediaRoute2ProviderService::onDestroySession, this, sessionId, sessionInfo)); + schedulePublishState(); } } @@ -268,23 +274,37 @@ public abstract class MediaRoute2ProviderService extends Service { /** * Updates provider info and publishes routes and session info. */ - public final void updateProviderInfo(MediaRoute2ProviderInfo info) { - mProviderInfo = info; - publishState(); + public final void updateProviderInfo(@NonNull MediaRoute2ProviderInfo providerInfo) { + mProviderInfo = Objects.requireNonNull(providerInfo, "providerInfo must not be null"); + schedulePublishState(); } void setClient(IMediaRoute2ProviderClient client) { mClient = client; - publishState(); + schedulePublishState(); } - void publishState() { - //TODO: sends session info + void schedulePublishState() { + if (mStatePublishScheduled.compareAndSet(false, true)) { + mHandler.post(this::publishState); + } + } + + private void publishState() { + if (!mStatePublishScheduled.compareAndSet(true, false)) { + return; + } + if (mClient == null) { return; } + + List sessionInfos; + synchronized (mSessionLock) { + sessionInfos = new ArrayList<>(mSessionInfo.values()); + } try { - mClient.updateProviderInfo(mProviderInfo); + mClient.updateState(mProviderInfo, sessionInfos); } catch (RemoteException ex) { Log.w(TAG, "Failed to send onProviderInfoUpdated"); } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 4213dd32d44a7..2e68e42e714f8 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -176,7 +176,18 @@ public class MediaRouter2Manager { } @NonNull - public List getRouteSessions() { + public List getActiveSessions() { + Client client; + synchronized (sLock) { + client = mClient; + } + if (client != null) { + try { + return mMediaRouterService.getActiveSessions(client); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to get sessions. Service probably died.", ex); + } + } return Collections.emptyList(); } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index cd3b50af91279..768716d40f442 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -280,12 +280,12 @@ public class MediaRouterManagerTest { }); //TODO: it fails due to not releasing session - assertEquals(0, mManager.getActiveRoutes().size()); + assertEquals(0, mManager.getActiveSessions().size()); mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)); latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); - assertEquals(1, mManager.getActiveRoutes().size()); + assertEquals(1, mManager.getActiveSessions().size()); //TODO: release the session /* diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index a30dd98987c1f..3cc18b6fe7d3e 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -24,6 +24,9 @@ import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.RouteSessionInfo; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Objects; abstract class MediaRoute2Provider { @@ -31,7 +34,8 @@ abstract class MediaRoute2Provider { final String mUniqueId; Callback mCallback; - private MediaRoute2ProviderInfo mProviderInfo; + private volatile MediaRoute2ProviderInfo mProviderInfo; + private volatile List mSessionInfos = Collections.emptyList(); MediaRoute2Provider(@NonNull ComponentName componentName) { mComponentName = Objects.requireNonNull(componentName, "Component name must not be null."); @@ -64,15 +68,29 @@ abstract class MediaRoute2Provider { return mProviderInfo; } - void setAndNotifyProviderInfo(MediaRoute2ProviderInfo info) { - //TODO: check if info is not updated - if (info == null) { + @NonNull + public List getSessionInfos() { + return mSessionInfos; + } + + void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo, + List sessionInfos) { + if (providerInfo == null) { mProviderInfo = null; } else { - mProviderInfo = new MediaRoute2ProviderInfo.Builder(info) + mProviderInfo = new MediaRoute2ProviderInfo.Builder(providerInfo) .setUniqueId(mUniqueId) .build(); } + List sessionInfoWithProviderId = new ArrayList(); + for (RouteSessionInfo sessionInfo : sessionInfos) { + sessionInfoWithProviderId.add( + new RouteSessionInfo.Builder(sessionInfo) + .setProviderId(mUniqueId) + .build()); + } + mSessionInfos = sessionInfoWithProviderId; + if (mCallback != null) { mCallback.onProviderStateChanged(this); } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 0fc06061624db..f9cfe7d73cacc 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -38,6 +38,8 @@ import android.util.Slog; import java.io.PrintWriter; import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -268,14 +270,15 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - private void onProviderInfoUpdated(Connection connection, MediaRoute2ProviderInfo info) { + private void onProviderStateUpdated(Connection connection, + MediaRoute2ProviderInfo providerInfo, List sessionInfos) { if (mActiveConnection != connection) { return; } if (DEBUG) { Slog.d(TAG, this + ": State changed "); } - setAndNotifyProviderInfo(info); + setAndNotifyProviderState(providerInfo, sessionInfos); } private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo, @@ -291,7 +294,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mConnectionReady = false; mActiveConnection.dispose(); mActiveConnection = null; - setAndNotifyProviderInfo(null); + setAndNotifyProviderState(null, Collections.emptyList()); } } @@ -397,8 +400,10 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mHandler.post(() -> onConnectionDied(Connection.this)); } - void postProviderInfoUpdated(MediaRoute2ProviderInfo info) { - mHandler.post(() -> onProviderInfoUpdated(Connection.this, info)); + void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo, + List sessionInfos) { + mHandler.post(() -> onProviderStateUpdated(Connection.this, + providerInfo, sessionInfos)); } void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { @@ -419,10 +424,11 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void updateProviderInfo(MediaRoute2ProviderInfo info) { + public void updateState(MediaRoute2ProviderInfo providerInfo, + List sessionInfos) { Connection connection = mConnectionRef.get(); if (connection != null) { - connection.postProviderInfoUpdated(info); + connection.postProviderStateUpdated(providerInfo, sessionInfos); } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index fd41b6fc363d8..d78879f63de5f 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -288,6 +288,18 @@ class MediaRouter2ServiceImpl { } } + @NonNull + public List getActiveSessions(IMediaRouter2Manager manager) { + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + return getActiveSessionsLocked(manager); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + //TODO: Review this is handling multi-user properly. void switchUser() { synchronized (mLock) { @@ -523,6 +535,21 @@ class MediaRouter2ServiceImpl { } } + private List getActiveSessionsLocked(IMediaRouter2Manager manager) { + final IBinder binder = manager.asBinder(); + ManagerRecord managerRecord = mAllManagerRecords.get(binder); + + if (managerRecord == null) { + return Collections.emptyList(); + } + + List sessionInfos = new ArrayList<>(); + for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mMediaProviders) { + sessionInfos.addAll(provider.getSessionInfos()); + } + return sessionInfos; + } + private void initializeUserLocked(UserRecord userRecord) { if (DEBUG) { Slog.d(TAG, userRecord + ": Initialized"); @@ -731,6 +758,7 @@ class MediaRouter2ServiceImpl { this, provider, sessionInfo, requestId)); } + //TODO: notify session info updates private void updateProvider(MediaRoute2Provider provider) { int providerIndex = getProviderInfoIndex(provider.getUniqueId()); MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo(); diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index a51158b4ae023..8a0d2324cc433 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -39,6 +39,7 @@ import android.media.MediaRouter; import android.media.MediaRouterClientState; import android.media.RemoteDisplayState; import android.media.RemoteDisplayState.RemoteDisplayInfo; +import android.media.RouteSessionInfo; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -523,6 +524,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub mService2.requestUpdateVolume2Manager(manager, route, delta); } + // Binder call + @Override + public List getActiveSessions(IMediaRouter2Manager manager) { + return mService2.getActiveSessions(manager); + } + void restoreBluetoothA2dp() { try { boolean a2dpOn; diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 8451662baa155..263af70a7cadb 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -35,6 +35,8 @@ import android.util.Log; import com.android.internal.R; +import java.util.Collections; + /** * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers. */ @@ -214,6 +216,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { builder.addRoute(mBluetoothA2dpRoute); } builder.addRoute(mDefaultRoute); - setAndNotifyProviderInfo(builder.build()); + setAndNotifyProviderState(builder.build(), Collections.emptyList()); } }