blob: 25bbfa02fa058dc6e5fedf742a9193546ca1c455 [file] [log] [blame]
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +09001/*
2 * Copyright 2020 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
Kyunglyul Hyune77fbcb2020-05-29 15:18:11 +090019import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO;
Kyunglyul Hyune1b80062020-06-21 21:14:43 +090020import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
21import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED;
Kyunglyul Hyune77fbcb2020-05-29 15:18:11 +090022
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090023import android.annotation.NonNull;
Hyundo Moon67c41fd2020-01-17 14:22:42 +090024import android.annotation.Nullable;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090025import android.bluetooth.BluetoothA2dp;
26import android.bluetooth.BluetoothAdapter;
27import android.bluetooth.BluetoothDevice;
28import android.bluetooth.BluetoothHearingAid;
29import android.bluetooth.BluetoothProfile;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
Hyundo Moon717ef722020-02-06 18:44:13 +090034import android.media.AudioManager;
Kyunglyul Hyunf8f170c2020-05-13 21:31:26 +090035import android.media.AudioSystem;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090036import android.media.MediaRoute2Info;
Sungsoo Lim2f19fd12020-05-07 09:53:09 +090037import android.text.TextUtils;
Kyunglyul Hyune1b80062020-06-21 21:14:43 +090038import android.util.Log;
Kyunglyul Hyun581fc982020-01-21 16:30:28 +090039import android.util.Slog;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090040import android.util.SparseBooleanArray;
Kyunglyul Hyunf8f170c2020-05-13 21:31:26 +090041import android.util.SparseIntArray;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090042
43import com.android.internal.R;
44
45import java.util.ArrayList;
Kyunglyul Hyun581fc982020-01-21 16:30:28 +090046import java.util.Collections;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090047import java.util.HashMap;
48import java.util.List;
49import java.util.Map;
50import java.util.Objects;
51
52class BluetoothRouteProvider {
53 private static final String TAG = "BTRouteProvider";
Kyunglyul Hyune1b80062020-06-21 21:14:43 +090054 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
55
Kyunglyul Hyunf6d5b3c2020-06-11 22:23:11 +090056 private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090057 private static BluetoothRouteProvider sInstance;
58
59 @SuppressWarnings("WeakerAccess") /* synthetic access */
Kyunglyul Hyuna8f2a062020-06-17 15:46:38 +090060 // Maps hardware address to BluetoothRouteInfo
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090061 final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
62 @SuppressWarnings("WeakerAccess") /* synthetic access */
Kyunglyul Hyune1b80062020-06-21 21:14:43 +090063 final List<BluetoothRouteInfo> mActiveRoutes = new ArrayList<>();
Sungsoo Limcfa7abc2020-02-12 09:32:05 +090064 @SuppressWarnings("WeakerAccess") /* synthetic access */
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090065 BluetoothA2dp mA2dpProfile;
66 @SuppressWarnings("WeakerAccess") /* synthetic access */
67 BluetoothHearingAid mHearingAidProfile;
68
Kyunglyul Hyunf8f170c2020-05-13 21:31:26 +090069 // Route type -> volume map
70 private final SparseIntArray mVolumeMap = new SparseIntArray();
71
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090072 private final Context mContext;
73 private final BluetoothAdapter mBluetoothAdapter;
74 private final BluetoothRoutesUpdatedListener mListener;
Hyundo Moon717ef722020-02-06 18:44:13 +090075 private final AudioManager mAudioManager;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090076 private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
77 private final IntentFilter mIntentFilter = new IntentFilter();
78 private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
79 private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
80
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +090081 static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
82 @NonNull BluetoothRoutesUpdatedListener listener) {
83 Objects.requireNonNull(context);
84 Objects.requireNonNull(listener);
85
86 if (sInstance == null) {
87 BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
88 if (btAdapter == null) {
89 return null;
90 }
91 sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
92 }
93 return sInstance;
94 }
95
96 private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
97 BluetoothRoutesUpdatedListener listener) {
98 mContext = context;
99 mBluetoothAdapter = btAdapter;
100 mListener = listener;
Hyundo Moon717ef722020-02-06 18:44:13 +0900101 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900102 buildBluetoothRoutes();
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900103 }
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900104
Sungsoo Lim613a77a2020-03-16 15:44:51 +0900105 public void start() {
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900106 mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
107 mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
108
109 // Bluetooth on/off broadcasts
110 addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
111
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900112 DeviceStateChangedReceiver deviceStateChangedReceiver = new DeviceStateChangedReceiver();
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900113 addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
114 addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
115 addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
116 deviceStateChangedReceiver);
117 addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
118 deviceStateChangedReceiver);
119
120 mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
121 }
122
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900123 /**
Sungsoo Limcfa7abc2020-02-12 09:32:05 +0900124 * Transfers to a given bluetooth route.
125 * The dedicated BT device with the route would be activated.
126 *
127 * @param routeId the id of the Bluetooth device. {@code null} denotes to clear the use of
128 * BT routes.
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900129 */
Sungsoo Limcfa7abc2020-02-12 09:32:05 +0900130 public void transferTo(@Nullable String routeId) {
131 if (routeId == null) {
132 clearActiveDevices();
133 return;
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900134 }
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900135
Kyunglyul Hyuna8f2a062020-06-17 15:46:38 +0900136 BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId);
137
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900138 if (btRouteInfo == null) {
Kyunglyul Hyuna8f2a062020-06-17 15:46:38 +0900139 Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId);
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900140 return;
141 }
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900142
Kyunglyul Hyune77fbcb2020-05-29 15:18:11 +0900143 if (mBluetoothAdapter != null) {
144 mBluetoothAdapter.setActiveDevice(btRouteInfo.btDevice, ACTIVE_DEVICE_AUDIO);
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900145 }
146 }
147
Sungsoo Limcfa7abc2020-02-12 09:32:05 +0900148 /**
149 * Clears the active device for all known profiles.
150 */
151 private void clearActiveDevices() {
Kyunglyul Hyune77fbcb2020-05-29 15:18:11 +0900152 if (mBluetoothAdapter != null) {
153 mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO);
Sungsoo Limcfa7abc2020-02-12 09:32:05 +0900154 }
155 }
156
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900157 private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
158 mEventReceiverMap.put(action, eventReceiver);
159 mIntentFilter.addAction(action);
160 }
161
162 private void buildBluetoothRoutes() {
163 mBluetoothRoutes.clear();
164 for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
165 if (device.isConnected()) {
166 BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
Kyunglyul Hyun6eefb5f2020-05-15 16:21:20 +0900167 if (newBtRoute.connectedProfiles.size() > 0) {
168 mBluetoothRoutes.put(device.getAddress(), newBtRoute);
169 }
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900170 }
171 }
172 }
173
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900174 @Nullable
175 MediaRoute2Info getSelectedRoute() {
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900176 // For now, active routes can be multiple only when a pair of hearing aid devices is active.
177 // Let the first active device represent them.
178 return (mActiveRoutes.isEmpty() ? null : mActiveRoutes.get(0).route);
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900179 }
180
181 @NonNull
182 List<MediaRoute2Info> getTransferableRoutes() {
183 List<MediaRoute2Info> routes = getAllBluetoothRoutes();
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900184 for (BluetoothRouteInfo btRoute : mActiveRoutes) {
185 routes.remove(btRoute.route);
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900186 }
187 return routes;
188 }
189
Sungsoo Limcfa7abc2020-02-12 09:32:05 +0900190 @NonNull
191 List<MediaRoute2Info> getAllBluetoothRoutes() {
Kyunglyul Hyunf6d5b3c2020-06-11 22:23:11 +0900192 List<MediaRoute2Info> routes = new ArrayList<>();
193 List<String> routeIds = new ArrayList<>();
194
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900195 MediaRoute2Info selectedRoute = getSelectedRoute();
196 if (selectedRoute != null) {
197 routes.add(selectedRoute);
198 routeIds.add(selectedRoute.getId());
199 }
200
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900201 for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900202 // A pair of hearing aid devices or having the same hardware address
Kyunglyul Hyunf6d5b3c2020-06-11 22:23:11 +0900203 if (routeIds.contains(btRoute.route.getId())) {
204 continue;
205 }
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900206 routes.add(btRoute.route);
Kyunglyul Hyunf6d5b3c2020-06-11 22:23:11 +0900207 routeIds.add(btRoute.route.getId());
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900208 }
209 return routes;
210 }
211
Kyunglyul Hyuna8f2a062020-06-17 15:46:38 +0900212 BluetoothRouteInfo findBluetoothRouteWithRouteId(String routeId) {
213 if (routeId == null) {
214 return null;
215 }
216 for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) {
217 if (TextUtils.equals(btRouteInfo.route.getId(), routeId)) {
218 return btRouteInfo;
219 }
220 }
221 return null;
222 }
223
Kyunglyul Hyunf8f170c2020-05-13 21:31:26 +0900224 /**
225 * Updates the volume for {@link AudioManager#getDevicesForStream(int) devices}.
226 *
227 * @return true if devices can be handled by the provider.
228 */
229 public boolean updateVolumeForDevices(int devices, int volume) {
230 int routeType;
231 if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) {
232 routeType = MediaRoute2Info.TYPE_HEARING_AID;
233 } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP
234 | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
235 | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
236 routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
237 } else {
238 return false;
239 }
240 mVolumeMap.put(routeType, volume);
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900241
242 boolean shouldNotify = false;
243 for (BluetoothRouteInfo btRoute : mActiveRoutes) {
244 if (btRoute.route.getType() != routeType) {
245 continue;
246 }
247 btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
248 .setVolume(volume)
249 .build();
250 shouldNotify = true;
Kyunglyul Hyunf8f170c2020-05-13 21:31:26 +0900251 }
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900252 if (shouldNotify) {
253 notifyBluetoothRoutesUpdated();
254 }
Sungsoo Limea1eaf72020-02-12 11:00:06 +0900255 return true;
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900256 }
257
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900258 private void notifyBluetoothRoutesUpdated() {
259 if (mListener != null) {
Sungsoo Limcfa7abc2020-02-12 09:32:05 +0900260 mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900261 }
262 }
263
264 private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
265 BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
266 newBtRoute.btDevice = device;
Kyunglyul Hyunf6d5b3c2020-06-11 22:23:11 +0900267
268 String routeId = device.getAddress();
Sungsoo Lim2f19fd12020-05-07 09:53:09 +0900269 String deviceName = device.getName();
270 if (TextUtils.isEmpty(deviceName)) {
271 deviceName = mContext.getResources().getText(R.string.unknownName).toString();
272 }
Kyunglyul Hyun6eefb5f2020-05-15 16:21:20 +0900273 int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
274 newBtRoute.connectedProfiles = new SparseBooleanArray();
275 if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
276 newBtRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
277 }
278 if (mHearingAidProfile != null
279 && mHearingAidProfile.getConnectedDevices().contains(device)) {
280 newBtRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
Kyunglyul Hyunf6d5b3c2020-06-11 22:23:11 +0900281 // Intentionally assign the same ID for a pair of devices to publish only one of them.
282 routeId = HEARING_AID_ROUTE_ID_PREFIX + mHearingAidProfile.getHiSyncId(device);
Kyunglyul Hyun6eefb5f2020-05-15 16:21:20 +0900283 type = MediaRoute2Info.TYPE_HEARING_AID;
284 }
285
Kyunglyul Hyunf6d5b3c2020-06-11 22:23:11 +0900286 // Current volume will be set when connected.
287 newBtRoute.route = new MediaRoute2Info.Builder(routeId, deviceName)
Hyundo Moon42bef142020-01-14 14:16:30 +0900288 .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
Kyunglyul Hyunc0363502020-06-19 13:33:16 +0900289 .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK)
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900290 .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
291 .setDescription(mContext.getResources().getText(
292 R.string.bluetooth_a2dp_audio_route_name).toString())
Kyunglyul Hyun6eefb5f2020-05-15 16:21:20 +0900293 .setType(type)
Hyundo Moon717ef722020-02-06 18:44:13 +0900294 .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
Kyunglyul Hyunf8f170c2020-05-13 21:31:26 +0900295 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
Kyunglyul Hyun1ea19b12020-06-10 20:04:15 +0900296 .setAddress(device.getAddress())
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900297 .build();
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900298 return newBtRoute;
299 }
300
Sungsoo Limcfa7abc2020-02-12 09:32:05 +0900301 private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute,
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900302 @MediaRoute2Info.ConnectionState int state) {
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900303 if (btRoute == null) {
Sungsoo Limcfa7abc2020-02-12 09:32:05 +0900304 Slog.w(TAG, "setRouteConnectionState: route shouldn't be null");
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900305 return;
306 }
Hyundo Moon717ef722020-02-06 18:44:13 +0900307 if (btRoute.route.getConnectionState() == state) {
308 return;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900309 }
Hyundo Moon717ef722020-02-06 18:44:13 +0900310
Hyundo Moon717ef722020-02-06 18:44:13 +0900311 MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.route)
312 .setConnectionState(state);
Kyunglyul Hyunf8f170c2020-05-13 21:31:26 +0900313 builder.setType(btRoute.getRouteType());
Hyundo Moon717ef722020-02-06 18:44:13 +0900314
315 if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
Kyunglyul Hyunf8f170c2020-05-13 21:31:26 +0900316 builder.setVolume(mVolumeMap.get(btRoute.getRouteType(), 0));
Hyundo Moon717ef722020-02-06 18:44:13 +0900317 }
318 btRoute.route = builder.build();
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900319 }
320
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900321 private void clearActiveRoutes() {
322 if (DEBUG) {
323 Log.d(TAG, "Clearing active routes");
324 }
325 for (BluetoothRouteInfo btRoute : mActiveRoutes) {
326 setRouteConnectionState(btRoute, STATE_DISCONNECTED);
327 }
328 mActiveRoutes.clear();
329 }
330
331 private void addActiveRoute(BluetoothRouteInfo btRoute) {
332 if (DEBUG) {
333 Log.d(TAG, "Adding active route: " + btRoute.route);
334 }
335 if (btRoute == null || mActiveRoutes.contains(btRoute)) {
336 return;
337 }
338 setRouteConnectionState(btRoute, STATE_CONNECTED);
339 mActiveRoutes.add(btRoute);
340 }
341
342 private void removeActiveRoute(BluetoothRouteInfo btRoute) {
343 if (DEBUG) {
344 Log.d(TAG, "Removing active route: " + btRoute.route);
345 }
346 if (mActiveRoutes.remove(btRoute)) {
347 setRouteConnectionState(btRoute, STATE_DISCONNECTED);
348 }
349 }
350
351 private void findAndSetActiveHearingAidDevices() {
352 if (DEBUG) {
353 Log.d(TAG, "Setting active hearing aid devices");
354 }
355
356 BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
357 if (hearingAidProfile == null) {
358 return;
359 }
360 List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices();
361 for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
362 if (activeDevices.contains(btRoute.btDevice)) {
363 addActiveRoute(btRoute);
364 }
365 }
366 }
367
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900368 interface BluetoothRoutesUpdatedListener {
369 void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
370 }
371
372 private class BluetoothRouteInfo {
373 public BluetoothDevice btDevice;
374 public MediaRoute2Info route;
375 public SparseBooleanArray connectedProfiles;
Kyunglyul Hyunf8f170c2020-05-13 21:31:26 +0900376
377 @MediaRoute2Info.Type
378 int getRouteType() {
379 // Let hearing aid profile have a priority.
380 if (connectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
381 return MediaRoute2Info.TYPE_HEARING_AID;
382 }
383 return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
384 }
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900385 }
386
387 // These callbacks run on the main thread.
388 private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
389 public void onServiceConnected(int profile, BluetoothProfile proxy) {
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900390 List<BluetoothDevice> activeDevices;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900391 switch (profile) {
392 case BluetoothProfile.A2DP:
393 mA2dpProfile = (BluetoothA2dp) proxy;
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900394 // It may contain null.
395 activeDevices = Collections.singletonList(mA2dpProfile.getActiveDevice());
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900396 break;
397 case BluetoothProfile.HEARING_AID:
398 mHearingAidProfile = (BluetoothHearingAid) proxy;
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900399 activeDevices = mHearingAidProfile.getActiveDevices();
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900400 break;
401 default:
402 return;
403 }
404 for (BluetoothDevice device : proxy.getConnectedDevices()) {
405 BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
406 if (btRoute == null) {
407 btRoute = createBluetoothRoute(device);
408 mBluetoothRoutes.put(device.getAddress(), btRoute);
409 }
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900410 if (activeDevices.contains(device)) {
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900411 addActiveRoute(btRoute);
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900412 }
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900413 }
Kyunglyul Hyun581fc982020-01-21 16:30:28 +0900414 notifyBluetoothRoutesUpdated();
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900415 }
416
417 public void onServiceDisconnected(int profile) {
418 switch (profile) {
419 case BluetoothProfile.A2DP:
420 mA2dpProfile = null;
421 break;
422 case BluetoothProfile.HEARING_AID:
423 mHearingAidProfile = null;
424 break;
425 default:
426 return;
427 }
428 }
429 }
430 private class BluetoothBroadcastReceiver extends BroadcastReceiver {
431 @Override
432 public void onReceive(Context context, Intent intent) {
433 String action = intent.getAction();
434 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
435
436 BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
437 if (receiver != null) {
438 receiver.onReceive(context, intent, device);
439 }
440 }
441 }
442
443 private interface BluetoothEventReceiver {
444 void onReceive(Context context, Intent intent, BluetoothDevice device);
445 }
446
447 private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
448 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
449 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
450 if (state == BluetoothAdapter.STATE_OFF
451 || state == BluetoothAdapter.STATE_TURNING_OFF) {
452 mBluetoothRoutes.clear();
453 notifyBluetoothRoutesUpdated();
454 } else if (state == BluetoothAdapter.STATE_ON) {
455 buildBluetoothRoutes();
456 if (!mBluetoothRoutes.isEmpty()) {
457 notifyBluetoothRoutesUpdated();
458 }
459 }
460 }
461 }
462
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900463 private class DeviceStateChangedReceiver implements BluetoothEventReceiver {
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900464 @Override
465 public void onReceive(Context context, Intent intent, BluetoothDevice device) {
466 switch (intent.getAction()) {
467 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900468 clearActiveRoutes();
469 if (device != null) {
470 addActiveRoute(mBluetoothRoutes.get(device.getAddress()));
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900471 }
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900472 notifyBluetoothRoutesUpdated();
473 break;
474 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
475 clearActiveDevices();
476 if (device != null) {
477 findAndSetActiveHearingAidDevices();
478 }
479 notifyBluetoothRoutesUpdated();
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900480 break;
481 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
482 handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
483 break;
Kyunglyul Hyune77fbcb2020-05-29 15:18:11 +0900484 case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
485 handleConnectionStateChanged(BluetoothProfile.HEARING_AID, intent, device);
486 break;
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900487 }
488 }
489
490 private void handleConnectionStateChanged(int profile, Intent intent,
491 BluetoothDevice device) {
492 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
493 BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
494 if (state == BluetoothProfile.STATE_CONNECTED) {
495 if (btRoute == null) {
496 btRoute = createBluetoothRoute(device);
Kyunglyul Hyun6eefb5f2020-05-15 16:21:20 +0900497 if (btRoute.connectedProfiles.size() > 0) {
498 mBluetoothRoutes.put(device.getAddress(), btRoute);
499 notifyBluetoothRoutesUpdated();
500 }
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900501 } else {
502 btRoute.connectedProfiles.put(profile, true);
503 }
504 } else if (state == BluetoothProfile.STATE_DISCONNECTING
505 || state == BluetoothProfile.STATE_DISCONNECTED) {
Kyunglyul Hyundb74f3d2020-01-15 11:12:14 +0900506 if (btRoute != null) {
507 btRoute.connectedProfiles.delete(profile);
508 if (btRoute.connectedProfiles.size() == 0) {
Kyunglyul Hyune1b80062020-06-21 21:14:43 +0900509 removeActiveRoute(mBluetoothRoutes.remove(device.getAddress()));
Kyunglyul Hyundb74f3d2020-01-15 11:12:14 +0900510 notifyBluetoothRoutesUpdated();
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900511 }
Sungsoo Limbb3ee6e2019-12-23 17:47:24 +0900512 }
513 }
514 }
515 }
516}