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