• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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