Merge "System MediaRouter2: Add RouteCallback functionality" into sc-dev

This commit is contained in:
Hyundo Moon
2021-03-11 10:16:26 +00:00
committed by Android (Google) Code Review
2 changed files with 201 additions and 40 deletions

View File

@@ -60,6 +60,7 @@ import java.util.stream.Collectors;
public final class MediaRouter2 {
private static final String TAG = "MR2";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final Object sSystemRouterLock = new Object();
private static final Object sRouterLock = new Object();
// The maximum time for the old routing controller available after transfer.
@@ -67,8 +68,8 @@ public final class MediaRouter2 {
// The manager request ID representing that no manager is involved.
private static final long MANAGER_REQUEST_ID_NONE = MediaRoute2ProviderService.REQUEST_ID_NONE;
@GuardedBy("sRouterLock")
private static Map<String, MediaRouter2> sMediaRouter2Map = new ArrayMap<>();
@GuardedBy("sSystemRouterLock")
private static Map<String, MediaRouter2> sSystemMediaRouter2Map = new ArrayMap<>();
private static MediaRouter2Manager sManager;
@GuardedBy("sRouterLock")
@@ -76,6 +77,7 @@ public final class MediaRouter2 {
private final Context mContext;
private final IMediaRouterService mMediaRouterService;
private final Object mLock = new Object();
private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords =
new CopyOnWriteArrayList<>();
@@ -88,27 +90,29 @@ public final class MediaRouter2 {
new CopyOnWriteArrayList<>();
private final String mClientPackageName;
private final ManagerCallback mManagerCallback;
private final String mPackageName;
@GuardedBy("sRouterLock")
@GuardedBy("mLock")
final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>();
final RoutingController mSystemController;
@GuardedBy("sRouterLock")
@GuardedBy("mLock")
private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
// TODO: Make MediaRouter2 is always connected to the MediaRouterService.
@GuardedBy("sRouterLock")
@GuardedBy("mLock")
MediaRouter2Stub mStub;
@GuardedBy("sRouterLock")
@GuardedBy("mLock")
private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
private final AtomicInteger mNextRequestId = new AtomicInteger(1);
final Handler mHandler;
@GuardedBy("sRouterLock")
@GuardedBy("mLock")
private boolean mShouldUpdateRoutes = true;
private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
private volatile OnGetControllerHintsListener mOnGetControllerHintsListener;
@@ -130,6 +134,11 @@ public final class MediaRouter2 {
/**
* Gets an instance of the media router which controls the app's media routing.
* Returns {@code null} if the given package name is invalid.
* <p>
* Note: For media routers created with this method, the discovery preference passed to
* {@link #registerRouteCallback} will have no effect. The callback will be called accordingly
* with the client app's discovery preference. Therefore, it is recommended to pass
* {@link RouteDiscoveryPreference#EMPTY} there.
*
* @param clientPackageName the package name of the app to control
* @hide
@@ -149,15 +158,17 @@ public final class MediaRouter2 {
return null;
}
synchronized (sRouterLock) {
MediaRouter2 instance = sMediaRouter2Map.get(clientPackageName);
synchronized (sSystemRouterLock) {
MediaRouter2 instance = sSystemMediaRouter2Map.get(clientPackageName);
if (instance == null) {
// TODO: Add permission check here using MODIFY_AUDIO_ROUTING.
if (sManager == null) {
sManager = MediaRouter2Manager.getInstance(context.getApplicationContext());
}
instance = new MediaRouter2(context, clientPackageName);
sMediaRouter2Map.put(clientPackageName, instance);
sSystemMediaRouter2Map.put(clientPackageName, instance);
// TODO: Remove router instance once it is not needed.
instance.registerManagerCallback();
}
return instance;
}
@@ -192,11 +203,14 @@ public final class MediaRouter2 {
}
mSystemController = new SystemRoutingController(currentSystemSessionInfo);
// Only used by system MediaRouter2.
mClientPackageName = null;
mManagerCallback = null;
}
private MediaRouter2(Context context, String clientPackageName) {
mClientPackageName = clientPackageName;
mManagerCallback = new ManagerCallback();
mContext = context;
mMediaRouterService = null;
mPackageName = null;
@@ -220,8 +234,8 @@ public final class MediaRouter2 {
}
/**
* Gets the target package name of the app which this media router controls.
* This is only non-null when the router instance is created with the target package name.
* Gets the client package name of the app which this media router controls.
* This is only non-null when the router instance is created with the client package name.
*
* @see #getInstance(Context, String)
* @hide
@@ -245,6 +259,9 @@ public final class MediaRouter2 {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(routeCallback, "callback must not be null");
Objects.requireNonNull(preference, "preference must not be null");
if (isSystemRouter()) {
preference = RouteDiscoveryPreference.EMPTY;
}
RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
@@ -253,7 +270,11 @@ public final class MediaRouter2 {
// is happening but it's okay because either this or the other registration should be done.
mRouteCallbackRecords.addIfAbsent(record);
synchronized (sRouterLock) {
if (isSystemRouter()) {
return;
}
synchronized (mLock) {
if (mStub == null) {
MediaRouter2Stub stub = new MediaRouter2Stub();
try {
@@ -289,7 +310,11 @@ public final class MediaRouter2 {
return;
}
synchronized (sRouterLock) {
if (isSystemRouter()) {
return;
}
synchronized (mLock) {
if (mStub == null) {
return;
}
@@ -325,23 +350,38 @@ public final class MediaRouter2 {
return true;
}
/**
* Gets the list of all discovered routes.
* This list includes the routes that are not related to the client app.
* <p>
* This will return an empty list for non-system media routers.
*
* @hide
*/
//@SystemApi
public List<MediaRoute2Info> getAllRoutes() {
if (isSystemRouter()) {
return sManager.getAllRoutes();
}
return Collections.emptyList();
}
/**
* Gets the unmodifiable list of {@link MediaRoute2Info routes} currently
* known to the media router.
* <p>
* Please note that the list can be changed before callbacks are invoked.
* </p>
*
* @return the list of routes that contains at least one of the route features in discovery
* preferences registered by the application
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
if (mClientPackageName != null) {
if (isSystemRouter()) {
return sManager.getAvailableRoutes(mClientPackageName);
}
synchronized (sRouterLock) {
synchronized (mLock) {
if (mShouldUpdateRoutes) {
mShouldUpdateRoutes = false;
@@ -449,7 +489,7 @@ public final class MediaRouter2 {
* @see TransferCallback#onTransferFailure
*/
public void transferTo(@NonNull MediaRoute2Info route) {
if (mClientPackageName != null) {
if (isSystemRouter()) {
sManager.selectRoute(mClientPackageName, route);
return;
}
@@ -464,7 +504,7 @@ public final class MediaRouter2 {
* controls the media routing, this method is a no-op.
*/
public void stop() {
if (mClientPackageName != null) {
if (isSystemRouter()) {
List<RoutingSessionInfo> sessionInfos = sManager.getRoutingSessions(mClientPackageName);
RoutingSessionInfo sessionToRelease = sessionInfos.get(sessionInfos.size() - 1);
sManager.releaseSession(sessionToRelease);
@@ -481,7 +521,7 @@ public final class MediaRouter2 {
*/
//@SystemApi
public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
if (mClientPackageName != null) {
if (isSystemRouter()) {
sManager.transfer(controller.getRoutingSessionInfo(), route);
return;
}
@@ -490,7 +530,7 @@ public final class MediaRouter2 {
Objects.requireNonNull(route, "route must not be null");
boolean routeFound;
synchronized (sRouterLock) {
synchronized (mLock) {
// TODO: Check thread-safety
routeFound = mRoutes.containsKey(route.getId());
}
@@ -526,7 +566,7 @@ public final class MediaRouter2 {
}
MediaRouter2Stub stub;
synchronized (sRouterLock) {
synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -576,7 +616,7 @@ public final class MediaRouter2 {
public List<RoutingController> getControllers() {
// TODO: Do not create the controller instances every time,
// Instead, update the list using the sessions' ID and session related callbacks.
if (mClientPackageName != null) {
if (isSystemRouter()) {
return sManager.getRoutingSessions(mClientPackageName).stream()
.map(info -> new RoutingController(info))
.collect(Collectors.toList());
@@ -584,7 +624,7 @@ public final class MediaRouter2 {
List<RoutingController> result = new ArrayList<>();
result.add(0, mSystemController);
synchronized (sRouterLock) {
synchronized (mLock) {
result.addAll(mNonSystemRoutingControllers.values());
}
return result;
@@ -603,7 +643,7 @@ public final class MediaRouter2 {
Objects.requireNonNull(route, "route must not be null");
MediaRouter2Stub stub;
synchronized (sRouterLock) {
synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -627,7 +667,7 @@ public final class MediaRouter2 {
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
synchronized (sRouterLock) {
synchronized (mLock) {
List<String> currentRoutesIds = currentRoutes.stream().map(MediaRoute2Info::getId)
.collect(Collectors.toList());
@@ -685,7 +725,7 @@ public final class MediaRouter2 {
void addRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
synchronized (sRouterLock) {
synchronized (mLock) {
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
@@ -701,7 +741,7 @@ public final class MediaRouter2 {
void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
synchronized (sRouterLock) {
synchronized (mLock) {
for (MediaRoute2Info route : routes) {
mRoutes.remove(route.getId());
if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
@@ -717,7 +757,7 @@ public final class MediaRouter2 {
void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
synchronized (sRouterLock) {
synchronized (mLock) {
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
@@ -789,7 +829,7 @@ public final class MediaRouter2 {
newController.setRoutingSessionInfo(sessionInfo);
} else {
newController = new RoutingController(sessionInfo);
synchronized (sRouterLock) {
synchronized (mLock) {
mNonSystemRoutingControllers.put(newController.getId(), newController);
}
}
@@ -812,7 +852,7 @@ public final class MediaRouter2 {
}
RoutingController matchingController;
synchronized (sRouterLock) {
synchronized (mLock) {
matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
}
@@ -840,7 +880,7 @@ public final class MediaRouter2 {
}
RoutingController matchingController;
synchronized (sRouterLock) {
synchronized (mLock) {
matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
}
@@ -868,7 +908,7 @@ public final class MediaRouter2 {
if (oldSession.isSystemSession()) {
controller = getSystemController();
} else {
synchronized (sRouterLock) {
synchronized (mLock) {
controller = mNonSystemRoutingControllers.get(oldSession.getId());
}
}
@@ -878,6 +918,22 @@ public final class MediaRouter2 {
requestCreateController(controller, route, managerRequestId);
}
/**
* Returns whether this router is created with {@link #getInstance(Context, String)}.
* This kind of router can control the target app's media routing.
*/
private boolean isSystemRouter() {
return mClientPackageName != null;
}
/**
* Registers {@link MediaRouter2Manager.Callback} for getting events.
*/
private void registerManagerCallback() {
// Using direct executor here, since MediaRouter2Manager also posts to the main handler.
sManager.registerCallback(Runnable::run, mManagerCallback);
}
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
RouteDiscoveryPreference discoveryRequest) {
return routes.stream()
@@ -1236,7 +1292,7 @@ public final class MediaRouter2 {
}
MediaRouter2Stub stub;
synchronized (sRouterLock) {
synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -1283,7 +1339,7 @@ public final class MediaRouter2 {
}
MediaRouter2Stub stub;
synchronized (sRouterLock) {
synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -1318,7 +1374,7 @@ public final class MediaRouter2 {
}
MediaRouter2Stub stub;
synchronized (sRouterLock) {
synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -1352,7 +1408,7 @@ public final class MediaRouter2 {
return;
}
MediaRouter2Stub stub;
synchronized (sRouterLock) {
synchronized (mLock) {
stub = mStub;
}
if (stub != null) {
@@ -1386,7 +1442,7 @@ public final class MediaRouter2 {
mState = CONTROLLER_STATE_RELEASING;
}
synchronized (sRouterLock) {
synchronized (mLock) {
// It could happen if the controller is released by the another thread
// in between two locks
if (!mNonSystemRoutingControllers.remove(getId(), this)) {
@@ -1415,7 +1471,7 @@ public final class MediaRouter2 {
mState = CONTROLLER_STATE_RELEASED;
}
synchronized (sRouterLock) {
synchronized (mLock) {
mNonSystemRoutingControllers.remove(getId(), this);
if (shouldReleaseSession && mStub != null) {
@@ -1483,7 +1539,7 @@ public final class MediaRouter2 {
}
private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
synchronized (sRouterLock) {
synchronized (mLock) {
return routeIds.stream().map(mRoutes::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
@@ -1665,4 +1721,79 @@ public final class MediaRouter2 {
MediaRouter2.this, oldSession, route, managerRequestId));
}
}
class ManagerCallback implements MediaRouter2Manager.Callback {
@Override
public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
List<MediaRoute2Info> filteredRoutes =
sManager.filterRoutesForPackage(routes, mClientPackageName);
if (filteredRoutes.isEmpty()) {
return;
}
for (RouteCallbackRecord record: mRouteCallbackRecords) {
record.mExecutor.execute(
() -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
}
}
@Override
public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
List<MediaRoute2Info> filteredRoutes =
sManager.filterRoutesForPackage(routes, mClientPackageName);
if (filteredRoutes.isEmpty()) {
return;
}
for (RouteCallbackRecord record: mRouteCallbackRecords) {
record.mExecutor.execute(
() -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
}
}
@Override
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
List<MediaRoute2Info> filteredRoutes =
sManager.filterRoutesForPackage(routes, mClientPackageName);
if (filteredRoutes.isEmpty()) {
return;
}
for (RouteCallbackRecord record: mRouteCallbackRecords) {
record.mExecutor.execute(
() -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
}
}
@Override
public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
// TODO: Call ControllerCallback.onControllerUpdated
}
@Override
public void onTransferred(@NonNull RoutingSessionInfo oldSession,
@Nullable RoutingSessionInfo newSession) {
// TODO: Call TransferCallback.onTransfer
}
@Override
public void onTransferFailed(@NonNull RoutingSessionInfo session,
@NonNull MediaRoute2Info route) {
// TODO: Call TransferCallback.onTransferFailure
}
@Override
public void onSessionReleased(@NonNull RoutingSessionInfo session) {
// TODO: Call TransferCallback.onStop()
}
@Override
public void onPreferredFeaturesChanged(@NonNull String packageName,
@NonNull List<String> preferredFeatures) {
// Does nothing.
}
@Override
public void onRequestFailed(int reason) {
// Does nothing.
}
}
}

View File

@@ -236,6 +236,36 @@ public final class MediaRouter2Manager {
return routes;
}
/**
* Returns a list of routes which are related to the given package name in the given route list.
*/
@NonNull
public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes,
@NonNull String packageName) {
Objects.requireNonNull(routes, "routes must not be null");
Objects.requireNonNull(packageName, "packageName must not be null");
List<RoutingSessionInfo> sessions = getRoutingSessions(packageName);
RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1);
List<MediaRoute2Info> result = new ArrayList<>();
List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
if (preferredFeatures == null) {
preferredFeatures = Collections.emptyList();
}
synchronized (mRoutesLock) {
for (MediaRoute2Info route : routes) {
if (route.hasAnyFeatures(preferredFeatures)
|| sessionInfo.getSelectedRoutes().contains(route.getId())
|| sessionInfo.getTransferableRoutes().contains(route.getId())) {
result.add(route);
}
}
}
return result;
}
/**
* Gets the system routing session associated with no specific application.
*/