blob: 7e78a78852a87238fcb3f8759c6b7f55ef03bf8c [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;
20import static android.media.MediaRoute2Info.TYPE_GROUP;
21import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
22import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
23import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
24import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
25import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
26import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
hughchen491b9fc2020-02-05 18:31:36 +080027
hughchena73686f2018-11-12 17:42:08 +080028import android.app.Notification;
hughchen491b9fc2020-02-05 18:31:36 +080029import android.bluetooth.BluetoothAdapter;
30import android.bluetooth.BluetoothDevice;
hughchena73686f2018-11-12 17:42:08 +080031import android.content.Context;
hughchene6a4f482019-10-18 17:34:15 +080032import android.media.MediaRoute2Info;
33import android.media.MediaRouter2Manager;
hughchendf7b8382020-02-03 17:48:49 +080034import android.media.RoutingSessionInfo;
hughchene6a4f482019-10-18 17:34:15 +080035import android.text.TextUtils;
hughchen491b9fc2020-02-05 18:31:36 +080036import android.util.Log;
hughchena73686f2018-11-12 17:42:08 +080037
hughchenbd0dd492019-01-08 14:34:10 +080038import com.android.internal.annotations.VisibleForTesting;
hughchen491b9fc2020-02-05 18:31:36 +080039import com.android.settingslib.bluetooth.CachedBluetoothDevice;
40import com.android.settingslib.bluetooth.LocalBluetoothManager;
hughchenbd0dd492019-01-08 14:34:10 +080041
hughchen6ab62ca2020-02-12 10:40:04 +080042import java.util.ArrayList;
hughchene6a4f482019-10-18 17:34:15 +080043import java.util.List;
44import java.util.concurrent.Executor;
45import java.util.concurrent.Executors;
46
hughchena73686f2018-11-12 17:42:08 +080047/**
48 * InfoMediaManager provide interface to get InfoMediaDevice list.
49 */
50public class InfoMediaManager extends MediaManager {
51
52 private static final String TAG = "InfoMediaManager";
53
hughchenbd0dd492019-01-08 14:34:10 +080054 @VisibleForTesting
hughchene6a4f482019-10-18 17:34:15 +080055 final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
hughchenbd0dd492019-01-08 14:34:10 +080056 @VisibleForTesting
hughchene6a4f482019-10-18 17:34:15 +080057 final Executor mExecutor = Executors.newSingleThreadExecutor();
hughchenbd0dd492019-01-08 14:34:10 +080058 @VisibleForTesting
hughchene6a4f482019-10-18 17:34:15 +080059 MediaRouter2Manager mRouterManager;
hughchen0d202042020-01-17 18:30:03 +080060 @VisibleForTesting
61 String mPackageName;
hughchena73686f2018-11-12 17:42:08 +080062
hughchene6a4f482019-10-18 17:34:15 +080063 private MediaDevice mCurrentConnectedDevice;
hughchen491b9fc2020-02-05 18:31:36 +080064 private LocalBluetoothManager mBluetoothManager;
hughchena73686f2018-11-12 17:42:08 +080065
hughchen491b9fc2020-02-05 18:31:36 +080066 public InfoMediaManager(Context context, String packageName, Notification notification,
67 LocalBluetoothManager localBluetoothManager) {
hughchena73686f2018-11-12 17:42:08 +080068 super(context, notification);
69
hughchene6a4f482019-10-18 17:34:15 +080070 mRouterManager = MediaRouter2Manager.getInstance(context);
hughchen491b9fc2020-02-05 18:31:36 +080071 mBluetoothManager = localBluetoothManager;
hughchen0d202042020-01-17 18:30:03 +080072 if (!TextUtils.isEmpty(packageName)) {
hughchene6a4f482019-10-18 17:34:15 +080073 mPackageName = packageName;
74 }
hughchena73686f2018-11-12 17:42:08 +080075 }
76
77 @Override
78 public void startScan() {
79 mMediaDevices.clear();
hughchene6a4f482019-10-18 17:34:15 +080080 mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
hughchen0d202042020-01-17 18:30:03 +080081 refreshDevices();
hughchena73686f2018-11-12 17:42:08 +080082 }
83
hughchena73686f2018-11-12 17:42:08 +080084 @Override
85 public void stopScan() {
hughchene6a4f482019-10-18 17:34:15 +080086 mRouterManager.unregisterCallback(mMediaRouterCallback);
hughchena73686f2018-11-12 17:42:08 +080087 }
88
hughchene6a4f482019-10-18 17:34:15 +080089 /**
90 * Get current device that played media.
91 * @return MediaDevice
92 */
hughchen6ab62ca2020-02-12 10:40:04 +080093 MediaDevice getCurrentConnectedDevice() {
hughchene6a4f482019-10-18 17:34:15 +080094 return mCurrentConnectedDevice;
95 }
96
hughchen6ab62ca2020-02-12 10:40:04 +080097 /**
98 * Transfer MediaDevice for media without package name.
99 */
100 boolean connectDeviceWithoutPackageName(MediaDevice device) {
101 boolean isConnected = false;
102 final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions();
103 if (infos.size() > 0) {
104 final RoutingSessionInfo info = infos.get(0);
105 final MediaRouter2Manager.RoutingController controller =
106 mRouterManager.getControllerForSession(info);
107
108 controller.transferToRoute(device.mRouteInfo);
109 isConnected = true;
110 }
111 return isConnected;
112 }
113
114 /**
115 * Add a MediaDevice to let it play current media.
116 *
117 * @param device MediaDevice
118 * @return If add device successful return {@code true}, otherwise return {@code false}
119 */
120 boolean addDeviceToPlayMedia(MediaDevice device) {
121 if (TextUtils.isEmpty(mPackageName)) {
122 Log.w(TAG, "addDeviceToPlayMedia() package name is null or empty!");
123 return false;
124 }
125
126 final RoutingSessionInfo info = getRoutingSessionInfo();
127 if (info != null && info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
128 mRouterManager.getControllerForSession(info).selectRoute(device.mRouteInfo);
129 return true;
130 }
131
132 Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
133 + device.getName());
134
135 return false;
136 }
137
138 private RoutingSessionInfo getRoutingSessionInfo() {
139 for (RoutingSessionInfo info : mRouterManager.getRoutingSessions(mPackageName)) {
140 if (TextUtils.equals(info.getClientPackageName(), mPackageName)) {
141 return info;
142 }
143 }
144
145 Log.w(TAG, "RoutingSessionInfo() cannot found match packagename : " + mPackageName);
146 return null;
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 */
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.getControllerForSession(info).deselectRoute(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 /**
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
182 final RoutingSessionInfo info = getRoutingSessionInfo();
183 if (info != null) {
184 mRouterManager.getControllerForSession(info).release();
185 return true;
186 }
187
188 Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
189
190 return false;
191 }
192
193 /**
hughchen6ab62ca2020-02-12 10:40:04 +0800194 * Get the MediaDevice list that can be added to current media.
195 *
196 * @return list of MediaDevice
197 */
198 List<MediaDevice> getSelectableMediaDevice() {
199 final List<MediaDevice> deviceList = new ArrayList<>();
200 if (TextUtils.isEmpty(mPackageName)) {
201 Log.w(TAG, "getSelectableMediaDevice() package name is null or empty!");
202 return deviceList;
203 }
204
205 final RoutingSessionInfo info = getRoutingSessionInfo();
206 if (info != null) {
207 for (MediaRoute2Info route : mRouterManager.getControllerForSession(info)
208 .getSelectableRoutes()) {
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 /**
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) {
235 for (MediaRoute2Info route : mRouterManager.getControllerForSession(info)
236 .getSelectedRoutes()) {
237 deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
238 route, mPackageName));
239 }
240 return deviceList;
241 }
242
243 Log.w(TAG, "getSelectedMediaDevice() cannot found selectable MediaDevice from : "
244 + mPackageName);
245
246 return deviceList;
247 }
248
249 /**
hughchen6ab62ca2020-02-12 10:40:04 +0800250 * Adjust the volume of {@link android.media.RoutingSessionInfo}.
251 *
252 * @param volume the value of volume
253 */
254 void adjustSessionVolume(int volume) {
255 if (TextUtils.isEmpty(mPackageName)) {
256 Log.w(TAG, "adjustSessionVolume() package name is null or empty!");
257 return;
258 }
259
260 final RoutingSessionInfo info = getRoutingSessionInfo();
261 if (info != null) {
262 Log.d(TAG, "adjustSessionVolume() adjust volume : " + volume + ", with : "
263 + mPackageName);
264 mRouterManager.setSessionVolume(info, volume);
265 return;
266 }
267
268 Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
269 + mPackageName);
270 }
271
272 /**
273 * Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
274 *
275 * @return maximum volume of the session, and return -1 if not found.
276 */
277 public int getSessionVolumeMax() {
278 if (TextUtils.isEmpty(mPackageName)) {
279 Log.w(TAG, "getSessionVolumeMax() package name is null or empty!");
280 return -1;
281 }
282
283 final RoutingSessionInfo info = getRoutingSessionInfo();
284 if (info != null) {
285 return info.getVolumeMax();
286 }
287
288 Log.w(TAG, "getSessionVolumeMax() can't found corresponding RoutingSession with : "
289 + mPackageName);
290 return -1;
291 }
292
293 /**
294 * Gets the current volume of the {@link android.media.RoutingSessionInfo}.
295 *
296 * @return current volume of the session, and return -1 if not found.
297 */
298 public int getSessionVolume() {
299 if (TextUtils.isEmpty(mPackageName)) {
300 Log.w(TAG, "getSessionVolume() package name is null or empty!");
301 return -1;
302 }
303
304 final RoutingSessionInfo info = getRoutingSessionInfo();
305 if (info != null) {
306 return info.getVolume();
307 }
308
309 Log.w(TAG, "getSessionVolume() can't found corresponding RoutingSession with : "
310 + mPackageName);
311 return -1;
312 }
313
Tim Peng635b04a2020-03-05 13:08:02 +0800314 CharSequence getSessionName() {
315 if (TextUtils.isEmpty(mPackageName)) {
316 Log.w(TAG, "Unable to get session name. The package name is null or empty!");
317 return null;
318 }
319
320 final RoutingSessionInfo info = getRoutingSessionInfo();
321 if (info != null) {
322 return info.getName();
323 }
324
325 Log.w(TAG, "Unable to get session name for package: " + mPackageName);
326 return null;
327 }
328
hughchen0d202042020-01-17 18:30:03 +0800329 private void refreshDevices() {
330 mMediaDevices.clear();
331 mCurrentConnectedDevice = null;
332 if (TextUtils.isEmpty(mPackageName)) {
333 buildAllRoutes();
334 } else {
335 buildAvailableRoutes();
hughchena73686f2018-11-12 17:42:08 +0800336 }
hughchen0d202042020-01-17 18:30:03 +0800337 dispatchDeviceListAdded();
338 }
339
340 private void buildAllRoutes() {
341 for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
hughchen61bced02020-02-20 18:46:32 +0800342 if (route.isSystemRoute()) {
343 addMediaDevice(route);
344 }
hughchen0d202042020-01-17 18:30:03 +0800345 }
346 }
347
348 private void buildAvailableRoutes() {
349 for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) {
hughchen491b9fc2020-02-05 18:31:36 +0800350 addMediaDevice(route);
351 }
352 }
353
354 private void addMediaDevice(MediaRoute2Info route) {
hughchen62837412020-02-24 17:03:26 +0800355 final int deviceType = route.getType();
hughchen491b9fc2020-02-05 18:31:36 +0800356 MediaDevice mediaDevice = null;
357 switch (deviceType) {
hughchen62837412020-02-24 17:03:26 +0800358 case TYPE_UNKNOWN:
359 case TYPE_REMOTE_TV:
360 case TYPE_REMOTE_SPEAKER:
361 case TYPE_GROUP:
hughchen491b9fc2020-02-05 18:31:36 +0800362 //TODO(b/148765806): use correct device type once api is ready.
hughchen62837412020-02-24 17:03:26 +0800363 mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
364 mPackageName);
365 if (!TextUtils.isEmpty(mPackageName)
366 && TextUtils.equals(route.getClientPackageName(), mPackageName)) {
367 mCurrentConnectedDevice = mediaDevice;
hughchen491b9fc2020-02-05 18:31:36 +0800368 }
369 break;
hughchen62837412020-02-24 17:03:26 +0800370 case TYPE_BUILTIN_SPEAKER:
371 case TYPE_WIRED_HEADSET:
372 case TYPE_WIRED_HEADPHONES:
373 mediaDevice =
374 new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
hughchen491b9fc2020-02-05 18:31:36 +0800375 break;
hughchen62837412020-02-24 17:03:26 +0800376 case TYPE_HEARING_AID:
377 case TYPE_BLUETOOTH_A2DP:
hughchen491b9fc2020-02-05 18:31:36 +0800378 final BluetoothDevice device =
379 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getOriginalId());
380 final CachedBluetoothDevice cachedDevice =
381 mBluetoothManager.getCachedDeviceManager().findDevice(device);
382 mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
383 route, mPackageName);
384 break;
385 default:
386 Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
387 break;
388
389 }
390
391 if (mediaDevice != null) {
392 mMediaDevices.add(mediaDevice);
hughchen0d202042020-01-17 18:30:03 +0800393 }
394 }
395
396 class RouterManagerCallback extends MediaRouter2Manager.Callback {
hughchena73686f2018-11-12 17:42:08 +0800397
398 @Override
hughchene6a4f482019-10-18 17:34:15 +0800399 public void onRoutesAdded(List<MediaRoute2Info> routes) {
400 refreshDevices();
401 }
402
403 @Override
404 public void onControlCategoriesChanged(String packageName, List<String> controlCategories) {
405 if (TextUtils.equals(mPackageName, packageName)) {
406 refreshDevices();
hughchena73686f2018-11-12 17:42:08 +0800407 }
408 }
hughchendf7b8382020-02-03 17:48:49 +0800409
410 @Override
411 public void onRoutesChanged(List<MediaRoute2Info> routes) {
412 refreshDevices();
413 }
hughchenb3d68f62020-02-11 14:30:14 +0800414
415 @Override
416 public void onRoutesRemoved(List<MediaRoute2Info> routes) {
417 refreshDevices();
418 }
hughchena73686f2018-11-12 17:42:08 +0800419 }
420}