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