diff options
| -rw-r--r-- | packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java | 7 | ||||
| -rw-r--r-- | packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java | 320 |
2 files changed, 327 insertions, 0 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 7e275607e36f..1ea155a6a623 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -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; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java new file mode 100644 index 000000000000..70956e9221e2 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java @@ -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(); + } + } +} |