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
This commit is contained in:
Kyunglyul Hyun
2019-12-31 18:32:28 +09:00
parent f6a55e1ba1
commit efe4374eb4
10 changed files with 126 additions and 30 deletions

View File

@@ -25,6 +25,7 @@ import android.os.Bundle;
* @hide
*/
oneway interface IMediaRoute2ProviderClient {
void updateProviderInfo(in MediaRoute2ProviderInfo info);
void updateState(in MediaRoute2ProviderInfo providerInfo,
in List<RouteSessionInfo> sessionInfos);
void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId);
}

View File

@@ -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<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
}

View File

@@ -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<RouteSessionInfo> 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");
}

View File

@@ -176,7 +176,18 @@ public class MediaRouter2Manager {
}
@NonNull
public List<RouteSessionInfo> getRouteSessions() {
public List<RouteSessionInfo> 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();
}

View File

@@ -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
/*

View File

@@ -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<RouteSessionInfo> 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<RouteSessionInfo> getSessionInfos() {
return mSessionInfos;
}
void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
List<RouteSessionInfo> sessionInfos) {
if (providerInfo == null) {
mProviderInfo = null;
} else {
mProviderInfo = new MediaRoute2ProviderInfo.Builder(info)
mProviderInfo = new MediaRoute2ProviderInfo.Builder(providerInfo)
.setUniqueId(mUniqueId)
.build();
}
List<RouteSessionInfo> sessionInfoWithProviderId = new ArrayList<RouteSessionInfo>();
for (RouteSessionInfo sessionInfo : sessionInfos) {
sessionInfoWithProviderId.add(
new RouteSessionInfo.Builder(sessionInfo)
.setProviderId(mUniqueId)
.build());
}
mSessionInfos = sessionInfoWithProviderId;
if (mCallback != null) {
mCallback.onProviderStateChanged(this);
}

View File

@@ -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<RouteSessionInfo> 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<RouteSessionInfo> 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<RouteSessionInfo> sessionInfos) {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.postProviderInfoUpdated(info);
connection.postProviderStateUpdated(providerInfo, sessionInfos);
}
}

View File

@@ -288,6 +288,18 @@ class MediaRouter2ServiceImpl {
}
}
@NonNull
public List<RouteSessionInfo> 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<RouteSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
if (managerRecord == null) {
return Collections.emptyList();
}
List<RouteSessionInfo> 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();

View File

@@ -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<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
return mService2.getActiveSessions(manager);
}
void restoreBluetoothA2dp() {
try {
boolean a2dpOn;

View File

@@ -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());
}
}