blob: 5298a46a9c89dbbe02ac9574c92a422545e5d686 [file] [log] [blame]
/*
* 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.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
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 Consumer<RouteListingPreference> mRouteListingPreferenceCallback =
(preference) -> {
notifyRouteListingPreferenceUpdated(preference);
refreshDevices();
};
// 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.registerRouteListingPreferenceUpdatedCallback(
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.unregisterRouteListingPreferenceUpdatedCallback(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<RoutingController> controllers = mRouter.getControllers();
RoutingController activeController = controllers.get(controllers.size() - 1);
HashMap<String, MediaRoute2Info> transferableRoutes = new HashMap<>();
activeController
.getTransferableRoutes()
.forEach(route -> transferableRoutes.put(route.getId(), route));
if (activeController.getRoutingSessionInfo().isSystemSession()) {
mRouter.getRoutes().stream()
.filter(route -> !route.isSystemRoute())
.forEach(route -> transferableRoutes.put(route.getId(), route));
} else {
mRouter.getRoutes().stream()
.filter(route -> route.isSystemRoute())
.forEach(route -> transferableRoutes.put(route.getId(), route));
}
return new ArrayList<>(transferableRoutes.values());
}
@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();
}
}
}