• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 
17 package com.android.settings.connecteddevice.audiosharing;
18 
19 import static com.android.settingslib.Utils.isAudioModeOngoingCall;
20 import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE;
21 
22 import android.app.settings.SettingsEnums;
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothDevice;
25 import android.bluetooth.BluetoothLeBroadcastAssistant;
26 import android.bluetooth.BluetoothLeBroadcastMetadata;
27 import android.bluetooth.BluetoothLeBroadcastReceiveState;
28 import android.bluetooth.BluetoothProfile;
29 import android.content.ContentResolver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.database.ContentObserver;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.provider.Settings;
37 import android.util.Log;
38 
39 import androidx.annotation.NonNull;
40 import androidx.annotation.Nullable;
41 import androidx.annotation.VisibleForTesting;
42 import androidx.lifecycle.DefaultLifecycleObserver;
43 import androidx.lifecycle.LifecycleOwner;
44 import androidx.preference.Preference;
45 import androidx.preference.PreferenceGroup;
46 import androidx.preference.PreferenceScreen;
47 
48 import com.android.settings.R;
49 import com.android.settings.SettingsActivity;
50 import com.android.settings.bluetooth.BluetoothDevicePreference;
51 import com.android.settings.bluetooth.BluetoothDeviceUpdater;
52 import com.android.settings.bluetooth.Utils;
53 import com.android.settings.connecteddevice.DevicePreferenceCallback;
54 import com.android.settings.core.BasePreferenceController;
55 import com.android.settings.dashboard.DashboardFragment;
56 import com.android.settings.overlay.FeatureFactory;
57 import com.android.settingslib.bluetooth.A2dpProfile;
58 import com.android.settingslib.bluetooth.BluetoothCallback;
59 import com.android.settingslib.bluetooth.BluetoothEventManager;
60 import com.android.settingslib.bluetooth.BluetoothUtils;
61 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
62 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
63 import com.android.settingslib.bluetooth.HeadsetProfile;
64 import com.android.settingslib.bluetooth.HearingAidProfile;
65 import com.android.settingslib.bluetooth.LeAudioProfile;
66 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
67 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
68 import com.android.settingslib.bluetooth.LocalBluetoothManager;
69 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
70 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
71 import com.android.settingslib.flags.Flags;
72 import com.android.settingslib.utils.ThreadUtils;
73 
74 import java.util.Locale;
75 import java.util.concurrent.Executor;
76 import java.util.concurrent.Executors;
77 import java.util.concurrent.atomic.AtomicBoolean;
78 
79 public class AudioSharingDevicePreferenceController extends BasePreferenceController
80         implements DefaultLifecycleObserver,
81                 DevicePreferenceCallback,
82                 BluetoothCallback,
83                 LocalBluetoothProfileManager.ServiceListener {
84     private static final boolean DEBUG = BluetoothUtils.D;
85 
86     private static final String TAG = "AudioSharingDevicePrefController";
87     private static final String KEY = "audio_sharing_device_list";
88 
89     @Nullable private final LocalBluetoothManager mBtManager;
90     @Nullable private final CachedBluetoothDeviceManager mDeviceManager;
91     @Nullable private final BluetoothEventManager mEventManager;
92     @Nullable private final LocalBluetoothProfileManager mProfileManager;
93     @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
94     @Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant;
95     @Nullable private final ContentResolver mContentResolver;
96     private final Executor mExecutor;
97     private final MetricsFeatureProvider mMetricsFeatureProvider;
98     @Nullable private PreferenceGroup mPreferenceGroup;
99     @Nullable private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
100     @Nullable private DashboardFragment mFragment;
101     @Nullable private AudioSharingDialogHandler mDialogHandler;
102     private AtomicBoolean mIntentHandled = new AtomicBoolean(false);
103     private AtomicBoolean mIsAudioModeOngoingCall = new AtomicBoolean(false);
104 
105     @VisibleForTesting
106     BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
107             new BluetoothLeBroadcastAssistant.Callback() {
108                 @Override
109                 public void onSearchStarted(int reason) {}
110 
111                 @Override
112                 public void onSearchStartFailed(int reason) {}
113 
114                 @Override
115                 public void onSearchStopped(int reason) {}
116 
117                 @Override
118                 public void onSearchStopFailed(int reason) {}
119 
120                 @Override
121                 public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
122 
123                 @Override
124                 public void onSourceAdded(
125                         @NonNull BluetoothDevice sink, int sourceId, int reason) {
126                     Log.d(TAG, "onSourceAdded: update sharing device list.");
127                     if (mBluetoothDeviceUpdater != null) {
128                         mBluetoothDeviceUpdater.forceUpdate();
129                     }
130                     if (mDeviceManager != null && mDialogHandler != null) {
131                         CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(sink);
132                         if (cachedDevice != null) {
133                             mDialogHandler.closeOpeningDialogsForLeaDevice(cachedDevice);
134                         }
135                     }
136                 }
137 
138                 @Override
139                 public void onSourceAddFailed(
140                         @NonNull BluetoothDevice sink,
141                         @NonNull BluetoothLeBroadcastMetadata source,
142                         int reason) {
143                     mMetricsFeatureProvider.action(
144                             mContext,
145                             SettingsEnums.ACTION_AUDIO_SHARING_JOIN_FAILED,
146                             SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY);
147                     AudioSharingUtils.toastMessage(
148                             mContext,
149                             String.format(
150                                     Locale.US,
151                                     "Fail to add source to %s reason %d",
152                                     sink.getAddress(),
153                                     reason));
154                 }
155 
156                 @Override
157                 public void onSourceModified(
158                         @NonNull BluetoothDevice sink, int sourceId, int reason) {}
159 
160                 @Override
161                 public void onSourceModifyFailed(
162                         @NonNull BluetoothDevice sink, int sourceId, int reason) {}
163 
164                 @Override
165                 public void onSourceRemoved(
166                         @NonNull BluetoothDevice sink, int sourceId, int reason) {
167                     Log.d(TAG, "onSourceRemoved: update sharing device list.");
168                     if (mBluetoothDeviceUpdater != null) {
169                         mBluetoothDeviceUpdater.forceUpdate();
170                     }
171                 }
172 
173                 @Override
174                 public void onSourceRemoveFailed(
175                         @NonNull BluetoothDevice sink, int sourceId, int reason) {
176                     mMetricsFeatureProvider.action(
177                             mContext,
178                             SettingsEnums.ACTION_AUDIO_SHARING_LEAVE_FAILED,
179                             SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY);
180                     AudioSharingUtils.toastMessage(
181                             mContext,
182                             String.format(
183                                     Locale.US,
184                                     "Fail to remove source from %s reason %d",
185                                     sink.getAddress(),
186                                     reason));
187                 }
188 
189                 @Override
190                 public void onReceiveStateChanged(
191                         @NonNull BluetoothDevice sink,
192                         int sourceId,
193                         @NonNull BluetoothLeBroadcastReceiveState state) {}
194             };
195 
196     @VisibleForTesting
197     ContentObserver mSettingsObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
198         @Override
199         public void onChange(boolean selfChange) {
200             Log.d(TAG, "onChange, primary group id has been changed, refresh list");
201             if (mBluetoothDeviceUpdater != null) {
202                 mBluetoothDeviceUpdater.refreshPreference();
203             }
204         }
205     };
206 
AudioSharingDevicePreferenceController(Context context)207     public AudioSharingDevicePreferenceController(Context context) {
208         super(context, KEY);
209         mBtManager = Utils.getLocalBtManager(mContext);
210         mEventManager = mBtManager == null ? null : mBtManager.getEventManager();
211         mDeviceManager = mBtManager == null ? null : mBtManager.getCachedDeviceManager();
212         mProfileManager = mBtManager == null ? null : mBtManager.getProfileManager();
213         mBroadcast = mProfileManager == null ? null : mProfileManager.getLeAudioBroadcastProfile();
214         mAssistant =
215                 mProfileManager == null
216                         ? null
217                         : mProfileManager.getLeAudioBroadcastAssistantProfile();
218         mContentResolver = context.getContentResolver();
219         mExecutor = Executors.newSingleThreadExecutor();
220         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
221     }
222 
223     @Override
onStart(@onNull LifecycleOwner owner)224     public void onStart(@NonNull LifecycleOwner owner) {
225         var unused = ThreadUtils.postOnBackgroundThread(() -> {
226             if (!isAvailable()) {
227                 Log.d(TAG, "Skip onStart(), feature is not supported.");
228                 return;
229             }
230             if (!AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)
231                     && mProfileManager != null) {
232                 Log.d(TAG, "Register profile service listener");
233                 mProfileManager.addServiceListener(this);
234             }
235             if (mEventManager == null
236                     || mAssistant == null
237                     || mDialogHandler == null
238                     || mContentResolver == null
239                     || mBluetoothDeviceUpdater == null) {
240                 Log.d(TAG, "Skip onStart(), profile is not ready.");
241                 return;
242             }
243             Log.d(TAG, "onStart() Register callbacks.");
244             mEventManager.registerCallback(this);
245             mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
246             mDialogHandler.registerCallbacks(mExecutor);
247             mContentResolver.registerContentObserver(
248                     Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
249                     false,
250                     mSettingsObserver);
251             mBluetoothDeviceUpdater.registerCallback();
252             mBluetoothDeviceUpdater.refreshPreference();
253             mIsAudioModeOngoingCall.set(isAudioModeOngoingCall(mContext));
254             updateTitle();
255         });
256     }
257 
258     @Override
onStop(@onNull LifecycleOwner owner)259     public void onStop(@NonNull LifecycleOwner owner) {
260         var unused = ThreadUtils.postOnBackgroundThread(() -> {
261             if (!isAvailable()) {
262                 Log.d(TAG, "Skip onStop(), feature is not supported.");
263                 return;
264             }
265             if (mProfileManager != null) {
266                 mProfileManager.removeServiceListener(this);
267             }
268             if (mEventManager == null
269                     || mAssistant == null
270                     || mDialogHandler == null
271                     || mContentResolver == null
272                     || mBluetoothDeviceUpdater == null) {
273                 Log.d(TAG, "Skip onStop(), profile is not ready.");
274                 return;
275             }
276             Log.d(TAG, "onStop() Unregister callbacks.");
277             mEventManager.unregisterCallback(this);
278             mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
279             mDialogHandler.unregisterCallbacks();
280             mContentResolver.unregisterContentObserver(mSettingsObserver);
281             mBluetoothDeviceUpdater.unregisterCallback();
282         });
283     }
284 
285     @Override
onServiceConnected()286     public void onServiceConnected() {
287         if (AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
288             if (mProfileManager != null) {
289                 mProfileManager.removeServiceListener(this);
290             }
291             if (!mIntentHandled.get()) {
292                 Log.d(TAG, "onServiceConnected: handleDeviceClickFromIntent");
293                 handleDeviceClickFromIntent();
294                 mIntentHandled.set(true);
295             }
296         }
297     }
298 
299     @Override
onServiceDisconnected()300     public void onServiceDisconnected() {
301         // Do nothing
302     }
303 
304     @Override
displayPreference(PreferenceScreen screen)305     public void displayPreference(PreferenceScreen screen) {
306         super.displayPreference(screen);
307         mPreferenceGroup = screen.findPreference(KEY);
308         if (mPreferenceGroup != null) {
309             mPreferenceGroup.setVisible(false);
310         }
311 
312         if (isAvailable()) {
313             if (mBluetoothDeviceUpdater != null) {
314                 mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
315                 mBluetoothDeviceUpdater.forceUpdate();
316             }
317             if (AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
318                 if (!mIntentHandled.get()) {
319                     Log.d(TAG, "displayPreference: profile ready, handleDeviceClickFromIntent");
320                     var unused =
321                             ThreadUtils.postOnBackgroundThread(() -> handleDeviceClickFromIntent());
322                     mIntentHandled.set(true);
323                 }
324             }
325         }
326     }
327 
328     @Override
getAvailabilityStatus()329     public int getAvailabilityStatus() {
330         return (BluetoothUtils.isAudioSharingUIAvailable(mContext)
331                 && mBluetoothDeviceUpdater != null)
332                 ? AVAILABLE_UNSEARCHABLE
333                 : UNSUPPORTED_ON_DEVICE;
334     }
335 
336     @Override
getPreferenceKey()337     public String getPreferenceKey() {
338         return KEY;
339     }
340 
341     @Override
onDeviceAdded(Preference preference)342     public void onDeviceAdded(Preference preference) {
343         if (mPreferenceGroup != null) {
344             if (mPreferenceGroup.getPreferenceCount() == 0) {
345                 mPreferenceGroup.setVisible(true);
346             }
347             mPreferenceGroup.addPreference(preference);
348         }
349     }
350 
351     @Override
onDeviceRemoved(Preference preference)352     public void onDeviceRemoved(Preference preference) {
353         if (mPreferenceGroup != null) {
354             mPreferenceGroup.removePreference(preference);
355             if (mPreferenceGroup.getPreferenceCount() == 0) {
356                 mPreferenceGroup.setVisible(false);
357             }
358         }
359     }
360 
361     @Override
onProfileConnectionStateChanged( @onNull CachedBluetoothDevice cachedDevice, @ConnectionState int state, int bluetoothProfile)362     public void onProfileConnectionStateChanged(
363             @NonNull CachedBluetoothDevice cachedDevice,
364             @ConnectionState int state,
365             int bluetoothProfile) {
366         if (mDialogHandler == null || mBroadcast == null || mAssistant == null
367                 || mFragment == null) {
368             Log.d(TAG, "Ignore onProfileConnectionStateChanged, not init correctly");
369             return;
370         }
371         if (Flags.promoteAudioSharingForSecondAutoConnectedLeaDevice() && mBroadcast.isEnabled(
372                 null)) {
373             Log.d(TAG, "Ignore onProfileConnectionStateChanged, in broadcast");
374             // Device connected in broadcast will be handled in sysui via settingslib
375             return;
376         }
377         if (!isMediaDevice(cachedDevice)) {
378             Log.d(TAG, "Ignore onProfileConnectionStateChanged, not a media device");
379             return;
380         }
381         // Close related dialogs if the BT remote device is disconnected.
382         if (state == BluetoothAdapter.STATE_DISCONNECTED) {
383             boolean isLeAudioSupported = BluetoothUtils.isLeAudioSupported(cachedDevice);
384             if (isLeAudioSupported
385                     && bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
386                 Log.d(TAG, "closeOpeningDialogsForLeaDevice");
387                 mDialogHandler.closeOpeningDialogsForLeaDevice(cachedDevice);
388                 return;
389             }
390             if (!isLeAudioSupported && !cachedDevice.isConnected()) {
391                 Log.d(TAG, "closeOpeningDialogsForNonLeaDevice");
392                 mDialogHandler.closeOpeningDialogsForNonLeaDevice(cachedDevice);
393                 return;
394             }
395         }
396         if (state != BluetoothAdapter.STATE_CONNECTED || !cachedDevice.getDevice().isConnected()) {
397             Log.d(TAG, "Ignore onProfileConnectionStateChanged, not connected state");
398             return;
399         }
400         handleOnProfileStateChanged(cachedDevice, bluetoothProfile);
401     }
402 
403     @Override
onBluetoothStateChanged(@dapterState int bluetoothState)404     public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
405         if (bluetoothState == BluetoothAdapter.STATE_OFF && mDialogHandler != null) {
406             mDialogHandler.closeOpeningDialogsOtherThan("");
407         }
408     }
409 
410     @Override
onAudioModeChanged()411     public void onAudioModeChanged() {
412         mIsAudioModeOngoingCall.set(isAudioModeOngoingCall(mContext));
413         updateTitle();
414     }
415 
416     @Override
onDeviceClick(@onNull Preference preference)417     public void onDeviceClick(@NonNull Preference preference) {
418         boolean isCallMode = mIsAudioModeOngoingCall.get();
419         if (isCallMode) {
420             Log.d(TAG, "onDeviceClick, set active in call mode");
421             CachedBluetoothDevice cachedDevice =
422                     ((BluetoothDevicePreference) preference).getBluetoothDevice();
423             cachedDevice.setActive();
424             AudioSharingUtils.setUserPreferredPrimary(mContext, cachedDevice);
425         }
426         mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUDIO_SHARING_DEVICE_CLICK,
427                 isCallMode);
428     }
429 
430     /**
431      * Initialize the controller.
432      *
433      * @param fragment The fragment to provide the context and metrics category for {@link
434      *     AudioSharingBluetoothDeviceUpdater} and provide the host for dialogs.
435      */
init(DashboardFragment fragment)436     public void init(DashboardFragment fragment) {
437         mFragment = fragment;
438         mBluetoothDeviceUpdater =
439                 new AudioSharingBluetoothDeviceUpdater(
440                         fragment.getContext(),
441                         AudioSharingDevicePreferenceController.this,
442                         fragment.getMetricsCategory());
443         mDialogHandler = new AudioSharingDialogHandler(mContext, fragment);
444     }
445 
446     @VisibleForTesting
setBluetoothDeviceUpdater(@ullable BluetoothDeviceUpdater bluetoothDeviceUpdater)447     void setBluetoothDeviceUpdater(@Nullable BluetoothDeviceUpdater bluetoothDeviceUpdater) {
448         mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
449     }
450 
451     @VisibleForTesting
setDialogHandler(@ullable AudioSharingDialogHandler dialogHandler)452     void setDialogHandler(@Nullable AudioSharingDialogHandler dialogHandler) {
453         mDialogHandler = dialogHandler;
454     }
455 
456     @VisibleForTesting
setHostFragment(@ullable DashboardFragment fragment)457     void setHostFragment(@Nullable DashboardFragment fragment) {
458         mFragment = fragment;
459     }
460 
461     /** Test only: set intent handle state for test. */
462     @VisibleForTesting
setIntentHandled(boolean handled)463     void setIntentHandled(boolean handled) {
464         mIntentHandled.set(handled);
465     }
466 
handleOnProfileStateChanged( @onNull CachedBluetoothDevice cachedDevice, int bluetoothProfile)467     private void handleOnProfileStateChanged(
468             @NonNull CachedBluetoothDevice cachedDevice, int bluetoothProfile) {
469         boolean isLeAudioSupported = BluetoothUtils.isLeAudioSupported(cachedDevice);
470         // For eligible (LE audio) remote device, we only check its connected LE audio assistant
471         // profile.
472         if (isLeAudioSupported
473                 && bluetoothProfile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
474             Log.d(
475                     TAG,
476                     "Ignore onProfileConnectionStateChanged, not the le assistant profile for"
477                             + " le audio device");
478             return;
479         }
480         boolean isFirstConnectedProfile = isFirstConnectedProfile(cachedDevice, bluetoothProfile);
481         // For ineligible (non LE audio) remote device, we only check its first connected profile.
482         if (!isLeAudioSupported && !isFirstConnectedProfile) {
483             Log.d(
484                     TAG,
485                     "Ignore onProfileConnectionStateChanged, not the first connected profile for"
486                             + " non le audio device");
487             return;
488         }
489         Log.d(TAG, "Start handling onProfileConnectionStateChanged for "
490                 + cachedDevice.getDevice().getAnonymizedAddress());
491         // Check nullability to pass NullAway check
492         if (mDialogHandler != null) {
493             mDialogHandler.handleDeviceConnected(cachedDevice, /* userTriggered= */ false);
494         }
495     }
496 
isMediaDevice(CachedBluetoothDevice cachedDevice)497     private boolean isMediaDevice(CachedBluetoothDevice cachedDevice) {
498         return cachedDevice.getUiAccessibleProfiles().stream()
499                 .anyMatch(
500                         profile ->
501                                 profile instanceof A2dpProfile
502                                         || profile instanceof HearingAidProfile
503                                         || profile instanceof LeAudioProfile
504                                         || profile instanceof HeadsetProfile);
505     }
506 
isFirstConnectedProfile( CachedBluetoothDevice cachedDevice, int bluetoothProfile)507     private boolean isFirstConnectedProfile(
508             CachedBluetoothDevice cachedDevice, int bluetoothProfile) {
509         return cachedDevice.getProfiles().stream()
510                 .noneMatch(
511                         profile ->
512                                 profile.getProfileId() != bluetoothProfile
513                                         && profile.getConnectionStatus(cachedDevice.getDevice())
514                                                 == BluetoothProfile.STATE_CONNECTED);
515     }
516 
517     /**
518      * Handle device click triggered by intent.
519      *
520      * <p>When user click device from BT QS dialog, BT QS will send intent to open {@link
521      * com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment} and handle device
522      * click event under some conditions.
523      *
524      * <p>This method will be called when displayPreference if the audio sharing profiles are ready.
525      * If the profiles are not ready when the preference display, this method will be called when
526      * onServiceConnected.
527      */
handleDeviceClickFromIntent()528     private void handleDeviceClickFromIntent() {
529         if (mFragment == null
530                 || mFragment.getActivity() == null
531                 || mFragment.getActivity().getIntent() == null) {
532             Log.d(TAG, "Skip handleDeviceClickFromIntent, fragment intent is null");
533             return;
534         }
535         Intent intent = mFragment.getActivity().getIntent();
536         Bundle args = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
537         BluetoothDevice device =
538                 args == null
539                         ? null
540                         : args.getParcelable(EXTRA_BLUETOOTH_DEVICE, BluetoothDevice.class);
541         CachedBluetoothDevice cachedDevice =
542                 (device == null || mDeviceManager == null)
543                         ? null
544                         : mDeviceManager.findDevice(device);
545         if (cachedDevice == null) {
546             Log.d(TAG, "Skip handleDeviceClickFromIntent, device is null");
547             return;
548         }
549         // Check nullability to pass NullAway check
550         if (device != null && !device.isConnected()) {
551             Log.d(TAG, "handleDeviceClickFromIntent: connect");
552             cachedDevice.connect();
553         } else if (mDialogHandler != null) {
554             Log.d(TAG, "handleDeviceClickFromIntent: trigger dialog handler");
555             mDialogHandler.handleDeviceConnected(cachedDevice, /* userTriggered= */ true);
556         }
557     }
558 
updateTitle()559     private void updateTitle() {
560         if (mPreferenceGroup == null) return;
561         int titleResId;
562         if (mIsAudioModeOngoingCall.get()) {
563             // in phone call
564             titleResId = R.string.connected_device_call_device_title;
565         } else {
566             // without phone call
567             titleResId = R.string.audio_sharing_device_group_title;
568         }
569         AudioSharingUtils.postOnMainThread(mContext,
570                 () -> {
571                     if (mPreferenceGroup != null) {
572                         mPreferenceGroup.setTitle(titleResId);
573                     }
574                 });
575     }
576 }
577