Loading src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java +8 −27 Original line number Diff line number Diff line Loading @@ -31,15 +31,12 @@ import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import com.android.settings.R; import com.android.settings.SettingsActivity; Loading Loading @@ -71,8 +68,9 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere private volatile BluetoothDevice mJustBonded = null; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); @VisibleForTesting @Nullable private AlertDialog mProgressDialog = null; ProgressDialogFragment mProgressDialog = null; @VisibleForTesting boolean mShouldTriggerAudioSharingShareThenPairFlow = false; private CopyOnWriteArrayList<BluetoothDevice> mDevicesWithMetadataChangedListener = Loading Loading @@ -384,41 +382,24 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere finish(); } // TODO: use DialogFragment private void showConnectingDialog(@NonNull String deviceName) { postOnMainThread(() -> { String message = getContext().getString(R.string.progress_dialog_connect_device_content, deviceName); if (mProgressDialog != null) { Log.d(getLogTag(), "showConnectingDialog, is already showing"); TextView textView = mProgressDialog.findViewById(R.id.message); if (textView != null && !message.equals(textView.getText().toString())) { Log.d(getLogTag(), "showConnectingDialog, update message"); textView.setText(message); if (mProgressDialog == null) { mProgressDialog = ProgressDialogFragment.newInstance(this); } return; if (mProgressDialog != null) { mProgressDialog.show(message); } Log.d(getLogTag(), "showConnectingDialog, show dialog"); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); LayoutInflater inflater = LayoutInflater.from(builder.getContext()); View customView = inflater.inflate( R.layout.dialog_audio_sharing_progress, /* root= */ null); TextView textView = customView.findViewById(R.id.message); if (textView != null) { textView.setText(message); } AlertDialog dialog = builder.setView(customView).setCancelable(false).create(); dialog.setCanceledOnTouchOutside(false); mProgressDialog = dialog; dialog.show(); }); } private void dismissConnectingDialog() { postOnMainThread(() -> { if (mProgressDialog != null) { mProgressDialog.dismiss(); Log.d(getLogTag(), "Dismiss connecting dialog."); mProgressDialog.dismissAllowingStateLoss(); } }); } Loading src/com/android/settings/bluetooth/ProgressDialogFragment.java 0 → 100644 +133 −0 Original line number Diff line number Diff line /* * 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.bluetooth; import android.app.Dialog; import android.os.Bundle; 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.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.Lifecycle; import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.google.common.base.Strings; public class ProgressDialogFragment extends InstrumentedDialogFragment { private static final String TAG = "BTProgressDialog"; private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message"; @Nullable private static FragmentManager sManager; @Nullable private static Lifecycle sLifecycle; private String mMessage = ""; @Nullable private AlertDialog mAlertDialog; @Override public int getMetricsCategory() { // TODO: add metrics return 0; } /** * Returns a new instance of {@link ProgressDialogFragment} dialog. * * @param host The Fragment this dialog will be hosted. */ @Nullable public static ProgressDialogFragment newInstance(@Nullable Fragment host) { if (host == null) return null; try { sManager = host.getChildFragmentManager(); sLifecycle = host.getLifecycle(); } catch (IllegalStateException e) { Log.d(TAG, "Fail to create new instance: " + e.getMessage()); return null; } return new ProgressDialogFragment(); } /** * Display {@link ProgressDialogFragment} dialog. * * @param message The message to be shown on the dialog */ public void show(@NonNull String message) { if (sManager == null) return; Lifecycle.State currentState = sLifecycle == null ? null : sLifecycle.getCurrentState(); if (currentState == null || !currentState.isAtLeast(Lifecycle.State.STARTED)) { Log.d(TAG, "Fail to show dialog with state: " + currentState); return; } if (mAlertDialog != null && mAlertDialog.isShowing()) { if (!mMessage.equals(message)) { Log.d(TAG, "Update dialog message."); TextView messageView = mAlertDialog.findViewById(R.id.message); if (messageView != null) { messageView.setText(message); } mMessage = message; } Log.d(TAG, "Dialog is showing, return."); return; } mMessage = message; Log.d(TAG, "Show up the progress dialog."); Bundle args = new Bundle(); args.putString(BUNDLE_KEY_MESSAGE, message); setArguments(args); show(sManager, TAG); } /** Returns the current message on the dialog. */ @VisibleForTesting @NonNull public String getMessage() { return mMessage; } private ProgressDialogFragment() { } @Override @NonNull public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 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_progress, /* root= */ null); TextView textView = customView.findViewById(R.id.message); if (textView != null && !Strings.isNullOrEmpty(message)) { textView.setText(message); } AlertDialog dialog = builder.setView(customView).setCancelable(false).create(); dialog.setCanceledOnTouchOutside(false); mAlertDialog = dialog; return dialog; } } src/com/android/settings/connecteddevice/audiosharing/AudioSharingProgressDialogFragment.java +1 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ public class AudioSharingProgressDialogFragment extends InstrumentedDialogFragme if (messageView != null) { messageView.setText(message); } sMessage = message; } Log.d(TAG, "Dialog is showing, return."); return; Loading tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java +69 −9 Original line number Diff line number Diff line Loading @@ -46,17 +46,21 @@ import android.os.Bundle; import android.os.Looper; import android.platform.test.flag.junit.SetFlagsRule; import android.util.Pair; import android.widget.TextView; import androidx.appcompat.app.AlertDialog; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.Lifecycle; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.flags.Flags; Loading @@ -73,8 +77,14 @@ import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; import org.robolectric.shadow.api.Shadow; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; /** Tests for {@link BluetoothDevicePairingDetailBase}. */ Loading @@ -82,7 +92,7 @@ import java.util.concurrent.Executor; @Config(shadows = { ShadowBluetoothAdapter.class, ShadowAlertDialogCompat.class, com.android.settings.testutils.shadow.ShadowFragment.class, ShadowFragment.class, }) public class BluetoothDevicePairingDetailBaseTest { Loading Loading @@ -133,7 +143,6 @@ public class BluetoothDevicePairingDetailBaseTest { mFragment.mLocalManager = mLocalManager; mFragment.mBluetoothAdapter = mBluetoothAdapter; mFragment.initPreferencesFromPreferenceScreen(); } @Test Loading Loading @@ -199,22 +208,26 @@ public class BluetoothDevicePairingDetailBaseTest { } @Test @Config(shadows = ShadowDialogFragment.class) public void onDeviceBondStateChanged_bonded_pairAndJoinSharingEnabled_handle() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); ShadowDialogFragment.reset(); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); mFragment.mSelectedList.add(mBluetoothDevice); setUpFragmentWithPairAndJoinSharingIntent(true); mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED); shadowOf(Looper.getMainLooper()).idle(); AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog).isNotNull(); TextView message = dialog.findViewById(R.id.message); assertThat(message).isNotNull(); assertThat(message.getText().toString()).isEqualTo( ProgressDialogFragment progressDialog = mFragment.mProgressDialog; assertThat(progressDialog).isNotNull(); assertThat(progressDialog.getMessage()).isEqualTo( mContext.getString(R.string.progress_dialog_connect_device_content, TEST_DEVICE_ADDRESS)); assertThat( ShadowDialogFragment.isIsShowing(ProgressDialogFragment.class.getName())).isTrue(); verify(mFragment, never()).finish(); ShadowDialogFragment.reset(); } @Test Loading Loading @@ -283,9 +296,11 @@ public class BluetoothDevicePairingDetailBaseTest { } @Test @Config(shadows = ShadowDialogFragment.class) public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_pairAndJoinSharing() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); ShadowDialogFragment.reset(); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); mFragment.mSelectedList.add(mBluetoothDevice); setUpFragmentWithPairAndJoinSharingIntent(true); Loading @@ -309,6 +324,8 @@ public class BluetoothDevicePairingDetailBaseTest { assertThat(btDevice).isNotNull(); assertThat(btDevice).isEqualTo(mBluetoothDevice); verify(mFragment).finish(); ShadowDialogFragment.reset(); } @Test Loading Loading @@ -393,7 +410,13 @@ public class BluetoothDevicePairingDetailBaseTest { doReturn(intent).when(activity).getIntent(); doReturn(activity).when(mFragment).getActivity(); FragmentManager fragmentManager = mock(FragmentManager.class); FragmentTransaction fragmentTransaction = mock(FragmentTransaction.class); doReturn(fragmentTransaction).when(fragmentManager).beginTransaction(); doReturn(fragmentManager).when(mFragment).getFragmentManager(); doReturn(fragmentManager).when(mFragment).getChildFragmentManager(); Lifecycle lifecycle = mock(Lifecycle.class); when(lifecycle.getCurrentState()).thenReturn(Lifecycle.State.RESUMED); doReturn(lifecycle).when(mFragment).getLifecycle(); mFragment.mShouldTriggerAudioSharingShareThenPairFlow = mFragment.shouldTriggerAudioSharingShareThenPairFlow(); } Loading Loading @@ -425,4 +448,41 @@ public class BluetoothDevicePairingDetailBaseTest { return "test_tag"; } } /** Shadow of DialogFragment. */ @Implements(value = DialogFragment.class) public static class ShadowDialogFragment { @RealObject private DialogFragment mDialogFragment; private static Map<String, Boolean> sDialogStatus = new HashMap<>(); /** Resetter of the shadow. */ @Resetter public static void reset() { sDialogStatus.clear(); } /** Implementation for DialogFragment#show. */ @Implementation public void show(@NonNull FragmentManager manager, @Nullable String tag) { sDialogStatus.put(mDialogFragment.getClass().getName(), true); } /** Implementation for DialogFragment#dismissAllowingStateLoss. */ @Implementation public void dismissAllowingStateLoss() { sDialogStatus.put(mDialogFragment.getClass().getName(), false); } /** Implementation for DialogFragment#dismiss. */ @Implementation public void dismiss() { sDialogStatus.put(mDialogFragment.getClass().getName(), false); } /** Check if DialogFragment is showing. */ public static boolean isIsShowing(String clazzName) { return sDialogStatus.getOrDefault(clazzName, false); } } } tests/robotests/src/com/android/settings/bluetooth/ProgressDialogFragmentTest.java 0 → 100644 +140 −0 Original line number Diff line number Diff line /* * 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.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; 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 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.shadows.androidx.fragment.FragmentController; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowAlertDialogCompat.class}) public class ProgressDialogFragmentTest { @Rule public final MockitoRule mocks = MockitoJUnit.rule(); private static final String TEST_MESSAGE1 = "message1"; private static final String TEST_MESSAGE2 = "message2"; private Fragment mParent; @Before public void setUp() { ShadowAlertDialogCompat.reset(); mParent = new Fragment(); FragmentController.setupFragment( mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null); } @After public void tearDown() { ShadowAlertDialogCompat.reset(); } @Test public void getMetricsCategory_correctValue() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); // TODO: update real metric assertThat(fragment.getMetricsCategory()).isEqualTo(0); } @Test public void onCreateDialog_unattachedFragment_nullDialogFragment() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(new Fragment()); assertThat(fragment).isNull(); } @Test public void onCreateDialog_showDialog() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); fragment.show(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() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); fragment.show(TEST_MESSAGE1); shadowMainLooper().idle(); AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog).isNotNull(); assertThat(dialog.isShowing()).isTrue(); fragment.dismissAllowingStateLoss(); shadowMainLooper().idle(); assertThat(dialog.isShowing()).isFalse(); } @Test public void showDialog_sameMessage_keepExistingDialog() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); fragment.show(TEST_MESSAGE1); shadowMainLooper().idle(); AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog).isNotNull(); assertThat(dialog.isShowing()).isTrue(); fragment.show(TEST_MESSAGE1); shadowMainLooper().idle(); assertThat(dialog.isShowing()).isTrue(); TextView view = dialog.findViewById(R.id.message); assertThat(view).isNotNull(); assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE1); } @Test public void showDialog_newMessage_keepAndUpdateDialog() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); fragment.show(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); fragment.show(TEST_MESSAGE2); shadowMainLooper().idle(); assertThat(dialog.isShowing()).isTrue(); assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2); } } Loading
src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java +8 −27 Original line number Diff line number Diff line Loading @@ -31,15 +31,12 @@ import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import com.android.settings.R; import com.android.settings.SettingsActivity; Loading Loading @@ -71,8 +68,9 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere private volatile BluetoothDevice mJustBonded = null; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); @VisibleForTesting @Nullable private AlertDialog mProgressDialog = null; ProgressDialogFragment mProgressDialog = null; @VisibleForTesting boolean mShouldTriggerAudioSharingShareThenPairFlow = false; private CopyOnWriteArrayList<BluetoothDevice> mDevicesWithMetadataChangedListener = Loading Loading @@ -384,41 +382,24 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere finish(); } // TODO: use DialogFragment private void showConnectingDialog(@NonNull String deviceName) { postOnMainThread(() -> { String message = getContext().getString(R.string.progress_dialog_connect_device_content, deviceName); if (mProgressDialog != null) { Log.d(getLogTag(), "showConnectingDialog, is already showing"); TextView textView = mProgressDialog.findViewById(R.id.message); if (textView != null && !message.equals(textView.getText().toString())) { Log.d(getLogTag(), "showConnectingDialog, update message"); textView.setText(message); if (mProgressDialog == null) { mProgressDialog = ProgressDialogFragment.newInstance(this); } return; if (mProgressDialog != null) { mProgressDialog.show(message); } Log.d(getLogTag(), "showConnectingDialog, show dialog"); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); LayoutInflater inflater = LayoutInflater.from(builder.getContext()); View customView = inflater.inflate( R.layout.dialog_audio_sharing_progress, /* root= */ null); TextView textView = customView.findViewById(R.id.message); if (textView != null) { textView.setText(message); } AlertDialog dialog = builder.setView(customView).setCancelable(false).create(); dialog.setCanceledOnTouchOutside(false); mProgressDialog = dialog; dialog.show(); }); } private void dismissConnectingDialog() { postOnMainThread(() -> { if (mProgressDialog != null) { mProgressDialog.dismiss(); Log.d(getLogTag(), "Dismiss connecting dialog."); mProgressDialog.dismissAllowingStateLoss(); } }); } Loading
src/com/android/settings/bluetooth/ProgressDialogFragment.java 0 → 100644 +133 −0 Original line number Diff line number Diff line /* * 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.bluetooth; import android.app.Dialog; import android.os.Bundle; 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.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.Lifecycle; import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.google.common.base.Strings; public class ProgressDialogFragment extends InstrumentedDialogFragment { private static final String TAG = "BTProgressDialog"; private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message"; @Nullable private static FragmentManager sManager; @Nullable private static Lifecycle sLifecycle; private String mMessage = ""; @Nullable private AlertDialog mAlertDialog; @Override public int getMetricsCategory() { // TODO: add metrics return 0; } /** * Returns a new instance of {@link ProgressDialogFragment} dialog. * * @param host The Fragment this dialog will be hosted. */ @Nullable public static ProgressDialogFragment newInstance(@Nullable Fragment host) { if (host == null) return null; try { sManager = host.getChildFragmentManager(); sLifecycle = host.getLifecycle(); } catch (IllegalStateException e) { Log.d(TAG, "Fail to create new instance: " + e.getMessage()); return null; } return new ProgressDialogFragment(); } /** * Display {@link ProgressDialogFragment} dialog. * * @param message The message to be shown on the dialog */ public void show(@NonNull String message) { if (sManager == null) return; Lifecycle.State currentState = sLifecycle == null ? null : sLifecycle.getCurrentState(); if (currentState == null || !currentState.isAtLeast(Lifecycle.State.STARTED)) { Log.d(TAG, "Fail to show dialog with state: " + currentState); return; } if (mAlertDialog != null && mAlertDialog.isShowing()) { if (!mMessage.equals(message)) { Log.d(TAG, "Update dialog message."); TextView messageView = mAlertDialog.findViewById(R.id.message); if (messageView != null) { messageView.setText(message); } mMessage = message; } Log.d(TAG, "Dialog is showing, return."); return; } mMessage = message; Log.d(TAG, "Show up the progress dialog."); Bundle args = new Bundle(); args.putString(BUNDLE_KEY_MESSAGE, message); setArguments(args); show(sManager, TAG); } /** Returns the current message on the dialog. */ @VisibleForTesting @NonNull public String getMessage() { return mMessage; } private ProgressDialogFragment() { } @Override @NonNull public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 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_progress, /* root= */ null); TextView textView = customView.findViewById(R.id.message); if (textView != null && !Strings.isNullOrEmpty(message)) { textView.setText(message); } AlertDialog dialog = builder.setView(customView).setCancelable(false).create(); dialog.setCanceledOnTouchOutside(false); mAlertDialog = dialog; return dialog; } }
src/com/android/settings/connecteddevice/audiosharing/AudioSharingProgressDialogFragment.java +1 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ public class AudioSharingProgressDialogFragment extends InstrumentedDialogFragme if (messageView != null) { messageView.setText(message); } sMessage = message; } Log.d(TAG, "Dialog is showing, return."); return; Loading
tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java +69 −9 Original line number Diff line number Diff line Loading @@ -46,17 +46,21 @@ import android.os.Bundle; import android.os.Looper; import android.platform.test.flag.junit.SetFlagsRule; import android.util.Pair; import android.widget.TextView; import androidx.appcompat.app.AlertDialog; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.Lifecycle; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.flags.Flags; Loading @@ -73,8 +77,14 @@ import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; import org.robolectric.shadow.api.Shadow; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; /** Tests for {@link BluetoothDevicePairingDetailBase}. */ Loading @@ -82,7 +92,7 @@ import java.util.concurrent.Executor; @Config(shadows = { ShadowBluetoothAdapter.class, ShadowAlertDialogCompat.class, com.android.settings.testutils.shadow.ShadowFragment.class, ShadowFragment.class, }) public class BluetoothDevicePairingDetailBaseTest { Loading Loading @@ -133,7 +143,6 @@ public class BluetoothDevicePairingDetailBaseTest { mFragment.mLocalManager = mLocalManager; mFragment.mBluetoothAdapter = mBluetoothAdapter; mFragment.initPreferencesFromPreferenceScreen(); } @Test Loading Loading @@ -199,22 +208,26 @@ public class BluetoothDevicePairingDetailBaseTest { } @Test @Config(shadows = ShadowDialogFragment.class) public void onDeviceBondStateChanged_bonded_pairAndJoinSharingEnabled_handle() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); ShadowDialogFragment.reset(); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); mFragment.mSelectedList.add(mBluetoothDevice); setUpFragmentWithPairAndJoinSharingIntent(true); mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED); shadowOf(Looper.getMainLooper()).idle(); AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog).isNotNull(); TextView message = dialog.findViewById(R.id.message); assertThat(message).isNotNull(); assertThat(message.getText().toString()).isEqualTo( ProgressDialogFragment progressDialog = mFragment.mProgressDialog; assertThat(progressDialog).isNotNull(); assertThat(progressDialog.getMessage()).isEqualTo( mContext.getString(R.string.progress_dialog_connect_device_content, TEST_DEVICE_ADDRESS)); assertThat( ShadowDialogFragment.isIsShowing(ProgressDialogFragment.class.getName())).isTrue(); verify(mFragment, never()).finish(); ShadowDialogFragment.reset(); } @Test Loading Loading @@ -283,9 +296,11 @@ public class BluetoothDevicePairingDetailBaseTest { } @Test @Config(shadows = ShadowDialogFragment.class) public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_pairAndJoinSharing() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); ShadowDialogFragment.reset(); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); mFragment.mSelectedList.add(mBluetoothDevice); setUpFragmentWithPairAndJoinSharingIntent(true); Loading @@ -309,6 +324,8 @@ public class BluetoothDevicePairingDetailBaseTest { assertThat(btDevice).isNotNull(); assertThat(btDevice).isEqualTo(mBluetoothDevice); verify(mFragment).finish(); ShadowDialogFragment.reset(); } @Test Loading Loading @@ -393,7 +410,13 @@ public class BluetoothDevicePairingDetailBaseTest { doReturn(intent).when(activity).getIntent(); doReturn(activity).when(mFragment).getActivity(); FragmentManager fragmentManager = mock(FragmentManager.class); FragmentTransaction fragmentTransaction = mock(FragmentTransaction.class); doReturn(fragmentTransaction).when(fragmentManager).beginTransaction(); doReturn(fragmentManager).when(mFragment).getFragmentManager(); doReturn(fragmentManager).when(mFragment).getChildFragmentManager(); Lifecycle lifecycle = mock(Lifecycle.class); when(lifecycle.getCurrentState()).thenReturn(Lifecycle.State.RESUMED); doReturn(lifecycle).when(mFragment).getLifecycle(); mFragment.mShouldTriggerAudioSharingShareThenPairFlow = mFragment.shouldTriggerAudioSharingShareThenPairFlow(); } Loading Loading @@ -425,4 +448,41 @@ public class BluetoothDevicePairingDetailBaseTest { return "test_tag"; } } /** Shadow of DialogFragment. */ @Implements(value = DialogFragment.class) public static class ShadowDialogFragment { @RealObject private DialogFragment mDialogFragment; private static Map<String, Boolean> sDialogStatus = new HashMap<>(); /** Resetter of the shadow. */ @Resetter public static void reset() { sDialogStatus.clear(); } /** Implementation for DialogFragment#show. */ @Implementation public void show(@NonNull FragmentManager manager, @Nullable String tag) { sDialogStatus.put(mDialogFragment.getClass().getName(), true); } /** Implementation for DialogFragment#dismissAllowingStateLoss. */ @Implementation public void dismissAllowingStateLoss() { sDialogStatus.put(mDialogFragment.getClass().getName(), false); } /** Implementation for DialogFragment#dismiss. */ @Implementation public void dismiss() { sDialogStatus.put(mDialogFragment.getClass().getName(), false); } /** Check if DialogFragment is showing. */ public static boolean isIsShowing(String clazzName) { return sDialogStatus.getOrDefault(clazzName, false); } } }
tests/robotests/src/com/android/settings/bluetooth/ProgressDialogFragmentTest.java 0 → 100644 +140 −0 Original line number Diff line number Diff line /* * 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.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; 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 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.shadows.androidx.fragment.FragmentController; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowAlertDialogCompat.class}) public class ProgressDialogFragmentTest { @Rule public final MockitoRule mocks = MockitoJUnit.rule(); private static final String TEST_MESSAGE1 = "message1"; private static final String TEST_MESSAGE2 = "message2"; private Fragment mParent; @Before public void setUp() { ShadowAlertDialogCompat.reset(); mParent = new Fragment(); FragmentController.setupFragment( mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null); } @After public void tearDown() { ShadowAlertDialogCompat.reset(); } @Test public void getMetricsCategory_correctValue() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); // TODO: update real metric assertThat(fragment.getMetricsCategory()).isEqualTo(0); } @Test public void onCreateDialog_unattachedFragment_nullDialogFragment() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(new Fragment()); assertThat(fragment).isNull(); } @Test public void onCreateDialog_showDialog() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); fragment.show(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() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); fragment.show(TEST_MESSAGE1); shadowMainLooper().idle(); AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog).isNotNull(); assertThat(dialog.isShowing()).isTrue(); fragment.dismissAllowingStateLoss(); shadowMainLooper().idle(); assertThat(dialog.isShowing()).isFalse(); } @Test public void showDialog_sameMessage_keepExistingDialog() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); fragment.show(TEST_MESSAGE1); shadowMainLooper().idle(); AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog).isNotNull(); assertThat(dialog.isShowing()).isTrue(); fragment.show(TEST_MESSAGE1); shadowMainLooper().idle(); assertThat(dialog.isShowing()).isTrue(); TextView view = dialog.findViewById(R.id.message); assertThat(view).isNotNull(); assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE1); } @Test public void showDialog_newMessage_keepAndUpdateDialog() { ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(mParent); fragment.show(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); fragment.show(TEST_MESSAGE2); shadowMainLooper().idle(); assertThat(dialog.isShowing()).isTrue(); assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2); } }