• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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 package com.android.settingslib.media;
17 
18 import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
19 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
20 import static android.media.MediaRoute2Info.TYPE_DOCK;
21 import static android.media.MediaRoute2Info.TYPE_GROUP;
22 import static android.media.MediaRoute2Info.TYPE_HDMI;
23 import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
24 import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
25 import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
26 import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
27 import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
28 import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
29 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
30 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
31 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
32 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
33 
34 import android.app.Notification;
35 import android.bluetooth.BluetoothAdapter;
36 import android.bluetooth.BluetoothDevice;
37 import android.content.Context;
38 import android.media.MediaRoute2Info;
39 import android.media.MediaRouter2Manager;
40 import android.media.RoutingSessionInfo;
41 import android.text.TextUtils;
42 import android.util.Log;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
46 import com.android.settingslib.bluetooth.LocalBluetoothManager;
47 
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.concurrent.Executor;
51 import java.util.concurrent.Executors;
52 
53 /**
54  * InfoMediaManager provide interface to get InfoMediaDevice list.
55  */
56 public class InfoMediaManager extends MediaManager {
57 
58     private static final String TAG = "InfoMediaManager";
59     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
60     @VisibleForTesting
61     final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
62     @VisibleForTesting
63     final Executor mExecutor = Executors.newSingleThreadExecutor();
64     @VisibleForTesting
65     MediaRouter2Manager mRouterManager;
66     @VisibleForTesting
67     String mPackageName;
68 
69     private MediaDevice mCurrentConnectedDevice;
70     private LocalBluetoothManager mBluetoothManager;
71 
InfoMediaManager(Context context, String packageName, Notification notification, LocalBluetoothManager localBluetoothManager)72     public InfoMediaManager(Context context, String packageName, Notification notification,
73             LocalBluetoothManager localBluetoothManager) {
74         super(context, notification);
75 
76         mRouterManager = MediaRouter2Manager.getInstance(context);
77         mBluetoothManager = localBluetoothManager;
78         if (!TextUtils.isEmpty(packageName)) {
79             mPackageName = packageName;
80         }
81     }
82 
83     @Override
startScan()84     public void startScan() {
85         mMediaDevices.clear();
86         mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
87         refreshDevices();
88     }
89 
90     @Override
stopScan()91     public void stopScan() {
92         mRouterManager.unregisterCallback(mMediaRouterCallback);
93     }
94 
95     /**
96      * Get current device that played media.
97      * @return MediaDevice
98      */
getCurrentConnectedDevice()99     MediaDevice getCurrentConnectedDevice() {
100         return mCurrentConnectedDevice;
101     }
102 
103     /**
104      * Transfer MediaDevice for media without package name.
105      */
connectDeviceWithoutPackageName(MediaDevice device)106     boolean connectDeviceWithoutPackageName(MediaDevice device) {
107         boolean isConnected = false;
108         final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions();
109         if (infos.size() > 0) {
110             final RoutingSessionInfo info = infos.get(0);
111             mRouterManager.transfer(info, device.mRouteInfo);
112 
113             isConnected = true;
114         }
115         return isConnected;
116     }
117 
118     /**
119      * Add a MediaDevice to let it play current media.
120      *
121      * @param device MediaDevice
122      * @return If add device successful return {@code true}, otherwise return {@code false}
123      */
addDeviceToPlayMedia(MediaDevice device)124     boolean addDeviceToPlayMedia(MediaDevice device) {
125         if (TextUtils.isEmpty(mPackageName)) {
126             Log.w(TAG, "addDeviceToPlayMedia() package name is null or empty!");
127             return false;
128         }
129 
130         final RoutingSessionInfo info = getRoutingSessionInfo();
131         if (info != null && info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
132             mRouterManager.selectRoute(info, device.mRouteInfo);
133             return true;
134         }
135 
136         Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
137                 + device.getName());
138 
139         return false;
140     }
141 
getRoutingSessionInfo()142     private RoutingSessionInfo getRoutingSessionInfo() {
143         final List<RoutingSessionInfo> sessionInfos =
144                 mRouterManager.getRoutingSessions(mPackageName);
145 
146         return sessionInfos.get(sessionInfos.size() - 1);
147     }
148 
149     /**
150      * Remove a {@code device} from current media.
151      *
152      * @param device MediaDevice
153      * @return If device stop successful return {@code true}, otherwise return {@code false}
154      */
removeDeviceFromPlayMedia(MediaDevice device)155     boolean removeDeviceFromPlayMedia(MediaDevice device) {
156         if (TextUtils.isEmpty(mPackageName)) {
157             Log.w(TAG, "removeDeviceFromMedia() package name is null or empty!");
158             return false;
159         }
160 
161         final RoutingSessionInfo info = getRoutingSessionInfo();
162         if (info != null && info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
163             mRouterManager.deselectRoute(info, device.mRouteInfo);
164             return true;
165         }
166 
167         Log.w(TAG, "removeDeviceFromMedia() Ignoring deselecting a non-deselectable device : "
168                 + device.getName());
169 
170         return false;
171     }
172 
173     /**
174      * Release session to stop playing media on MediaDevice.
175      */
releaseSession()176     boolean releaseSession() {
177         if (TextUtils.isEmpty(mPackageName)) {
178             Log.w(TAG, "releaseSession() package name is null or empty!");
179             return false;
180         }
181 
182         final RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
183 
184         if (sessionInfo != null) {
185             mRouterManager.releaseSession(sessionInfo);
186             return true;
187         }
188 
189         Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
190 
191         return false;
192     }
193 
194     /**
195      * Get the MediaDevice list that can be added to current media.
196      *
197      * @return list of MediaDevice
198      */
getSelectableMediaDevice()199     List<MediaDevice> getSelectableMediaDevice() {
200         final List<MediaDevice> deviceList = new ArrayList<>();
201         if (TextUtils.isEmpty(mPackageName)) {
202             Log.w(TAG, "getSelectableMediaDevice() package name is null or empty!");
203             return deviceList;
204         }
205 
206         final RoutingSessionInfo info = getRoutingSessionInfo();
207         if (info != null) {
208             for (MediaRoute2Info route : mRouterManager.getSelectableRoutes(info)) {
209                 deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
210                         route, mPackageName));
211             }
212             return deviceList;
213         }
214 
215         Log.w(TAG, "getSelectableMediaDevice() cannot found selectable MediaDevice from : "
216                 + mPackageName);
217 
218         return deviceList;
219     }
220 
221     /**
222      * Get the MediaDevice list that can be removed from current media session.
223      *
224      * @return list of MediaDevice
225      */
getDeselectableMediaDevice()226     List<MediaDevice> getDeselectableMediaDevice() {
227         final List<MediaDevice> deviceList = new ArrayList<>();
228         if (TextUtils.isEmpty(mPackageName)) {
229             Log.d(TAG, "getDeselectableMediaDevice() package name is null or empty!");
230             return deviceList;
231         }
232 
233         final RoutingSessionInfo info = getRoutingSessionInfo();
234         if (info != null) {
235             for (MediaRoute2Info route : mRouterManager.getDeselectableRoutes(info)) {
236                 deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
237                         route, mPackageName));
238                 Log.d(TAG, route.getName() + " is deselectable for " + mPackageName);
239             }
240             return deviceList;
241         }
242         Log.d(TAG, "getDeselectableMediaDevice() cannot found deselectable MediaDevice from : "
243                 + mPackageName);
244 
245         return deviceList;
246     }
247 
248     /**
249      * Get the MediaDevice list that has been selected to current media.
250      *
251      * @return list of MediaDevice
252      */
getSelectedMediaDevice()253     List<MediaDevice> getSelectedMediaDevice() {
254         final List<MediaDevice> deviceList = new ArrayList<>();
255         if (TextUtils.isEmpty(mPackageName)) {
256             Log.w(TAG, "getSelectedMediaDevice() package name is null or empty!");
257             return deviceList;
258         }
259 
260         final RoutingSessionInfo info = getRoutingSessionInfo();
261         if (info != null) {
262             for (MediaRoute2Info route : mRouterManager.getSelectedRoutes(info)) {
263                 deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
264                         route, mPackageName));
265             }
266             return deviceList;
267         }
268 
269         Log.w(TAG, "getSelectedMediaDevice() cannot found selectable MediaDevice from : "
270                 + mPackageName);
271 
272         return deviceList;
273     }
274 
adjustSessionVolume(RoutingSessionInfo info, int volume)275     void adjustSessionVolume(RoutingSessionInfo info, int volume) {
276         if (info == null) {
277             Log.w(TAG, "Unable to adjust session volume. RoutingSessionInfo is empty");
278             return;
279         }
280 
281         mRouterManager.setSessionVolume(info, volume);
282     }
283 
284     /**
285      * Adjust the volume of {@link android.media.RoutingSessionInfo}.
286      *
287      * @param volume the value of volume
288      */
adjustSessionVolume(int volume)289     void adjustSessionVolume(int volume) {
290         if (TextUtils.isEmpty(mPackageName)) {
291             Log.w(TAG, "adjustSessionVolume() package name is null or empty!");
292             return;
293         }
294 
295         final RoutingSessionInfo info = getRoutingSessionInfo();
296         if (info != null) {
297             Log.d(TAG, "adjustSessionVolume() adjust volume : " + volume + ", with : "
298                     + mPackageName);
299             mRouterManager.setSessionVolume(info, volume);
300             return;
301         }
302 
303         Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
304                 + mPackageName);
305     }
306 
307     /**
308      * Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
309      *
310      * @return  maximum volume of the session, and return -1 if not found.
311      */
getSessionVolumeMax()312     public int getSessionVolumeMax() {
313         if (TextUtils.isEmpty(mPackageName)) {
314             Log.w(TAG, "getSessionVolumeMax() package name is null or empty!");
315             return -1;
316         }
317 
318         final RoutingSessionInfo info = getRoutingSessionInfo();
319         if (info != null) {
320             return info.getVolumeMax();
321         }
322 
323         Log.w(TAG, "getSessionVolumeMax() can't found corresponding RoutingSession with : "
324                 + mPackageName);
325         return -1;
326     }
327 
328     /**
329      * Gets the current volume of the {@link android.media.RoutingSessionInfo}.
330      *
331      * @return current volume of the session, and return -1 if not found.
332      */
getSessionVolume()333     public int getSessionVolume() {
334         if (TextUtils.isEmpty(mPackageName)) {
335             Log.w(TAG, "getSessionVolume() package name is null or empty!");
336             return -1;
337         }
338 
339         final RoutingSessionInfo info = getRoutingSessionInfo();
340         if (info != null) {
341             return info.getVolume();
342         }
343 
344         Log.w(TAG, "getSessionVolume() can't found corresponding RoutingSession with : "
345                 + mPackageName);
346         return -1;
347     }
348 
getSessionName()349     CharSequence getSessionName() {
350         if (TextUtils.isEmpty(mPackageName)) {
351             Log.w(TAG, "Unable to get session name. The package name is null or empty!");
352             return null;
353         }
354 
355         final RoutingSessionInfo info = getRoutingSessionInfo();
356         if (info != null) {
357             return info.getName();
358         }
359 
360         Log.w(TAG, "Unable to get session name for package: " + mPackageName);
361         return null;
362     }
363 
refreshDevices()364     private void refreshDevices() {
365         mMediaDevices.clear();
366         mCurrentConnectedDevice = null;
367         if (TextUtils.isEmpty(mPackageName)) {
368             buildAllRoutes();
369         } else {
370             buildAvailableRoutes();
371         }
372         dispatchDeviceListAdded();
373     }
374 
buildAllRoutes()375     private void buildAllRoutes() {
376         for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
377             if (DEBUG) {
378                 Log.d(TAG, "buildAllRoutes() route : " + route.getName() + ", volume : "
379                         + route.getVolume() + ", type : " + route.getType());
380             }
381             if (route.isSystemRoute()) {
382                 addMediaDevice(route);
383             }
384         }
385     }
386 
getActiveMediaSession()387     List<RoutingSessionInfo> getActiveMediaSession() {
388         return mRouterManager.getActiveSessions();
389     }
390 
buildAvailableRoutes()391     private void buildAvailableRoutes() {
392         for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) {
393             if (DEBUG) {
394                 Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : "
395                         + route.getVolume() + ", type : " + route.getType());
396             }
397             addMediaDevice(route);
398         }
399     }
400 
401     @VisibleForTesting
addMediaDevice(MediaRoute2Info route)402     void addMediaDevice(MediaRoute2Info route) {
403         final int deviceType = route.getType();
404         MediaDevice mediaDevice = null;
405         switch (deviceType) {
406             case TYPE_UNKNOWN:
407             case TYPE_REMOTE_TV:
408             case TYPE_REMOTE_SPEAKER:
409             case TYPE_GROUP:
410                 //TODO(b/148765806): use correct device type once api is ready.
411                 mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
412                         mPackageName);
413                 if (!TextUtils.isEmpty(mPackageName)
414                         && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())
415                         && mCurrentConnectedDevice == null) {
416                     mCurrentConnectedDevice = mediaDevice;
417                 }
418                 break;
419             case TYPE_BUILTIN_SPEAKER:
420             case TYPE_USB_DEVICE:
421             case TYPE_USB_HEADSET:
422             case TYPE_USB_ACCESSORY:
423             case TYPE_DOCK:
424             case TYPE_HDMI:
425             case TYPE_WIRED_HEADSET:
426             case TYPE_WIRED_HEADPHONES:
427                 mediaDevice =
428                         new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
429                 break;
430             case TYPE_HEARING_AID:
431             case TYPE_BLUETOOTH_A2DP:
432                 final BluetoothDevice device =
433                         BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress());
434                 final CachedBluetoothDevice cachedDevice =
435                         mBluetoothManager.getCachedDeviceManager().findDevice(device);
436                 if (cachedDevice != null) {
437                     mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
438                             route, mPackageName);
439                 }
440                 break;
441             default:
442                 Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
443                 break;
444 
445         }
446 
447         if (mediaDevice != null) {
448             mMediaDevices.add(mediaDevice);
449         }
450     }
451 
452     class RouterManagerCallback extends MediaRouter2Manager.Callback {
453 
454         @Override
onRoutesAdded(List<MediaRoute2Info> routes)455         public void onRoutesAdded(List<MediaRoute2Info> routes) {
456             refreshDevices();
457         }
458 
459         @Override
onPreferredFeaturesChanged(String packageName, List<String> preferredFeatures)460         public void onPreferredFeaturesChanged(String packageName, List<String> preferredFeatures) {
461             if (TextUtils.equals(mPackageName, packageName)) {
462                 refreshDevices();
463             }
464         }
465 
466         @Override
onRoutesChanged(List<MediaRoute2Info> routes)467         public void onRoutesChanged(List<MediaRoute2Info> routes) {
468             refreshDevices();
469         }
470 
471         @Override
onRoutesRemoved(List<MediaRoute2Info> routes)472         public void onRoutesRemoved(List<MediaRoute2Info> routes) {
473             refreshDevices();
474         }
475 
476         @Override
onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession)477         public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
478             if (DEBUG) {
479                 Log.d(TAG, "onTransferred() oldSession : " + oldSession.getName()
480                         + ", newSession : " + newSession.getName());
481             }
482             mMediaDevices.clear();
483             mCurrentConnectedDevice = null;
484             if (TextUtils.isEmpty(mPackageName)) {
485                 buildAllRoutes();
486             } else {
487                 buildAvailableRoutes();
488             }
489 
490             final String id = mCurrentConnectedDevice != null
491                     ? mCurrentConnectedDevice.getId()
492                     : null;
493             dispatchConnectedDeviceChanged(id);
494         }
495 
496         @Override
onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route)497         public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
498             dispatchOnRequestFailed(REASON_UNKNOWN_ERROR);
499         }
500 
501         @Override
onRequestFailed(int reason)502         public void onRequestFailed(int reason) {
503             dispatchOnRequestFailed(reason);
504         }
505 
506         @Override
onSessionUpdated(RoutingSessionInfo sessionInfo)507         public void onSessionUpdated(RoutingSessionInfo sessionInfo) {
508             dispatchDataChanged();
509         }
510     }
511 }
512