• 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.settings.connecteddevice;
17 
18 import static com.android.settingslib.Utils.isAudioModeOngoingCall;
19 
20 import android.app.settings.SettingsEnums;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothLeBroadcast;
23 import android.bluetooth.BluetoothLeBroadcastAssistant;
24 import android.bluetooth.BluetoothLeBroadcastMetadata;
25 import android.bluetooth.BluetoothLeBroadcastReceiveState;
26 import android.bluetooth.BluetoothProfile;
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.util.Log;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 import androidx.annotation.VisibleForTesting;
34 import androidx.fragment.app.FragmentManager;
35 import androidx.lifecycle.DefaultLifecycleObserver;
36 import androidx.lifecycle.LifecycleOwner;
37 import androidx.preference.Preference;
38 import androidx.preference.PreferenceGroup;
39 import androidx.preference.PreferenceScreen;
40 
41 import com.android.settings.R;
42 import com.android.settings.accessibility.HearingAidUtils;
43 import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
44 import com.android.settings.bluetooth.BluetoothDevicePreference;
45 import com.android.settings.bluetooth.BluetoothDeviceUpdater;
46 import com.android.settings.bluetooth.Utils;
47 import com.android.settings.connecteddevice.audiosharing.AudioSharingDialogHandler;
48 import com.android.settings.core.BasePreferenceController;
49 import com.android.settings.dashboard.DashboardFragment;
50 import com.android.settings.overlay.FeatureFactory;
51 import com.android.settingslib.bluetooth.BluetoothCallback;
52 import com.android.settingslib.bluetooth.BluetoothUtils;
53 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
54 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
55 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
56 import com.android.settingslib.bluetooth.LocalBluetoothManager;
57 import com.android.settingslib.utils.ThreadUtils;
58 
59 import java.util.concurrent.Executor;
60 import java.util.concurrent.Executors;
61 
62 /**
63  * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all available media
64  * devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference}
65  */
66 public class AvailableMediaDeviceGroupController extends BasePreferenceController
67         implements DefaultLifecycleObserver, DevicePreferenceCallback, BluetoothCallback {
68     private static final String TAG = "AvailableMediaDeviceGroupController";
69     private static final String KEY = "available_device_list";
70 
71     private final Executor mExecutor;
72     @VisibleForTesting @Nullable LocalBluetoothManager mBtManager;
73     @VisibleForTesting @Nullable PreferenceGroup mPreferenceGroup;
74     @Nullable private LocalBluetoothLeBroadcast mBroadcast;
75     @Nullable private LocalBluetoothLeBroadcastAssistant mAssistant;
76     @Nullable private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
77     @Nullable private FragmentManager mFragmentManager;
78     @Nullable private AudioSharingDialogHandler mDialogHandler;
79     private BluetoothLeBroadcast.Callback mBroadcastCallback =
80             new BluetoothLeBroadcast.Callback() {
81                 @Override
82                 public void onBroadcastMetadataChanged(
83                         int broadcastId, BluetoothLeBroadcastMetadata metadata) {}
84 
85                 @Override
86                 public void onBroadcastStartFailed(int reason) {}
87 
88                 @Override
89                 public void onBroadcastStarted(int reason, int broadcastId) {
90                     Log.d(TAG, "onBroadcastStarted: update title.");
91                     updateTitle();
92                 }
93 
94                 @Override
95                 public void onBroadcastStopFailed(int reason) {}
96 
97                 @Override
98                 public void onBroadcastStopped(int reason, int broadcastId) {
99                     Log.d(TAG, "onBroadcastStopped: update title.");
100                     updateTitle();
101                 }
102 
103                 @Override
104                 public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
105 
106                 @Override
107                 public void onBroadcastUpdated(int reason, int broadcastId) {}
108 
109                 @Override
110                 public void onPlaybackStarted(int reason, int broadcastId) {}
111 
112                 @Override
113                 public void onPlaybackStopped(int reason, int broadcastId) {}
114             };
115 
116     private BluetoothLeBroadcastAssistant.Callback mAssistantCallback =
117             new BluetoothLeBroadcastAssistant.Callback() {
118                 @Override
119                 public void onSearchStarted(int reason) {}
120 
121                 @Override
122                 public void onSearchStartFailed(int reason) {}
123 
124                 @Override
125                 public void onSearchStopped(int reason) {}
126 
127                 @Override
128                 public void onSearchStopFailed(int reason) {}
129 
130                 @Override
131                 public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
132 
133                 @Override
134                 public void onSourceAdded(
135                         @NonNull BluetoothDevice sink, int sourceId, int reason) {
136                     Log.d(TAG, "onSourceAdded: update media device list.");
137                     if (mBluetoothDeviceUpdater != null) {
138                         mBluetoothDeviceUpdater.forceUpdate();
139                     }
140                 }
141 
142                 @Override
143                 public void onSourceAddFailed(
144                         @NonNull BluetoothDevice sink,
145                         @NonNull BluetoothLeBroadcastMetadata source,
146                         int reason) {}
147 
148                 @Override
149                 public void onSourceModified(
150                         @NonNull BluetoothDevice sink, int sourceId, int reason) {}
151 
152                 @Override
153                 public void onSourceModifyFailed(
154                         @NonNull BluetoothDevice sink, int sourceId, int reason) {}
155 
156                 @Override
157                 public void onSourceRemoved(
158                         @NonNull BluetoothDevice sink, int sourceId, int reason) {
159                     Log.d(TAG, "onSourceRemoved: update media device list.");
160                     if (mBluetoothDeviceUpdater != null) {
161                         mBluetoothDeviceUpdater.forceUpdate();
162                     }
163                 }
164 
165                 @Override
166                 public void onSourceRemoveFailed(
167                         @NonNull BluetoothDevice sink, int sourceId, int reason) {}
168 
169                 @Override
170                 public void onReceiveStateChanged(
171                         @NonNull BluetoothDevice sink,
172                         int sourceId,
173                         @NonNull BluetoothLeBroadcastReceiveState state) {}
174             };
175 
AvailableMediaDeviceGroupController(Context context)176     public AvailableMediaDeviceGroupController(Context context) {
177         super(context, KEY);
178         mBtManager = Utils.getLocalBtManager(mContext);
179         mExecutor = Executors.newSingleThreadExecutor();
180         if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
181             mBroadcast =
182                     mBtManager == null
183                             ? null
184                             : mBtManager.getProfileManager().getLeAudioBroadcastProfile();
185             mAssistant =
186                     mBtManager == null
187                             ? null
188                             : mBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
189         }
190     }
191 
192     @Override
onStart(@onNull LifecycleOwner owner)193     public void onStart(@NonNull LifecycleOwner owner) {
194         if (isAvailable()) {
195             updateTitle();
196         }
197         if (mBtManager == null) {
198             Log.d(TAG, "onStart() Bluetooth is not supported on this device");
199             return;
200         }
201         if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
202             registerAudioSharingCallbacks();
203         }
204         mBtManager.getEventManager().registerCallback(this);
205         if (mBluetoothDeviceUpdater != null) {
206             mBluetoothDeviceUpdater.registerCallback();
207             mBluetoothDeviceUpdater.refreshPreference();
208         }
209     }
210 
211     @Override
onStop(@onNull LifecycleOwner owner)212     public void onStop(@NonNull LifecycleOwner owner) {
213         if (mBtManager == null) {
214             Log.d(TAG, "onStop() Bluetooth is not supported on this device");
215             return;
216         }
217         if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
218             unregisterAudioSharingCallbacks();
219         }
220         if (mBluetoothDeviceUpdater != null) {
221             mBluetoothDeviceUpdater.unregisterCallback();
222         }
223         mBtManager.getEventManager().unregisterCallback(this);
224     }
225 
226     @Override
displayPreference(PreferenceScreen screen)227     public void displayPreference(PreferenceScreen screen) {
228         super.displayPreference(screen);
229 
230         mPreferenceGroup = screen.findPreference(KEY);
231         if (mPreferenceGroup != null) {
232             mPreferenceGroup.setVisible(false);
233         }
234 
235         if (isAvailable()) {
236             if (mBluetoothDeviceUpdater != null) {
237                 mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
238                 mBluetoothDeviceUpdater.forceUpdate();
239             }
240         }
241     }
242 
243     @Override
getAvailabilityStatus()244     public int getAvailabilityStatus() {
245         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
246                 ? AVAILABLE_UNSEARCHABLE
247                 : UNSUPPORTED_ON_DEVICE;
248     }
249 
250     @Override
getPreferenceKey()251     public String getPreferenceKey() {
252         return KEY;
253     }
254 
255     @Override
onDeviceAdded(Preference preference)256     public void onDeviceAdded(Preference preference) {
257         if (mPreferenceGroup != null) {
258             if (mPreferenceGroup.getPreferenceCount() == 0) {
259                 mPreferenceGroup.setVisible(true);
260             }
261             mPreferenceGroup.addPreference(preference);
262         }
263     }
264 
265     @Override
onDeviceRemoved(Preference preference)266     public void onDeviceRemoved(Preference preference) {
267         if (mPreferenceGroup != null) {
268             mPreferenceGroup.removePreference(preference);
269             if (mPreferenceGroup.getPreferenceCount() == 0) {
270                 mPreferenceGroup.setVisible(false);
271             }
272         }
273     }
274 
275     @Override
onDeviceClick(Preference preference)276     public void onDeviceClick(Preference preference) {
277         final CachedBluetoothDevice cachedDevice =
278                 ((BluetoothDevicePreference) preference).getBluetoothDevice();
279         if (BluetoothUtils.isAudioSharingUIAvailable(mContext) && mDialogHandler != null) {
280             mDialogHandler.handleDeviceConnected(cachedDevice, /* userTriggered= */ true);
281             FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
282                     .action(mContext, SettingsEnums.ACTION_MEDIA_DEVICE_CLICK);
283         } else {
284             cachedDevice.setActive();
285         }
286     }
287 
init(DashboardFragment fragment)288     public void init(DashboardFragment fragment) {
289         mFragmentManager = fragment.getParentFragmentManager();
290         mBluetoothDeviceUpdater =
291                 new AvailableMediaBluetoothDeviceUpdater(
292                         fragment.getContext(),
293                         AvailableMediaDeviceGroupController.this,
294                         fragment.getMetricsCategory());
295         if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
296             mDialogHandler = new AudioSharingDialogHandler(mContext, fragment);
297         }
298     }
299 
300     @VisibleForTesting
setFragmentManager(FragmentManager fragmentManager)301     public void setFragmentManager(FragmentManager fragmentManager) {
302         mFragmentManager = fragmentManager;
303     }
304 
305     @VisibleForTesting
setBluetoothDeviceUpdater(BluetoothDeviceUpdater bluetoothDeviceUpdater)306     public void setBluetoothDeviceUpdater(BluetoothDeviceUpdater bluetoothDeviceUpdater) {
307         mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
308     }
309 
310     @VisibleForTesting
setDialogHandler(AudioSharingDialogHandler dialogHandler)311     public void setDialogHandler(AudioSharingDialogHandler dialogHandler) {
312         mDialogHandler = dialogHandler;
313     }
314 
315     @Override
onAudioModeChanged()316     public void onAudioModeChanged() {
317         updateTitle();
318     }
319 
320     @Override
onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile)321     public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
322         // exclude inactive device
323         if (activeDevice == null) {
324             return;
325         }
326 
327         if (bluetoothProfile == BluetoothProfile.HEARING_AID) {
328             HearingAidUtils.launchHearingAidPairingDialog(
329                     mFragmentManager, activeDevice, getMetricsCategory());
330         }
331     }
332 
updateTitle()333     private void updateTitle() {
334         if (mPreferenceGroup == null) return;
335         var unused =
336                 ThreadUtils.postOnBackgroundThread(
337                         () -> {
338                             Log.d(TAG, "updateTitle, check current status");
339                             int titleResId;
340                             if (isAudioModeOngoingCall(mContext)) {
341                                 // in phone call
342                                 titleResId = R.string.connected_device_call_device_title;
343                             } else if (BluetoothUtils.isAudioSharingUIAvailable(mContext)
344                                     && BluetoothUtils.isBroadcasting(mBtManager)) {
345                                 // without phone call, in audio sharing
346                                 titleResId = R.string.audio_sharing_media_device_group_title;
347                             } else {
348                                 // without phone call, not audio sharing
349                                 titleResId = R.string.connected_device_media_device_title;
350                             }
351                             Log.d(TAG, "updateTitle, title = " + titleResId);
352                             mContext.getMainExecutor()
353                                     .execute(
354                                             () -> {
355                                                 if (mPreferenceGroup != null) {
356                                                     mPreferenceGroup.setTitle(titleResId);
357                                                 }
358                                             });
359                         });
360     }
361 
registerAudioSharingCallbacks()362     private void registerAudioSharingCallbacks() {
363         if (mBroadcast != null) {
364             mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
365         }
366         if (mAssistant != null) {
367             mAssistant.registerServiceCallBack(mExecutor, mAssistantCallback);
368         }
369         if (mDialogHandler != null) {
370             mDialogHandler.registerCallbacks(mExecutor);
371         }
372     }
373 
unregisterAudioSharingCallbacks()374     private void unregisterAudioSharingCallbacks() {
375         if (mBroadcast != null) {
376             mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
377         }
378         if (mAssistant != null) {
379             mAssistant.unregisterServiceCallBack(mAssistantCallback);
380         }
381         if (mDialogHandler != null) {
382             mDialogHandler.unregisterCallbacks();
383         }
384     }
385 }
386