Merge changes from topic "talkback_dialog" into main

* changes:
  Show turn off talkback dialog if needed in find an audio stream page.
  Show turn off talkback dialog if needed in `AudioStreamConfirmDialog`.
This commit is contained in:
Chelsea Hao
2025-02-18 03:01:49 -08:00
committed by Android (Google) Code Review
11 changed files with 423 additions and 31 deletions

View File

@@ -14113,6 +14113,12 @@
<string name="audio_streams_media_service_notification_leave_broadcast_text">Stop listening</string> <string name="audio_streams_media_service_notification_leave_broadcast_text">Stop listening</string>
<!-- Le audio streams no le device dialog title [CHAR LIMIT=NONE] --> <!-- Le audio streams no le device dialog title [CHAR LIMIT=NONE] -->
<string name="audio_streams_dialog_no_le_device_title">Connect compatible headphones</string> <string name="audio_streams_dialog_no_le_device_title">Connect compatible headphones</string>
<!-- Le audio streams turn off talkback dialog title [CHAR LIMIT=NONE] -->
<string name="audio_streams_dialog_turn_off_talkback_title">Turn off Talkback temporarily</string>
<!-- Le audio streams turn off talkback dialog subtitle [CHAR LIMIT=NONE] -->
<string name="audio_streams_dialog_turn_off_talkback_subtitle">Talkback cannot be used when listening to audio streams. Turn off talkback to start listening.</string>
<!-- Le audio streams turn off talkback dialog button [CHAR LIMIT=NONE] -->
<string name="audio_streams_dialog_turn_off_talkback_button">Turn off</string>
<!-- Le audio streams no le device dialog button [CHAR LIMIT=NONE] --> <!-- Le audio streams no le device dialog button [CHAR LIMIT=NONE] -->
<string name="audio_streams_dialog_no_le_device_button">Connect a device</string> <string name="audio_streams_dialog_no_le_device_button">Connect a device</string>
<!-- Le audio streams detail page title [CHAR LIMIT=NONE] --> <!-- Le audio streams detail page title [CHAR LIMIT=NONE] -->

View File

