1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settingslib.media; 18 19 import android.annotation.SuppressLint; 20 import android.content.Context; 21 import android.media.MediaRoute2Info; 22 import android.media.MediaRouter2; 23 import android.media.MediaRouter2.RoutingController; 24 import android.media.MediaRouter2Manager; 25 import android.media.RouteDiscoveryPreference; 26 import android.media.RouteListingPreference; 27 import android.media.RoutingSessionInfo; 28 import android.media.session.MediaController; 29 import android.os.UserHandle; 30 import android.text.TextUtils; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 35 import com.android.media.flags.Flags; 36 import com.android.settingslib.bluetooth.LocalBluetoothManager; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.List; 42 import java.util.concurrent.Executor; 43 import java.util.concurrent.Executors; 44 import java.util.concurrent.atomic.AtomicReference; 45 import java.util.function.Consumer; 46 import java.util.stream.Collectors; 47 48 /** Implements {@link InfoMediaManager} using {@link MediaRouter2}. */ 49 @SuppressLint("MissingPermission") 50 public final class RouterInfoMediaManager extends InfoMediaManager { 51 52 private static final String TAG = "RouterInfoMediaManager"; 53 54 private final MediaRouter2 mRouter; 55 private final MediaRouter2Manager mRouterManager; 56 57 private final Executor mExecutor = Executors.newSingleThreadExecutor(); 58 59 private final RouteCallback mRouteCallback = new RouteCallback(); 60 private final TransferCallback mTransferCallback = new TransferCallback(); 61 private final ControllerCallback mControllerCallback = new ControllerCallback(); 62 private final Consumer<RouteListingPreference> mRouteListingPreferenceCallback = 63 (preference) -> { 64 notifyRouteListingPreferenceUpdated(preference); 65 refreshDevices(); 66 }; 67 68 private final AtomicReference<MediaRouter2.ScanToken> mScanToken = new AtomicReference<>(); 69 70 // TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager. RouterInfoMediaManager( Context context, @NonNull String packageName, @NonNull UserHandle userHandle, LocalBluetoothManager localBluetoothManager, @Nullable MediaController mediaController)71 /* package */ RouterInfoMediaManager( 72 Context context, 73 @NonNull String packageName, 74 @NonNull UserHandle userHandle, 75 LocalBluetoothManager localBluetoothManager, 76 @Nullable MediaController mediaController) 77 throws PackageNotAvailableException { 78 super(context, packageName, userHandle, localBluetoothManager, mediaController); 79 80 MediaRouter2 router = null; 81 82 if (Flags.enableCrossUserRoutingInMediaRouter2()) { 83 try { 84 router = MediaRouter2.getInstance(context, packageName, userHandle); 85 } catch (IllegalArgumentException ex) { 86 // Do nothing 87 } 88 } else { 89 router = MediaRouter2.getInstance(context, packageName); 90 } 91 if (router == null) { 92 throw new PackageNotAvailableException( 93 "Package name " + packageName + " does not exist."); 94 } 95 // We have to defer initialization because mRouter is final. 96 mRouter = router; 97 98 mRouterManager = MediaRouter2Manager.getInstance(context); 99 } 100 101 @Override startScanOnRouter()102 protected void startScanOnRouter() { 103 if (Flags.enableScreenOffScanning()) { 104 MediaRouter2.ScanRequest request = new MediaRouter2.ScanRequest.Builder().build(); 105 mScanToken.compareAndSet(null, mRouter.requestScan(request)); 106 } else { 107 mRouter.startScan(); 108 } 109 } 110 111 @Override registerRouter()112 protected void registerRouter() { 113 mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY); 114 mRouter.registerRouteListingPreferenceUpdatedCallback( 115 mExecutor, mRouteListingPreferenceCallback); 116 mRouter.registerTransferCallback(mExecutor, mTransferCallback); 117 mRouter.registerControllerCallback(mExecutor, mControllerCallback); 118 } 119 120 @Override stopScanOnRouter()121 protected void stopScanOnRouter() { 122 if (Flags.enableScreenOffScanning()) { 123 MediaRouter2.ScanToken token = mScanToken.getAndSet(null); 124 if (token != null) { 125 mRouter.cancelScanRequest(token); 126 } 127 } else { 128 mRouter.stopScan(); 129 } 130 } 131 132 @Override unregisterRouter()133 protected void unregisterRouter() { 134 mRouter.unregisterControllerCallback(mControllerCallback); 135 mRouter.unregisterTransferCallback(mTransferCallback); 136 mRouter.unregisterRouteListingPreferenceUpdatedCallback(mRouteListingPreferenceCallback); 137 mRouter.unregisterRouteCallback(mRouteCallback); 138 } 139 140 @Override transferToRoute(@onNull MediaRoute2Info route)141 protected void transferToRoute(@NonNull MediaRoute2Info route) { 142 mRouter.transferTo(route); 143 } 144 145 @Override selectRoute(@onNull MediaRoute2Info route, @NonNull RoutingSessionInfo info)146 protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) { 147 RoutingController controller = getControllerForSession(info); 148 if (controller != null) { 149 controller.selectRoute(route); 150 } 151 } 152 153 @Override deselectRoute(@onNull MediaRoute2Info route, @NonNull RoutingSessionInfo info)154 protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) { 155 RoutingController controller = getControllerForSession(info); 156 if (controller != null) { 157 controller.deselectRoute(route); 158 } 159 } 160 161 @Override releaseSession(@onNull RoutingSessionInfo sessionInfo)162 protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) { 163 RoutingController controller = getControllerForSession(sessionInfo); 164 if (controller != null) { 165 controller.release(); 166 } 167 } 168 169 @NonNull 170 @Override getSelectableRoutes(@onNull RoutingSessionInfo info)171 protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) { 172 RoutingController controller = getControllerForSession(info); 173 if (controller == null) { 174 return Collections.emptyList(); 175 } 176 177 // Filter out selected routes. 178 List<String> selectedRouteIds = controller.getRoutingSessionInfo().getSelectedRoutes(); 179 return controller.getSelectableRoutes().stream() 180 .filter(route -> !selectedRouteIds.contains(route.getId())) 181 .collect(Collectors.toList()); 182 } 183 184 @NonNull 185 @Override getDeselectableRoutes(@onNull RoutingSessionInfo info)186 protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) { 187 RoutingController controller = getControllerForSession(info); 188 if (controller == null) { 189 return Collections.emptyList(); 190 } 191 192 return controller.getDeselectableRoutes(); 193 } 194 195 @NonNull 196 @Override getSelectedRoutes(@onNull RoutingSessionInfo info)197 protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) { 198 RoutingController controller = getControllerForSession(info); 199 if (controller == null) { 200 return Collections.emptyList(); 201 } 202 return controller.getSelectedRoutes(); 203 } 204 205 @Override setSessionVolume(@onNull RoutingSessionInfo info, int volume)206 protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) { 207 // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as 208 // MR2 filters information by package name. 209 mRouterManager.setSessionVolume(info, volume); 210 } 211 212 @Override setRouteVolume(@onNull MediaRoute2Info route, int volume)213 protected void setRouteVolume(@NonNull MediaRoute2Info route, int volume) { 214 mRouter.setRouteVolume(route, volume); 215 } 216 217 @Nullable 218 @Override getRouteListingPreference()219 protected RouteListingPreference getRouteListingPreference() { 220 return mRouter.getRouteListingPreference(); 221 } 222 223 @NonNull 224 @Override getRemoteSessions()225 protected List<RoutingSessionInfo> getRemoteSessions() { 226 // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as 227 // MR2 filters information by package name. 228 return mRouterManager.getRemoteSessions(); 229 } 230 231 @NonNull 232 @Override getRoutingSessionsForPackage()233 protected List<RoutingSessionInfo> getRoutingSessionsForPackage() { 234 return mRouter.getControllers().stream() 235 .map(RoutingController::getRoutingSessionInfo) 236 .collect(Collectors.toList()); 237 } 238 239 @Nullable 240 @Override getRoutingSessionById(@onNull String sessionId)241 protected RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId) { 242 // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager calls as 243 // MR2 filters information by package name. 244 245 for (RoutingSessionInfo sessionInfo : getRemoteSessions()) { 246 if (TextUtils.equals(sessionInfo.getId(), sessionId)) { 247 return sessionInfo; 248 } 249 } 250 251 RoutingSessionInfo systemSession = mRouterManager.getSystemRoutingSession(null); 252 return TextUtils.equals(systemSession.getId(), sessionId) ? systemSession : null; 253 } 254 255 @NonNull 256 @Override getAvailableRoutesFromRouter()257 protected List<MediaRoute2Info> getAvailableRoutesFromRouter() { 258 return mRouter.getRoutes(); 259 } 260 261 @NonNull 262 @Override getTransferableRoutes(@onNull String packageName)263 protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) { 264 List<RoutingController> controllers = mRouter.getControllers(); 265 RoutingController activeController = controllers.get(controllers.size() - 1); 266 HashMap<String, MediaRoute2Info> transferableRoutes = new HashMap<>(); 267 268 activeController 269 .getTransferableRoutes() 270 .forEach(route -> transferableRoutes.put(route.getId(), route)); 271 272 if (activeController.getRoutingSessionInfo().isSystemSession()) { 273 mRouter.getRoutes().stream() 274 .filter(route -> !route.isSystemRoute()) 275 .forEach(route -> transferableRoutes.put(route.getId(), route)); 276 } else { 277 mRouter.getRoutes().stream() 278 .filter(route -> route.isSystemRoute()) 279 .forEach(route -> transferableRoutes.put(route.getId(), route)); 280 } 281 282 return new ArrayList<>(transferableRoutes.values()); 283 } 284 285 @Nullable getControllerForSession(@onNull RoutingSessionInfo sessionInfo)286 private RoutingController getControllerForSession(@NonNull RoutingSessionInfo sessionInfo) { 287 return mRouter.getController(sessionInfo.getId()); 288 } 289 290 private final class RouteCallback extends MediaRouter2.RouteCallback { 291 @Override onRoutesUpdated(@onNull List<MediaRoute2Info> routes)292 public void onRoutesUpdated(@NonNull List<MediaRoute2Info> routes) { 293 refreshDevices(); 294 } 295 296 @Override onPreferredFeaturesChanged(@onNull List<String> preferredFeatures)297 public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) { 298 refreshDevices(); 299 } 300 } 301 302 private final class TransferCallback extends MediaRouter2.TransferCallback { 303 @Override onTransfer( @onNull RoutingController oldController, @NonNull RoutingController newController)304 public void onTransfer( 305 @NonNull RoutingController oldController, 306 @NonNull RoutingController newController) { 307 rebuildDeviceList(); 308 notifyCurrentConnectedDeviceChanged(); 309 } 310 311 @Override onTransferFailure(@onNull MediaRoute2Info requestedRoute)312 public void onTransferFailure(@NonNull MediaRoute2Info requestedRoute) { 313 // Do nothing. 314 } 315 316 @Override onStop(@onNull RoutingController controller)317 public void onStop(@NonNull RoutingController controller) { 318 refreshDevices(); 319 } 320 321 @Override onRequestFailed(int reason)322 public void onRequestFailed(int reason) { 323 dispatchOnRequestFailed(reason); 324 } 325 } 326 327 private final class ControllerCallback extends MediaRouter2.ControllerCallback { 328 @Override onControllerUpdated(@onNull RoutingController controller)329 public void onControllerUpdated(@NonNull RoutingController controller) { 330 refreshDevices(); 331 } 332 } 333 } 334