• 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 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