diff options
| author | 2020-01-20 00:26:52 +0000 | |
|---|---|---|
| committer | 2020-01-20 00:26:52 +0000 | |
| commit | ece12a89bcac2d7daf98cbdc7c11518c63d7d658 (patch) | |
| tree | 896bf0de5e949e7c402d9c4df46e4a5bebf316c9 | |
| parent | f1cbec5f5ded6af059bd91b2ff7f0aa42a7f1a40 (diff) | |
| parent | 67c41fddd8926f70774878e5af70a9cb81393641 (diff) | |
Merge "MediaRouter2: Add getDefaultController()"
10 files changed, 203 insertions, 13 deletions
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 281e7c6b6f68..dac0fba876be 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -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, diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 51d08ec96e6f..a6198842abe8 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -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); } /** @@ -341,6 +350,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. * * @param route the route that will receive the control request @@ -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; diff --git a/media/java/android/media/MediaRouter2Utils.java b/media/java/android/media/MediaRouter2Utils.java index 49045828dbe8..c15972dcff2e 100644 --- a/media/java/android/media/MediaRouter2Utils.java +++ b/media/java/android/media/MediaRouter2Utils.java @@ -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) { diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index 228addebe029..cb996f493eb4 100644 --- a/media/java/android/media/RoutingSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -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; } /** @@ -491,6 +507,16 @@ public final class RoutingSessionInfo implements Parcelable { } /** + * 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. * * @throws IllegalArgumentException if no selected routes are added. diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java index 32d03db6f7ee..a9fcf7703cfc 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java @@ -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<>(); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index cba8452fe9c2..39313eec6bf8 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -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 diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index b67335ab82bc..9f42c3625a51 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -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: diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 216753017010..f9169ee69d41 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -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) { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 5437fadf2d74..e1d38039b955 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -444,6 +444,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override + public RoutingSessionInfo getSystemSessionInfo() { + return mService2.getSystemSessionInfo(); + } + + // Binder call + @Override public void registerClient2(IMediaRouter2Client client, String packageName) { final int uid = Binder.getCallingUid(); if (!validatePackageName(uid, packageName)) { diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 56c33fe339ea..ebea6c3dc558 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -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) { @@ -203,6 +217,35 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } /** + * 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), * BT will be the first route in the list. @@ -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); + } } |