Implement MediaRouter2-based InfoMediaManager

Bug: 192657812
Test: SettingsLibTests
Change-Id: I52b9e10d46777c5b1841d5a94247e7d32156a99a
This commit is contained in:
Iván Budnik
2023-07-21 14:31:18 +00:00
parent eb9df37e70
commit 5bb1e5e803
2 changed files with 327 additions and 0 deletions

View File

@@ -82,6 +82,13 @@ public abstract class InfoMediaManager extends MediaManager {
private static final String TAG = "InfoMediaManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/** Checked exception that signals the specified package is not present in the system. */
public static class PackageNotAvailableException extends Exception {
public PackageNotAvailableException(String message) {
super(message);
}
}
protected String mPackageName;
private MediaDevice mCurrentConnectedDevice;
private final LocalBluetoothManager mBluetoothManager;

View File

@@ -0,0 +1,320 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.media;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.RoutingController;
import android.media.MediaRouter2Manager;
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/** Implements {@link InfoMediaManager} using {@link MediaRouter2}. */
@SuppressLint("MissingPermission")
public final class RouterInfoMediaManager extends InfoMediaManager {
private static final String TAG = "RouterInfoMediaManager";
private final MediaRouter2 mRouter;
private final MediaRouter2Manager mRouterManager;
private final Executor mExecutor = Executors.newSingleThreadExecutor();
private final RouteCallback mRouteCallback = new RouteCallback();
private final TransferCallback mTransferCallback = new TransferCallback();
private final ControllerCallback mControllerCallback = new ControllerCallback();
private final RouteListingPreferenceCallback mRouteListingPreferenceCallback =
new RouteListingPreferenceCallback();
// TODO: b/192657812 - Create factory method in InfoMediaManager to return
// RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
public RouterInfoMediaManager(
Context context,
String packageName,
Notification notification,
LocalBluetoothManager localBluetoothManager) throws PackageNotAvailableException {
super(context, packageName, notification, localBluetoothManager);
// TODO: b/291277292 - Change optional package name for a mandatory uid.
if (packageName == null) {
packageName = context.getPackageName();
}
mRouter = MediaRouter2.getInstance(context, packageName);
if (mRouter == null) {
throw new PackageNotAvailableException(
"Package name " + packageName + " does not exist.");
}
mRouterManager = MediaRouter2Manager.getInstance(context);
}
@Override
protected void startScanOnRouter() {
mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
mRouter.registerRouteListingPreferenceCallback(mExecutor, mRouteListingPreferenceCallback);
mRouter.registerTransferCallback(mExecutor, mTransferCallback);
mRouter.registerControllerCallback(mExecutor, mControllerCallback);
mRouter.startScan();
}
@Override
public void stopScan() {
mRouter.stopScan();
mRouter.unregisterControllerCallback(mControllerCallback);
mRouter.unregisterTransferCallback(mTransferCallback);
mRouter.unregisterRouteListingPreferenceCallback(mRouteListingPreferenceCallback);
mRouter.unregisterRouteCallback(mRouteCallback);
}
@Override
protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
if (device.mRouteInfo == null) {
return false;
}
RoutingController controller = mRouter.getSystemController();
mRouter.transfer(controller, device.mRouteInfo);
return true;
}
@Override
protected void transferToRoute(@NonNull MediaRoute2Info route) {
mRouter.transferTo(route);
}
@Override
protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
RoutingController controller = getControllerForSession(info);
if (controller != null) {
controller.selectRoute(route);
}
}
@Override
protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
RoutingController controller = getControllerForSession(info);
if (controller != null) {
controller.deselectRoute(route);
}
}
@Override
protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
RoutingController controller = getControllerForSession(sessionInfo);
if (controller != null) {
controller.release();
}
}
@NonNull
@Override
protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) {
RoutingController controller = getControllerForSession(info);
if (controller == null) {
return Collections.emptyList();
}
// Filter out selected routes.
List<String> selectedRouteIds = controller.getRoutingSessionInfo().getSelectedRoutes();
return controller.getSelectableRoutes().stream()
.filter(route -> !selectedRouteIds.contains(route.getId()))
.collect(Collectors.toList());
}
@NonNull
@Override
protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
RoutingController controller = getControllerForSession(info);
if (controller == null) {
return Collections.emptyList();
}
return controller.getDeselectableRoutes();
}
@NonNull
@Override
protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
RoutingController controller = getControllerForSession(info);
if (controller == null) {
return Collections.emptyList();
}
return controller.getSelectedRoutes();
}
@Override
protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) {
// TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as
// MR2 filters information by package name.
mRouterManager.setSessionVolume(info, volume);
}
@Override
protected void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
mRouter.setRouteVolume(route, volume);
}
@Nullable
@Override
protected RouteListingPreference getRouteListingPreference() {
return mRouter.getRouteListingPreference();
}
@NonNull
@Override
protected List<RoutingSessionInfo> getRemoteSessions() {
// TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as
// MR2 filters information by package name.
return mRouterManager.getRemoteSessions();
}
@NonNull
@Override
protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
return mRouter.getControllers().stream()
.map(RoutingController::getRoutingSessionInfo)
.collect(Collectors.toList());
}
@Nullable
@Override
protected RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId) {
// TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager calls as
// MR2 filters information by package name.
for (RoutingSessionInfo sessionInfo : getRemoteSessions()) {
if (TextUtils.equals(sessionInfo.getId(), sessionId)) {
return sessionInfo;
}
}
RoutingSessionInfo systemSession = mRouterManager.getSystemRoutingSession(null);
return TextUtils.equals(systemSession.getId(), sessionId) ? systemSession : null;
}
@NonNull
@Override
protected List<MediaRoute2Info> getAllRoutes() {
return mRouter.getAllRoutes();
}
@NonNull
@Override
protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
return mRouter.getRoutes();
}
@NonNull
@Override
protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
List<MediaRoute2Info> transferableRoutes = new ArrayList<>();
List<RoutingController> controllers = mRouter.getControllers();
RoutingController activeController = controllers.get(controllers.size() - 1);
RoutingSessionInfo sessionInfo = activeController.getRoutingSessionInfo();
List<MediaRoute2Info> routes = mRouter.getRoutes();
for (MediaRoute2Info route : routes) {
boolean isCrossDeviceTransfer = sessionInfo.isSystemSession() ^ route.isSystemRoute();
// Always show remote routes if transfer is local -> remote or viceversa regardless of
// whether route is in transferable routes list.
if (sessionInfo.getTransferableRoutes().contains(route.getId())
|| isCrossDeviceTransfer) {
transferableRoutes.add(route);
}
}
return transferableRoutes;
}
@Nullable
private RoutingController getControllerForSession(@NonNull RoutingSessionInfo sessionInfo) {
return mRouter.getController(sessionInfo.getId());
}
private final class RouteCallback extends MediaRouter2.RouteCallback {
@Override
public void onRoutesUpdated(@NonNull List<MediaRoute2Info> routes) {
refreshDevices();
}
@Override
public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {
refreshDevices();
}
}
private final class TransferCallback extends MediaRouter2.TransferCallback {
@Override
public void onTransfer(
@NonNull RoutingController oldController,
@NonNull RoutingController newController) {
rebuildDeviceList();
notifyCurrentConnectedDeviceChanged();
}
@Override
public void onTransferFailure(@NonNull MediaRoute2Info requestedRoute) {
// Do nothing.
}
@Override
public void onStop(@NonNull RoutingController controller) {
refreshDevices();
}
@Override
public void onRequestFailed(int reason) {
dispatchOnRequestFailed(reason);
}
}
private final class ControllerCallback extends MediaRouter2.ControllerCallback {
@Override
public void onControllerUpdated(@NonNull RoutingController controller) {
refreshDevices();
}
}
private final class RouteListingPreferenceCallback
extends MediaRouter2.RouteListingPreferenceCallback {
@Override
public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {
notifyRouteListingPreferenceUpdated(preference);
refreshDevices();
}
}
}