Show turn off talkback dialog if needed in find an audio stream page.

Test: atest
Bug: b/362151254
Flag: com.android.settingslib.flags.enable_le_audio_sharing
Change-Id: Id48b6c360473202fed55f5b622a1967084ceef1c
This commit is contained in:
chelseahao
2025-02-13 12:51:04 +08:00
committed by Chelsea Hao
parent 1dfd19e5e0
commit 8b42157d39
5 changed files with 158 additions and 21 deletions

View File

@@ -68,7 +68,10 @@ public class AudioStreamsDialogFragment extends InstrumentedDialogFragment {
* @param dialogBuilder The builder for constructing the dialog.
* @param dialogId The dialog settings enum for logging
*/
public static void show(Fragment host, DialogBuilder dialogBuilder, int dialogId) {
public static void show(@Nullable Fragment host, DialogBuilder dialogBuilder, int dialogId) {
if (host == null) {
return;
}
if (!host.isAdded()) {
Log.w(TAG, "The host fragment is not added to the activity!");
return;
@@ -77,7 +80,10 @@ public class AudioStreamsDialogFragment extends InstrumentedDialogFragment {
(new AudioStreamsDialogFragment(dialogBuilder, dialogId)).show(manager, TAG);
}
static void dismissAll(Fragment host) {
static void dismissAll(@Nullable Fragment host) {
if (host == null) {
return;
}
if (!host.isAdded()) {
Log.w(TAG, "The host fragment is not added to the activity!");
return;

View File

@@ -21,6 +21,7 @@ import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssista
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothStatusCodes;
public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback {
private final AudioStreamsProgressCategoryController mCategoryController;
@@ -44,6 +45,9 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA
@Override
public void onSearchStartFailed(int reason) {
if (reason == BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE) {
return;
}
super.onSearchStartFailed(reason);
mCategoryController.showToast("Failed to start scanning. Try again.");
mCategoryController.setScanning(false);
@@ -57,6 +61,9 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA
@Override
public void onSearchStopFailed(int reason) {
if (reason == BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE) {
return;
}
super.onSearchStopFailed(reason);
mCategoryController.showToast("Failed to stop scanning. Try again.");
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsHelper.getEnabledScreenReaderServices;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsHelper.setAccessibilityServiceOff;
import static com.android.settingslib.bluetooth.BluetoothUtils.isAudioSharingHysteresisModeFixAvailable;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
@@ -32,8 +34,10 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.content.ComponentName;
import android.content.Context;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
@@ -59,6 +63,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -103,6 +108,9 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
}
};
private final AccessibilityManager.AccessibilityServicesStateChangeListener
mAccessibilityListener = manager -> init();
private final Comparator<AudioStreamPreference> mComparator =
Comparator.<AudioStreamPreference, Boolean>comparing(
p ->
@@ -148,6 +156,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
private SourceOriginForLogging mSourceFromQrCodeOriginForLogging;
@Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference;
@Nullable private Fragment mFragment;
@Nullable AccessibilityManager mAccessibilityManager;
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -159,6 +168,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(this);
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
mContext);
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
}
@Override
@@ -177,6 +187,10 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
if (mBluetoothManager != null) {
mBluetoothManager.getEventManager().registerCallback(mBluetoothCallback);
}
if (mAccessibilityManager != null) {
mAccessibilityManager.addAccessibilityServicesStateChangeListener(
mExecutor, mAccessibilityListener);
}
mExecutor.execute(this::init);
}
@@ -185,6 +199,10 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
if (mBluetoothManager != null) {
mBluetoothManager.getEventManager().unregisterCallback(mBluetoothCallback);
}
if (mAccessibilityManager != null) {
mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
mAccessibilityListener);
}
mExecutor.execute(this::stopScanning);
}
@@ -542,6 +560,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
boolean hasConnected =
AudioStreamsHelper.getCachedBluetoothDeviceInSharingOrLeConnected(mBluetoothManager)
.isPresent();
Set<ComponentName> screenReaderServices = getEnabledScreenReaderServices(mContext);
AudioSharingUtils.postOnMainThread(
mContext,
() -> {
@@ -550,27 +569,27 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
mCategoryPreference.setVisible(hasConnected);
}
});
if (hasConnected) {
if (hasConnected && screenReaderServices.isEmpty()) {
startScanning();
AudioSharingUtils.postOnMainThread(
mContext,
() -> {
if (mFragment != null) {
AudioStreamsDialogFragment.dismissAll(mFragment);
}
});
AudioSharingUtils.postOnMainThread(mContext,
() -> AudioStreamsDialogFragment.dismissAll(mFragment));
} else {
stopScanning();
AudioSharingUtils.postOnMainThread(
mContext,
() -> {
if (mFragment != null) {
AudioStreamsDialogFragment.show(
mFragment,
getNoLeDeviceDialog(),
SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_NO_LE_DEVICE);
}
});
if (!hasConnected) {
AudioSharingUtils.postOnMainThread(
mContext, () -> AudioStreamsDialogFragment.show(
mFragment,
getNoLeDeviceDialog(),
SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_NO_LE_DEVICE)
);
} else if (!screenReaderServices.isEmpty()) {
AudioSharingUtils.postOnMainThread(
mContext, () -> AudioStreamsDialogFragment.show(
mFragment,
getTurnOffTalkbackDialog(screenReaderServices),
SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_TURN_OFF_TALKBACK)
);
}
}
}
@@ -601,6 +620,9 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
stateList.forEach(
state -> handleSourcePaused(device, state)));
}
if (DEBUG) {
Log.d(TAG, "startScanning()");
}
mLeBroadcastAssistant.startSearchingForSources(emptyList());
mMediaControlHelper.start();
});
@@ -735,4 +757,31 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
dialog.dismiss();
});
}
private AudioStreamsDialogFragment.DialogBuilder getTurnOffTalkbackDialog(
Set<ComponentName> enabledScreenReader) {
return new AudioStreamsDialogFragment.DialogBuilder(mContext)
.setTitle(mContext.getString(R.string.audio_streams_dialog_turn_off_talkback_title))
.setSubTitle2(mContext.getString(
R.string.audio_streams_dialog_turn_off_talkback_subtitle))
.setLeftButtonText(mContext.getString(R.string.cancel))
.setLeftButtonOnClickListener(dialog -> {
dialog.dismiss();
if (mFragment != null && mFragment.getActivity() != null) {
// Navigate back
mFragment.getActivity().finish();
}
})
.setRightButtonText(
mContext.getString(R.string.audio_streams_dialog_turn_off_talkback_button))
.setRightButtonOnClickListener(
dialog -> {
ThreadUtils.postOnBackgroundThread(() -> {
if (!enabledScreenReader.isEmpty()) {
setAccessibilityServiceOff(mContext, enabledScreenReader);
}
});
dialog.dismiss();
});
}
}

