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.os.Bundle; 22 import android.util.Log; 23 import android.util.Pair; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 import androidx.annotation.VisibleForTesting; 28 import androidx.appcompat.app.AlertDialog; 29 import androidx.fragment.app.Fragment; 30 import androidx.fragment.app.FragmentManager; 31 32 import com.android.settings.R; 33 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 34 import com.android.settings.overlay.FeatureFactory; 35 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 36 import com.android.settingslib.utils.ThreadUtils; 37 38 import java.util.List; 39 import java.util.Locale; 40 41 public class AudioSharingDisconnectDialogFragment extends InstrumentedDialogFragment { 42 private static final String TAG = "AudioSharingDisconnectDialog"; 43 44 private static final String BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS = 45 "bundle_key_device_to_disconnect_items"; 46 private static final String BUNDLE_KEY_NEW_DEVICE_NAME = "bundle_key_new_device_name"; 47 48 // The host creates an instance of this dialog fragment must implement this interface to receive 49 // event callbacks. 50 public interface DialogEventListener { 51 /** 52 * Called when users click the device item to disconnect from the audio sharing in the 53 * dialog. 54 * 55 * @param item The device item clicked. 56 */ onItemClick(AudioSharingDeviceItem item)57 void onItemClick(AudioSharingDeviceItem item); 58 } 59 60 @Nullable private static DialogEventListener sListener; 61 @Nullable private static CachedBluetoothDevice sNewDevice; 62 private static Pair<Integer, Object>[] sEventData = new Pair[0]; 63 64 @Override getMetricsCategory()65 public int getMetricsCategory() { 66 return SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE; 67 } 68 69 /** 70 * Display the {@link AudioSharingDisconnectDialogFragment} dialog. 71 * 72 * <p>If the dialog is showing for the same group, update the dialog event listener. 73 * 74 * @param host The Fragment this dialog will be hosted. 75 * @param deviceItems The existing connected device items in audio sharing session. 76 * @param newDevice The latest connected device triggered this dialog. 77 * @param listener The callback to handle the user action on this dialog. 78 * @param eventData The eventData to log with for dialog onClick events. 79 */ show( @onNull Fragment host, @NonNull List<AudioSharingDeviceItem> deviceItems, @NonNull CachedBluetoothDevice newDevice, @NonNull DialogEventListener listener, @NonNull Pair<Integer, Object>[] eventData)80 public static void show( 81 @NonNull Fragment host, 82 @NonNull List<AudioSharingDeviceItem> deviceItems, 83 @NonNull CachedBluetoothDevice newDevice, 84 @NonNull DialogEventListener listener, 85 @NonNull Pair<Integer, Object>[] eventData) { 86 if (!AudioSharingUtils.isFeatureEnabled()) return; 87 FragmentManager manager = host.getChildFragmentManager(); 88 AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG); 89 if (dialog != null) { 90 int newGroupId = AudioSharingUtils.getGroupId(newDevice); 91 if (sNewDevice != null && newGroupId == AudioSharingUtils.getGroupId(sNewDevice)) { 92 Log.d( 93 TAG, 94 String.format( 95 Locale.US, 96 "Dialog is showing for the same device group %d, " 97 + "update the content.", 98 newGroupId)); 99 sListener = listener; 100 sNewDevice = newDevice; 101 sEventData = eventData; 102 return; 103 } else { 104 Log.d( 105 TAG, 106 String.format( 107 Locale.US, 108 "Dialog is showing for new device group %d, " 109 + "dismiss current dialog.", 110 newGroupId)); 111 dialog.dismiss(); 112 var unused = 113 ThreadUtils.postOnBackgroundThread( 114 () -> 115 FeatureFactory.getFeatureFactory() 116 .getMetricsFeatureProvider() 117 .action( 118 dialog.getContext(), 119 SettingsEnums 120 .ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS, 121 SettingsEnums 122 .DIALOG_AUDIO_SHARING_SWITCH_DEVICE)); 123 } 124 } 125 sListener = listener; 126 sNewDevice = newDevice; 127 sEventData = eventData; 128 Log.d(TAG, "Show up the dialog."); 129 final Bundle bundle = new Bundle(); 130 bundle.putParcelableList(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS, deviceItems); 131 bundle.putString(BUNDLE_KEY_NEW_DEVICE_NAME, newDevice.getName()); 132 AudioSharingDisconnectDialogFragment dialogFrag = 133 new AudioSharingDisconnectDialogFragment(); 134 dialogFrag.setArguments(bundle); 135 dialogFrag.show(manager, TAG); 136 } 137 138 /** Return the tag of {@link AudioSharingDisconnectDialogFragment} dialog. */ tag()139 public static @NonNull String tag() { 140 return TAG; 141 } 142 143 /** Get the latest connected device which triggers the dialog. */ getDevice()144 public @Nullable CachedBluetoothDevice getDevice() { 145 return sNewDevice; 146 } 147 148 /** Test only: get the {@link DialogEventListener} passed to the dialog. */ 149 @VisibleForTesting 150 @Nullable getListener()151 DialogEventListener getListener() { 152 return sListener; 153 } 154 155 /** Test only: get the event data passed to the dialog. */ 156 @VisibleForTesting 157 @NonNull getEventData()158 Pair<Integer, Object>[] getEventData() { 159 return sEventData; 160 } 161 162 @Override 163 @NonNull onCreateDialog(@ullable Bundle savedInstanceState)164 public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 165 Bundle arguments = requireArguments(); 166 List<AudioSharingDeviceItem> deviceItems = 167 arguments.getParcelable(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS, List.class); 168 AudioSharingDialogFactory.DialogBuilder builder = 169 AudioSharingDialogFactory.newBuilder(getActivity()) 170 .setTitle(R.string.audio_sharing_disconnect_dialog_title) 171 .setTitleIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing) 172 .setIsCustomBodyEnabled(true) 173 .setCustomMessage(R.string.audio_sharing_dialog_disconnect_content) 174 .setCustomNegativeButton( 175 com.android.settings.R.string.cancel, 176 v -> { 177 mMetricsFeatureProvider.action( 178 getContext(), 179 SettingsEnums 180 .ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED, 181 sEventData); 182 dismiss(); 183 }); 184 if (deviceItems == null) { 185 Log.d(TAG, "Create dialog error: null deviceItems"); 186 return builder.build(); 187 } 188 builder.setCustomDeviceActions( 189 new AudioSharingDeviceAdapter( 190 getContext(), 191 deviceItems, 192 (AudioSharingDeviceItem item) -> { 193 if (sListener != null) { 194 sListener.onItemClick(item); 195 mMetricsFeatureProvider.action( 196 getContext(), 197 SettingsEnums 198 .ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED, 199 sEventData); 200 } 201 dismiss(); 202 }, 203 AudioSharingDeviceAdapter.ActionType.REMOVE)); 204 return builder.build(); 205 } 206 } 207