1 /* 2 * Copyright 2019 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.server.media; 18 19 import android.annotation.NonNull; 20 import android.content.BroadcastReceiver; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.media.AudioAttributes; 26 import android.media.AudioDeviceAttributes; 27 import android.media.AudioManager; 28 import android.media.MediaRoute2Info; 29 import android.media.MediaRoute2ProviderInfo; 30 import android.media.MediaRoute2ProviderService; 31 import android.media.RouteDiscoveryPreference; 32 import android.media.RoutingSessionInfo; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.UserHandle; 37 import android.text.TextUtils; 38 import android.util.Log; 39 import android.util.Slog; 40 41 import com.android.internal.annotations.GuardedBy; 42 43 import java.util.List; 44 import java.util.Objects; 45 import java.util.Set; 46 47 /** 48 * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers. 49 */ 50 // TODO: check thread safety. We may need to use lock to protect variables. 51 class SystemMediaRoute2Provider extends MediaRoute2Provider { 52 private static final String TAG = "MR2SystemProvider"; 53 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 54 55 private static final ComponentName COMPONENT_NAME = new ComponentName( 56 SystemMediaRoute2Provider.class.getPackage().getName(), 57 SystemMediaRoute2Provider.class.getName()); 58 59 static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION"; 60 61 private final AudioManager mAudioManager; 62 private final Handler mHandler; 63 private final Context mContext; 64 private final UserHandle mUser; 65 66 private final DeviceRouteController mDeviceRouteController; 67 private final BluetoothRouteController mBluetoothRouteController; 68 69 private String mSelectedRouteId; 70 // For apps without MODIFYING_AUDIO_ROUTING permission. 71 // This should be the currently selected route. 72 MediaRoute2Info mDefaultRoute; 73 RoutingSessionInfo mDefaultSessionInfo; 74 75 private final AudioManagerBroadcastReceiver mAudioReceiver = 76 new AudioManagerBroadcastReceiver(); 77 78 private final AudioManager.OnDevicesForAttributesChangedListener 79 mOnDevicesForAttributesChangedListener = 80 new AudioManager.OnDevicesForAttributesChangedListener() { 81 @Override 82 public void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes, 83 @NonNull List<AudioDeviceAttributes> devices) { 84 if (attributes.getUsage() != AudioAttributes.USAGE_MEDIA) { 85 return; 86 } 87 88 mHandler.post(() -> { 89 updateSelectedAudioDevice(devices); 90 notifyProviderState(); 91 if (updateSessionInfosIfNeeded()) { 92 notifySessionInfoUpdated(); 93 } 94 }); 95 } 96 }; 97 98 private final Object mRequestLock = new Object(); 99 @GuardedBy("mRequestLock") 100 private volatile SessionCreationRequest mPendingSessionCreationRequest; 101 SystemMediaRoute2Provider(Context context, UserHandle user)102 SystemMediaRoute2Provider(Context context, UserHandle user) { 103 super(COMPONENT_NAME); 104 mIsSystemRouteProvider = true; 105 mContext = context; 106 mUser = user; 107 mHandler = new Handler(Looper.getMainLooper()); 108 109 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 110 111 mBluetoothRouteController = BluetoothRouteController.createInstance(context, (routes) -> { 112 publishProviderState(); 113 if (updateSessionInfosIfNeeded()) { 114 notifySessionInfoUpdated(); 115 } 116 }); 117 118 mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> { 119 mHandler.post(() -> { 120 publishProviderState(); 121 if (updateSessionInfosIfNeeded()) { 122 notifySessionInfoUpdated(); 123 } 124 }); 125 }); 126 127 mAudioManager.addOnDevicesForAttributesChangedListener( 128 AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(), 129 mOnDevicesForAttributesChangedListener); 130 131 // These methods below should be called after all fields are initialized, as they 132 // access the fields inside. 133 List<AudioDeviceAttributes> devices = 134 mAudioManager.getDevicesForAttributes(AudioAttributesUtils.ATTRIBUTES_MEDIA); 135 updateSelectedAudioDevice(devices); 136 updateProviderState(); 137 updateSessionInfosIfNeeded(); 138 } 139 start()140 public void start() { 141 IntentFilter intentFilter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); 142 intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); 143 mContext.registerReceiverAsUser(mAudioReceiver, mUser, 144 intentFilter, null, null); 145 146 mHandler.post(() -> { 147 mBluetoothRouteController.start(mUser); 148 notifyProviderState(); 149 }); 150 updateVolume(); 151 } 152 stop()153 public void stop() { 154 mContext.unregisterReceiver(mAudioReceiver); 155 mHandler.post(() -> { 156 mBluetoothRouteController.stop(); 157 notifyProviderState(); 158 }); 159 } 160 161 @Override setCallback(Callback callback)162 public void setCallback(Callback callback) { 163 super.setCallback(callback); 164 notifyProviderState(); 165 notifySessionInfoUpdated(); 166 } 167 168 @Override requestCreateSession(long requestId, String packageName, String routeId, Bundle sessionHints)169 public void requestCreateSession(long requestId, String packageName, String routeId, 170 Bundle sessionHints) { 171 // Assume a router without MODIFY_AUDIO_ROUTING permission can't request with 172 // a route ID different from the default route ID. The service should've filtered. 173 if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { 174 mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo); 175 return; 176 } 177 if (TextUtils.equals(routeId, mSelectedRouteId)) { 178 mCallback.onSessionCreated(this, requestId, mSessionInfos.get(0)); 179 return; 180 } 181 182 synchronized (mRequestLock) { 183 // Handle the previous request as a failure if exists. 184 if (mPendingSessionCreationRequest != null) { 185 mCallback.onRequestFailed(this, mPendingSessionCreationRequest.mRequestId, 186 MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); 187 } 188 mPendingSessionCreationRequest = new SessionCreationRequest(requestId, routeId); 189 } 190 191 transferToRoute(requestId, SYSTEM_SESSION_ID, routeId); 192 } 193 194 @Override releaseSession(long requestId, String sessionId)195 public void releaseSession(long requestId, String sessionId) { 196 // Do nothing 197 } 198 199 @Override updateDiscoveryPreference( Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference)200 public void updateDiscoveryPreference( 201 Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) { 202 // Do nothing 203 } 204 205 @Override selectRoute(long requestId, String sessionId, String routeId)206 public void selectRoute(long requestId, String sessionId, String routeId) { 207 // Do nothing since we don't support multiple BT yet. 208 } 209 210 @Override deselectRoute(long requestId, String sessionId, String routeId)211 public void deselectRoute(long requestId, String sessionId, String routeId) { 212 // Do nothing since we don't support multiple BT yet. 213 } 214 215 @Override transferToRoute(long requestId, String sessionId, String routeId)216 public void transferToRoute(long requestId, String sessionId, String routeId) { 217 if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { 218 // The currently selected route is the default route. 219 return; 220 } 221 222 MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); 223 if (TextUtils.equals(routeId, deviceRoute.getId())) { 224 mBluetoothRouteController.transferTo(null); 225 } else { 226 mBluetoothRouteController.transferTo(routeId); 227 } 228 } 229 230 @Override setRouteVolume(long requestId, String routeId, int volume)231 public void setRouteVolume(long requestId, String routeId, int volume) { 232 if (!TextUtils.equals(routeId, mSelectedRouteId)) { 233 return; 234 } 235 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); 236 } 237 238 @Override setSessionVolume(long requestId, String sessionId, int volume)239 public void setSessionVolume(long requestId, String sessionId, int volume) { 240 // Do nothing since we don't support grouping volume yet. 241 } 242 243 @Override prepareReleaseSession(String sessionId)244 public void prepareReleaseSession(String sessionId) { 245 // Do nothing since the system session persists. 246 } 247 getDefaultRoute()248 public MediaRoute2Info getDefaultRoute() { 249 return mDefaultRoute; 250 } 251 getDefaultSessionInfo()252 public RoutingSessionInfo getDefaultSessionInfo() { 253 return mDefaultSessionInfo; 254 } 255 generateDeviceRouteSelectedSessionInfo(String packageName)256 public RoutingSessionInfo generateDeviceRouteSelectedSessionInfo(String packageName) { 257 synchronized (mLock) { 258 if (mSessionInfos.isEmpty()) { 259 return null; 260 } 261 262 MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); 263 264 RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( 265 SYSTEM_SESSION_ID, packageName).setSystemSession(true); 266 builder.addSelectedRoute(deviceRoute.getId()); 267 for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) { 268 builder.addTransferableRoute(route.getId()); 269 } 270 return builder.setProviderId(mUniqueId).build(); 271 } 272 } 273 updateSelectedAudioDevice(@onNull List<AudioDeviceAttributes> devices)274 private void updateSelectedAudioDevice(@NonNull List<AudioDeviceAttributes> devices) { 275 if (devices.isEmpty()) { 276 Slog.w(TAG, "The list of preferred devices was empty."); 277 return; 278 } 279 280 AudioDeviceAttributes audioDeviceAttributes = devices.get(0); 281 282 if (AudioAttributesUtils.isDeviceOutputAttributes(audioDeviceAttributes)) { 283 mDeviceRouteController.selectRoute( 284 AudioAttributesUtils.mapToMediaRouteType(audioDeviceAttributes)); 285 mBluetoothRouteController.selectRoute(null); 286 } else if (AudioAttributesUtils.isBluetoothOutputAttributes(audioDeviceAttributes)) { 287 mDeviceRouteController.selectRoute(null); 288 mBluetoothRouteController.selectRoute(audioDeviceAttributes.getAddress()); 289 } else { 290 Slog.w(TAG, "Unknown audio attributes: " + audioDeviceAttributes); 291 } 292 } 293 updateProviderState()294 private void updateProviderState() { 295 MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); 296 297 // We must have a device route in the provider info. 298 builder.addRoute(mDeviceRouteController.getDeviceRoute()); 299 300 for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) { 301 builder.addRoute(route); 302 } 303 MediaRoute2ProviderInfo providerInfo = builder.build(); 304 setProviderState(providerInfo); 305 if (DEBUG) { 306 Slog.d(TAG, "Updating system provider info : " + providerInfo); 307 } 308 } 309 310 /** 311 * Updates the mSessionInfo. Returns true if the session info is changed. 312 */ updateSessionInfosIfNeeded()313 boolean updateSessionInfosIfNeeded() { 314 synchronized (mLock) { 315 RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get( 316 0); 317 318 RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( 319 SYSTEM_SESSION_ID, "" /* clientPackageName */) 320 .setSystemSession(true); 321 322 MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); 323 MediaRoute2Info selectedRoute = deviceRoute; 324 MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute(); 325 if (selectedBtRoute != null) { 326 selectedRoute = selectedBtRoute; 327 builder.addTransferableRoute(deviceRoute.getId()); 328 } 329 mSelectedRouteId = selectedRoute.getId(); 330 mDefaultRoute = 331 new MediaRoute2Info.Builder(MediaRoute2Info.ROUTE_ID_DEFAULT, selectedRoute) 332 .setSystemRoute(true) 333 .setProviderId(mUniqueId) 334 .build(); 335 builder.addSelectedRoute(mSelectedRouteId); 336 337 for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) { 338 builder.addTransferableRoute(route.getId()); 339 } 340 341 RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build(); 342 343 if (mPendingSessionCreationRequest != null) { 344 SessionCreationRequest sessionCreationRequest; 345 synchronized (mRequestLock) { 346 sessionCreationRequest = mPendingSessionCreationRequest; 347 mPendingSessionCreationRequest = null; 348 } 349 if (sessionCreationRequest != null) { 350 if (TextUtils.equals(mSelectedRouteId, sessionCreationRequest.mRouteId)) { 351 mCallback.onSessionCreated(this, 352 sessionCreationRequest.mRequestId, newSessionInfo); 353 } else { 354 mCallback.onRequestFailed(this, sessionCreationRequest.mRequestId, 355 MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); 356 } 357 } 358 } 359 360 if (Objects.equals(oldSessionInfo, newSessionInfo)) { 361 return false; 362 } else { 363 if (DEBUG) { 364 Slog.d(TAG, "Updating system routing session info : " + newSessionInfo); 365 } 366 mSessionInfos.clear(); 367 mSessionInfos.add(newSessionInfo); 368 mDefaultSessionInfo = 369 new RoutingSessionInfo.Builder( 370 SYSTEM_SESSION_ID, "" /* clientPackageName */) 371 .setProviderId(mUniqueId) 372 .setSystemSession(true) 373 .addSelectedRoute(MediaRoute2Info.ROUTE_ID_DEFAULT) 374 .build(); 375 return true; 376 } 377 } 378 } 379 publishProviderState()380 void publishProviderState() { 381 updateProviderState(); 382 notifyProviderState(); 383 } 384 notifySessionInfoUpdated()385 void notifySessionInfoUpdated() { 386 if (mCallback == null) { 387 return; 388 } 389 390 RoutingSessionInfo sessionInfo; 391 synchronized (mLock) { 392 sessionInfo = mSessionInfos.get(0); 393 } 394 395 mCallback.onSessionUpdated(this, sessionInfo); 396 } 397 398 @Override getDebugString()399 protected String getDebugString() { 400 return TextUtils.formatSimple( 401 "SystemMR2Provider - package: %s, selected route id: %s, bluetooth impl: %s", 402 mComponentName.getPackageName(), 403 mSelectedRouteId, 404 mBluetoothRouteController.getClass().getSimpleName()); 405 } 406 407 private static class SessionCreationRequest { 408 final long mRequestId; 409 final String mRouteId; 410 SessionCreationRequest(long requestId, String routeId)411 SessionCreationRequest(long requestId, String routeId) { 412 this.mRequestId = requestId; 413 this.mRouteId = routeId; 414 } 415 } 416 updateVolume()417 void updateVolume() { 418 int devices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 419 int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 420 421 if (mDefaultRoute.getVolume() != volume) { 422 mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute) 423 .setVolume(volume) 424 .build(); 425 } 426 427 if (mBluetoothRouteController.updateVolumeForDevices(devices, volume)) { 428 return; 429 } 430 431 mDeviceRouteController.updateVolume(volume); 432 433 publishProviderState(); 434 } 435 436 private class AudioManagerBroadcastReceiver extends BroadcastReceiver { 437 // This will be called in the main thread. 438 @Override onReceive(Context context, Intent intent)439 public void onReceive(Context context, Intent intent) { 440 if (!intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION) 441 && !intent.getAction().equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) { 442 return; 443 } 444 445 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 446 if (streamType != AudioManager.STREAM_MUSIC) { 447 return; 448 } 449 450 updateVolume(); 451 } 452 } 453 } 454