From c347fba969d6b175cdd58303e0226f6d92eba4a0 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Fri, 6 Sep 2024 16:50:49 +0800 Subject: [PATCH] [Audiosharing] Add loading state dialog Test: atest Flag: com.android.settingslib.flags.enable_le_audio_sharing Bug: 362858894 Change-Id: Ic40c3c5971e29ce1dac896a8e4efa2284d95f45e --- .../dialog_audio_sharing_loading_state.xml | 39 ++++ ...udioSharingLoadingStateDialogFragment.java | 138 ++++++++++++++ ...SharingLoadingStateDialogFragmentTest.java | 174 ++++++++++++++++++ 3 files changed, 351 insertions(+) create mode 100644 res/layout/dialog_audio_sharing_loading_state.xml create mode 100644 src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java create mode 100644 tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java diff --git a/res/layout/dialog_audio_sharing_loading_state.xml b/res/layout/dialog_audio_sharing_loading_state.xml new file mode 100644 index 00000000000..024692f56eb --- /dev/null +++ b/res/layout/dialog_audio_sharing_loading_state.xml @@ -0,0 +1,39 @@ + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java new file mode 100644 index 00000000000..b00f407030d --- /dev/null +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.connecteddevice.audiosharing; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settingslib.bluetooth.BluetoothUtils; + +import com.google.common.base.Strings; + +import java.util.concurrent.TimeUnit; + +public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFragment { + private static final String TAG = "AudioSharingLoadingDlg"; + + private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message"; + private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10); + private static final int AUTO_DISMISS_MESSAGE_ID = R.id.message; + + private static String sMessage = ""; + @Nullable + private Handler mHandler; + + @Override + public int getMetricsCategory() { + // TODO: add metrics + return 0; + } + + /** + * Display the {@link AudioSharingLoadingStateDialogFragment} dialog. + * + * @param host The Fragment this dialog will be hosted by. + * @param message The content to be shown on the dialog. + */ + public static void show(@Nullable Fragment host, @NonNull String message) { + if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return; + final FragmentManager manager; + try { + manager = host.getChildFragmentManager(); + } catch (IllegalStateException e) { + Log.d(TAG, "Fail to show dialog: " + e.getMessage()); + return; + } + AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG); + if (dialog != null) { + if (sMessage.equals(message)) { + Log.d(TAG, "Dialog is showing with same message, return."); + return; + } else { + Log.d(TAG, "Dialog is showing with different message, dismiss and reshow."); + dialog.dismiss(); + } + } + sMessage = message; + Log.d(TAG, "Show up the loading dialog."); + Bundle args = new Bundle(); + args.putString(BUNDLE_KEY_MESSAGE, message); + AudioSharingLoadingStateDialogFragment dialogFrag = + new AudioSharingLoadingStateDialogFragment(); + dialogFrag.setArguments(args); + dialogFrag.show(manager, TAG); + } + + /** Dismiss the {@link AudioSharingLoadingStateDialogFragment} dialog. */ + public static void dismiss(@Nullable Fragment host) { + if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return; + final FragmentManager manager; + try { + manager = host.getChildFragmentManager(); + } catch (IllegalStateException e) { + Log.d(TAG, "Fail to dismiss dialog: " + e.getMessage()); + return; + } + AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG); + if (dialog != null) { + Log.d(TAG, "Dialog is showing, dismiss."); + dialog.dismiss(); + } + } + + @Override + @NonNull + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + mHandler = new Handler(Looper.getMainLooper()); + mHandler.postDelayed(() -> dismiss(), AUTO_DISMISS_MESSAGE_ID, + AUTO_DISMISS_TIME_THRESHOLD_MS); + Bundle args = requireArguments(); + String message = args.getString(BUNDLE_KEY_MESSAGE, ""); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + LayoutInflater inflater = LayoutInflater.from(builder.getContext()); + View customView = inflater.inflate(R.layout.dialog_audio_sharing_loading_state, /* root= */ + null); + TextView textView = customView.findViewById(R.id.message); + if (!Strings.isNullOrEmpty(message)) textView.setText(message); + AlertDialog dialog = builder.setView(customView).setCancelable(false).create(); + dialog.setCanceledOnTouchOutside(false); + return dialog; + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + super.onDismiss(dialog); + if (mHandler != null) { + mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID); + } + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java new file mode 100644 index 00000000000..b5da88cff1c --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.connecteddevice.audiosharing; + +import static com.google.common.truth.Truth.assertThat; + +import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothStatusCodes; +import android.platform.test.flag.junit.SetFlagsRule; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import com.android.settings.R; +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settingslib.flags.Flags; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.androidx.fragment.FragmentController; + +@RunWith(RobolectricTestRunner.class) +@Config( + shadows = { + ShadowAlertDialogCompat.class, + ShadowBluetoothAdapter.class, + }) +public class AudioSharingLoadingStateDialogFragmentTest { + @Rule public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final String TEST_MESSAGE1 = "message1"; + private static final String TEST_MESSAGE2 = "message2"; + + private Fragment mParent; + private AudioSharingLoadingStateDialogFragment mFragment; + + @Before + public void setUp() { + ShadowAlertDialogCompat.reset(); + ShadowBluetoothAdapter shadowBluetoothAdapter = + Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + shadowBluetoothAdapter.setEnabled(true); + shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mFragment = new AudioSharingLoadingStateDialogFragment(); + mParent = new Fragment(); + FragmentController.setupFragment( + mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null); + } + + @After + public void tearDown() { + ShadowAlertDialogCompat.reset(); + } + + @Test + public void getMetricsCategory_correctValue() { + // TODO: update real metric + assertThat(mFragment.getMetricsCategory()) + .isEqualTo(0); + } + + @Test + public void onCreateDialog_flagOff_dialogNotExist() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNull(); + } + + @Test + public void onCreateDialog_unattachedFragment_dialogNotExist() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingLoadingStateDialogFragment.show(new Fragment(), TEST_MESSAGE1); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNull(); + } + + @Test + public void onCreateDialog_flagOn_showDialog() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNotNull(); + assertThat(dialog.isShowing()).isTrue(); + TextView view = dialog.findViewById(R.id.message); + assertThat(view).isNotNull(); + assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE1); + } + + @Test + public void dismissDialog_succeed() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNotNull(); + assertThat(dialog.isShowing()).isTrue(); + + AudioSharingLoadingStateDialogFragment.dismiss(mParent); + shadowMainLooper().idle(); + assertThat(dialog.isShowing()).isFalse(); + } + + @Test + public void showDialog_sameMessage_keepExistingDialog() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNotNull(); + assertThat(dialog.isShowing()).isTrue(); + + AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1); + shadowMainLooper().idle(); + assertThat(dialog.isShowing()).isTrue(); + } + + @Test + public void showDialog_newMessage_dismissAndShowNewDialog() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNotNull(); + assertThat(dialog.isShowing()).isTrue(); + TextView view = dialog.findViewById(R.id.message); + assertThat(view).isNotNull(); + assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE1); + + AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE2); + shadowMainLooper().idle(); + assertThat(dialog.isShowing()).isFalse(); + AlertDialog newDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(newDialog).isNotNull(); + assertThat(newDialog.isShowing()).isTrue(); + view = newDialog.findViewById(R.id.message); + assertThat(view).isNotNull(); + assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2); + } +}