@@ -17,6 +17,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams; package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsDashboardFragment.KEY_BROADCAST_METADATA; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsDashboardFragment.KEY_BROADCAST_METADATA;
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.BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA; import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA;
import android.app.Activity; import android.app.Activity;
@@ -41,6 +43,7 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt; import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.utils.ThreadUtils;
public class AudioStreamConfirmDialog extends InstrumentedDialogFragment { public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
private static final String TAG = "AudioStreamConfirmDialog"; private static final String TAG = "AudioStreamConfirmDialog";
@@ -86,6 +89,8 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_FEATURE_UNSUPPORTED -> case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_FEATURE_UNSUPPORTED ->
getUnsupportedDialog(); getUnsupportedDialog();
case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE -> getNoLeDeviceDialog(); case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE -> getNoLeDeviceDialog();
case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_TURN_OFF_TALKBACK ->
getTurnOffTalkbackDialog();
case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_LISTEN -> getConfirmDialog(); case SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_LISTEN -> getConfirmDialog();
default -> getErrorDialog(); default -> getErrorDialog();
}; };
@@ -168,6 +173,36 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
.build(); .build();
} }
private Dialog getTurnOffTalkbackDialog() {
return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
.setTitle(getString(R.string.audio_streams_dialog_turn_off_talkback_title))
.setSubTitle2(getString(R.string.audio_streams_dialog_turn_off_talkback_subtitle))
.setLeftButtonText(getString(R.string.cancel))
.setLeftButtonOnClickListener(
unused -> {
dismiss();
if (mActivity != null) {
mActivity.finish();
}
})
.setRightButtonText(
getString(R.string.audio_streams_dialog_turn_off_talkback_button))
.setRightButtonOnClickListener(
dialog -> {
var unused = ThreadUtils.postOnBackgroundThread(() -> {
var enabledScreenReader = getEnabledScreenReaderServices(mContext);
if (!enabledScreenReader.isEmpty()) {
setAccessibilityServiceOff(mContext, enabledScreenReader);
}
});
dismiss();
if (mActivity != null) {
mActivity.finish();
}
})
.build();
}
private Dialog getNoLeDeviceDialog() { private Dialog getNoLeDeviceDialog() {
return new AudioStreamsDialogFragment.DialogBuilder(getActivity()) return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
.setTitle(getString(R.string.audio_streams_dialog_no_le_device_title)) .setTitle(getString(R.string.audio_streams_dialog_no_le_device_title))
@@ -234,6 +269,9 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
if (!hasConnectedDevice) { if (!hasConnectedDevice) {
return SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE; return SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_NO_LE_DEVICE;
} }
if (!getEnabledScreenReaderServices(mContext).isEmpty()) {
return SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_TURN_OFF_TALKBACK;
}
return hasMetadata return hasMetadata
? SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_LISTEN ? SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_LISTEN
: SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_DATA_ERROR; : SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_DATA_ERROR;

View File

@@ -68,7 +68,10 @@ public class AudioStreamsDialogFragment extends InstrumentedDialogFragment {
* @param dialogBuilder The builder for constructing the dialog. * @param dialogBuilder The builder for constructing the dialog.
* @param dialogId The dialog settings enum for logging * @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()) { if (!host.isAdded()) {
Log.w(TAG, "The host fragment is not added to the activity!"); Log.w(TAG, "The host fragment is not added to the activity!");
return; return;
@@ -77,7 +80,10 @@ public class AudioStreamsDialogFragment extends InstrumentedDialogFragment {
(new AudioStreamsDialogFragment(dialogBuilder, dialogId)).show(manager, TAG); (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()) { if (!host.isAdded()) {
Log.w(TAG, "The host fragment is not added to the activity!"); Log.w(TAG, "The host fragment is not added to the activity!");
return; return;

View File

@@ -19,6 +19,7 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_TITLE; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_TITLE;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES;
import static com.android.settingslib.accessibility.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.settingslib.bluetooth.BluetoothUtils.isAudioSharingHysteresisModeFixAvailable; import static com.android.settingslib.bluetooth.BluetoothUtils.isAudioSharingHysteresisModeFixAvailable;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
@@ -30,15 +31,18 @@ import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toMap;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudioContentMetadata; import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
@@ -56,9 +60,12 @@ import com.google.android.material.appbar.AppBarLayout;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -399,4 +406,55 @@ public class AudioStreamsHelper {
} }
} }
} }
/**
* Retrieves a set of enabled screen reader services that are pre-installed.
*
* <p>This method checks the accessibility manager for enabled accessibility services
* and filters them based on a list of pre-installed screen reader service component names
* defined in the {@code config_preinstalled_screen_reader_services} resource array.</p>
*
* @param context The context.
* @return A set of {@link ComponentName} objects representing the enabled pre-installed
* screen reader services, or an empty set if no services are found, or if an error occurs.
*/
public static Set<ComponentName> getEnabledScreenReaderServices(Context context) {
AccessibilityManager manager = context.getSystemService(AccessibilityManager.class);
if (manager == null) {
return Collections.emptySet();
}
Set<String> screenReaderServices = new HashSet<>();
Collections.addAll(screenReaderServices, context.getResources()
.getStringArray(R.array.config_preinstalled_screen_reader_services));
if (screenReaderServices.isEmpty()) {
return Collections.emptySet();
}
Set<ComponentName> enabledScreenReaderServices = new HashSet<>();
List<AccessibilityServiceInfo> enabledServices = manager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
for (AccessibilityServiceInfo service : enabledServices) {
ComponentName componentName = service.getComponentName();
if (screenReaderServices.contains(componentName.flattenToString())) {
enabledScreenReaderServices.add(componentName);
}
}
Log.d(TAG, "getEnabledScreenReaderServices(): " + enabledScreenReaderServices);
return enabledScreenReaderServices;
}
/**
* Turns off the specified accessibility services.
*
* This method iterates through a set of ComponentName objects, each representing an
* accessibility service, and disables them.
*
* @param context The application context.
* @param services A set of ComponentName objects representing the services to disable.
*/
public static void setAccessibilityServiceOff(Context context, Set<ComponentName> services) {
for (ComponentName service : services) {
Log.d(TAG, "setScreenReaderOff(): " + service);
setAccessibilityServiceState(context, service, false);
}
}
} }

View File

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

View File

