Merge "Support flagging for PreferenceScreenCreator" into main
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0999f0a..887491d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7867,7 +7867,9 @@
<string name="keywords_keyboard_vibration">keyboard, haptics, vibrate,</string>
<!-- Summary for sound settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
- <string name="sound_dashboard_summary">Volume, vibration, Do Not Disturb</string>
+ <string name="sound_dashboard_summary">Volume and vibration</string>
+ <!-- Summary for sound settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
+ <string name="sound_dashboard_summary_with_dnd">Volume, vibration, Do Not Disturb</string>
<!-- Sound: Title for the option managing media volume. [CHAR LIMIT=30] -->
<string name="media_volume_option_title">Media volume</string>
@@ -13577,6 +13579,8 @@
<string name="audio_streams_dialog_cannot_play">Can\u0027t play this audio stream on <xliff:g example="LE headset" id="device_name">%1$s</xliff:g>.</string>
<!-- The preference summary when add source succeed [CHAR LIMIT=NONE] -->
<string name="audio_streams_listening_now">Listening now</string>
+ <!-- The preference summary when source is present on sinks [CHAR LIMIT=NONE] -->
+ <string name="audio_streams_present_now">Paused by host</string>
<!-- Le audio streams service notification leave broadcast text [CHAR LIMIT=NONE] -->
<string name="audio_streams_media_service_notification_leave_broadcast_text">Stop listening</string>
<!-- Le audio streams no le device dialog title [CHAR LIMIT=NONE] -->
diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml
index 1ec968a..44fe7fc 100644
--- a/res/xml/top_level_settings.xml
+++ b/res/xml/top_level_settings.xml
@@ -104,8 +104,9 @@
android:key="top_level_sound"
android:order="-90"
android:title="@string/sound_settings"
- android:summary="@string/sound_dashboard_summary"
- settings:highlightableMenuKey="@string/menu_key_sound"/>
+ android:summary="@string/sound_dashboard_summary_with_dnd"
+ settings:highlightableMenuKey="@string/menu_key_sound"
+ settings:controller="com.android.settings.sound.TopLevelSoundPreferenceController"/>
<com.android.settings.widget.HomepagePreference
android:fragment="com.android.settings.DisplaySettings"
diff --git a/res/xml/top_level_settings_v2.xml b/res/xml/top_level_settings_v2.xml
index fe98a71..4bc66f6 100644
--- a/res/xml/top_level_settings_v2.xml
+++ b/res/xml/top_level_settings_v2.xml
@@ -79,8 +79,9 @@
android:key="top_level_sound"
android:order="-40"
android:title="@string/sound_settings"
- android:summary="@string/sound_dashboard_summary"
- settings:highlightableMenuKey="@string/menu_key_sound"/>
+ android:summary="@string/sound_dashboard_summary_with_dnd"
+ settings:highlightableMenuKey="@string/menu_key_sound"
+ settings:controller="com.android.settings.sound.TopLevelSoundPreferenceController"/>
<com.android.settings.widget.RestrictedHomepagePreference
android:fragment="com.android.settings.notification.modes.ZenModesListFragment"
diff --git a/src/com/android/settings/accessibility/AccessibilityActivityPreference.java b/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
index 914d9cf..a8e456d 100644
--- a/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
+++ b/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
@@ -26,6 +26,7 @@
import android.os.Bundle;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.android.settings.R;
@@ -101,6 +102,11 @@
return mLabel;
}
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
private Drawable getA11yActivityIcon() {
ActivityInfo activityInfo = mA11yShortcutInfo.getActivityInfo();
Drawable serviceIcon;
diff --git a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java
index 6aa8c84..6a0b5e2 100644
--- a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java
+++ b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java
@@ -16,8 +16,12 @@
package com.android.settings.accessibility;
+import android.content.ComponentName;
import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.List;
@@ -28,10 +32,22 @@
public interface AccessibilitySearchFeatureProvider {
/**
- * Returns a list of raw data for indexing. See {@link SearchIndexableRaw}
+ * Returns accessibility features to be searched where the accessibility features are always on
+ * the device and their feature names won't change.
*
* @param context a valid context {@link Context} instance
- * @return a list of {@link SearchIndexableRaw} references. Can be null.
+ * @return a list of {@link SearchIndexableRaw} references
*/
+ @Nullable
List<SearchIndexableRaw> getSearchIndexableRawData(Context context);
+
+ /**
+ * Returns synonyms of the Accessibility component that is used for search.
+ *
+ * @param context the context that is used for grabbing resources
+ * @param componentName the ComponentName of the accessibility feature
+ * @return a comma separated synonyms e.g. "wifi, wi-fi, network connection"
+ */
+ @NonNull
+ String getSynonymsForComponent(@NonNull Context context, @NonNull ComponentName componentName);
}
diff --git a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java
index c358af1..94594a1 100644
--- a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java
+++ b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java
@@ -16,8 +16,12 @@
package com.android.settings.accessibility;
+import android.content.ComponentName;
import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.List;
@@ -27,8 +31,16 @@
*/
public class AccessibilitySearchFeatureProviderImpl implements AccessibilitySearchFeatureProvider {
+ @Nullable
@Override
public List<SearchIndexableRaw> getSearchIndexableRawData(Context context) {
return null;
}
+
+ @NonNull
+ @Override
+ public String getSynonymsForComponent(@NonNull Context context,
+ @NonNull ComponentName componentName) {
+ return "";
+ }
}
diff --git a/src/com/android/settings/accessibility/AccessibilityServicePreference.java b/src/com/android/settings/accessibility/AccessibilityServicePreference.java
index c1dfae8..8a22d82 100644
--- a/src/com/android/settings/accessibility/AccessibilityServicePreference.java
+++ b/src/com/android/settings/accessibility/AccessibilityServicePreference.java
@@ -26,6 +26,7 @@
import android.os.Bundle;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.android.settings.R;
@@ -95,6 +96,11 @@
super.performClick();
}
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
private Drawable getA11yServiceIcon() {
ResolveInfo resolveInfo = mA11yServiceInfo.getResolveInfo();
Drawable serviceIcon;
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index 8de4936..db8f937 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -473,7 +473,7 @@
* @param installedShortcutList A list of installed {@link AccessibilityShortcutInfo}s.
* @param installedServiceList A list of installed {@link AccessibilityServiceInfo}s.
*/
- private List<RestrictedPreference> getInstalledAccessibilityPreferences(Context context,
+ private static List<RestrictedPreference> getInstalledAccessibilityPreferences(Context context,
List<AccessibilityShortcutInfo> installedShortcutList,
List<AccessibilityServiceInfo> installedServiceList) {
final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context);
@@ -623,6 +623,51 @@
.getAccessibilitySearchFeatureProvider().getSearchIndexableRawData(
context);
}
+
+ @Override
+ public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context,
+ boolean enabled) {
+ List<SearchIndexableRaw> dynamicRawData = super.getDynamicRawDataToIndex(
+ context, enabled);
+ if (dynamicRawData == null) {
+ dynamicRawData = new ArrayList<>();
+ }
+ if (!Flags.fixA11ySettingsSearch()) {
+ return dynamicRawData;
+ }
+
+ AccessibilityManager a11yManager = context.getSystemService(
+ AccessibilityManager.class);
+ AccessibilitySearchFeatureProvider a11ySearchFeatureProvider =
+ FeatureFactory.getFeatureFactory()
+ .getAccessibilitySearchFeatureProvider();
+ List<RestrictedPreference> installedA11yFeaturesPref =
+ AccessibilitySettings.getInstalledAccessibilityPreferences(
+ context,
+ a11yManager.getInstalledAccessibilityShortcutListAsUser(
+ context, UserHandle.myUserId()),
+ a11yManager.getInstalledAccessibilityServiceList()
+ );
+ for (RestrictedPreference pref : installedA11yFeaturesPref) {
+ SearchIndexableRaw indexableRaw = new SearchIndexableRaw(context);
+ indexableRaw.key = pref.getKey();
+ indexableRaw.title = pref.getTitle().toString();
+ @NonNull String synonyms = "";
+ if (pref instanceof AccessibilityServicePreference) {
+ synonyms = a11ySearchFeatureProvider.getSynonymsForComponent(
+ context,
+ ((AccessibilityServicePreference) pref).getComponentName());
+ } else if (pref instanceof AccessibilityActivityPreference) {
+ synonyms = a11ySearchFeatureProvider.getSynonymsForComponent(
+ context,
+ ((AccessibilityActivityPreference) pref).getComponentName());
+ }
+ indexableRaw.keywords = synonyms;
+ dynamicRawData.add(indexableRaw);
+ }
+
+ return dynamicRawData;
+ }
};
@Override
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
index 939dd5c..48acf32 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant;
@@ -41,6 +43,7 @@
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.ActionButtonsPreference;
+import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -73,12 +76,18 @@
int sourceId,
BluetoothLeBroadcastReceiveState state) {
super.onReceiveStateChanged(sink, sourceId, state);
- if (AudioStreamsHelper.isConnected(state)) {
+ boolean shouldUpdateButton =
+ audioSharingHysteresisModeFix()
+ ? AudioStreamsHelper.hasSourcePresent(state)
+ : AudioStreamsHelper.isConnected(state);
+ if (shouldUpdateButton) {
updateButton();
- mMetricsFeatureProvider.action(
- mContext,
- SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
- SOURCE_ORIGIN_REPOSITORY);
+ if (AudioStreamsHelper.isConnected(state)) {
+ mMetricsFeatureProvider.action(
+ mContext,
+ SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
+ SOURCE_ORIGIN_REPOSITORY);
+ }
}
}
@@ -146,8 +155,13 @@
Log.w(TAG, "updateButton(): preference is null!");
return;
}
+
+ List<BluetoothLeBroadcastReceiveState> sources =
+ audioSharingHysteresisModeFix()
+ ? mAudioStreamsHelper.getAllPresentSources()
+ : mAudioStreamsHelper.getAllConnectedSources();
boolean isConnected =
- mAudioStreamsHelper.getAllConnectedSources().stream()
+ sources.stream()
.map(BluetoothLeBroadcastReceiveState::getBroadcastId)
.anyMatch(connectedBroadcastId -> connectedBroadcastId == mBroadcastId);
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
index e1a178d..0ee93e7 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
@@ -16,6 +16,10 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
+import static java.util.stream.Collectors.toList;
+
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -48,6 +52,8 @@
static final int AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY =
R.string.audio_streams_listening_now;
+ static final int AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY = R.string.audio_streams_present_now;
+
@VisibleForTesting static final String AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY = "";
private static final String TAG = "AudioStreamHeaderController";
private static final String KEY = "audio_stream_header";
@@ -80,6 +86,10 @@
updateSummary();
mAudioStreamsHelper.startMediaService(
mContext, mBroadcastId, mBroadcastName);
+ } else if (audioSharingHysteresisModeFix()
+ && AudioStreamsHelper.hasSourcePresent(state)) {
+ // if source present but not connected, only update the summary
+ updateSummary();
}
}
};
@@ -140,8 +150,27 @@
var unused =
ThreadUtils.postOnBackgroundThread(
() -> {
+ var connectedSourceList =
+ mAudioStreamsHelper.getAllPresentSources().stream()
+ .filter(
+ state ->
+ (state.getBroadcastId()
+ == mBroadcastId))
+ .collect(toList());
+
var latestSummary =
- mAudioStreamsHelper.getAllConnectedSources().stream()
+ audioSharingHysteresisModeFix()
+ ? connectedSourceList.isEmpty()
+ ? AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY
+ : (connectedSourceList.stream()
+ .anyMatch(
+ AudioStreamsHelper
+ ::isConnected)
+ ? mContext.getString(
+ AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
+ : mContext.getString(
+ AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY))
+ : mAudioStreamsHelper.getAllConnectedSources().stream()
.map(
BluetoothLeBroadcastReceiveState
::getBroadcastId)
@@ -149,9 +178,10 @@
connectedBroadcastId ->
connectedBroadcastId
== mBroadcastId)
- ? mContext.getString(
- AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
- : AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
+ ? mContext.getString(
+ AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
+ : AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
+
ThreadUtils.postOnMainThread(
() -> {
if (mHeaderController != null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java
index 758984f..458cfab 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java
@@ -18,6 +18,8 @@
import static android.text.Spanned.SPAN_EXCLUSIVE_INCLUSIVE;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableString;
@@ -94,8 +96,12 @@
}
preference.setIsConnected(
newState
- == AudioStreamsProgressCategoryController.AudioStreamState
- .SOURCE_ADDED);
+ == AudioStreamsProgressCategoryController
+ .AudioStreamState.SOURCE_ADDED
+ || (audioSharingHysteresisModeFix()
+ && newState
+ == AudioStreamsProgressCategoryController
+ .AudioStreamState.SOURCE_PRESENT));
preference.setOnPreferenceClickListener(getOnClickListener(controller));
});
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
index c219e0b..c0d9162 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
@@ -19,6 +19,7 @@
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.DEVICES;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
import static java.util.Collections.emptyList;
@@ -63,6 +64,12 @@
private final @Nullable LocalBluetoothManager mBluetoothManager;
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
+ // Referring to Broadcast Audio Scan Service 1.0
+ // Table 3.9: Broadcast Receive State characteristic format
+ // 0x00000000: 0b0 = Not synchronized to BIS_index[x]
+ // 0xFFFFFFFF: Failed to sync to BIG
+ private static final long BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L;
+ private static final long BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL;
AudioStreamsHelper(@Nullable LocalBluetoothManager bluetoothManager) {
mBluetoothManager = bluetoothManager;
@@ -144,6 +151,19 @@
.toList();
}
+ /** Retrieves a list of all LE broadcast receive states from sinks with source present. */
+ @VisibleForTesting
+ public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "getAllPresentSources(): LeBroadcastAssistant is null!");
+ return emptyList();
+ }
+ return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream()
+ .flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
+ .filter(AudioStreamsHelper::hasSourcePresent)
+ .toList();
+ }
+
/** Retrieves LocalBluetoothLeBroadcastAssistant. */
@VisibleForTesting
@Nullable
@@ -153,7 +173,18 @@
/** Checks the connectivity status based on the provided broadcast receive state. */
public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
- return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0);
+ return state.getBisSyncState().stream()
+ .anyMatch(
+ bitmap ->
+ (bitmap != BIS_SYNC_NOT_SYNC_TO_BIS
+ && bitmap != BIS_SYNC_FAILED_SYNC_TO_BIG));
+ }
+
+ /** Checks the connectivity status based on the provided broadcast receive state. */
+ public static boolean hasSourcePresent(BluetoothLeBroadcastReceiveState state) {
+ // Referring to Broadcast Audio Scan Service 1.0
+ // All zero address means no source on the sink device
+ return !state.getSourceDevice().getAddress().equals("00:00:00:00:00:00");
}
static boolean isBadCode(BluetoothLeBroadcastReceiveState state) {
@@ -242,7 +273,8 @@
List<BluetoothLeBroadcastReceiveState> sourceList =
assistant.getAllSources(cachedDevice.getDevice());
if (!sourceList.isEmpty()
- && sourceList.stream().anyMatch(AudioStreamsHelper::isConnected)) {
+ && (audioSharingHysteresisModeFix()
+ || sourceList.stream().anyMatch(AudioStreamsHelper::isConnected))) {
Log.d(
TAG,
"Lead device has connected broadcast source, device = "
@@ -253,7 +285,9 @@
for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
List<BluetoothLeBroadcastReceiveState> list =
assistant.getAllSources(device.getDevice());
- if (!list.isEmpty() && list.stream().anyMatch(AudioStreamsHelper::isConnected)) {
+ if (!list.isEmpty()
+ && (audioSharingHysteresisModeFix()
+ || list.stream().anyMatch(AudioStreamsHelper::isConnected))) {
Log.d(
TAG,
"Member device has connected broadcast source, device = "
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
index 3370d8d..b379d4e 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -39,6 +41,9 @@
mCategoryController.handleSourceConnected(state);
} else if (AudioStreamsHelper.isBadCode(state)) {
mCategoryController.handleSourceConnectBadCode(state);
+ } else if (audioSharingHysteresisModeFix() && AudioStreamsHelper.hasSourcePresent(state)) {
+ // Keep this check as the last, source might also present in above states
+ mCategoryController.handleSourcePresent(state);
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index 9bbf135..7ab5882 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
import static java.util.Collections.emptyList;
import android.app.AlertDialog;
@@ -48,6 +50,7 @@
import com.android.settingslib.utils.ThreadUtils;
import java.util.Comparator;
+import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -95,9 +98,14 @@
private final Comparator<AudioStreamPreference> mComparator =
Comparator.<AudioStreamPreference, Boolean>comparing(
p ->
- p.getAudioStreamState()
- == AudioStreamsProgressCategoryController
- .AudioStreamState.SOURCE_ADDED)
+ (p.getAudioStreamState()
+ == AudioStreamsProgressCategoryController
+ .AudioStreamState.SOURCE_ADDED
+ || (audioSharingHysteresisModeFix()
+ && p.getAudioStreamState()
+ == AudioStreamsProgressCategoryController
+ .AudioStreamState
+ .SOURCE_PRESENT)))
.thenComparingInt(AudioStreamPreference::getAudioStreamRssi)
.reversed();
@@ -113,6 +121,8 @@
ADD_SOURCE_BAD_CODE,
// When addSource result in other bad state.
ADD_SOURCE_FAILED,
+ // Source is present on sink.
+ SOURCE_PRESENT,
// Source is added to active sink.
SOURCE_ADDED,
}
@@ -243,10 +253,13 @@
existingPreference, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
} else {
// A preference with source founded existed either because it's already
- // connected (SOURCE_ADDED). Any other reason is unexpected. We update the
- // preference with this source and won't change it's state.
+ // connected (SOURCE_ADDED) or present (SOURCE_PRESENT). Any other reason
+ // is unexpected. We update the preference with this source and won't
+ // change it's state.
existingPreference.setAudioStreamMetadata(source);
- if (fromState != AudioStreamState.SOURCE_ADDED) {
+ if (fromState != AudioStreamState.SOURCE_ADDED
+ && (!audioSharingHysteresisModeFix()
+ || fromState != AudioStreamState.SOURCE_PRESENT)) {
Log.w(
TAG,
"handleSourceFound(): unexpected state : "
@@ -346,10 +359,14 @@
for (var entry : mBroadcastIdToPreferenceMap.entrySet()) {
var preference = entry.getValue();
- // Look for preference has SOURCE_ADDED state, re-check if they are still connected. If
+ // Look for preference has SOURCE_ADDED or SOURCE_PRESENT state, re-check if they are
+ // still connected. If
// not, means the source is removed from the sink, we move back the preference to SYNCED
// state.
- if (preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED
+ if ((preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED
+ || (audioSharingHysteresisModeFix()
+ && preference.getAudioStreamState()
+ == AudioStreamState.SOURCE_PRESENT))
&& mAudioStreamsHelper.getAllConnectedSources().stream()
.noneMatch(
connected ->
@@ -383,6 +400,7 @@
if (!AudioStreamsHelper.isConnected(receiveState)) {
return;
}
+
var broadcastIdConnected = receiveState.getBroadcastId();
if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) {
// mSourceFromQrCode could have no broadcast Id, we fill in the broadcast Id from the
@@ -455,6 +473,58 @@
});
}
+ // Find preference by receiveState and decide next state.
+ // Expect one preference existed, move to SOURCE_PRESENT
+ void handleSourcePresent(BluetoothLeBroadcastReceiveState receiveState) {
+ if (DEBUG) {
+ Log.d(TAG, "handleSourcePresent()");
+ }
+ if (!AudioStreamsHelper.hasSourcePresent(receiveState)) {
+ return;
+ }
+
+ var broadcastIdConnected = receiveState.getBroadcastId();
+ if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) {
+ // mSourceFromQrCode could have no broadcast Id, we fill in the broadcast Id from the
+ // connected source receiveState.
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "handleSourcePresent() : processing mSourceFromQrCode with broadcastId"
+ + " unset");
+ }
+ boolean updated =
+ maybeUpdateId(
+ AudioStreamsHelper.getBroadcastName(receiveState),
+ receiveState.getBroadcastId());
+ if (updated && mBroadcastIdToPreferenceMap.containsKey(UNSET_BROADCAST_ID)) {
+ var preference = mBroadcastIdToPreferenceMap.remove(UNSET_BROADCAST_ID);
+ mBroadcastIdToPreferenceMap.put(receiveState.getBroadcastId(), preference);
+ }
+ }
+
+ mBroadcastIdToPreferenceMap.compute(
+ broadcastIdConnected,
+ (k, existingPreference) -> {
+ if (existingPreference == null) {
+ // No existing preference for this source even if it's already connected,
+ // add one and set initial state to SOURCE_PRESENT. This could happen
+ // because
+ // we retrieves the connected source during onStart() from
+ // AudioStreamsHelper#getAllPresentSources() even before the source is
+ // founded by scanning.
+ return addNewPreference(receiveState, AudioStreamState.SOURCE_PRESENT);
+ }
+ if (existingPreference.getAudioStreamState() == AudioStreamState.WAIT_FOR_SYNC
+ && existingPreference.getAudioStreamBroadcastId() == UNSET_BROADCAST_ID
+ && mSourceFromQrCode != null) {
+ existingPreference.setAudioStreamMetadata(mSourceFromQrCode);
+ }
+ moveToState(existingPreference, AudioStreamState.SOURCE_PRESENT);
+ return existingPreference;
+ });
+ }
+
// Find preference by metadata and decide next state.
// Expect one preference existed, move to ADD_SOURCE_WAIT_FOR_RESPONSE
void handleSourceAddRequest(
@@ -530,9 +600,23 @@
// Handle QR code scan, display currently connected streams then start scanning
// sequentially
handleSourceFromQrCodeIfExists();
- mAudioStreamsHelper
- .getAllConnectedSources()
- .forEach(this::handleSourceConnected);
+ if (audioSharingHysteresisModeFix()) {
+ // With hysteresis mode, we prioritize showing connected sources first.
+ // If no connected sources are found, we then show present sources.
+ List<BluetoothLeBroadcastReceiveState> sources =
+ mAudioStreamsHelper.getAllConnectedSources();
+ if (!sources.isEmpty()) {
+ sources.forEach(this::handleSourceConnected);
+ } else {
+ mAudioStreamsHelper
+ .getAllPresentSources()
+ .forEach(this::handleSourcePresent);
+ }
+ } else {
+ mAudioStreamsHelper
+ .getAllConnectedSources()
+ .forEach(this::handleSourceConnected);
+ }
mLeBroadcastAssistant.startSearchingForSources(emptyList());
mMediaControlHelper.start();
});
@@ -581,6 +665,7 @@
AddSourceWaitForResponseState.getInstance();
case ADD_SOURCE_BAD_CODE -> AddSourceBadCodeState.getInstance();
case ADD_SOURCE_FAILED -> AddSourceFailedState.getInstance();
+ case SOURCE_PRESENT -> SourcePresentState.getInstance();
case SOURCE_ADDED -> SourceAddedState.getInstance();
default -> throw new IllegalArgumentException("Unsupported state: " + state);
};
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentState.java
new file mode 100644
index 0000000..1e724f1
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentState.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing.audiostreams;
+
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.dashboard.DashboardFragment;
+
+class SourcePresentState extends AudioStreamStateHandler {
+ @VisibleForTesting
+ static final int AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY = R.string.audio_streams_present_now;
+
+ @Nullable private static SourcePresentState sInstance = null;
+
+ SourcePresentState() {}
+
+ static SourcePresentState getInstance() {
+ if (sInstance == null) {
+ sInstance = new SourcePresentState();
+ }
+ return sInstance;
+ }
+
+ @Override
+ void performAction(
+ AudioStreamPreference preference,
+ AudioStreamsProgressCategoryController controller,
+ AudioStreamsHelper helper) {
+ // nothing to do
+ }
+
+ @Override
+ int getSummary() {
+ return AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY;
+ }
+
+ @Override
+ Preference.OnPreferenceClickListener getOnClickListener(
+ AudioStreamsProgressCategoryController controller) {
+ return preference -> {
+ var p = (AudioStreamPreference) preference;
+ Bundle broadcast = new Bundle();
+ broadcast.putString(
+ AudioStreamDetailsFragment.BROADCAST_NAME_ARG, (String) p.getTitle());
+ broadcast.putInt(
+ AudioStreamDetailsFragment.BROADCAST_ID_ARG, p.getAudioStreamBroadcastId());
+
+ new SubSettingLauncher(p.getContext())
+ .setTitleRes(R.string.audio_streams_detail_page_title)
+ .setDestination(AudioStreamDetailsFragment.class.getName())
+ .setSourceMetricsCategory(
+ !(controller.getFragment() instanceof DashboardFragment)
+ ? SettingsEnums.PAGE_UNKNOWN
+ : ((DashboardFragment) controller.getFragment())
+ .getMetricsCategory())
+ .setArguments(broadcast)
+ .launch();
+ return true;
+ };
+ }
+
+ @Override
+ AudioStreamsProgressCategoryController.AudioStreamState getStateEnum() {
+ return AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT;
+ }
+}
diff --git a/src/com/android/settings/datausage/DataUsageSummary.java b/src/com/android/settings/datausage/DataUsageSummary.java
index 5681c92..dfb3508 100644
--- a/src/com/android/settings/datausage/DataUsageSummary.java
+++ b/src/com/android/settings/datausage/DataUsageSummary.java
@@ -180,14 +180,16 @@
void addWifiSection() {
TemplatePreferenceCategory category = (TemplatePreferenceCategory)
inflatePreferences(R.xml.data_usage_wifi);
- category.setTemplate(new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build(), 0);
+ category.setTemplate(new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
private void addEthernetSection() {
TemplatePreferenceCategory category = (TemplatePreferenceCategory)
inflatePreferences(R.xml.data_usage_ethernet);
category.setTemplate(
- new NetworkTemplate.Builder(NetworkTemplate.MATCH_ETHERNET).build(), 0);
+ new NetworkTemplate.Builder(NetworkTemplate.MATCH_ETHERNET).build(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
private Preference inflatePreferences(int resId) {
diff --git a/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceController.java
index 9332c9b..82f0816 100644
--- a/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceController.java
@@ -76,7 +76,7 @@
// DND turned on by an automatic rule with deprecated zen mode
for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
- if (automaticRule.isAutomaticActive() && isDeprecatedZenMode(
+ if (automaticRule.isActive() && isDeprecatedZenMode(
automaticRule.zenMode)) {
ComponentName component = automaticRule.component;
if (component != null) {
diff --git a/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceController.java
index 6a57441..4781b36 100644
--- a/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceController.java
@@ -153,7 +153,7 @@
// DND turned on by an automatic rule
for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
- if (automaticRule.isAutomaticActive()) {
+ if (automaticRule.isActive()) {
// set footer if 3rd party rule
if (!mZenModeConfigWrapper.isTimeRule(automaticRule.conditionId)) {
return mContext.getString(R.string.zen_mode_settings_dnd_automatic_rule,
@@ -180,7 +180,7 @@
}
for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
- if (automaticRule.isAutomaticActive()) {
+ if (automaticRule.isActive()) {
zenRules.add(automaticRule);
}
}
diff --git a/src/com/android/settings/sound/TopLevelSoundPreferenceController.java b/src/com/android/settings/sound/TopLevelSoundPreferenceController.java
new file mode 100644
index 0000000..ddc3399
--- /dev/null
+++ b/src/com/android/settings/sound/TopLevelSoundPreferenceController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sound;
+
+import android.app.Flags;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+public class TopLevelSoundPreferenceController extends BasePreferenceController {
+
+ public TopLevelSoundPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ preference.setSummary(Flags.modesApi() && Flags.modesUi()
+ ? R.string.sound_dashboard_summary
+ : R.string.sound_dashboard_summary_with_dnd);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
index 3982dc0..36578a9 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
@@ -42,7 +42,6 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
import androidx.fragment.app.Fragment;
import androidx.test.core.app.ApplicationProvider;
@@ -50,6 +49,7 @@
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.XmlTestUtils;
import com.android.settings.testutils.shadow.ShadowAccessibilityManager;
import com.android.settings.testutils.shadow.ShadowApplicationPackageManager;
@@ -78,6 +78,7 @@
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowLooper;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
@@ -155,6 +156,53 @@
assertThat(indexableRawList).isNull();
}
+ @DisableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
+ @Test
+ public void getDynamicRawDataToIndex_hasInstalledA11yFeatures_flagOff_returnEmpty() {
+ mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
+ List.of(mServiceInfo));
+ mShadowAccessibilityManager.setInstalledAccessibilityShortcutListAsUser(
+ List.of(getMockAccessibilityShortcutInfo()));
+
+ assertThat(AccessibilitySettings.SEARCH_INDEX_DATA_PROVIDER.getDynamicRawDataToIndex(
+ mContext, /* enabled= */ true))
+ .isEmpty();
+ }
+
+ @EnableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
+ @Test
+ public void getDynamicRawDataToIndex_hasInstalledA11yFeatures_flagOn_returnRawDataForInstalledA11yFeatures() {
+ mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
+ List.of(mServiceInfo));
+ mShadowAccessibilityManager.setInstalledAccessibilityShortcutListAsUser(
+ List.of(getMockAccessibilityShortcutInfo()));
+ final AccessibilitySearchFeatureProvider featureProvider =
+ FakeFeatureFactory.setupForTest().getAccessibilitySearchFeatureProvider();
+ final String synonyms = "fake keyword1, fake keyword2";
+ when(featureProvider.getSynonymsForComponent(mContext, ACTIVITY_COMPONENT_NAME))
+ .thenReturn("");
+ when(featureProvider.getSynonymsForComponent(mContext, SERVICE_COMPONENT_NAME))
+ .thenReturn(synonyms);
+
+ final List<SearchIndexableRaw> indexableRawDataList =
+ AccessibilitySettings.SEARCH_INDEX_DATA_PROVIDER.getDynamicRawDataToIndex(
+ mContext, /* enabled= */ true);
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+ assertThat(indexableRawDataList).hasSize(2);
+ SearchIndexableRaw a11yActivityIndexableData = indexableRawDataList.get(0);
+ assertThat(a11yActivityIndexableData.key).isEqualTo(
+ ACTIVITY_COMPONENT_NAME.flattenToString());
+ assertThat(a11yActivityIndexableData.title).isEqualTo(DEFAULT_LABEL);
+ assertThat(a11yActivityIndexableData.keywords).isEmpty();
+
+ SearchIndexableRaw a11yServiceIndexableData = indexableRawDataList.get(1);
+ assertThat(a11yServiceIndexableData.key).isEqualTo(
+ SERVICE_COMPONENT_NAME.flattenToString());
+ assertThat(a11yServiceIndexableData.title).isEqualTo(DEFAULT_LABEL);
+ assertThat(a11yServiceIndexableData.keywords).isEqualTo(synonyms);
+ }
+
@Test
public void getServiceSummary_serviceCrash_showsStopped() {
mServiceInfo.crashed = true;
@@ -328,7 +376,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCreate_flagDisabled_haveRegisterToSpecificUrisAndActions() {
setupFragment();
@@ -341,7 +389,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCreate_flagEnabled_haveRegisterToSpecificUrisAndActions() {
setupFragment();
@@ -415,7 +463,7 @@
}
@Test
- @EnableFlags(com.android.settings.accessibility.Flags.FLAG_CHECK_PREBUNDLED_IS_PREINSTALLED)
+ @EnableFlags(Flags.FLAG_CHECK_PREBUNDLED_IS_PREINSTALLED)
public void testNonPreinstalledApp_IncludedInDownloadedCategory() {
mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
List.of(getMockAccessibilityServiceInfo(
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java
index c6fb361..1d39bc9 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -34,6 +36,7 @@
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.View;
import androidx.lifecycle.LifecycleOwner;
@@ -72,8 +75,8 @@
ShadowAudioStreamsHelper.class,
})
public class AudioStreamButtonControllerTest {
-
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String KEY = "audio_stream_button";
private static final int BROADCAST_ID = 1;
private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -83,6 +86,7 @@
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock private AudioStreamsRepository mRepository;
@Mock private ActionButtonsPreference mPreference;
+ @Mock private BluetoothDevice mSourceDevice;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
private FakeFeatureFactory mFeatureFactory;
@@ -90,6 +94,7 @@
@Before
public void setUp() {
+ mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mAssistant);
mFeatureFactory = FakeFeatureFactory.setupForTest();
@@ -255,6 +260,33 @@
}
@Test
+ public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ String address = "11:22:33:44:55:66";
+
+ BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
+ when(state.getBroadcastId()).thenReturn(BROADCAST_ID);
+ when(state.getSourceDevice()).thenReturn(mSourceDevice);
+ when(mSourceDevice.getAddress()).thenReturn(address);
+ List<Long> bisSyncState = new ArrayList<>();
+ when(state.getBisSyncState()).thenReturn(bisSyncState);
+ when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(List.of(state));
+
+ mController.displayPreference(mScreen);
+ mController.mBroadcastAssistantCallback.onReceiveStateChanged(
+ mock(BluetoothDevice.class), /* sourceId= */ 0, state);
+
+ verify(mFeatureFactory.metricsFeatureProvider, never())
+ .action(any(), eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED), anyInt());
+
+ // Called twice, once in displayPreference, the other one in callback
+ verify(mPreference, times(2)).setButton1Enabled(true);
+ verify(mPreference, times(2)).setButton1Text(R.string.audio_streams_disconnect);
+ verify(mPreference, times(2))
+ .setButton1Icon(com.android.settings.R.drawable.ic_settings_close);
+ }
+
+ @Test
public void testCallback_onSourceAddFailed_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java
index 327090d..5cdc797 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java
@@ -18,6 +18,8 @@
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY;
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -31,6 +33,7 @@
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;
@@ -68,8 +71,9 @@
ShadowAudioStreamsHelper.class,
})
public class AudioStreamHeaderControllerTest {
-
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final String KEY = "audio_stream_header";
private static final int BROADCAST_ID = 1;
private static final String BROADCAST_NAME = "broadcast name";
@@ -81,12 +85,15 @@
@Mock private AudioStreamDetailsFragment mFragment;
@Mock private LayoutPreference mPreference;
@Mock private EntityHeaderController mHeaderController;
+ @Mock private BluetoothDevice mBluetoothDevice;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
private AudioStreamHeaderController mController;
@Before
public void setUp() {
+ mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
ShadowEntityHeaderController.setUseMock(mHeaderController);
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mAssistant);
@@ -169,6 +176,44 @@
}
@Test
+ public void testDisplayPreference_sourcePresent_setSummary() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ String address = "11:22:33:44:55:66";
+
+ when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
+ when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getAddress()).thenReturn(address);
+ List<Long> bisSyncState = new ArrayList<>();
+ when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+ when(mAudioStreamsHelper.getAllPresentSources())
+ .thenReturn(List.of(mBroadcastReceiveState));
+
+ mController.displayPreference(mScreen);
+
+ verify(mHeaderController).setLabel(BROADCAST_NAME);
+ verify(mHeaderController).setIcon(any(Drawable.class));
+ verify(mHeaderController)
+ .setSummary(mContext.getString(AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY));
+ verify(mHeaderController).done(true);
+ verify(mScreen).addPreference(any());
+ }
+
+ @Test
+ public void testDisplayPreference_sourceNotPresent_setSummary() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
+ when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(Collections.emptyList());
+
+ mController.displayPreference(mScreen);
+
+ verify(mHeaderController).setLabel(BROADCAST_NAME);
+ verify(mHeaderController).setIcon(any(Drawable.class));
+ verify(mHeaderController).setSummary(AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY);
+ verify(mHeaderController).done(true);
+ verify(mScreen).addPreference(any());
+ }
+
+ @Test
public void testCallback_onSourceRemoved_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
@@ -212,4 +257,25 @@
.setSummary(mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY));
verify(mHeaderController, times(2)).done(true);
}
+
+ @Test
+ public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ String address = "11:22:33:44:55:66";
+
+ when(mAudioStreamsHelper.getAllPresentSources())
+ .thenReturn(List.of(mBroadcastReceiveState));
+ when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
+ when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getAddress()).thenReturn(address);
+
+ mController.displayPreference(mScreen);
+ mController.mBroadcastAssistantCallback.onReceiveStateChanged(
+ mock(BluetoothDevice.class), /* sourceId= */ 0, mBroadcastReceiveState);
+
+ // Called twice, once in displayPreference, the other one in callback
+ verify(mHeaderController, times(2))
+ .setSummary(mContext.getString(AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY));
+ verify(mHeaderController, times(2)).done(true);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java
index e44dee9..bb873d4 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -30,6 +32,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.SpannableString;
import androidx.preference.Preference;
@@ -48,6 +51,8 @@
@RunWith(RobolectricTestRunner.class)
public class AudioStreamStateHandlerTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int SUMMARY_RES = 1;
private static final String SUMMARY = "summary";
private final Context mContext = spy(ApplicationProvider.getApplicationContext());
@@ -58,6 +63,7 @@
@Before
public void setUp() {
+ mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mHandler = spy(new AudioStreamStateHandler());
}
@@ -102,6 +108,28 @@
}
@Test
+ public void testHandleStateChange_setNewState_sourcePresent() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
+ when(mHandler.getStateEnum())
+ .thenReturn(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT);
+ when(mPreference.getAudioStreamState())
+ .thenReturn(
+ AudioStreamsProgressCategoryController.AudioStreamState
+ .ADD_SOURCE_BAD_CODE);
+
+ mHandler.handleStateChange(mPreference, mController, mHelper);
+
+ verify(mPreference)
+ .setAudioStreamState(
+ AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT);
+ verify(mHandler).performAction(any(), any(), any());
+ verify(mPreference).setIsConnected(eq(true));
+ verify(mPreference).setSummary(eq(""));
+ verify(mPreference).setOnPreferenceClickListener(eq(null));
+ }
+
+ @Test
public void testHandleStateChange_setNewState_newSummary_newListener() {
Preference.OnPreferenceClickListener listener =
mock(Preference.OnPreferenceClickListener.class);
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java
index 4266798..fca1137 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java
@@ -19,6 +19,8 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -37,6 +39,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.fragment.app.FragmentActivity;
import androidx.test.core.app.ApplicationProvider;
@@ -74,6 +77,8 @@
})
public class AudioStreamsHelperTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int GROUP_ID = 1;
private static final int BROADCAST_ID_1 = 1;
private static final int BROADCAST_ID_2 = 2;
@@ -86,10 +91,12 @@
@Mock private BluetoothLeBroadcastMetadata mMetadata;
@Mock private CachedBluetoothDevice mCachedDevice;
@Mock private BluetoothDevice mDevice;
+ @Mock private BluetoothDevice mSourceDevice;
private AudioStreamsHelper mHelper;
@Before
public void setUp() {
+ mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile())
@@ -166,6 +173,7 @@
@Test
public void removeSource_memberHasConnectedSource() {
+ String address = "11:22:33:44:55:66";
List<BluetoothDevice> devices = new ArrayList<>();
var memberDevice = mock(BluetoothDevice.class);
devices.add(mDevice);
@@ -184,6 +192,8 @@
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
when(source.getBisSyncState()).thenReturn(bisSyncState);
+ when(source.getSourceDevice()).thenReturn(mSourceDevice);
+ when(mSourceDevice.getAddress()).thenReturn(address);
mHelper.removeSource(BROADCAST_ID_2);
@@ -218,6 +228,52 @@
}
@Test
+ public void getAllPresentSources_noSource() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
+ List<BluetoothDevice> devices = new ArrayList<>();
+ devices.add(mDevice);
+
+ String address = "00:00:00:00:00:00";
+
+ when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
+ BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class);
+ when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID);
+ when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(source));
+ when(source.getSourceDevice()).thenReturn(mSourceDevice);
+ when(mSourceDevice.getAddress()).thenReturn(address);
+
+ var list = mHelper.getAllPresentSources();
+ assertThat(list).isEmpty();
+ }
+
+ @Test
+ public void getAllPresentSources_returnSource() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ String address = "11:22:33:44:55:66";
+
+ List<BluetoothDevice> devices = new ArrayList<>();
+ devices.add(mDevice);
+
+ when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
+ BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class);
+ when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID);
+ when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(source));
+ when(source.getSourceDevice()).thenReturn(mSourceDevice);
+ when(mSourceDevice.getAddress()).thenReturn(address);
+ List<Long> bisSyncState = new ArrayList<>();
+ when(source.getBisSyncState()).thenReturn(bisSyncState);
+
+ var list = mHelper.getAllPresentSources();
+ assertThat(list).isNotEmpty();
+ assertThat(list.get(0)).isEqualTo(source);
+ }
+
+ @Test
public void startMediaService_noDevice_doNothing() {
mHelper.startMediaService(mContext, BROADCAST_ID_1, BROADCAST_NAME);
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
index 164c2f0..1e64528 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
@@ -25,6 +27,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
import org.junit.Rule;
@@ -41,14 +44,18 @@
@RunWith(RobolectricTestRunner.class)
public class AudioStreamsProgressCategoryCallbackTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock private AudioStreamsProgressCategoryController mController;
@Mock private BluetoothDevice mDevice;
@Mock private BluetoothLeBroadcastReceiveState mState;
@Mock private BluetoothLeBroadcastMetadata mMetadata;
+ @Mock private BluetoothDevice mSourceDevice;
private AudioStreamsProgressCategoryCallback mCallback;
@Before
public void setUp() {
+ mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mCallback = new AudioStreamsProgressCategoryCallback(mController);
}
@@ -63,6 +70,20 @@
}
@Test
+ public void testOnReceiveStateChanged_sourcePresent() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ String address = "11:22:33:44:55:66";
+
+ List<Long> bisSyncState = new ArrayList<>();
+ when(mState.getBisSyncState()).thenReturn(bisSyncState);
+ when(mState.getSourceDevice()).thenReturn(mSourceDevice);
+ when(mSourceDevice.getAddress()).thenReturn(address);
+ mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
+
+ verify(mController).handleSourcePresent(any());
+ }
+
+ @Test
public void testOnReceiveStateChanged_badCode() {
when(mState.getPaSyncState())
.thenReturn(BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED);
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
index fd1b649..227748a 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
@@ -20,10 +20,12 @@
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SYNCED;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.google.common.truth.Truth.assertThat;
@@ -41,12 +43,14 @@
import static java.util.Collections.emptyList;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
@@ -96,6 +100,8 @@
})
public class AudioStreamsProgressCategoryControllerTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final String VALID_METADATA =
"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=;;";
@@ -115,6 +121,7 @@
@Mock private BluetoothLeBroadcastMetadata mMetadata;
@Mock private CachedBluetoothDevice mDevice;
@Mock private AudioStreamsProgressCategoryPreference mPreference;
+ @Mock private BluetoothDevice mSourceDevice;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
private Fragment mFragment;
@@ -125,6 +132,7 @@
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant);
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
+ mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
@@ -283,6 +291,29 @@
}
@Test
+ public void testOnStart_initHasDevice_getPresentSources() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ List<BluetoothLeBroadcastReceiveState> connectedList = new ArrayList<>();
+ // Empty connected device list
+ when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(connectedList);
+
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ verify(mAudioStreamsHelper).getAllPresentSources();
+ verify(mLeBroadcastAssistant).startSearchingForSources(any());
+
+ var dialog = ShadowAlertDialog.getLatestAlertDialog();
+ assertThat(dialog).isNull();
+
+ verify(mController, never()).moveToState(any(), any());
+ }
+
+ @Test
public void testOnStart_handleSourceFromQrCode() {
// Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
@@ -764,6 +795,58 @@
assertThat(states.get(1)).isEqualTo(ADD_SOURCE_FAILED);
}
+ @Test
+ public void testHandleSourcePresent_updateState() {
+ mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ String address = "11:22:33:44:55:66";
+
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup mPreference so it's not null
+ mController.displayPreference(mScreen);
+
+ // A new source found
+ when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
+ mController.handleSourceFound(mMetadata);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // The connected source is identified as having a bad code
+ BluetoothLeBroadcastReceiveState receiveState =
+ mock(BluetoothLeBroadcastReceiveState.class);
+ when(receiveState.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
+ when(receiveState.getSourceDevice()).thenReturn(mSourceDevice);
+ when(mSourceDevice.getAddress()).thenReturn(address);
+ List<Long> bisSyncState = new ArrayList<>();
+ when(receiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ // The new found source is identified as failed to connect
+ mController.handleSourcePresent(receiveState);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ ArgumentCaptor<AudioStreamPreference> preference =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ verify(mController, times(2)).moveToState(preference.capture(), state.capture());
+ List<AudioStreamPreference> preferences = preference.getAllValues();
+ assertThat(preferences.size()).isEqualTo(2);
+ List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
+ assertThat(states.size()).isEqualTo(2);
+
+ // Verify one preference is created with SYNCED
+ assertThat(preferences.get(0).getAudioStreamBroadcastId())
+ .isEqualTo(NEWLY_FOUND_BROADCAST_ID);
+ assertThat(states.get(0)).isEqualTo(SYNCED);
+
+ // Verify the preference is updated to state ADD_SOURCE_FAILED
+ assertThat(preferences.get(1).getAudioStreamBroadcastId())
+ .isEqualTo(NEWLY_FOUND_BROADCAST_ID);
+ assertThat(states.get(1)).isEqualTo(SOURCE_PRESENT);
+ }
+
private static BluetoothLeBroadcastReceiveState createConnectedMock(int id) {
var connected = mock(BluetoothLeBroadcastReceiveState.class);
List<Long> bisSyncState = new ArrayList<>();
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentStateTest.java
new file mode 100644
index 0000000..fd84fef
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentStateTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing.audiostreams;
+
+import static android.app.settings.SettingsEnums.AUDIO_STREAM_MAIN;
+
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.SourcePresentState.AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowFragment;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ shadows = {
+ ShadowFragment.class,
+ })
+public class SourcePresentStateTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private static final int BROADCAST_ID = 1;
+ private static final String BROADCAST_TITLE = "title";
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock private AudioStreamPreference mPreference;
+ @Mock private AudioStreamsProgressCategoryController mController;
+ @Mock private AudioStreamsHelper mHelper;
+ @Mock private AudioStreamsRepository mRepository;
+ @Mock private AudioStreamsDashboardFragment mFragment;
+ @Mock private FragmentActivity mActivity;
+ private FakeFeatureFactory mFeatureFactory;
+ private SourcePresentState mInstance;
+
+ @Before
+ public void setUp() {
+ when(mFragment.getActivity()).thenReturn(mActivity);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mInstance = new SourcePresentState();
+ when(mPreference.getAudioStreamBroadcastId()).thenReturn(BROADCAST_ID);
+ when(mPreference.getTitle()).thenReturn(BROADCAST_TITLE);
+ }
+
+ @Test
+ public void testGetInstance() {
+ mInstance = SourcePresentState.getInstance();
+ assertThat(mInstance).isNotNull();
+ assertThat(mInstance).isInstanceOf(SourcePresentState.class);
+ }
+
+ @Test
+ public void testGetSummary() {
+ int summary = mInstance.getSummary();
+ assertThat(summary).isEqualTo(AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY);
+ }
+
+ @Test
+ public void testGetStateEnum() {
+ AudioStreamsProgressCategoryController.AudioStreamState stateEnum =
+ mInstance.getStateEnum();
+ assertThat(stateEnum)
+ .isEqualTo(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT);
+ }
+
+ @Test
+ public void testGetOnClickListener_startSubSettings() {
+ when(mController.getFragment()).thenReturn(mFragment);
+ when(mFragment.getMetricsCategory()).thenReturn(AUDIO_STREAM_MAIN);
+
+ Preference.OnPreferenceClickListener listener = mInstance.getOnClickListener(mController);
+ assertThat(listener).isNotNull();
+
+ // mContext is not an Activity context, calling startActivity() from outside of an Activity
+ // context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
+ // AndroidRuntimeException.
+ Context activityContext = mock(Context.class);
+ when(mPreference.getContext()).thenReturn(activityContext);
+
+ listener.onPreferenceClick(mPreference);
+
+ ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(activityContext).startActivity(argumentCaptor.capture());
+
+ Intent intent = argumentCaptor.getValue();
+ assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(AudioStreamDetailsFragment.class.getName());
+ assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
+ .isEqualTo(R.string.audio_streams_detail_page_title);
+ assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
+ .isEqualTo(AUDIO_STREAM_MAIN);
+
+ Bundle bundle = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ assertThat(bundle).isNotNull();
+ assertThat(bundle.getString(AudioStreamDetailsFragment.BROADCAST_NAME_ARG))
+ .isEqualTo(BROADCAST_TITLE);
+ assertThat(bundle.getInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG))
+ .isEqualTo(BROADCAST_ID);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
index 051eda7..c7d0c60 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
@@ -59,6 +59,11 @@
return sMockHelper.getAllConnectedSources();
}
+ @Implementation
+ public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
+ return sMockHelper.getAllPresentSources();
+ }
+
/** Gets {@link CachedBluetoothDevice} in sharing or le connected */
@Implementation
public static Optional<CachedBluetoothDevice> getCachedBluetoothDeviceInSharingOrLeConnected(
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceControllerTest.java
index fd79515..2b0d6e7 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceControllerTest.java
@@ -43,7 +43,6 @@
import com.android.settings.R;
import com.android.settings.notification.zen.AbstractZenModePreferenceController.ZenModeConfigWrapper;
-import com.android.settings.notification.zen.ZenModeBehaviorFooterPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
@@ -206,7 +205,7 @@
ZenRule injectedRule = spy(new ZenRule());
injectedRule.zenMode = ZEN_MODE_ALARMS;
injectedRule.component = mock(ComponentName.class);
- when(injectedRule.isAutomaticActive()).thenReturn(true);
+ when(injectedRule.isActive()).thenReturn(true);
when(injectedRule.component.getPackageName()).thenReturn(TEST_APP_NAME);
injectedAutomaticRules.put("testid", injectedRule);
@@ -226,7 +225,7 @@
ZenRule injectedRule = spy(new ZenRule());
injectedRule.zenMode = ZEN_MODE_NO_INTERRUPTIONS;
injectedRule.component = mock(ComponentName.class);
- when(injectedRule.isAutomaticActive()).thenReturn(true);
+ when(injectedRule.isActive()).thenReturn(true);
when(injectedRule.component.getPackageName()).thenReturn(TEST_APP_NAME);
injectedAutomaticRules.put("testid", injectedRule);
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceControllerTest.java
index efa2f55..e5c2d42 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceControllerTest.java
@@ -44,7 +44,6 @@
import androidx.preference.PreferenceScreen;
import com.android.settings.notification.zen.AbstractZenModePreferenceController.ZenModeConfigWrapper;
-import com.android.settings.notification.zen.ZenModeSettingsFooterPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
@@ -289,7 +288,7 @@
injectedRule.component = mock(ComponentName.class);
injectedRule.name = nameAndId;
injectedRule.conditionId = new Uri.Builder().authority(nameAndId).build(); // unique uri
- when(injectedRule.isAutomaticActive()).thenReturn(isActive);
+ when(injectedRule.isActive()).thenReturn(isActive);
when(mConfigWrapper.isTimeRule(injectedRule.conditionId)).thenReturn(!isApp);
if (isApp) {
when(injectedRule.component.getPackageName()).thenReturn(TEST_APP_NAME);