Merge "MediaRouter2: Add getDefaultController()"

This commit is contained in:
Hyundo Moon
2020-01-20 00:26:52 +00:00
committed by Android (Google) Code Review
10 changed files with 203 additions and 13 deletions

View File

@@ -46,6 +46,7 @@ interface IMediaRouterService {
// Methods for media router 2
List<MediaRoute2Info> getSystemRoutes();
RoutingSessionInfo getSystemSessionInfo();
void registerClient2(IMediaRouter2Client client, String packageName);
void unregisterClient2(IMediaRouter2Client client);
void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route,

View File

@@ -76,6 +76,8 @@ public class MediaRouter2 {
@GuardedBy("sRouterLock")
final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
final RoutingController mDefaultController;
@GuardedBy("sRouterLock")
private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
@@ -116,19 +118,26 @@ public class MediaRouter2 {
mHandler = new Handler(Looper.getMainLooper());
List<MediaRoute2Info> currentSystemRoutes = null;
RoutingSessionInfo currentSystemSessionInfo = null;
try {
currentSystemRoutes = mMediaRouterService.getSystemRoutes();
currentSystemSessionInfo = mMediaRouterService.getSystemSessionInfo();
} catch (RemoteException ex) {
Log.e(TAG, "Unable to get current currentSystemRoutes", ex);
Log.e(TAG, "Unable to get current system's routes / session info", ex);
}
if (currentSystemRoutes == null || currentSystemRoutes.isEmpty()) {
throw new RuntimeException("Null or empty currentSystemRoutes. Something is wrong.");
}
if (currentSystemSessionInfo == null) {
throw new RuntimeException("Null currentSystemSessionInfo. Something is wrong.");
}
for (MediaRoute2Info route : currentSystemRoutes) {
mRoutes.put(route.getId(), route);
}
mDefaultController = new DefaultRoutingController(currentSystemSessionInfo);
}
/**
@@ -340,6 +349,22 @@ public class MediaRouter2 {
}
}
/**
* Gets a {@link RoutingController} which can control the routes provided by system.
* e.g. Phone speaker, wired headset, Bluetooth, etc.
* <p>
* Note: The default controller can't be released. Calling {@link RoutingController#release()}
* will be no-op.
* <p>
* This method will always return the same instance.
*
* @hide
*/
@NonNull
public RoutingController getDefaultController() {
return mDefaultController;
}
/**
* Sends a media control request to be performed asynchronously by the route's destination.
*
@@ -526,6 +551,15 @@ public class MediaRouter2 {
return;
}
if (sessionInfo.isSystemSession()) {
// The session info is sent from SystemMediaRoute2Provider.
RoutingController defaultController = getDefaultController();
RoutingSessionInfo oldInfo = defaultController.getRoutingSessionInfo();
defaultController.setRoutingSessionInfo(sessionInfo);
notifySessionInfoChanged(defaultController, oldInfo, sessionInfo);
return;
}
RoutingController matchingController;
synchronized (sRouterLock) {
matchingController = mRoutingControllers.get(sessionInfo.getId());
@@ -766,7 +800,7 @@ public class MediaRouter2 {
*
* @hide
*/
public final class RoutingController {
public class RoutingController {
private final Object mControllerLock = new Object();
@GuardedBy("mControllerLock")
@@ -1074,7 +1108,6 @@ public class MediaRouter2 {
// TODO: This method uses two locks (mLock outside, sLock inside).
// Check if there is any possiblity of deadlock.
private List<MediaRoute2Info> getRoutesWithIdsLocked(List<String> routeIds) {
List<MediaRoute2Info> routes = new ArrayList<>();
synchronized (sRouterLock) {
// TODO: Maybe able to change using Collection.stream()?
@@ -1089,6 +1122,23 @@ public class MediaRouter2 {
}
}
class DefaultRoutingController extends RoutingController {
DefaultRoutingController(@NonNull RoutingSessionInfo sessionInfo) {
super(sessionInfo);
}
@Override
public void release() {
// Do nothing. DefaultRoutingController will never be released
}
@Override
public boolean isReleased() {
// DefaultRoutingController will never be released
return false;
}
}
final class RouteCallbackRecord {
public final Executor mExecutor;
public final RouteCallback mRouteCallback;

View File

@@ -29,9 +29,6 @@ public class MediaRouter2Utils {
static final String TAG = "MR2Utils";
static final String SEPARATOR = ":";
/**
* @hide
*/
@NonNull
public static String toUniqueId(@NonNull String providerId, @NonNull String id) {
if (TextUtils.isEmpty(providerId)) {
@@ -49,8 +46,6 @@ public class MediaRouter2Utils {
/**
* Gets provider ID from unique ID.
* If the corresponding provider ID could not be generated, it will return null.
*
* @hide
*/
@Nullable
public static String getProviderId(@NonNull String uniqueId) {
@@ -75,8 +70,6 @@ public class MediaRouter2Utils {
/**
* Gets the original ID (i.e. non-unique route/session ID) from unique ID.
* If the corresponding ID could not be generated, it will return null.
*
* @hide
*/
@Nullable
public static String getOriginalId(@NonNull String uniqueId) {

View File

@@ -59,6 +59,7 @@ public final class RoutingSessionInfo implements Parcelable {
final List<String> mTransferrableRoutes;
@Nullable
final Bundle mControlHints;
final boolean mIsSystemSession;
RoutingSessionInfo(@NonNull Builder builder) {
Objects.requireNonNull(builder, "builder must not be null.");
@@ -78,6 +79,7 @@ public final class RoutingSessionInfo implements Parcelable {
convertToUniqueRouteIds(builder.mTransferrableRoutes));
mControlHints = builder.mControlHints;
mIsSystemSession = builder.mIsSystemSession;
}
RoutingSessionInfo(@NonNull Parcel src) {
@@ -93,6 +95,7 @@ public final class RoutingSessionInfo implements Parcelable {
mTransferrableRoutes = ensureList(src.createStringArrayList());
mControlHints = src.readBundle();
mIsSystemSession = src.readBoolean();
}
private static String ensureString(String str) {
@@ -193,6 +196,15 @@ public final class RoutingSessionInfo implements Parcelable {
return mControlHints;
}
/**
* Gets whether this session is in system media route provider.
* @hide
*/
@Nullable
public boolean isSystemSession() {
return mIsSystemSession;
}
@Override
public int describeContents() {
return 0;
@@ -208,6 +220,7 @@ public final class RoutingSessionInfo implements Parcelable {
dest.writeStringList(mDeselectableRoutes);
dest.writeStringList(mTransferrableRoutes);
dest.writeBundle(mControlHints);
dest.writeBoolean(mIsSystemSession);
}
@Override
@@ -278,6 +291,7 @@ public final class RoutingSessionInfo implements Parcelable {
* Builder class for {@link RoutingSessionInfo}.
*/
public static final class Builder {
// TODO: Reorder these (important ones first)
final String mId;
final String mClientPackageName;
String mProviderId;
@@ -286,6 +300,7 @@ public final class RoutingSessionInfo implements Parcelable {
final List<String> mDeselectableRoutes;
final List<String> mTransferrableRoutes;
Bundle mControlHints;
boolean mIsSystemSession;
/**
* Constructor for builder to create {@link RoutingSessionInfo}.
@@ -333,6 +348,7 @@ public final class RoutingSessionInfo implements Parcelable {
mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
mControlHints = sessionInfo.mControlHints;
mIsSystemSession = sessionInfo.mIsSystemSession;
}
/**
@@ -490,6 +506,16 @@ public final class RoutingSessionInfo implements Parcelable {
return this;
}
/**
* Sets whether this session is in system media route provider.
* @hide
*/
@NonNull
public Builder setSystemSession(boolean isSystemSession) {
mIsSystemSession = isSystemSession;
return this;
}
/**
* Builds a routing session info.
*

View File

@@ -692,6 +692,14 @@ public class MediaRouter2Test {
}
}
// TODO: Consider adding tests with bluetooth connection/disconnection.
@Test
public void testGetDefaultController() {
final RoutingController defaultController = mRouter2.getDefaultController();
assertNotNull(defaultController);
assertFalse(defaultController.isReleased());
}
// Helper for getting routes easily
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
Map<String, MediaRoute2Info> routeMap = new HashMap<>();

View File

@@ -227,7 +227,7 @@ public class MediaRouterManagerTest {
mManager.selectRoute(mPackageName, routeToSelect);
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(1, mManager.getActiveSessions().size());
assertEquals(2, mManager.getActiveSessions().size());
}
@Test

View File

@@ -17,6 +17,7 @@
package com.android.server.media;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -58,6 +59,7 @@ class BluetoothRouteProvider {
private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
// TODO: The mActiveDevice should be set when BluetoothRouteProvider is created.
private BluetoothDevice mActiveDevice = null;
static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
@@ -125,6 +127,14 @@ class BluetoothRouteProvider {
return routes;
}
@Nullable String getActiveDeviceAddress() {
BluetoothDevice device = mActiveDevice;
if (device == null) {
return null;
}
return device.getAddress();
}
private void notifyBluetoothRoutesUpdated() {
if (mListener != null) {
mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
@@ -281,8 +291,8 @@ class BluetoothRouteProvider {
setRouteConnectionStateForDevice(device,
MediaRoute2Info.CONNECTION_STATE_CONNECTED);
}
notifyBluetoothRoutesUpdated();
mActiveDevice = device;
notifyBluetoothRoutesUpdated();
}
break;
case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:

View File

@@ -112,6 +112,30 @@ class MediaRouter2ServiceImpl {
}
}
@NonNull
public RoutingSessionInfo getSystemSessionInfo() {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
RoutingSessionInfo systemSessionInfo = null;
synchronized (mLock) {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
List<RoutingSessionInfo> sessionInfos =
userRecord.mHandler.mSystemProvider.getSessionInfos();
if (sessionInfos != null && !sessionInfos.isEmpty()) {
systemSessionInfo = sessionInfos.get(0);
} else {
Slog.w(TAG, "System provider does not have any session info.");
}
}
return systemSessionInfo;
} finally {
Binder.restoreCallingIdentity(token);
}
}
public void registerClient(@NonNull IMediaRouter2Client client,
@NonNull String packageName) {
Objects.requireNonNull(client, "client must not be null");
@@ -1351,6 +1375,16 @@ class MediaRouter2ServiceImpl {
List<IMediaRouter2Manager> managers = getManagers();
notifySessionInfosChangedToManagers(managers);
// For system provider, notify all clients.
if (provider == mSystemProvider) {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
return;
}
notifySessionInfoChangedToClients(getClients(), sessionInfo);
return;
}
Client2Record client2Record = mSessionToClientMap.get(
sessionInfo.getId());
if (client2Record == null) {
@@ -1509,6 +1543,17 @@ class MediaRouter2ServiceImpl {
}
}
private void notifySessionInfoChangedToClients(List<IMediaRouter2Client> clients,
RoutingSessionInfo sessionInfo) {
for (IMediaRouter2Client client : clients) {
try {
client.notifySessionInfoChanged(sessionInfo);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify session info changed. Client probably died.", ex);
}
}
}
private void notifyRoutesToManager(IMediaRouter2Manager manager) {
List<MediaRoute2Info> routes = new ArrayList<>();
for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {

View File

@@ -442,6 +442,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub
return mService2.getSystemRoutes();
}
// Binder call
@Override
public RoutingSessionInfo getSystemSessionInfo() {
return mService2.getSystemSessionInfo();
}
// Binder call
@Override
public void registerClient2(IMediaRouter2Client client, String packageName) {

View File

@@ -26,17 +26,20 @@ import android.media.IAudioRoutesObserver;
import android.media.IAudioService;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
@@ -46,7 +49,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
static final String BLUETOOTH_ROUTE_ID = "BLUETOOTH_ROUTE";
static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
// TODO: Move these to a proper place
public static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO";
@@ -92,6 +95,14 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
mBluetoothRoutes = routes;
publishRoutes();
boolean sessionInfoChanged;
synchronized (mLock) {
sessionInfoChanged = updateSessionInfosIfNeededLocked();
}
if (sessionInfoChanged) {
notifySessionInfoUpdated();
}
});
initializeRoutes();
}
@@ -172,6 +183,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
setProviderState(builder.build());
mHandler.post(() -> notifyProviderState());
// Note: No lock needed when initializing.
updateSessionInfosIfNeededLocked();
}
void updateAudioRoutes(AudioRoutesInfo newRoutes) {
@@ -202,6 +216,35 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
publishRoutes();
}
/**
* Updates the mSessionInfo. Returns true if the session info is changed.
*/
boolean updateSessionInfosIfNeededLocked() {
RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(0);
RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
SYSTEM_SESSION_ID, "" /* clientPackageName */)
.setSystemSession(true);
String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress();
RoutingSessionInfo newSessionInfo;
if (!TextUtils.isEmpty(activeBtDeviceAddress)) {
// Bluetooth route. Set the route ID with the device's address.
newSessionInfo = builder.addSelectedRoute(activeBtDeviceAddress).build();
} else {
// Default device
newSessionInfo = builder.addSelectedRoute(mDefaultRoute.getId()).build();
}
if (Objects.equals(oldSessionInfo, newSessionInfo)) {
return false;
} else {
mSessionInfos.clear();
mSessionInfos.add(newSessionInfo);
return true;
}
}
/**
* The first route should be the currently selected system route.
* For example, if there are two system routes (BT and device speaker),
@@ -215,4 +258,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
setAndNotifyProviderState(builder.build());
}
void notifySessionInfoUpdated() {
RoutingSessionInfo sessionInfo;
synchronized (mLock) {
sessionInfo = mSessionInfos.get(0);
}
mCallback.onSessionUpdated(this, sessionInfo);
}
}