Merge "Implement MediaRouter2-based InfoMediaManager" into main
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user