• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.audiostreams;
18 
19 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsDashboardFragment.KEY_BROADCAST_METADATA;
20 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsHelper.getEnabledScreenReaderServices;
21 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsHelper.setAccessibilityServiceOff;
22 import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA;
23 
24 import android.app.Activity;
25 import android.app.Dialog;
26 import android.app.settings.SettingsEnums;
27 import android.bluetooth.BluetoothDevice;
28 import android.bluetooth.BluetoothLeBroadcastMetadata;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.os.Bundle;
32 import android.text.TextUtils;
33 import android.util.Log;
34 
35 import androidx.annotation.Nullable;
36 import androidx.annotation.VisibleForTesting;
37 
38 import com.android.settings.R;
39 import com.android.settings.bluetooth.Utils;
40 import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
41 import com.android.settings.core.SubSettingLauncher;
42 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
43 import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
44 import com.android.settingslib.bluetooth.BluetoothUtils;
45 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
46 import com.android.settingslib.utils.ThreadUtils;
47 
48 public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
49     private static final String TAG = "AudioStreamConfirmDialog";
50 
51     @VisibleForTesting
52     static final int DEFAULT_DEVICE_NAME = R.string.audio_streams_dialog_default_device;
53 
54     private Context mContext;
55     @VisibleForTesting
56     @Nullable
57     Activity mActivity;
58     @Nullable
59     private BluetoothLeBroadcastMetadata mBroadcastMetadata;
60     @Nullable
61     private BluetoothDevice mConnectedDevice;
62     private int mAudioStreamConfirmDialogId = SettingsEnums.PAGE_UNKNOWN;
63 
64     @Override
onAttach(Context context)65     public void onAttach(Context context) {
66         mContext = context;
67         mActivity = getActivity();
68         if (mActivity == null) {
69             Log.w(TAG, "onAttach() mActivity is null!");
70             return;
71         }
72         Intent intent = mActivity.getIntent();
73         mBroadcastMetadata = getMetadata(intent);
74         mConnectedDevice = getConnectedDevice();
75         mAudioStreamConfirmDialogId =
76                 getDialogId(mBroadcastMetadata != null, mConnectedDevice != null);
77         super.onAttach(context);
78     }
79 
80     @Override
onCreate(Bundle savedInstanceState)81     public void onCreate(Bundle savedInstanceState) {
82         super.onCreate(savedInstanceState);
83         setShowsDialog(true);
84     }
85 
86     @Override
onCreateDialog(Bundle savedInstanceState)87     public Dialog onCreateDialog(Bundle savedInstanceState) {
88         return switch (mAudioStreamConfirmDialogId) {
89             case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_FEATURE_UNSUPPORTED ->
90                     getUnsupportedDialog();
91             case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE -> getNoLeDeviceDialog();
92             case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_TURN_OFF_TALKBACK ->
93                     getTurnOffTalkbackDialog();
94             case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_LISTEN -> getConfirmDialog();
95             default -> getErrorDialog();
96         };
97     }
98 
99     @Override
getMetricsCategory()100     public int getMetricsCategory() {
101         return mAudioStreamConfirmDialogId;
102     }
103 
getConfirmDialog()104     private Dialog getConfirmDialog() {
105         return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
106                 .setTitle(getString(R.string.audio_streams_dialog_listen_to_audio_stream))
107                 .setSubTitle1(
108                         mBroadcastMetadata != null
109                                 ? AudioStreamsHelper.getBroadcastName(mBroadcastMetadata)
110                                 : "")
111                 .setSubTitle2(
112                         getString(
113                                 R.string.audio_streams_dialog_control_volume,
114                                 getConnectedDeviceName()))
115                 .setLeftButtonText(getString(com.android.settings.R.string.cancel))
116                 .setLeftButtonOnClickListener(
117                         unused -> {
118                             dismiss();
119                             if (mActivity != null) {
120                                 mActivity.finish();
121                             }
122                         })
123                 .setRightButtonText(getString(R.string.audio_streams_dialog_listen))
124                 .setRightButtonOnClickListener(
125                         unused -> {
126                             mMetricsFeatureProvider.action(
127                                     getActivity(),
128                                     SettingsEnums
129                                             .ACTION_AUDIO_STREAM_CONFIRM_LAUNCH_MAIN_BUTTON_CLICK);
130                             launchAudioStreamsActivity();
131                             dismiss();
132                             if (mActivity != null) {
133                                 mActivity.finish();
134                             }
135                         })
136                 .build();
137     }
138 
139     private Dialog getUnsupportedDialog() {
140         return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
141                 .setTitle(getString(R.string.audio_streams_dialog_cannot_listen))
142                 .setSubTitle1(
143                         mBroadcastMetadata != null
144                                 ? AudioStreamsHelper.getBroadcastName(mBroadcastMetadata)
145                                 : "")
146                 .setSubTitle2(getString(R.string.audio_streams_dialog_unsupported_device_subtitle))
147                 .setRightButtonText(getString(R.string.audio_streams_dialog_close))
148                 .setRightButtonOnClickListener(
149                         unused -> {
150                             dismiss();
151                             if (mActivity != null) {
152                                 mActivity.finish();
153                             }
154                         })
155                 .build();
156     }
157 
158     private Dialog getErrorDialog() {
159         return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
160                 .setTitle(getString(R.string.audio_streams_dialog_cannot_listen))
161                 .setSubTitle2(
162                         getString(
163                                 R.string.audio_streams_dialog_cannot_play,
164                                 getConnectedDeviceName()))
165                 .setRightButtonText(getString(R.string.audio_streams_dialog_close))
166                 .setRightButtonOnClickListener(
167                         unused -> {
168                             dismiss();
169                             if (mActivity != null) {
170                                 mActivity.finish();
171                             }
172                         })
173                 .build();
174     }
175 
176     private Dialog getTurnOffTalkbackDialog() {
177         return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
178                 .setTitle(getString(R.string.audio_streams_dialog_turn_off_talkback_title))
179                 .setSubTitle2(getString(R.string.audio_streams_dialog_turn_off_talkback_subtitle))
180                 .setLeftButtonText(getString(R.string.cancel))
181                 .setLeftButtonOnClickListener(
182                         unused -> {
183                             dismiss();
184                             if (mActivity != null) {
185                                 mActivity.finish();
186                             }
187                         })
188                 .setRightButtonText(
189                         getString(R.string.audio_streams_dialog_turn_off_talkback_button))
190                 .setRightButtonOnClickListener(
191                         dialog -> {
192                             var unused = ThreadUtils.postOnBackgroundThread(() -> {
193                                 var enabledScreenReader = getEnabledScreenReaderServices(mContext);
194                                 if (!enabledScreenReader.isEmpty()) {
195                                     setAccessibilityServiceOff(mContext, enabledScreenReader);
196                                 }
197                             });
198                             dismiss();
199                             if (mActivity != null) {
200                                 mActivity.finish();
201                             }
202                         })
203                 .build();
204     }
205 
206     private Dialog getNoLeDeviceDialog() {
207         return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
208                 .setTitle(getString(R.string.audio_streams_dialog_no_le_device_title))
209                 .setSubTitle2(getString(R.string.audio_streams_dialog_no_le_device_subtitle))
210                 .setLeftButtonText(getString(R.string.audio_streams_dialog_close))
211                 .setLeftButtonOnClickListener(
212                         unused -> {
213                             dismiss();
214                             if (mActivity != null) {
215                                 mActivity.finish();
216                             }
217                         })
218                 .setRightButtonText(getString(R.string.audio_streams_dialog_no_le_device_button))
219                 .setRightButtonOnClickListener(
220                         dialog -> {
221                             new SubSettingLauncher(mContext)
222                                     .setDestination(
223                                             ConnectedDeviceDashboardFragment.class.getName())
224                                     .setSourceMetricsCategory(
225                                             SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE)
226                                     .launch();
227                             dismiss();
228                             if (mActivity != null) {
229                                 mActivity.finish();
230                             }
231                         })
232                 .build();
233     }
234 
235     private void launchAudioStreamsActivity() {
236         Bundle bundle = new Bundle();
237         bundle.putParcelable(KEY_BROADCAST_METADATA, mBroadcastMetadata);
238         if (mActivity != null) {
239             new SubSettingLauncher(getActivity())
240                     .setTitleText(getString(R.string.audio_streams_activity_title))
241                     .setDestination(AudioStreamsDashboardFragment.class.getName())
242                     .setArguments(bundle)
243                     .setSourceMetricsCategory(getMetricsCategory())
244                     .launch();
245         }
246     }
247 
248     private @Nullable BluetoothLeBroadcastMetadata getMetadata(Intent intent) {
249         // Get the metadata from the intent extras
250         String metadata = intent.getStringExtra(KEY_BROADCAST_METADATA);
251         if (metadata != null && !metadata.isEmpty()) {
252             return BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(metadata);
253         }
254         // Retrieve the generic data string from the intent
255         String genericData = intent.getDataString();
256         if (genericData != null && !genericData.isEmpty()) {
257             // Normalize the prefix by replacing lowercase "bluetooth" with uppercase "BLUETOOTH"
258             genericData = genericData.replaceFirst("bluetooth", "BLUETOOTH");
259             if (genericData.startsWith(SCHEME_BT_BROADCAST_METADATA)) {
260                 return BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
261                         genericData);
262             }
263         }
264         return null;
265     }
266 
267     private int getDialogId(boolean hasMetadata, boolean hasConnectedDevice) {
268         if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
269             if (!hasConnectedDevice) {
270                 return SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE;
271             }
272             if (!getEnabledScreenReaderServices(mContext).isEmpty()) {
273                 return SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_TURN_OFF_TALKBACK;
274             }
275             return hasMetadata
276                     ? SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_LISTEN
277                     : SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_DATA_ERROR;
278         } else {
279             return SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_FEATURE_UNSUPPORTED;
280         }
281     }
282 
283     @Nullable
284     private BluetoothDevice getConnectedDevice() {
285         var localBluetoothManager = Utils.getLocalBluetoothManager(getActivity());
286         if (localBluetoothManager == null) {
287             return null;
288         }
289         LocalBluetoothLeBroadcastAssistant assistant =
290                 localBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
291         if (assistant == null) {
292             return null;
293         }
294         var devices = assistant.getAllConnectedDevices();
295         return devices.isEmpty() ? null : devices.get(0);
296     }
297 
298     private String getConnectedDeviceName() {
299         if (mConnectedDevice != null) {
300             String alias = mConnectedDevice.getAlias();
301             return TextUtils.isEmpty(alias) ? getString(DEFAULT_DEVICE_NAME) : alias;
302         }
303         Log.w(TAG, "getConnectedDeviceName : no connected device!");
304         return getString(DEFAULT_DEVICE_NAME);
305     }
306 }
307