| /* |
| * 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(); |
| } |
| } |
| } |