Merge "MediaRouter2: Add getDefaultController()"
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user