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 android.app.Dialog; 20 import android.app.settings.SettingsEnums; 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.util.Log; 24 import android.util.Pair; 25 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 import androidx.annotation.VisibleForTesting; 29 import androidx.appcompat.app.AlertDialog; 30 import androidx.fragment.app.Fragment; 31 import androidx.fragment.app.FragmentActivity; 32 import androidx.fragment.app.FragmentManager; 33 import androidx.lifecycle.Lifecycle; 34 35 import com.android.settings.R; 36 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 37 import com.android.settingslib.bluetooth.BluetoothUtils; 38 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 39 import com.android.settingslib.flags.Flags; 40 41 import com.google.common.collect.Iterables; 42 43 import java.util.List; 44 45 public class AudioSharingStopDialogFragment extends InstrumentedDialogFragment { 46 private static final String TAG = "AudioSharingStopDialog"; 47 48 private static final String BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS = 49 "bundle_key_device_to_disconnect_items"; 50 private static final String BUNDLE_KEY_NEW_DEVICE_NAME = "bundle_key_new_device_name"; 51 52 // The host creates an instance of this dialog fragment must implement this interface to receive 53 // event callbacks. 54 public interface DialogEventListener { 55 /** Called when users click the stop sharing button in the dialog. */ onStopSharingClick()56 void onStopSharingClick(); 57 } 58 59 @Nullable private static DialogEventListener sListener; 60 @Nullable private static CachedBluetoothDevice sCachedDevice; 61 private static Pair<Integer, Object>[] sEventData = new Pair[0]; 62 63 @Override getMetricsCategory()64 public int getMetricsCategory() { 65 return SettingsEnums.DIALOG_STOP_AUDIO_SHARING; 66 } 67 68 /** 69 * Display the {@link AudioSharingStopDialogFragment} dialog. 70 * 71 * <p>If the dialog is showing, update the dialog message and event listener. 72 * 73 * @param host The Fragment this dialog will be hosted. 74 * @param deviceItems The existing connected device items in audio sharing session. 75 * @param newDevice The latest connected device triggered this dialog. 76 * @param listener The callback to handle the user action on this dialog. 77 * @param eventData The eventData to log with for dialog onClick events. 78 * 79 * @return whether the dialog is shown 80 */ show( @ullable Fragment host, @NonNull List<AudioSharingDeviceItem> deviceItems, @NonNull CachedBluetoothDevice newDevice, @NonNull DialogEventListener listener, @NonNull Pair<Integer, Object>[] eventData)81 public static boolean show( 82 @Nullable Fragment host, 83 @NonNull List<AudioSharingDeviceItem> deviceItems, 84 @NonNull CachedBluetoothDevice newDevice, 85 @NonNull DialogEventListener listener, 86 @NonNull Pair<Integer, Object>[] eventData) { 87 if (host == null) { 88 Log.d(TAG, "Fail to show dialog, host is null"); 89 return false; 90 } 91 if (!BluetoothUtils.isAudioSharingUIAvailable(host.getContext())) { 92 Log.d(TAG, "Fail to show dialog, feature disabled"); 93 return false; 94 } 95 final FragmentManager manager; 96 try { 97 manager = host.getChildFragmentManager(); 98 } catch (IllegalStateException e) { 99 Log.d(TAG, "Fail to show dialog: " + e.getMessage()); 100 return false; 101 } 102 Lifecycle.State currentState = host.getLifecycle().getCurrentState(); 103 if (!currentState.isAtLeast(Lifecycle.State.CREATED)) { 104 Log.d(TAG, "Fail to show dialog with state: " + currentState); 105 return false; 106 } 107 sListener = listener; 108 sCachedDevice = newDevice; 109 sEventData = eventData; 110 AudioSharingUtils.postOnMainThread( 111 host.getContext(), 112 () -> { 113 AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG); 114 if (dialog != null) { 115 Log.d(TAG, "Dialog is showing, update the content."); 116 updateDialog(host.getContext(), deviceItems, newDevice.getName(), dialog); 117 } else { 118 Log.d(TAG, "Show up the dialog."); 119 final Bundle bundle = new Bundle(); 120 bundle.putParcelableList(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS, 121 deviceItems); 122 bundle.putString(BUNDLE_KEY_NEW_DEVICE_NAME, newDevice.getName()); 123 AudioSharingStopDialogFragment dialogFrag = 124 new AudioSharingStopDialogFragment(); 125 dialogFrag.setArguments(bundle); 126 dialogFrag.show(manager, TAG); 127 } 128 }); 129 return true; 130 } 131 132 /** Return the tag of {@link AudioSharingStopDialogFragment} dialog. */ tag()133 public static @NonNull String tag() { 134 return TAG; 135 } 136 137 /** Get the latest connected device which triggers the dialog. */ getDevice()138 public @Nullable CachedBluetoothDevice getDevice() { 139 return sCachedDevice; 140 } 141 142 /** Test only: get the {@link DialogEventListener} passed to the dialog. */ 143 @VisibleForTesting 144 @Nullable getListener()145 DialogEventListener getListener() { 146 return sListener; 147 } 148 149 /** Test only: get the event data passed to the dialog. */ 150 @VisibleForTesting 151 @NonNull getEventData()152 Pair<Integer, Object>[] getEventData() { 153 return sEventData; 154 } 155 156 @Override 157 @NonNull onCreateDialog(@ullable Bundle savedInstanceState)158 public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 159 Bundle arguments = requireArguments(); 160 List<AudioSharingDeviceItem> deviceItems = 161 arguments.getParcelable(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS, List.class); 162 String newDeviceName = arguments.getString(BUNDLE_KEY_NEW_DEVICE_NAME); 163 AlertDialog dialog = 164 AudioSharingDialogFactory.newBuilder(getActivity()) 165 .setTitleIcon(com.android.settings.R.drawable.ic_warning_24dp) 166 .setIsCustomBodyEnabled(true) 167 .setPositiveButton( 168 R.string.audio_sharing_connect_button_label, 169 (dlg, which) -> { 170 if (sListener != null) { 171 sListener.onStopSharingClick(); 172 mMetricsFeatureProvider.action( 173 getContext(), 174 SettingsEnums 175 .ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED, 176 sEventData); 177 } 178 }) 179 .setNegativeButton( 180 com.android.settings.R.string.cancel, 181 (dlg, which) -> 182 mMetricsFeatureProvider.action( 183 getContext(), 184 SettingsEnums 185 .ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED, 186 sEventData)) 187 .build(); 188 dialog.show(); 189 updateDialog(getContext(), deviceItems, newDeviceName, dialog); 190 return dialog; 191 } 192 193 @Override onDestroy()194 public void onDestroy() { 195 super.onDestroy(); 196 FragmentActivity activity = getActivity(); 197 if (Flags.promoteAudioSharingForSecondAutoConnectedLeaDevice() 198 && activity instanceof AudioSharingJoinHandlerActivity 199 && !activity.isChangingConfigurations() && !activity.isFinishing()) { 200 Log.d(TAG, "onDestroy, finish activity = " + activity.getClass().getName()); 201 activity.finish(); 202 } 203 } 204 updateDialog( @onNull Context context, @Nullable List<AudioSharingDeviceItem> deviceItems, String newDeviceName, @NonNull AlertDialog dialog)205 private static void updateDialog( 206 @NonNull Context context, 207 @Nullable List<AudioSharingDeviceItem> deviceItems, 208 String newDeviceName, 209 @NonNull AlertDialog dialog) { 210 String title = context.getString(R.string.audio_sharing_stop_dialog_title, newDeviceName); 211 String customMessage = ""; 212 if (deviceItems != null) { 213 if (deviceItems.size() == 1) { 214 customMessage = context.getString( 215 R.string.audio_sharing_stop_dialog_content, 216 Iterables.getOnlyElement(deviceItems).getName()); 217 } else if (deviceItems.size() == 2) { 218 customMessage = context.getString( 219 R.string.audio_sharing_stop_dialog_with_two_content, 220 deviceItems.get(0).getName(), 221 deviceItems.get(1).getName()); 222 } else { 223 customMessage = context.getString( 224 R.string.audio_sharing_stop_dialog_with_more_content); 225 } 226 } 227 AudioSharingDialogFactory.updateTitle(dialog, title); 228 AudioSharingDialogFactory.updateCustomMessage(dialog, customMessage); 229 } 230 } 231