blob: 6e2feeb15e214fb1785d357f0df4a1c9e38df1fd [file] [log] [blame]
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +00001/*
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
17package com.android.server.media;
18
Hyundo Moon42bef142020-01-14 14:16:30 +090019import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
20import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
Kyunglyul Hyun8828c892020-02-17 20:49:58 +090021import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
Hyundo Moon42bef142020-01-14 14:16:30 +090022
Hyundo Moon717ef722020-02-06 18:44:13 +090023import android.content.BroadcastReceiver;
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000024import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
Hyundo Moon717ef722020-02-06 18:44:13 +090027import android.content.IntentFilter;
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000028import android.media.AudioManager;
29import android.media.AudioRoutesInfo;
30import android.media.IAudioRoutesObserver;
31import android.media.IAudioService;
32import android.media.MediaRoute2Info;
33import android.media.MediaRoute2ProviderInfo;
Kyunglyul Hyun1866d8a2020-01-31 11:56:34 +090034import android.media.RouteDiscoveryPreference;
Hyundo Moon67c41fd2020-01-17 14:22:42 +090035import android.media.RoutingSessionInfo;
Hyundo Moon84e027d2020-01-16 17:39:05 +090036import android.os.Bundle;
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000037import android.os.Handler;
38import android.os.Looper;
39import android.os.RemoteException;
40import android.os.ServiceManager;
Hyundo Moon67c41fd2020-01-17 14:22:42 +090041import android.text.TextUtils;
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000042import android.util.Log;
43
44import com.android.internal.R;
45
Hyundo Moon67c41fd2020-01-17 14:22:42 +090046import java.util.Objects;
Kyunglyul Hyunefe43742019-12-31 18:32:28 +090047
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000048/**
49 * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
50 */
Sungsoo Limea1eaf72020-02-12 11:00:06 +090051// TODO: check thread safety. We may need to use lock to protect variables.
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000052class SystemMediaRoute2Provider extends MediaRoute2Provider {
53 private static final String TAG = "MR2SystemProvider";
54 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
55
Hyundo Moon5736a612019-11-19 15:08:32 +090056 static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
Sungsoo Limddf140d2020-03-25 08:53:55 +090057 static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
Hyundo Moon67c41fd2020-01-17 14:22:42 +090058 static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000059
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000060 private final AudioManager mAudioManager;
61 private final IAudioService mAudioService;
62 private final Handler mHandler;
63 private final Context mContext;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090064 private final BluetoothRouteProvider mBtRouteProvider;
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000065
66 private static ComponentName sComponentName = new ComponentName(
Kyunglyul Hyund9bb4d72020-02-18 15:05:26 +090067 SystemMediaRoute2Provider.class.getPackage().getName(),
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000068 SystemMediaRoute2Provider.class.getName());
69
Kyunglyul Hyun5161b372020-02-05 18:45:35 +090070 private String mSelectedRouteId;
Sungsoo Limddf140d2020-03-25 08:53:55 +090071 // For apps without MODIFYING_AUDIO_ROUTING permission.
72 // This should be the currently selected route.
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000073 MediaRoute2Info mDefaultRoute;
Sungsoo Limddf140d2020-03-25 08:53:55 +090074 MediaRoute2Info mDeviceRoute;
Sungsoo Limc27785a2020-03-27 16:57:47 +090075 RoutingSessionInfo mDefaultSessionInfo;
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000076 final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
77
78 final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
79 @Override
80 public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
Sungsoo Limea1eaf72020-02-12 11:00:06 +090081 mHandler.post(() -> {
Sungsoo Limddf140d2020-03-25 08:53:55 +090082 updateDeviceRoute(newRoutes);
Sungsoo Limea1eaf72020-02-12 11:00:06 +090083 notifyProviderState();
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000084 });
85 }
86 };
87
88 SystemMediaRoute2Provider(Context context, Callback callback) {
89 super(sComponentName);
90 setCallback(callback);
91
Kyunglyul Hyun96d36ce2020-01-29 19:59:33 +090092 mIsSystemRouteProvider = true;
93
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +000094 mContext = context;
95 mHandler = new Handler(Looper.getMainLooper());
96
97 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
98 mAudioService = IAudioService.Stub.asInterface(
99 ServiceManager.getService(Context.AUDIO_SERVICE));
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900100 AudioRoutesInfo newAudioRoutes = null;
101 try {
102 newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
103 } catch (RemoteException e) {
104 }
Sungsoo Limddf140d2020-03-25 08:53:55 +0900105 updateDeviceRoute(newAudioRoutes);
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000106
Roman Kiryanovfe87df92020-03-27 17:40:27 -0700107 // .getInstance returns null if there is no bt adapter available
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900108 mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900109 publishProviderState();
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900110
111 boolean sessionInfoChanged;
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900112 sessionInfoChanged = updateSessionInfosIfNeeded();
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900113 if (sessionInfoChanged) {
114 notifySessionInfoUpdated();
115 }
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900116 });
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900117 updateSessionInfosIfNeeded();
Sungsoo Limc27785a2020-03-27 16:57:47 +0900118
Hyundo Moon717ef722020-02-06 18:44:13 +0900119 mContext.registerReceiver(new VolumeChangeReceiver(),
120 new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900121
Roman Kiryanovfe87df92020-03-27 17:40:27 -0700122 if (mBtRouteProvider != null) {
123 mHandler.post(() -> {
124 mBtRouteProvider.start();
125 notifyProviderState();
126 });
127 }
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000128 }
129
Hyundo Moon63a05402019-12-19 20:13:56 +0900130 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900131 public void requestCreateSession(long requestId, String packageName, String routeId,
Hyundo Moon84e027d2020-01-16 17:39:05 +0900132 Bundle sessionHints) {
Kyunglyul Hyun923ef0d2020-03-13 20:55:11 +0900133
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900134 transferToRoute(requestId, SYSTEM_SESSION_ID, routeId);
Kyunglyul Hyun923ef0d2020-03-13 20:55:11 +0900135 mCallback.onSessionCreated(this, requestId, mSessionInfos.get(0));
136 //TODO: We should call after the session info is changed.
Hyundo Moon63a05402019-12-19 20:13:56 +0900137 }
138
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000139 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900140 public void releaseSession(long requestId, String sessionId) {
Kyunglyul Hyuncb8894d2019-12-27 14:24:46 +0900141 // Do nothing
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000142 }
Hyundo Moon0fa60e82020-02-14 11:44:45 +0900143
Kyunglyul Hyun1866d8a2020-01-31 11:56:34 +0900144 @Override
145 public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
146 // Do nothing
147 }
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000148
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000149 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900150 public void selectRoute(long requestId, String sessionId, String routeId) {
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900151 // Do nothing since we don't support multiple BT yet.
Kyunglyul Hyuncb8894d2019-12-27 14:24:46 +0900152 }
153
154 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900155 public void deselectRoute(long requestId, String sessionId, String routeId) {
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900156 // Do nothing since we don't support multiple BT yet.
Kyunglyul Hyuncb8894d2019-12-27 14:24:46 +0900157 }
158
159 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900160 public void transferToRoute(long requestId, String sessionId, String routeId) {
Sungsoo Limc27785a2020-03-27 16:57:47 +0900161 if (TextUtils.equals(routeId, DEFAULT_ROUTE_ID)) {
162 // The currently selected route is the default route.
163 return;
164 }
Roman Kiryanovfe87df92020-03-27 17:40:27 -0700165 if (mBtRouteProvider != null) {
166 if (TextUtils.equals(routeId, mDeviceRoute.getId())) {
167 mBtRouteProvider.transferTo(null);
168 } else {
169 mBtRouteProvider.transferTo(routeId);
170 }
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900171 }
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000172 }
173
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000174 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900175 public void setRouteVolume(long requestId, String routeId, int volume) {
Kyunglyul Hyun5161b372020-02-05 18:45:35 +0900176 if (!TextUtils.equals(routeId, mSelectedRouteId)) {
177 return;
178 }
179 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
180 }
181
182 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900183 public void setSessionVolume(long requestId, String sessionId, int volume) {
Kyunglyul Hyun5161b372020-02-05 18:45:35 +0900184 // Do nothing since we don't support grouping volume yet.
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000185 }
186
Sungsoo Limddf140d2020-03-25 08:53:55 +0900187 public MediaRoute2Info getDefaultRoute() {
188 return mDefaultRoute;
189 }
190
Sungsoo Limc27785a2020-03-27 16:57:47 +0900191 public RoutingSessionInfo getDefaultSessionInfo() {
192 return mDefaultSessionInfo;
193 }
194
Sungsoo Limddf140d2020-03-25 08:53:55 +0900195 private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000196 int name = R.string.default_audio_route_name;
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900197 if (newRoutes != null) {
198 mCurAudioRoutesInfo.mainType = newRoutes.mainType;
199 if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0
200 || (newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
201 name = com.android.internal.R.string.default_audio_route_name_headphones;
202 } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
203 name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
204 } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
205 name = com.android.internal.R.string.default_audio_route_name_hdmi;
206 } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
207 name = com.android.internal.R.string.default_audio_route_name_usb;
208 }
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000209 }
Sungsoo Limddf140d2020-03-25 08:53:55 +0900210 mDeviceRoute = new MediaRoute2Info.Builder(
211 DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000212 .setVolumeHandling(mAudioManager.isVolumeFixed()
213 ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
214 : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
215 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
216 .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
Kyunglyul Hyun8828c892020-02-17 20:49:58 +0900217 //TODO: Guess the exact type using AudioDevice
218 .setType(TYPE_BUILTIN_SPEAKER)
Hyundo Moon42bef142020-01-14 14:16:30 +0900219 .addFeature(FEATURE_LIVE_AUDIO)
220 .addFeature(FEATURE_LIVE_VIDEO)
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900221 .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000222 .build();
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900223 updateProviderState();
224 }
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000225
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900226 private void updateProviderState() {
227 MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
Sungsoo Limddf140d2020-03-25 08:53:55 +0900228 builder.addRoute(mDeviceRoute);
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900229 if (mBtRouteProvider != null) {
230 for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
231 builder.addRoute(route);
232 }
233 }
234 setProviderState(builder.build());
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000235 }
Hyundo Moon5736a612019-11-19 15:08:32 +0900236
237 /**
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900238 * Updates the mSessionInfo. Returns true if the session info is changed.
239 */
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900240 boolean updateSessionInfosIfNeeded() {
241 synchronized (mLock) {
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900242 RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(
243 0);
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900244
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900245 RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
246 SYSTEM_SESSION_ID, "" /* clientPackageName */)
247 .setSystemSession(true);
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900248
Sungsoo Limc27785a2020-03-27 16:57:47 +0900249 MediaRoute2Info selectedRoute = mDeviceRoute;
250 if (mBtRouteProvider != null) {
251 MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute();
252 if (selectedBtRoute != null) {
253 selectedRoute = selectedBtRoute;
254 builder.addTransferableRoute(mDeviceRoute.getId());
255 }
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900256 }
257 mSelectedRouteId = selectedRoute.getId();
Sungsoo Limc27785a2020-03-27 16:57:47 +0900258 mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
259 .setSystemRoute(true)
260 .setProviderId(mUniqueId)
261 .build();
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900262 builder.addSelectedRoute(mSelectedRouteId);
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900263
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900264 for (MediaRoute2Info route : mBtRouteProvider.getTransferableRoutes()) {
265 builder.addTransferableRoute(route.getId());
266 }
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900267
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900268 RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
269 if (Objects.equals(oldSessionInfo, newSessionInfo)) {
270 return false;
271 } else {
272 mSessionInfos.clear();
273 mSessionInfos.add(newSessionInfo);
Sungsoo Limc27785a2020-03-27 16:57:47 +0900274 mDefaultSessionInfo = new RoutingSessionInfo.Builder(
275 SYSTEM_SESSION_ID, "" /* clientPackageName */)
276 .setProviderId(mUniqueId)
277 .setSystemSession(true)
278 .addSelectedRoute(DEFAULT_ROUTE_ID)
279 .build();
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900280 return true;
281 }
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900282 }
283 }
284
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900285 void publishProviderState() {
286 updateProviderState();
287 notifyProviderState();
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000288 }
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900289
290 void notifySessionInfoUpdated() {
291 RoutingSessionInfo sessionInfo;
292 synchronized (mLock) {
293 sessionInfo = mSessionInfos.get(0);
294 }
Kyunglyul Hyun923ef0d2020-03-13 20:55:11 +0900295
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900296 mCallback.onSessionUpdated(this, sessionInfo);
297 }
Hyundo Moon717ef722020-02-06 18:44:13 +0900298
299 private class VolumeChangeReceiver extends BroadcastReceiver {
300 // This will be called in the main thread.
301 @Override
302 public void onReceive(Context context, Intent intent) {
303 if (!intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
304 return;
305 }
306
307 final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
308 if (streamType != AudioManager.STREAM_MUSIC) {
309 return;
310 }
311
312 final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
313 final int oldVolume = intent.getIntExtra(
314 AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
315
316 if (newVolume != oldVolume) {
Sungsoo Limddf140d2020-03-25 08:53:55 +0900317 if (TextUtils.equals(mDeviceRoute.getId(), mSelectedRouteId)) {
318 mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
Hyundo Moon717ef722020-02-06 18:44:13 +0900319 .setVolume(newVolume)
320 .build();
Roman Kiryanovfe87df92020-03-27 17:40:27 -0700321 } else if (mBtRouteProvider != null) {
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900322 mBtRouteProvider.setSelectedRouteVolume(newVolume);
Hyundo Moon717ef722020-02-06 18:44:13 +0900323 }
Sungsoo Limc27785a2020-03-27 16:57:47 +0900324 mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute)
325 .setVolume(newVolume)
326 .build();
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900327 publishProviderState();
Hyundo Moon717ef722020-02-06 18:44:13 +0900328 }
329 }
330 }
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000331}