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