@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams; 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.BluetoothUtils.isAudioSharingHysteresisModeFixAvailable;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
@@ -32,8 +34,10 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -59,6 +63,7 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; 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 = private final Comparator<AudioStreamPreference> mComparator =
Comparator.<AudioStreamPreference, Boolean>comparing( Comparator.<AudioStreamPreference, Boolean>comparing(
p -> p ->
@@ -148,6 +156,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
private SourceOriginForLogging mSourceFromQrCodeOriginForLogging; private SourceOriginForLogging mSourceFromQrCodeOriginForLogging;
@Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference; @Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference;
@Nullable private Fragment mFragment; @Nullable private Fragment mFragment;
@Nullable AccessibilityManager mAccessibilityManager;
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) { public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
super(context, preferenceKey); super(context, preferenceKey);
@@ -159,6 +168,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(this); mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(this);
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable( mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
mContext); mContext);
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
} }
@Override @Override
@@ -177,6 +187,10 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
if (mBluetoothManager != null) { if (mBluetoothManager != null) {
mBluetoothManager.getEventManager().registerCallback(mBluetoothCallback); mBluetoothManager.getEventManager().registerCallback(mBluetoothCallback);
} }
if (mAccessibilityManager != null) {
mAccessibilityManager.addAccessibilityServicesStateChangeListener(
mExecutor, mAccessibilityListener);
}
mExecutor.execute(this::init); mExecutor.execute(this::init);
} }
@@ -185,6 +199,10 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
if (mBluetoothManager != null) { if (mBluetoothManager != null) {
mBluetoothManager.getEventManager().unregisterCallback(mBluetoothCallback); mBluetoothManager.getEventManager().unregisterCallback(mBluetoothCallback);
} }
if (mAccessibilityManager != null) {
mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
mAccessibilityListener);
}
mExecutor.execute(this::stopScanning); mExecutor.execute(this::stopScanning);
} }
@@ -548,6 +566,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
boolean hasConnected = boolean hasConnected =
AudioStreamsHelper.getCachedBluetoothDeviceInSharingOrLeConnected(mBluetoothManager) AudioStreamsHelper.getCachedBluetoothDeviceInSharingOrLeConnected(mBluetoothManager)
.isPresent(); .isPresent();
Set<ComponentName> screenReaderServices = getEnabledScreenReaderServices(mContext);
AudioSharingUtils.postOnMainThread( AudioSharingUtils.postOnMainThread(
mContext, mContext,
() -> { () -> {
@@ -556,27 +575,27 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
mCategoryPreference.setVisible(hasConnected); mCategoryPreference.setVisible(hasConnected);
} }
}); });
if (hasConnected) { if (hasConnected && screenReaderServices.isEmpty()) {
startScanning(); startScanning();
AudioSharingUtils.postOnMainThread( AudioSharingUtils.postOnMainThread(mContext,
mContext, () -> AudioStreamsDialogFragment.dismissAll(mFragment));
() -> {
if (mFragment != null) {
AudioStreamsDialogFragment.dismissAll(mFragment);
}
});
} else { } else {
stopScanning(); stopScanning();
AudioSharingUtils.postOnMainThread( if (!hasConnected) {
mContext, AudioSharingUtils.postOnMainThread(
() -> { mContext, () -> AudioStreamsDialogFragment.show(
if (mFragment != null) { mFragment,
AudioStreamsDialogFragment.show( getNoLeDeviceDialog(),
mFragment, SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_NO_LE_DEVICE)
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)
);
}
} }
} }
@@ -607,6 +626,9 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
stateList.forEach( stateList.forEach(
state -> handleSourcePaused(device, state))); state -> handleSourcePaused(device, state)));
} }
if (DEBUG) {
Log.d(TAG, "startScanning()");
}
mLeBroadcastAssistant.startSearchingForSources(emptyList()); mLeBroadcastAssistant.startSearchingForSources(emptyList());
mMediaControlHelper.start(); mMediaControlHelper.start();
}); });
@@ -741,4 +763,31 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
dialog.dismiss(); 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

