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