View File

@@ -22,6 +22,7 @@ import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -124,6 +125,15 @@ public class AudioStreamsProgressCategoryCallbackTest {
verify(mController).setScanning(anyBoolean());
}
@Test
public void testOnSearchStartFailed_ignoreAlreadyInTargetState() {
mCallback.onSearchStartFailed(/* reason= */
BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE);
verify(mController, never()).showToast(anyString());
verify(mController, never()).setScanning(anyBoolean());
}
@Test
public void testOnSearchStarted() {
mCallback.onSearchStarted(/* reason= */ 0);
@@ -138,6 +148,14 @@ public class AudioStreamsProgressCategoryCallbackTest {
verify(mController).showToast(anyString());
}
@Test
public void testOnSearchStopFailed_ignoreAlreadyInTargetState() {
mCallback.onSearchStopFailed(/* reason= */
BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE);
verify(mController, never()).showToast(anyString());
}
@Test
public void testOnSearchStopped() {
mCallback.onSearchStopped(/* reason= */ 0);

View File

@@ -52,10 +52,12 @@ import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.ComponentName;
import android.content.Context;
import android.os.Looper;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.TextView;
@@ -120,9 +122,11 @@ public class AudioStreamsProgressCategoryControllerTest {
private static final String BROADCAST_NAME_1 = "name_1";
private static final String BROADCAST_NAME_2 = "name_2";
private static final byte[] BROADCAST_CODE = new byte[] {1};
private final Context mContext = ApplicationProvider.getApplicationContext();
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
@Mock private LocalBluetoothManager mLocalBtManager;
@Mock private BluetoothEventManager mBluetoothEventManager;
@Mock
private AccessibilityManager mAccessibilityManager;
@Mock private PreferenceScreen mScreen;
@Mock private AudioStreamsHelper mAudioStreamsHelper;
@Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
@@ -152,6 +156,8 @@ public class AudioStreamsProgressCategoryControllerTest {
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(false);
when(mContext.getSystemService(AccessibilityManager.class)).thenReturn(
mAccessibilityManager);
when(mScreen.findPreference(anyString())).thenReturn(mPreference);
@@ -200,6 +206,7 @@ public class AudioStreamsProgressCategoryControllerTest {
mController.onStop(mLifecycleOwner);
verify(mBluetoothEventManager).unregisterCallback(any());
verify(mAccessibilityManager).removeAccessibilityServicesStateChangeListener(any());
}
@Test
@@ -252,6 +259,56 @@ public class AudioStreamsProgressCategoryControllerTest {
dialog.cancel();
}
@Test
public void testOnStart_initTalkbackOn_showDialog() {
// Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
// Enable a screen reader service
ShadowAudioStreamsHelper.setEnabledScreenReaderService(new ComponentName("pkg", "class"));
when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(true);
FragmentController.setupFragment(mFragment);
mController.setFragment(mFragment);
mController.displayPreference(mScreen);
mController.onStart(mLifecycleOwner);
shadowOf(Looper.getMainLooper()).idle();
// Called twice, once in displayPreference, the other in init()
verify(mPreference, times(2)).setVisible(anyBoolean());
verify(mPreference).removeAudioStreamPreferences();
verify(mLeBroadcastAssistant).stopSearchingForSources();
verify(mLeBroadcastAssistant).unregisterServiceCallBack(any());
var dialog = ShadowAlertDialog.getLatestAlertDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView title = dialog.findViewById(R.id.dialog_title);
assertThat(title).isNotNull();
assertThat(title.getText())
.isEqualTo(
mContext.getString(R.string.audio_streams_dialog_turn_off_talkback_title));
TextView subtitle1 = dialog.findViewById(R.id.dialog_subtitle);
assertThat(subtitle1).isNotNull();
assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
TextView subtitle2 = dialog.findViewById(R.id.dialog_subtitle_2);
assertThat(subtitle2).isNotNull();
assertThat(subtitle2.getText())
.isEqualTo(mContext.getString(
R.string.audio_streams_dialog_turn_off_talkback_subtitle));
View leftButton = dialog.findViewById(R.id.left_button);
assertThat(leftButton).isNotNull();
assertThat(leftButton.getVisibility()).isEqualTo(View.VISIBLE);
Button rightButton = dialog.findViewById(R.id.right_button);
assertThat(rightButton).isNotNull();
assertThat(rightButton.getText())
.isEqualTo(
mContext.getString(R.string.audio_streams_dialog_turn_off_talkback_button));
assertThat(rightButton.hasOnClickListeners()).isTrue();
dialog.cancel();
}
@Test
public void testBluetoothOff_triggerRunnable() {
mController.mBluetoothCallback.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);