@@ -34,6 +34,7 @@ import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothStatusCodes;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@@ -46,6 +47,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
@@ -74,12 +76,15 @@ import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config( @Config(
shadows = { shadows = {
ShadowBluetoothAdapter.class, ShadowBluetoothAdapter.class,
ShadowBluetoothUtils.class, ShadowBluetoothUtils.class,
ShadowAudioStreamsHelper.class,
}) })
public class AudioStreamConfirmDialogTest { public class AudioStreamConfirmDialogTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String VALID_METADATA = private static final String VALID_METADATA =
"BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;" "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"; + "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
@@ -88,12 +93,18 @@ public class AudioStreamConfirmDialogTest {
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"; + "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
private static final String DEVICE_NAME = "device_name"; private static final String DEVICE_NAME = "device_name";
private final Context mContext = ApplicationProvider.getApplicationContext(); private final Context mContext = ApplicationProvider.getApplicationContext();
@Mock private LocalBluetoothManager mLocalBluetoothManager; @Mock
@Mock private LocalBluetoothProfileManager mLocalBluetoothProfileManager; private LocalBluetoothManager mLocalBluetoothManager;
@Mock private LocalBluetoothLeBroadcast mBroadcast; @Mock
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant; private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
@Mock private VolumeControlProfile mVolumeControl; @Mock
@Mock private BluetoothDevice mBluetoothDevice; private LocalBluetoothLeBroadcast mBroadcast;
@Mock
private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock
private VolumeControlProfile mVolumeControl;
@Mock
private BluetoothDevice mBluetoothDevice;
private AudioStreamConfirmDialog mDialogFragment; private AudioStreamConfirmDialog mDialogFragment;
@Before @Before
@@ -376,6 +387,66 @@ public class AudioStreamConfirmDialogTest {
verify(mDialogFragment.mActivity, times(2)).finish(); verify(mDialogFragment.mActivity, times(2)).finish();
} }
@Test
public void showDialog_turnOffTalkback() {
List<BluetoothDevice> devices = new ArrayList<>();
devices.add(mBluetoothDevice);
when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
when(mBluetoothDevice.getAlias()).thenReturn("");
ShadowAudioStreamsHelper.setEnabledScreenReaderService(new ComponentName("pkg", "class"));
Intent intent = new Intent();
intent.putExtra(KEY_BROADCAST_METADATA, VALID_METADATA);
FragmentController.of(mDialogFragment, intent)
.create(/* containerViewId= */ 0, /* bundle= */ null)
.start()
.resume()
.visible()
.get();
shadowMainLooper().idle();
assertThat(mDialogFragment.getMetricsCategory())
.isEqualTo(SettingsEnums.DIALOG_AUDIO_STREAM_CONFIRM_TURN_OFF_TALKBACK);
assertThat(mDialogFragment.mActivity).isNotNull();
mDialogFragment.mActivity = spy(mDialogFragment.mActivity);
var dialog = mDialogFragment.getDialog();
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);
assertThat(leftButton.hasOnClickListeners()).isTrue();
leftButton.callOnClick();
assertThat(dialog.isShowing()).isFalse();
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();
rightButton.callOnClick();
assertThat(dialog.isShowing()).isFalse();
verify(mDialogFragment.mActivity, times(2)).finish();
}
@Test @Test
public void showDialog_getDataStringFromIntent_confirmListen() { public void showDialog_getDataStringFromIntent_confirmListen() {
List<BluetoothDevice> devices = new ArrayList<>(); List<BluetoothDevice> devices = new ArrayList<>();

View File

@@ -36,22 +36,28 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothStatusCodes;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.UserHandle;
import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.flag.junit.SetFlagsRule;
import android.view.accessibility.AccessibilityManager;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowAccessibilityManager;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowThreadUtils; import com.android.settings.testutils.shadow.ShadowThreadUtils;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
@@ -75,11 +81,14 @@ import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config( @Config(
shadows = { shadows = {
ShadowAccessibilityManager.class,
ShadowThreadUtils.class, ShadowThreadUtils.class,
ShadowBluetoothAdapter.class, ShadowBluetoothAdapter.class,
}) })
@@ -100,11 +109,17 @@ public class AudioStreamsHelperTest {
@Mock private CachedBluetoothDevice mCachedDevice; @Mock private CachedBluetoothDevice mCachedDevice;
@Mock private BluetoothDevice mDevice; @Mock private BluetoothDevice mDevice;
@Mock private BluetoothDevice mSourceDevice; @Mock private BluetoothDevice mSourceDevice;
@Mock
private AccessibilityServiceInfo mTalkbackServiceInfo;
private ShadowAccessibilityManager mShadowAccessibilityManager;
private AudioStreamsHelper mHelper; private AudioStreamsHelper mHelper;
@Before @Before
public void setUp() { public void setUp() {
mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mShadowAccessibilityManager = Shadow.extract(
mContext.getSystemService(AccessibilityManager.class));
mShadowAccessibilityManager.setEnabledAccessibilityServiceList(new ArrayList<>());
ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract( ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(
BluetoothAdapter.getDefaultAdapter()); BluetoothAdapter.getDefaultAdapter());
shadowBluetoothAdapter.setEnabled(true); shadowBluetoothAdapter.setEnabled(true);
@@ -348,6 +363,54 @@ public class AudioStreamsHelperTest {
verify(appBarLayout).setExpanded(eq(true)); verify(appBarLayout).setExpanded(eq(true));
} }
@Test
public void getEnabledScreenReaderServices_noAccessibilityManager_returnEmpty() {
mShadowAccessibilityManager = null;
Set<ComponentName> result = AudioStreamsHelper.getEnabledScreenReaderServices(mContext);
assertThat(result).isEmpty();
}
@Test
public void getEnabledScreenReaderServices_notEnabled_returnEmpty() {
Resources resources = spy(mContext.getResources());
when(mContext.getResources()).thenReturn(resources);
when(resources.getStringArray(R.array.config_preinstalled_screen_reader_services))
.thenReturn(new String[]{"pkg/serviceClassName"});
mShadowAccessibilityManager.setEnabledAccessibilityServiceList(
new ArrayList<>());
Set<ComponentName> result = AudioStreamsHelper.getEnabledScreenReaderServices(mContext);
assertThat(result).isEmpty();
}
@Test
public void getEnabledScreenReaderServices_enabled_returnService() {
Resources resources = spy(mContext.getResources());
when(mContext.getResources()).thenReturn(resources);
when(resources.getStringArray(R.array.config_preinstalled_screen_reader_services))
.thenReturn(new String[]{"pkg/serviceClassName"});
ComponentName expected = new ComponentName("pkg", "serviceClassName");
when(mTalkbackServiceInfo.getComponentName()).thenReturn(expected);
mShadowAccessibilityManager.setEnabledAccessibilityServiceList(
new ArrayList<>(List.of(mTalkbackServiceInfo)));
Set<ComponentName> result = AudioStreamsHelper.getEnabledScreenReaderServices(mContext);
assertThat(result).isNotEmpty();
assertThat(result.iterator().next()).isEqualTo(expected);
}
@Test
public void setAccessibilityServiceOff_valueOff() {
ComponentName componentName = new ComponentName("pkg", "serviceClassName");
var target = new HashSet<ComponentName>();
target.add(componentName);
AudioStreamsHelper.setAccessibilityServiceOff(mContext, target);
assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext,
UserHandle.myUserId())).isEmpty();
}
private void setUpFragment( private void setUpFragment(
FragmentActivity fragmentActivity, AppBarLayout appBarLayout, int orientationPortrait) { FragmentActivity fragmentActivity, AppBarLayout appBarLayout, int orientationPortrait) {
Resources resources = mock(Resources.class); Resources resources = mock(Resources.class);

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.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -124,6 +125,15 @@ public class AudioStreamsProgressCategoryCallbackTest {
verify(mController).setScanning(anyBoolean()); 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 @Test
public void testOnSearchStarted() { public void testOnSearchStarted() {
mCallback.onSearchStarted(/* reason= */ 0); mCallback.onSearchStarted(/* reason= */ 0);
@@ -138,6 +148,14 @@ public class AudioStreamsProgressCategoryCallbackTest {
verify(mController).showToast(anyString()); verify(mController).showToast(anyString());
} }
@Test
public void testOnSearchStopFailed_ignoreAlreadyInTargetState() {
mCallback.onSearchStopFailed(/* reason= */
BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE);
verify(mController, never()).showToast(anyString());
}
@Test @Test
public void testOnSearchStopped() { public void testOnSearchStopped() {
mCallback.onSearchStopped(/* reason= */ 0); mCallback.onSearchStopped(/* reason= */ 0);

View File

@@ -52,10 +52,12 @@ import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothStatusCodes;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.os.Looper; import android.os.Looper;
import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.flag.junit.SetFlagsRule;
import android.view.View; import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; 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_1 = "name_1";
private static final String BROADCAST_NAME_2 = "name_2"; private static final String BROADCAST_NAME_2 = "name_2";
private static final byte[] BROADCAST_CODE = new byte[] {1}; 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 LocalBluetoothManager mLocalBtManager;
@Mock private BluetoothEventManager mBluetoothEventManager; @Mock private BluetoothEventManager mBluetoothEventManager;
@Mock
private AccessibilityManager mAccessibilityManager;
@Mock private PreferenceScreen mScreen; @Mock private PreferenceScreen mScreen;
@Mock private AudioStreamsHelper mAudioStreamsHelper; @Mock private AudioStreamsHelper mAudioStreamsHelper;
@Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; @Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
@@ -152,6 +156,8 @@ public class AudioStreamsProgressCategoryControllerTest {
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager; ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager); when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(false); when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(false);
when(mContext.getSystemService(AccessibilityManager.class)).thenReturn(
mAccessibilityManager);
when(mScreen.findPreference(anyString())).thenReturn(mPreference); when(mScreen.findPreference(anyString())).thenReturn(mPreference);
@@ -200,6 +206,7 @@ public class AudioStreamsProgressCategoryControllerTest {
mController.onStop(mLifecycleOwner); mController.onStop(mLifecycleOwner);
verify(mBluetoothEventManager).unregisterCallback(any()); verify(mBluetoothEventManager).unregisterCallback(any());
verify(mAccessibilityManager).removeAccessibilityServicesStateChangeListener(any());
} }
@Test @Test
@@ -252,6 +259,56 @@ public class AudioStreamsProgressCategoryControllerTest {
dialog.cancel(); 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 @Test
public void testBluetoothOff_triggerRunnable() { public void testBluetoothOff_triggerRunnable() {
mController.mBluetoothCallback.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF); mController.mBluetoothCallback.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);

View File

@@ -21,6 +21,8 @@ import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssista
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.ComponentName;
import android.content.Context;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -33,14 +35,17 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements; import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter; import org.robolectric.annotation.Resetter;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
@Implements(value = AudioStreamsHelper.class, callThroughByDefault = true) @Implements(value = AudioStreamsHelper.class, callThroughByDefault = true)
public class ShadowAudioStreamsHelper { public class ShadowAudioStreamsHelper {
private static AudioStreamsHelper sMockHelper; private static AudioStreamsHelper sMockHelper;
@Nullable private static CachedBluetoothDevice sCachedBluetoothDevice; @Nullable private static CachedBluetoothDevice sCachedBluetoothDevice;
@Nullable private static ComponentName sEnabledScreenReaderService;
public static void setUseMock(AudioStreamsHelper mockAudioStreamsHelper) { public static void setUseMock(AudioStreamsHelper mockAudioStreamsHelper) {
sMockHelper = mockAudioStreamsHelper; sMockHelper = mockAudioStreamsHelper;
@@ -51,6 +56,7 @@ public class ShadowAudioStreamsHelper {
public static void reset() { public static void reset() {
sMockHelper = null; sMockHelper = null;
sCachedBluetoothDevice = null; sCachedBluetoothDevice = null;
sEnabledScreenReaderService = null;
} }
public static void setCachedBluetoothDeviceInSharingOrLeConnected( public static void setCachedBluetoothDeviceInSharingOrLeConnected(
@@ -58,6 +64,10 @@ public class ShadowAudioStreamsHelper {
sCachedBluetoothDevice = cachedBluetoothDevice; sCachedBluetoothDevice = cachedBluetoothDevice;
} }
public static void setEnabledScreenReaderService(ComponentName componentName) {
sEnabledScreenReaderService = componentName;
}
@Implementation @Implementation
public Map<Integer, LocalBluetoothLeBroadcastSourceState> getConnectedBroadcastIdAndState( public Map<Integer, LocalBluetoothLeBroadcastSourceState> getConnectedBroadcastIdAndState(
boolean hysteresisModeFixAvailable) { boolean hysteresisModeFixAvailable) {
@@ -76,6 +86,15 @@ public class ShadowAudioStreamsHelper {
return Optional.ofNullable(sCachedBluetoothDevice); return Optional.ofNullable(sCachedBluetoothDevice);
} }
/** Retrieves a set of enabled screen reader services that are pre-installed. */
@Implementation
public static Set<ComponentName> getEnabledScreenReaderServices(Context context) {
if (sEnabledScreenReaderService != null) {
return Set.of(sEnabledScreenReaderService);
}
return Collections.emptySet();
}
@Implementation @Implementation
public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() { public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
return sMockHelper.getLeBroadcastAssistant(); return sMockHelper.getLeBroadcastAssistant();