blob: 1d06df08bd3a627ca5185cef568ee1187dbf70df [file] [log] [blame]
hughchena73686f2018-11-12 17:42:08 +08001/*
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 */
16package com.android.settingslib.media;
17
hughchen62837412020-02-24 17:03:26 +080018import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
19import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
hughchene8c79b02020-04-21 17:26:21 +080020import static android.media.MediaRoute2Info.TYPE_DOCK;
hughchen62837412020-02-24 17:03:26 +080021import static android.media.MediaRoute2Info.TYPE_GROUP;
hughchene8c79b02020-04-21 17:26:21 +080022import static android.media.MediaRoute2Info.TYPE_HDMI;
hughchen62837412020-02-24 17:03:26 +080023import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
24import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
25import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
26import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
hughchene8c79b02020-04-21 17:26:21 +080027import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
28import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
29import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
hughchen62837412020-02-24 17:03:26 +080030import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
31import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
hughchen063a35a2020-03-04 17:51:57 +080032import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
hughchen491b9fc2020-02-05 18:31:36 +080033
hughchena73686f2018-11-12 17:42:08 +080034import android.app.Notification;
hughchen491b9fc2020-02-05 18:31:36 +080035import android.bluetooth.BluetoothAdapter;
36import android.bluetooth.BluetoothDevice;
hughchena73686f2018-11-12 17:42:08 +080037import android.content.Context;
hughchene6a4f482019-10-18 17:34:15 +080038import android.media.MediaRoute2Info;
39import android.media.MediaRouter2Manager;
hughchendf7b8382020-02-03 17:48:49 +080040import android.media.RoutingSessionInfo;
hughchene6a4f482019-10-18 17:34:15 +080041import android.text.TextUtils;
hughchen491b9fc2020-02-05 18:31:36 +080042import android.util.Log;
hughchena73686f2018-11-12 17:42:08 +080043
hughchenbd0dd492019-01-08 14:34:10 +080044import com.android.internal.annotations.VisibleForTesting;
hughchen491b9fc2020-02-05 18:31:36 +080045import com.android.settingslib.bluetooth.CachedBluetoothDevice;
46import com.android.settingslib.bluetooth.LocalBluetoothManager;
hughchenbd0dd492019-01-08 14:34:10 +080047
hughchen6ab62ca2020-02-12 10:40:04 +080048import java.util.ArrayList;
hughchene6a4f482019-10-18 17:34:15 +080049import java.util.List;
50import java.util.concurrent.Executor;
51import java.util.concurrent.Executors;
52
hughchena73686f2018-11-12 17:42:08 +080053/**
54 * InfoMediaManager provide interface to get InfoMediaDevice list.
55 */
56public class InfoMediaManager extends MediaManager {
57
58 private static final String TAG = "InfoMediaManager";
hughchen063a35a2020-03-04 17:51:57 +080059 private static final boolean DEBUG = false;
hughchenbd0dd492019-01-08 14:34:10 +080060 @VisibleForTesting
hughchene6a4f482019-10-18 17:34:15 +080061 final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
hughchenbd0dd492019-01-08 14:34:10 +080062 @VisibleForTesting
hughchene6a4f482019-10-18 17:34:15 +080063 final Executor mExecutor = Executors.newSingleThreadExecutor();
hughchenbd0dd492019-01-08 14:34:10 +080064 @VisibleForTesting
hughchene6a4f482019-10-18 17:34:15 +080065 MediaRouter2Manager mRouterManager;
hughchen0d202042020-01-17 18:30:03 +080066 @VisibleForTesting
67 String mPackageName;
hughchena73686f2018-11-12 17:42:08 +080068
hughchene6a4f482019-10-18 17:34:15 +080069 private MediaDevice mCurrentConnectedDevice;
hughchen491b9fc2020-02-05 18:31:36 +080070 private LocalBluetoothManager mBluetoothManager;
hughchena73686f2018-11-12 17:42:08 +080071
hughchen491b9fc2020-02-05 18:31:36 +080072 public InfoMediaManager(Context context, String packageName, Notification notification,
73 LocalBluetoothManager localBluetoothManager) {
hughchena73686f2018-11-12 17:42:08 +080074 super(context, notification);
75
hughchene6a4f482019-10-18 17:34:15 +080076 mRouterManager = MediaRouter2Manager.getInstance(context);
hughchen491b9fc2020-02-05 18:31:36 +080077 mBluetoothManager = localBluetoothManager;
hughchen0d202042020-01-17 18:30:03 +080078 if (!TextUtils.isEmpty(packageName)) {
hughchene6a4f482019-10-18 17:34:15 +080079 mPackageName = packageName;
80 }
hughchena73686f2018-11-12 17:42:08 +080081 }
82
83 @Override
84 public void startScan() {
85 mMediaDevices.clear();
hughchene6a4f482019-10-18 17:34:15 +080086 mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
hughchen0d202042020-01-17 18:30:03 +080087 refreshDevices();
hughchena73686f2018-11-12 17:42:08 +080088 }
89
hughchena73686f2018-11-12 17:42:08 +080090 @Override
91 public void stopScan() {
hughchene6a4f482019-10-18 17:34:15 +080092 mRouterManager.unregisterCallback(mMediaRouterCallback);
hughchena73686f2018-11-12 17:42:08 +080093 }
94
hughchene6a4f482019-10-18 17:34:15 +080095 /**
96 * Get current device that played media.
97 * @return MediaDevice
98 */
hughchen6ab62ca2020-02-12 10:40:04 +080099 MediaDevice getCurrentConnectedDevice() {
hughchene6a4f482019-10-18 17:34:15 +0800100 return mCurrentConnectedDevice;
101 }
102
hughchen6ab62ca2020-02-12 10:40:04 +0800103 /**
104 * Transfer MediaDevice for media without package name.
105 */
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);
Kyunglyul Hyunfa6f06d2020-04-30 02:56:40 +0900111 mRouterManager.transfer(info, device.mRouteInfo);
hughchen6ab62ca2020-02-12 10:40:04 +0800112
hughchen6ab62ca2020-02-12 10:40:04 +0800113 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 */
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())) {
Kyunglyul Hyunfa6f06d2020-04-30 02:56:40 +0900132 mRouterManager.selectRoute(info, device.mRouteInfo);
hughchen6ab62ca2020-02-12 10:40:04 +0800133 return true;
134 }
135
136 Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
137 + device.getName());
138
139 return false;
140 }
141
142 private RoutingSessionInfo getRoutingSessionInfo() {
hughchen8e9e4b42020-03-23 18:59:58 +0800143 final List<RoutingSessionInfo> sessionInfos =
144 mRouterManager.getRoutingSessions(mPackageName);
hughchen6ab62ca2020-02-12 10:40:04 +0800145
hughchen8e9e4b42020-03-23 18:59:58 +0800146 return sessionInfos.get(sessionInfos.size() - 1);
hughchen6ab62ca2020-02-12 10:40:04 +0800147 }
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 */
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())) {
Kyunglyul Hyunfa6f06d2020-04-30 02:56:40 +0900163 mRouterManager.deselectRoute(info, device.mRouteInfo);
hughchen6ab62ca2020-02-12 10:40:04 +0800164 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 /**
hughchen61bced02020-02-20 18:46:32 +0800174 * Release session to stop playing media on MediaDevice.
175 */
176 boolean releaseSession() {
177 if (TextUtils.isEmpty(mPackageName)) {
178 Log.w(TAG, "releaseSession() package name is null or empty!");
179 return false;
180 }
181
hughchen8e9e4b42020-03-23 18:59:58 +0800182 final RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
hughcheneed82d52020-03-20 14:54:10 +0800183
184 if (sessionInfo != null) {
185 mRouterManager.releaseSession(sessionInfo);
hughchen61bced02020-02-20 18:46:32 +0800186 return true;
187 }
188
189 Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
190
191 return false;
192 }
193
194 /**
hughchen6ab62ca2020-02-12 10:40:04 +0800195 * Get the MediaDevice list that can be added to current media.
196 *
197 * @return list of MediaDevice
198 */
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) {
Kyunglyul Hyunfa6f06d2020-04-30 02:56:40 +0900208 for (MediaRoute2Info route : mRouterManager.getSelectableRoutes(info)) {
hughchen6ab62ca2020-02-12 10:40:04 +0800209 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 /**
timhypeng6bbd5552020-02-17 14:53:45 +0800222 * Get the MediaDevice list that has been selected to current media.
223 *
224 * @return list of MediaDevice
225 */
226 List<MediaDevice> getSelectedMediaDevice() {
227 final List<MediaDevice> deviceList = new ArrayList<>();
228 if (TextUtils.isEmpty(mPackageName)) {
229 Log.w(TAG, "getSelectedMediaDevice() package name is null or empty!");
230 return deviceList;
231 }
232
233 final RoutingSessionInfo info = getRoutingSessionInfo();
234 if (info != null) {
Kyunglyul Hyunfa6f06d2020-04-30 02:56:40 +0900235 for (MediaRoute2Info route : mRouterManager.getSelectedRoutes(info)) {
timhypeng6bbd5552020-02-17 14:53:45 +0800236 deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
237 route, mPackageName));
238 }
239 return deviceList;
240 }
241
242 Log.w(TAG, "getSelectedMediaDevice() cannot found selectable MediaDevice from : "
243 + mPackageName);
244
245 return deviceList;
246 }
247
Tim Peng8a0f3612020-04-30 12:33:20 +0800248 void adjustSessionVolume(RoutingSessionInfo info, int volume) {
249 if (info == null) {
250 Log.w(TAG, "Unable to adjust session volume. RoutingSessionInfo is empty");
251 return;
252 }
253
254 mRouterManager.setSessionVolume(info, volume);
255 }
256
timhypeng6bbd5552020-02-17 14:53:45 +0800257 /**
hughchen6ab62ca2020-02-12 10:40:04 +0800258 * Adjust the volume of {@link android.media.RoutingSessionInfo}.
259 *
260 * @param volume the value of volume
261 */
262 void adjustSessionVolume(int volume) {
263 if (TextUtils.isEmpty(mPackageName)) {
264 Log.w(TAG, "adjustSessionVolume() package name is null or empty!");
265 return;
266 }
267
268 final RoutingSessionInfo info = getRoutingSessionInfo();
269 if (info != null) {
270 Log.d(TAG, "adjustSessionVolume() adjust volume : " + volume + ", with : "
271 + mPackageName);
272 mRouterManager.setSessionVolume(info, volume);
273 return;
274 }
275
276 Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
277 + mPackageName);
278 }
279
280 /**
281 * Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
282 *
283 * @return maximum volume of the session, and return -1 if not found.
284 */
285 public int getSessionVolumeMax() {
286 if (TextUtils.isEmpty(mPackageName)) {
287 Log.w(TAG, "getSessionVolumeMax() package name is null or empty!");
288 return -1;
289 }
290
291 final RoutingSessionInfo info = getRoutingSessionInfo();
292 if (info != null) {
293 return info.getVolumeMax();
294 }
295
296 Log.w(TAG, "getSessionVolumeMax() can't found corresponding RoutingSession with : "
297 + mPackageName);
298 return -1;
299 }
300
301 /**
302 * Gets the current volume of the {@link android.media.RoutingSessionInfo}.
303 *
304 * @return current volume of the session, and return -1 if not found.
305 */
306 public int getSessionVolume() {
307 if (TextUtils.isEmpty(mPackageName)) {
308 Log.w(TAG, "getSessionVolume() package name is null or empty!");
309 return -1;
310 }
311
312 final RoutingSessionInfo info = getRoutingSessionInfo();
313 if (info != null) {
314 return info.getVolume();
315 }
316
317 Log.w(TAG, "getSessionVolume() can't found corresponding RoutingSession with : "
318 + mPackageName);
319 return -1;
320 }
321
Tim Peng635b04a2020-03-05 13:08:02 +0800322 CharSequence getSessionName() {
323 if (TextUtils.isEmpty(mPackageName)) {
324 Log.w(TAG, "Unable to get session name. The package name is null or empty!");
325 return null;
326 }
327
328 final RoutingSessionInfo info = getRoutingSessionInfo();
329 if (info != null) {
330 return info.getName();
331 }
332
333 Log.w(TAG, "Unable to get session name for package: " + mPackageName);
334 return null;
335 }
336
hughchen0d202042020-01-17 18:30:03 +0800337 private void refreshDevices() {
338 mMediaDevices.clear();
339 mCurrentConnectedDevice = null;
340 if (TextUtils.isEmpty(mPackageName)) {
341 buildAllRoutes();
342 } else {
343 buildAvailableRoutes();
hughchena73686f2018-11-12 17:42:08 +0800344 }
hughchen0d202042020-01-17 18:30:03 +0800345 dispatchDeviceListAdded();
346 }
347
348 private void buildAllRoutes() {
349 for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
hughchen063a35a2020-03-04 17:51:57 +0800350 if (DEBUG) {
hughchenf961f2c2020-03-25 17:14:02 +0800351 Log.d(TAG, "buildAllRoutes() route : " + route.getName() + ", volume : "
hughchene8c79b02020-04-21 17:26:21 +0800352 + route.getVolume() + ", type : " + route.getType());
hughchen063a35a2020-03-04 17:51:57 +0800353 }
hughchen61bced02020-02-20 18:46:32 +0800354 if (route.isSystemRoute()) {
355 addMediaDevice(route);
356 }
hughchen0d202042020-01-17 18:30:03 +0800357 }
358 }
359
Tim Peng8a0f3612020-04-30 12:33:20 +0800360 List<RoutingSessionInfo> getActiveMediaSession() {
361 return mRouterManager.getActiveSessions();
362 }
363
hughchen0d202042020-01-17 18:30:03 +0800364 private void buildAvailableRoutes() {
365 for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) {
hughchen063a35a2020-03-04 17:51:57 +0800366 if (DEBUG) {
hughchene8c79b02020-04-21 17:26:21 +0800367 Log.d(TAG, "buildAvailableRoutes() route : " + route.getName()
368 + ", type : " + route.getType());
hughchen063a35a2020-03-04 17:51:57 +0800369 }
hughchen491b9fc2020-02-05 18:31:36 +0800370 addMediaDevice(route);
371 }
372 }
373
hughchene8c79b02020-04-21 17:26:21 +0800374 @VisibleForTesting
375 void addMediaDevice(MediaRoute2Info route) {
hughchen62837412020-02-24 17:03:26 +0800376 final int deviceType = route.getType();
hughchen491b9fc2020-02-05 18:31:36 +0800377 MediaDevice mediaDevice = null;
378 switch (deviceType) {
hughchen62837412020-02-24 17:03:26 +0800379 case TYPE_UNKNOWN:
380 case TYPE_REMOTE_TV:
381 case TYPE_REMOTE_SPEAKER:
382 case TYPE_GROUP:
hughchen491b9fc2020-02-05 18:31:36 +0800383 //TODO(b/148765806): use correct device type once api is ready.
hughchen62837412020-02-24 17:03:26 +0800384 mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
385 mPackageName);
386 if (!TextUtils.isEmpty(mPackageName)
hughchen8e9e4b42020-03-23 18:59:58 +0800387 && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())
hughchen063a35a2020-03-04 17:51:57 +0800388 && mCurrentConnectedDevice == null) {
hughchen62837412020-02-24 17:03:26 +0800389 mCurrentConnectedDevice = mediaDevice;
hughchen491b9fc2020-02-05 18:31:36 +0800390 }
391 break;
hughchen62837412020-02-24 17:03:26 +0800392 case TYPE_BUILTIN_SPEAKER:
hughchene8c79b02020-04-21 17:26:21 +0800393 case TYPE_USB_DEVICE:
394 case TYPE_USB_HEADSET:
395 case TYPE_USB_ACCESSORY:
396 case TYPE_DOCK:
397 case TYPE_HDMI:
hughchen62837412020-02-24 17:03:26 +0800398 case TYPE_WIRED_HEADSET:
399 case TYPE_WIRED_HEADPHONES:
400 mediaDevice =
401 new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
hughchen491b9fc2020-02-05 18:31:36 +0800402 break;
hughchen62837412020-02-24 17:03:26 +0800403 case TYPE_HEARING_AID:
404 case TYPE_BLUETOOTH_A2DP:
hughchen491b9fc2020-02-05 18:31:36 +0800405 final BluetoothDevice device =
406 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getOriginalId());
407 final CachedBluetoothDevice cachedDevice =
408 mBluetoothManager.getCachedDeviceManager().findDevice(device);
hughchen71a7f822020-04-24 14:59:05 +0800409 if (cachedDevice != null) {
410 mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
411 route, mPackageName);
412 }
hughchen491b9fc2020-02-05 18:31:36 +0800413 break;
414 default:
415 Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
416 break;
417
418 }
419
420 if (mediaDevice != null) {
421 mMediaDevices.add(mediaDevice);
hughchen0d202042020-01-17 18:30:03 +0800422 }
423 }
424
425 class RouterManagerCallback extends MediaRouter2Manager.Callback {
hughchena73686f2018-11-12 17:42:08 +0800426
427 @Override
hughchene6a4f482019-10-18 17:34:15 +0800428 public void onRoutesAdded(List<MediaRoute2Info> routes) {
429 refreshDevices();
430 }
431
432 @Override
Kyunglyul Hyunfa6f06d2020-04-30 02:56:40 +0900433 public void onPreferredFeaturesChanged(String packageName, List<String> preferredFeatures) {
hughchene6a4f482019-10-18 17:34:15 +0800434 if (TextUtils.equals(mPackageName, packageName)) {
435 refreshDevices();
hughchena73686f2018-11-12 17:42:08 +0800436 }
437 }
hughchendf7b8382020-02-03 17:48:49 +0800438
439 @Override
440 public void onRoutesChanged(List<MediaRoute2Info> routes) {
hughchenf961f2c2020-03-25 17:14:02 +0800441 refreshDevices();
hughchendf7b8382020-02-03 17:48:49 +0800442 }
hughchenb3d68f62020-02-11 14:30:14 +0800443
444 @Override
445 public void onRoutesRemoved(List<MediaRoute2Info> routes) {
446 refreshDevices();
447 }
hughchen063a35a2020-03-04 17:51:57 +0800448
449 @Override
450 public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
451 if (DEBUG) {
452 Log.d(TAG, "onTransferred() oldSession : " + oldSession.getName()
453 + ", newSession : " + newSession.getName());
454 }
hughchenf961f2c2020-03-25 17:14:02 +0800455 mMediaDevices.clear();
456 mCurrentConnectedDevice = null;
457 if (TextUtils.isEmpty(mPackageName)) {
458 buildAllRoutes();
459 } else {
460 buildAvailableRoutes();
461 }
462
463 final String id = mCurrentConnectedDevice != null
464 ? mCurrentConnectedDevice.getId()
465 : null;
466 dispatchConnectedDeviceChanged(id);
hughchen063a35a2020-03-04 17:51:57 +0800467 }
468
469 @Override
470 public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
471 dispatchOnRequestFailed(REASON_UNKNOWN_ERROR);
472 }
473
474 @Override
475 public void onRequestFailed(int reason) {
476 dispatchOnRequestFailed(reason);
477 }
hughchena73686f2018-11-12 17:42:08 +0800478 }
479}