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:
@@ -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] -->
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<>();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user