Merge "a11y: Hook ignore minor cursor movement setting to its controller" into main
diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig
index 268ae6a..f7ef9d5 100644
--- a/aconfig/accessibility/accessibility_flags.aconfig
+++ b/aconfig/accessibility/accessibility_flags.aconfig
@@ -59,6 +59,13 @@
}
flag {
+ name: "enable_magnification_cursor_following_dialog"
+ namespace: "accessibility"
+ description: "Decides whether to show the magnification cursor following dialog in Settings app."
+ bug: "388335935"
+}
+
+flag {
name: "enable_magnification_focus_following_dialog"
namespace: "accessibility"
description: "Decides whether to show the magnification focus following dialog in Settings app."
diff --git a/res/drawable/ic_settings_globe.xml b/res/drawable/ic_settings_globe.xml
new file mode 100644
index 0000000..9834df6
--- /dev/null
+++ b/res/drawable/ic_settings_globe.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2016 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp"
+ android:height="24dp" android:viewportWidth="960" android:viewportHeight="960"
+ android:tint="?android:attr/colorControlNormal">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,473 799.5,465.5Q799,458 799,453Q794,482 772,501Q750,520 720,520L640,520Q607,520 583.5,496.5Q560,473 560,440L560,400L400,400L400,320Q400,287 423.5,263.5Q447,240 480,240L520,240L520,240Q520,217 532.5,199.5Q545,182 563,171Q543,166 522.5,163Q502,160 480,160Q346,160 253,253Q160,346 160,480Q160,480 160,480Q160,480 160,480L360,480Q426,480 473,527Q520,574 520,640L520,680L400,680L400,790Q420,795 439.5,797.5Q459,800 480,800Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/notification_history.xml b/res/layout/notification_history.xml
index 29744cc..798ba4c 100644
--- a/res/layout/notification_history.xml
+++ b/res/layout/notification_history.xml
@@ -17,7 +17,6 @@
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scroll"
- android:background="?android:attr/colorBackgroundFloating"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -132,7 +131,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:padding="16dp">
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
<LinearLayout
android:id="@+id/snoozed_list"
@@ -145,12 +145,12 @@
android:layout_width="wrap_content"
android:text="@string/notification_history_snooze"
android:textColor="?android:attr/textColorPrimary"
+ android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
android:textAppearance="@style/TextAppearance.HomepageCardTitle"
android:paddingBottom="16dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/notification_list"
- android:background="@drawable/rounded_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="true"
@@ -170,10 +170,10 @@
android:text="@string/notification_history_dismiss"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="@style/TextAppearance.HomepageCardTitle"
+ android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
android:paddingBottom="16dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/notification_list"
- android:background="@drawable/rounded_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="true"
@@ -191,13 +191,13 @@
android:layout_width="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:textAppearance="@style/TextAppearance.HomepageCardTitle"
+ android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
android:paddingBottom="16dp" />
<LinearLayout
android:id="@+id/apps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:background="@drawable/rounded_bg">
+ android:orientation="vertical">
<!-- app based recycler views added here -->
</LinearLayout>
</LinearLayout>
diff --git a/res/layout/notification_history_app_layout.xml b/res/layout/notification_history_app_layout.xml
index 143fff8..c3cece7 100644
--- a/res/layout/notification_history_app_layout.xml
+++ b/res/layout/notification_history_app_layout.xml
@@ -25,6 +25,8 @@
android:id="@+id/app_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
+ android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingTop="20dp"
android:paddingBottom="18dp"
android:paddingStart="16dp"
@@ -84,15 +86,12 @@
</androidx.constraintlayout.widget.ConstraintLayout>
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?android:attr/listDivider" />
-
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/notification_list_wrapper"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
+ android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd">
<com.android.settings.notification.history.NotificationHistoryRecyclerView
android:id="@+id/notification_list"
diff --git a/res/layout/notification_history_log_row.xml b/res/layout/notification_history_log_row.xml
index 4a3c44c..e7114cf 100644
--- a/res/layout/notification_history_log_row.xml
+++ b/res/layout/notification_history_log_row.xml
@@ -17,17 +17,22 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
- android:paddingTop="16dp"
- android:paddingBottom="16dp"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="16dp"
android:orientation="vertical"
android:foreground="?android:attr/selectableItemBackground">
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/listDivider" />
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@*android:dimen/status_bar_icon_size"
+ android:layout_marginStart="54dp"
+ android:paddingTop="16dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:gravity="center_vertical">
<TextView
@@ -36,7 +41,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
- android:layout_marginStart="32dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
@@ -77,11 +81,14 @@
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:ellipsize="end"
- android:layout_marginStart="32dp"
+ android:layout_marginStart="54dp"
android:maxLines="7"
android:paddingTop="4dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification"
- android:textAlignment="viewStart" />
+ android:textAlignment="viewStart"
+ android:paddingBottom="16dp" />
</LinearLayout>
+
diff --git a/res/layout/notification_sbn_log_row.xml b/res/layout/notification_sbn_log_row.xml
index 23bc110..cfd74d3 100644
--- a/res/layout/notification_sbn_log_row.xml
+++ b/res/layout/notification_sbn_log_row.xml
@@ -22,8 +22,10 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
+ android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="16dp"
- android:layout_marginEnd="16dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:orientation="vertical">
@@ -128,11 +130,5 @@
/>
</LinearLayout>
-
</LinearLayout>
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?android:attr/listDivider" />
</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9842843..9930c36 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -527,6 +527,10 @@
<string name="top_intro_region_title">The region you choose affects how your phone displays time, dates, temperature, and more</string>
<!-- Category for more language settings. [CHAR LIMIT=NONE]-->
<string name="more_language_settings_category">More language settings</string>
+ <!-- Title for asking to change system locale region or not. [CHAR LIMIT=50]-->
+ <string name="title_change_system_locale_region">Change region to %s ?</string>
+ <!-- Message for asking to change system locale region or not. [CHAR LIMIT=50]-->
+ <string name="body_change_system_locale_region">Your device will keep %s as a system language</string>
<!-- Regional Preferences begin -->
<!-- The title of the menu entry of regional preferences. [CHAR LIMIT=50] -->
@@ -11326,11 +11330,11 @@
<!-- Double Tap Power Gesture camera launch action title [CHAR_LIMIT=60] -->
<string name="double_tap_power_camera_action_title">Camera</string>
<!-- Setting summary to describe double tap power button will open camera. [CHAR LIMIT=NONE] -->
- <string name="double_tap_power_camera_action_summary">Access Camera</string>
+ <string name="double_tap_power_camera_action_summary">Open Camera</string>
<!-- Double Tap Power Gesture wallet launch action title [CHAR_LIMIT=60] -->
<string name="double_tap_power_wallet_action_title">Wallet</string>
<!-- Setting summary to describe double tap power button will open wallet. [CHAR LIMIT=NONE] -->
- <string name="double_tap_power_wallet_action_summary">Access Wallet</string>
+ <string name="double_tap_power_wallet_action_summary">Open Wallet</string>
<!-- Title text for double twist for camera mode [CHAR LIMIT=60]-->
<string name="double_twist_for_camera_mode_title">Flip camera for selfie</string>
diff --git a/src/com/android/settings/Metrics.kt b/src/com/android/settings/Metrics.kt
new file mode 100644
index 0000000..0d5ea56
--- /dev/null
+++ b/src/com/android/settings/Metrics.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2025 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
+
+import android.content.Context
+import com.android.settings.overlay.FeatureFactory
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider
+import com.android.settingslib.metadata.PreferenceUiActionMetricsLogger
+import com.android.settingslib.metadata.PreferenceMetadata
+import com.android.settingslib.metadata.PreferenceScreenMetadata
+
+/** Provides metrics for preference action. */
+interface PreferenceActionMetricsProvider {
+
+ /** Metrics action id for the preference. */
+ val preferenceActionMetrics: Int
+}
+
+/** [PreferenceUiActionMetricsLogger] of Settings app. */
+class SettingsMetricsLogger
+@JvmOverloads
+constructor(
+ private val context: Context,
+ private val metricsFeatureProvider: MetricsFeatureProvider =
+ FeatureFactory.featureFactory.metricsFeatureProvider,
+) : PreferenceUiActionMetricsLogger {
+
+ override fun logPreferenceValueChange(
+ screen: PreferenceScreenMetadata,
+ preference: PreferenceMetadata,
+ value: Any?,
+ ) {
+ if (preference !is PreferenceActionMetricsProvider) return
+ when (value) {
+ is Boolean ->
+ metricsFeatureProvider.action(context, preference.preferenceActionMetrics, value)
+ else -> {}
+ }
+ }
+}
diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java
index 442e3c2..9c5671f 100644
--- a/src/com/android/settings/SettingsApplication.java
+++ b/src/com/android/settings/SettingsApplication.java
@@ -77,6 +77,8 @@
if (Flags.catalyst()) {
PreferenceScreenRegistry.INSTANCE.setPreferenceScreenMetadataFactories(
preferenceScreenFactories());
+ PreferenceScreenRegistry.INSTANCE.setPreferenceUiActionMetricsLogger(
+ new SettingsMetricsLogger(this));
PreferenceBindingFactory.setDefaultFactory(new SettingsPreferenceBindingFactory());
}
diff --git a/src/com/android/settings/accessibility/HighContrastTextMigrationReceiver.java b/src/com/android/settings/accessibility/HighContrastTextMigrationReceiver.java
index 8e31185..a0949cf 100644
--- a/src/com/android/settings/accessibility/HighContrastTextMigrationReceiver.java
+++ b/src/com/android/settings/accessibility/HighContrastTextMigrationReceiver.java
@@ -57,7 +57,7 @@
static final String ACTION_OPEN_SETTINGS =
"com.android.settings.accessibility.ACTION_OPEN_HIGH_CONTRAST_TEXT_SETTINGS";
@VisibleForTesting
- static final int NOTIFICATION_ID = 1;
+ static final int NOTIFICATION_ID = R.string.accessibility_notification_high_contrast_text_title;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java
index bb4f2a7..1e13c22 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java
@@ -82,7 +82,7 @@
mProfilesContainer.removeAll();
mProfilesContainer.addPreference(createAudioSharingPreference());
if ((BluetoothUtils.isActiveLeAudioDevice(mCachedDevice)
- || AudioStreamsHelper.hasConnectedBroadcastSource(
+ || AudioStreamsHelper.hasBroadcastSource(
mCachedDevice, mLocalBluetoothManager))
&& !BluetoothUtils.isBroadcasting(mLocalBluetoothManager)) {
mProfilesContainer.addPreference(createFindAudioStreamPreference());
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
index f9cce4c..b9228a9 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
@@ -16,6 +16,10 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
+
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant;
@@ -42,7 +46,6 @@
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;
@@ -75,18 +78,17 @@
int sourceId,
BluetoothLeBroadcastReceiveState state) {
super.onReceiveStateChanged(sink, sourceId, state);
- boolean shouldUpdateButton =
- BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)
- ? AudioStreamsHelper.hasSourcePresent(state)
- : AudioStreamsHelper.isConnected(state);
+ var localSourceState = getLocalSourceState(state);
+ boolean shouldUpdateButton = mHysteresisModeFixAvailable
+ ? (localSourceState == PAUSED || localSourceState == STREAMING)
+ : localSourceState == STREAMING;
if (shouldUpdateButton) {
updateButton();
- if (AudioStreamsHelper.isConnected(state)) {
- mMetricsFeatureProvider.action(
- mContext,
- SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
- SOURCE_ORIGIN_REPOSITORY);
- }
+ // TODO(b/308368124): Verify if this log is too noisy.
+ mMetricsFeatureProvider.action(
+ mContext,
+ SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
+ SOURCE_ORIGIN_REPOSITORY);
}
}
@@ -113,6 +115,7 @@
private final AudioStreamsHelper mAudioStreamsHelper;
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
private final MetricsFeatureProvider mMetricsFeatureProvider;
+ private final boolean mHysteresisModeFixAvailable;
private @Nullable ActionButtonsPreference mPreference;
private int mBroadcastId = -1;
@@ -121,6 +124,8 @@
mExecutor = Executors.newSingleThreadExecutor();
mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
+ mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
+ context);
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
@@ -155,14 +160,8 @@
return;
}
- List<BluetoothLeBroadcastReceiveState> sources =
- BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)
- ? mAudioStreamsHelper.getAllPresentSources()
- : mAudioStreamsHelper.getAllConnectedSources();
- boolean isConnected =
- sources.stream()
- .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
- .anyMatch(connectedBroadcastId -> connectedBroadcastId == mBroadcastId);
+ boolean isConnected = mAudioStreamsHelper.getConnectedBroadcastIdAndState(
+ mHysteresisModeFixAvailable).containsKey(mBroadcastId);
View.OnClickListener onClickListener;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
index 88efff2..6037577 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
@@ -16,9 +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 static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant;
@@ -61,6 +62,7 @@
private final Executor mExecutor;
private final AudioStreamsHelper mAudioStreamsHelper;
@Nullable private final LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
+ private final boolean mHysteresisModeFixAvailable;
@VisibleForTesting
final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
@@ -83,13 +85,13 @@
int sourceId,
BluetoothLeBroadcastReceiveState state) {
super.onReceiveStateChanged(sink, sourceId, state);
- if (AudioStreamsHelper.isConnected(state)) {
+ var localSourceState = getLocalSourceState(state);
+ if (localSourceState == STREAMING) {
updateSummary();
mAudioStreamsHelper.startMediaService(
mContext, mBroadcastId, mBroadcastName);
- } else if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)
- && AudioStreamsHelper.hasSourcePresent(state)) {
- // if source present but not connected, only update the summary
+ } else if (mHysteresisModeFixAvailable && localSourceState == PAUSED) {
+ // if source paused, only update the summary
updateSummary();
}
}
@@ -105,6 +107,8 @@
mExecutor = Executors.newSingleThreadExecutor();
mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
+ mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
+ context);
}
@Override
@@ -151,38 +155,9 @@
var unused =
ThreadUtils.postOnBackgroundThread(
() -> {
- var connectedSourceList =
- mAudioStreamsHelper.getAllPresentSources().stream()
- .filter(
- state ->
- (state.getBroadcastId()
- == mBroadcastId))
- .collect(toList());
-
- var latestSummary =
- 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)
- .anyMatch(
- connectedBroadcastId ->
- connectedBroadcastId
- == mBroadcastId)
- ? mContext.getString(
- AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
- : AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
-
+ var sourceState = mAudioStreamsHelper.getConnectedBroadcastIdAndState(
+ mHysteresisModeFixAvailable).get(mBroadcastId);
+ var latestSummary = getLatestSummary(sourceState);
ThreadUtils.postOnMainThread(
() -> {
if (mHeaderController != null) {
@@ -212,4 +187,16 @@
mBroadcastName = broadcastName;
mBroadcastId = broadcastId;
}
+
+ private String getLatestSummary(@Nullable LocalBluetoothLeBroadcastSourceState state) {
+ if (state == null) {
+ return AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
+ }
+ if (mHysteresisModeFixAvailable) {
+ return state == STREAMING
+ ? mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
+ : mContext.getString(AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY);
+ }
+ return mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY);
+ }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
index 322fd3c..5023166 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
@@ -16,8 +16,6 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
-import static java.util.Collections.emptyList;
-
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -25,7 +23,6 @@
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothVolumeControl;
import android.content.Intent;
@@ -107,6 +104,7 @@
// override this value. Otherwise, we raise the volume to 25 when the play button is clicked.
private final AtomicInteger mLatestPositiveVolume = new AtomicInteger(25);
private final Object mLocalSessionLock = new Object();
+ private boolean mHysteresisModeFixAvailable;
private int mBroadcastId;
@Nullable private List<BluetoothDevice> mDevices;
@Nullable private LocalBluetoothManager mLocalBtManager;
@@ -139,6 +137,7 @@
Log.w(TAG, "onCreate() : mLeBroadcastAssistant is null!");
return;
}
+ mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(this);
mNotificationManager = getSystemService(NotificationManager.class);
if (mNotificationManager == null) {
@@ -309,13 +308,9 @@
}
private void handleRemoveSource() {
- List<BluetoothLeBroadcastReceiveState> connected =
- mAudioStreamsHelper == null
- ? emptyList()
- : mAudioStreamsHelper.getAllConnectedSources();
- if (connected.stream()
- .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
- .noneMatch(id -> id == mBroadcastId)) {
+ if (mAudioStreamsHelper != null
+ && !mAudioStreamsHelper.getConnectedBroadcastIdAndState(
+ mHysteresisModeFixAvailable).containsKey(mBroadcastId)) {
stopSelf();
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
index 4d6c4ca..0890870 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
@@ -19,6 +19,12 @@
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.bluetooth.BluetoothUtils.isAudioSharingHysteresisModeFixAvailable;
+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.PAUSED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
@@ -32,6 +38,7 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity;
@@ -67,12 +74,6 @@
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;
@@ -141,16 +142,31 @@
});
}
- /** Retrieves a list of all LE broadcast receive states from active sinks. */
- public List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!");
- return emptyList();
+ /**
+ * Gets a map of connected broadcast IDs to their corresponding local broadcast source states.
+ *
+ * <p>If multiple sources have the same broadcast ID, the state of the source that is
+ * {@code STREAMING} is preferred.
+ */
+ public Map<Integer, LocalBluetoothLeBroadcastSourceState> getConnectedBroadcastIdAndState(
+ boolean hysteresisModeFixAvailable) {
+ if (mBluetoothManager == null || mLeBroadcastAssistant == null) {
+ Log.w(TAG,
+ "getConnectedBroadcastIdAndState(): BluetoothManager or LeBroadcastAssistant "
+ + "is null!");
+ return emptyMap();
}
return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream()
.flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
- .filter(AudioStreamsHelper::isConnected)
- .toList();
+ .map(state -> new Pair<>(state.getBroadcastId(), getLocalSourceState(state)))
+ .filter(pair -> pair.second == STREAMING
+ || (hysteresisModeFixAvailable && pair.second == PAUSED))
+ .collect(toMap(
+ p -> p.first,
+ p -> p.second,
+ (existingState, newState) -> existingState == STREAMING ? existingState
+ : newState
+ ));
}
/** Retrieves a list of all LE broadcast receive states keyed by each active device. */
@@ -163,47 +179,12 @@
.collect(toMap(Function.identity(), mLeBroadcastAssistant::getAllSources));
}
- /** 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. */
@Nullable
public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
return mLeBroadcastAssistant;
}
- /** Checks the connectivity status based on the provided broadcast receive state. */
- public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
- 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) {
- return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
- && state.getBigEncryptionState()
- == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE;
- }
-
/**
* Returns a {@code CachedBluetoothDevice} that is either connected to a broadcast source or is
* a connected LE device.
@@ -226,7 +207,7 @@
}
var deviceHasSource =
leadDevices.stream()
- .filter(device -> hasConnectedBroadcastSource(device, manager))
+ .filter(device -> hasBroadcastSource(device, manager))
.findFirst();
if (deviceHasSource.isPresent()) {
Log.d(
@@ -258,38 +239,38 @@
return Optional.empty();
}
return leadDevices.stream()
- .filter(device -> hasConnectedBroadcastSource(device, manager))
+ .filter(device -> hasBroadcastSource(device, manager))
.findFirst();
}
/**
- * Check if {@link CachedBluetoothDevice} has connected to a broadcast source.
+ * Check if {@link CachedBluetoothDevice} has a broadcast source that is in STREAMING, PAUSED
+ * or DECRYPTION_FAILED state.
*
- * @param cachedDevice The cached bluetooth device to check.
+ * @param cachedDevice The cached bluetooth device to check.
* @param localBtManager The BT manager to provide BT functions.
- * @return Whether the device has connected to a broadcast source.
+ * @return Whether the device has a broadcast source.
*/
- public static boolean hasConnectedBroadcastSource(
+ public static boolean hasBroadcastSource(
CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
if (localBtManager == null) {
- Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null");
+ Log.d(TAG, "Skip check hasBroadcastSource due to bt manager is null");
return false;
}
LocalBluetoothLeBroadcastAssistant assistant =
localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (assistant == null) {
- Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null");
+ Log.d(TAG, "Skip check hasBroadcastSource due to assistant profile is null");
return false;
}
List<BluetoothLeBroadcastReceiveState> sourceList =
assistant.getAllSources(cachedDevice.getDevice());
- if (!sourceList.isEmpty()
- && (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
- localBtManager.getContext())
- || sourceList.stream().anyMatch(AudioStreamsHelper::isConnected))) {
+ boolean hysteresisModeFixAvailable = isAudioSharingHysteresisModeFixAvailable(
+ localBtManager.getContext());
+ if (hasReceiveState(sourceList, hysteresisModeFixAvailable)) {
Log.d(
TAG,
- "Lead device has connected broadcast source, device = "
+ "Lead device has broadcast source, device = "
+ cachedDevice.getDevice().getAnonymizedAddress());
return true;
}
@@ -297,13 +278,10 @@
for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
List<BluetoothLeBroadcastReceiveState> list =
assistant.getAllSources(device.getDevice());
- if (!list.isEmpty()
- && (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
- localBtManager.getContext())
- || list.stream().anyMatch(AudioStreamsHelper::isConnected))) {
+ if (hasReceiveState(list, hysteresisModeFixAvailable)) {
Log.d(
TAG,
- "Member device has connected broadcast source, device = "
+ "Member device has broadcast source, device = "
+ device.getDevice().getAnonymizedAddress());
return true;
}
@@ -311,6 +289,18 @@
return false;
}
+ private static boolean hasReceiveState(List<BluetoothLeBroadcastReceiveState> states,
+ boolean hysteresisModeFixAvailable) {
+ return states.stream().anyMatch(state -> {
+ var localSourceState = getLocalSourceState(state);
+ if (hysteresisModeFixAvailable) {
+ return localSourceState == STREAMING || localSourceState == DECRYPTION_FAILED
+ || localSourceState == PAUSED;
+ }
+ return localSourceState == STREAMING || localSourceState == DECRYPTION_FAILED;
+ });
+ }
+
/**
* Retrieves a list of connected Bluetooth devices that belongs to one {@link
* CachedBluetoothDevice} that's either connected to a broadcast source or is a connected LE
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
index 87cea2c..f0d0beb 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
@@ -16,23 +16,17 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
+
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.content.Context;
-
-import com.android.settingslib.bluetooth.BluetoothUtils;
public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback {
- private static final String TAG = "AudioStreamsProgressCategoryCallback";
-
- private final Context mContext;
private final AudioStreamsProgressCategoryController mCategoryController;
public AudioStreamsProgressCategoryCallback(
- Context context,
AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) {
- mContext = context;
mCategoryController = audioStreamsProgressCategoryController;
}
@@ -40,15 +34,11 @@
public void onReceiveStateChanged(
BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
super.onReceiveStateChanged(sink, sourceId, state);
-
- if (AudioStreamsHelper.isConnected(state)) {
- mCategoryController.handleSourceConnected(sink, state);
- } else if (AudioStreamsHelper.isBadCode(state)) {
- mCategoryController.handleSourceConnectBadCode(state);
- } else if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)
- && AudioStreamsHelper.hasSourcePresent(state)) {
- // Keep this check as the last, source might also present in above states
- mCategoryController.handleSourcePresent(sink, state);
+ var sourceState = getLocalSourceState(state);
+ switch (sourceState) {
+ case STREAMING -> mCategoryController.handleSourceStreaming(sink, state);
+ case DECRYPTION_FAILED -> mCategoryController.handleSourceConnectBadCode(state);
+ case PAUSED -> mCategoryController.handleSourcePaused(sink, state);
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index 6831c5a..24978c6 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -16,6 +16,12 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.bluetooth.BluetoothUtils.isAudioSharingHysteresisModeFixAvailable;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
+
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toMap;
@@ -137,6 +143,7 @@
private final @Nullable LocalBluetoothManager mBluetoothManager;
private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap =
new ConcurrentHashMap<>();
+ private final boolean mHysteresisModeFixAvailable;
private @Nullable BluetoothLeBroadcastMetadata mSourceFromQrCode;
private SourceOriginForLogging mSourceFromQrCodeOriginForLogging;
@Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference;
@@ -149,7 +156,9 @@
mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager);
mMediaControlHelper = new MediaControlHelper(mContext, mBluetoothManager);
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
- mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(context, this);
+ mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(this);
+ mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
+ mContext);
}
@Override
@@ -260,8 +269,8 @@
// change it's state.
existingPreference.setAudioStreamMetadata(source);
if (fromState != AudioStreamState.SOURCE_ADDED
- && (!isAudioSharingHysteresisModeFixAvailable(mContext)
- || fromState != AudioStreamState.SOURCE_PRESENT)) {
+ && (!mHysteresisModeFixAvailable
+ || fromState != AudioStreamState.SOURCE_PRESENT)) {
Log.w(
TAG,
"handleSourceFound(): unexpected state : "
@@ -336,8 +345,8 @@
if (DEBUG) {
Log.d(TAG, "handleSourceLost()");
}
- if (mAudioStreamsHelper.getAllConnectedSources().stream()
- .anyMatch(connected -> connected.getBroadcastId() == broadcastId)) {
+ if (mAudioStreamsHelper.getConnectedBroadcastIdAndState(
+ mHysteresisModeFixAvailable).containsKey(broadcastId)) {
Log.d(
TAG,
"handleSourceLost() : keep this preference as the source is still connected.");
@@ -366,14 +375,12 @@
// not, means the source is removed from the sink, we move back the preference to SYNCED
// state.
if ((preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED
- || (isAudioSharingHysteresisModeFixAvailable(mContext)
+ || (mHysteresisModeFixAvailable
&& preference.getAudioStreamState()
== AudioStreamState.SOURCE_PRESENT))
- && mAudioStreamsHelper.getAllConnectedSources().stream()
- .noneMatch(
- connected ->
- connected.getBroadcastId()
- == preference.getAudioStreamBroadcastId())) {
+ && !mAudioStreamsHelper.getConnectedBroadcastIdAndState(
+ mHysteresisModeFixAvailable).containsKey(
+ preference.getAudioStreamBroadcastId())) {
ThreadUtils.postOnMainThread(
() -> {
@@ -395,27 +402,27 @@
// Expect one of the following:
// 1) No preference existed, create new preference with state SOURCE_ADDED
// 2) Any other state, move to SOURCE_ADDED
- void handleSourceConnected(
+ void handleSourceStreaming(
BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) {
if (DEBUG) {
- Log.d(TAG, "handleSourceConnected()");
+ Log.d(TAG, "handleSourceStreaming()");
}
- if (!AudioStreamsHelper.isConnected(receiveState)) {
+ if (getLocalSourceState(receiveState) != STREAMING) {
return;
}
- var broadcastIdConnected = receiveState.getBroadcastId();
+ var broadcastIdStreaming = receiveState.getBroadcastId();
Optional<BluetoothLeBroadcastMetadata> metadata =
getMetadataMatchingByBroadcastId(
- device, receiveState.getSourceId(), broadcastIdConnected);
+ device, receiveState.getSourceId(), broadcastIdStreaming);
handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState);
mBroadcastIdToPreferenceMap.compute(
- broadcastIdConnected,
+ broadcastIdStreaming,
(k, existingPreference) -> {
if (existingPreference == null) {
- // No existing preference for this source even if it's already connected,
+ // No existing preference for this source even if it's already streaming,
// add one and set initial state to SOURCE_ADDED. This could happen because
- // we retrieves the connected source during onStart() from
- // AudioStreamsHelper#getAllConnectedSources() even before the source is
+ // we retrieves the streaming source during onStart() from
+ // AudioStreamsHelper#getAllStreamingSources() even before the source is
// founded by scanning.
return metadata.isPresent()
? addNewPreference(
@@ -440,7 +447,7 @@
if (DEBUG) {
Log.d(TAG, "handleSourceConnectBadCode()");
}
- if (!AudioStreamsHelper.isBadCode(receiveState)) {
+ if (getLocalSourceState(receiveState) != DECRYPTION_FAILED) {
return;
}
mBroadcastIdToPreferenceMap.computeIfPresent(
@@ -467,29 +474,28 @@
// Find preference by receiveState and decide next state.
// Expect one preference existed, move to SOURCE_PRESENT
- void handleSourcePresent(
+ void handleSourcePaused(
BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) {
if (DEBUG) {
- Log.d(TAG, "handleSourcePresent()");
+ Log.d(TAG, "handleSourcePaused()");
}
- if (!AudioStreamsHelper.hasSourcePresent(receiveState)) {
+ if (!mHysteresisModeFixAvailable || getLocalSourceState(receiveState) != PAUSED) {
return;
}
- var broadcastIdConnected = receiveState.getBroadcastId();
+ var broadcastIdPaused = receiveState.getBroadcastId();
Optional<BluetoothLeBroadcastMetadata> metadata =
getMetadataMatchingByBroadcastId(
- device, receiveState.getSourceId(), broadcastIdConnected);
+ device, receiveState.getSourceId(), broadcastIdPaused);
handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState);
mBroadcastIdToPreferenceMap.compute(
- broadcastIdConnected,
+ broadcastIdPaused,
(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
+ // No existing preference for this source even if it's already existed but
+ // currently paused, add one and set initial state to SOURCE_PRESENT. This
+ // could happen because we retrieves the paused source during onStart() from
+ // AudioStreamsHelper#getAllPausedSources() even before the source is
// founded by scanning.
return metadata.isPresent()
? addNewPreference(
@@ -580,56 +586,43 @@
mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
mExecutor.execute(
() -> {
- // Handle QR code scan, display currently connected streams then start scanning
- // sequentially
+ // Handle QR code scan, display currently streaming or paused streams then start
+ // scanning sequentially
handleSourceFromQrCodeIfExists();
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources =
mAudioStreamsHelper.getAllSourcesByDevice();
- Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> connectedSources =
- getConnectedSources(sources);
- if (isAudioSharingHysteresisModeFixAvailable(mContext)) {
- // With hysteresis mode, we prioritize showing connected sources first.
- // If no connected sources are found, we then show present sources.
- if (!connectedSources.isEmpty()) {
- connectedSources.forEach(
- (device, stateList) ->
- stateList.forEach(
- state -> handleSourceConnected(device, state)));
- } else {
- Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>>
- presentSources = getPresentSources(sources);
- presentSources.forEach(
- (device, stateList) ->
- stateList.forEach(
- state -> handleSourcePresent(device, state)));
- }
- } else {
- connectedSources.forEach(
+ getStreamSourcesByDevice(sources).forEach(
+ (device, stateList) ->
+ stateList.forEach(
+ state -> handleSourceStreaming(device, state)));
+ if (mHysteresisModeFixAvailable) {
+ getPausedSourcesByDevice(sources).forEach(
(device, stateList) ->
stateList.forEach(
- state -> handleSourceConnected(device, state)));
+ state -> handleSourcePaused(device, state)));
}
mLeBroadcastAssistant.startSearchingForSources(emptyList());
mMediaControlHelper.start();
});
}
- private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getConnectedSources(
+ private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getStreamSourcesByDevice(
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) {
return sources.entrySet().stream()
.filter(
entry ->
- entry.getValue().stream().anyMatch(AudioStreamsHelper::isConnected))
+ entry.getValue().stream().anyMatch(
+ state -> getLocalSourceState(state) == STREAMING))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
}
- private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPresentSources(
+ private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPausedSourcesByDevice(
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) {
return sources.entrySet().stream()
.filter(
entry ->
entry.getValue().stream()
- .anyMatch(AudioStreamsHelper::hasSourcePresent))
+ .anyMatch(state -> getLocalSourceState(state) == PAUSED))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@@ -742,8 +735,4 @@
dialog.dismiss();
});
}
-
- private static boolean isAudioSharingHysteresisModeFixAvailable(Context context) {
- return BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(context);
- }
}
diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
index ac87ea5..2ec93b6 100644
--- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java
+++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
@@ -21,12 +21,8 @@
import android.content.Context;
import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import androidx.annotation.XmlRes;
import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import androidx.recyclerview.widget.RecyclerView;
@@ -109,12 +105,6 @@
}
@Override
- public void addPreferencesFromResource(@XmlRes int preferencesResId) {
- super.addPreferencesFromResource(preferencesResId);
- updateActivityTitleWithScreenTitle(getPreferenceScreen());
- }
-
- @Override
public <T extends Preference> T findPreference(CharSequence key) {
if (key == null) {
return null;
@@ -147,17 +137,6 @@
mMetricsFeatureProvider.logClickedPreference(preference, getMetricsCategory());
}
- protected void updateActivityTitleWithScreenTitle(PreferenceScreen screen) {
- if (screen != null) {
- final CharSequence title = screen.getTitle();
- if (!TextUtils.isEmpty(title)) {
- getActivity().setTitle(title);
- } else {
- Log.w(TAG, "Screen title missing for fragment " + this.getClass().getName());
- }
- }
- }
-
private static final class OnScrollListener extends RecyclerView.OnScrollListener {
private final InteractionJankMonitor mMonitor = InteractionJankMonitor.getInstance();
private final String mClassName;
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index c79cc65..d163bda 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -415,7 +415,6 @@
removeControllersForHybridMode();
}
setPreferenceScreen(screen);
- updateActivityTitleWithScreenTitle(screen);
} else {
addPreferencesFromResource(resId);
screen = getPreferenceScreen();
diff --git a/src/com/android/settings/display/AutoBrightnessScreen.kt b/src/com/android/settings/display/AutoBrightnessScreen.kt
index 48ef210..c425888 100644
--- a/src/com/android/settings/display/AutoBrightnessScreen.kt
+++ b/src/com/android/settings/display/AutoBrightnessScreen.kt
@@ -21,15 +21,16 @@
import android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
import android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
import com.android.settings.PreferenceRestrictionMixin
import com.android.settings.R
import com.android.settings.flags.Flags
-import com.android.settingslib.PrimarySwitchPreference
+import com.android.settingslib.PrimarySwitchPreferenceBinding
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyedObservableDelegate
import com.android.settingslib.datastore.SettingsStore
import com.android.settingslib.datastore.SettingsSystemStore
-import com.android.settingslib.metadata.BooleanPreference
+import com.android.settingslib.metadata.BooleanValuePreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ProvidePreferenceScreen
@@ -42,10 +43,11 @@
@ProvidePreferenceScreen(AutoBrightnessScreen.KEY)
class AutoBrightnessScreen :
PreferenceScreenCreator,
- PreferenceScreenBinding,
+ PreferenceScreenBinding, // binding for screen page
+ PrimarySwitchPreferenceBinding, // binding for screen entry point widget
PreferenceAvailabilityProvider,
PreferenceRestrictionMixin,
- BooleanPreference {
+ BooleanValuePreference {
override val key: String
get() = KEY
@@ -93,16 +95,11 @@
override val useAdminDisabledSummary: Boolean
get() = true
- override fun createWidget(context: Context) = PrimarySwitchPreference(context)
-
- override fun bind(preference: Preference, metadata: PreferenceMetadata) {
- super.bind(preference, metadata)
- (preference as PrimarySwitchPreference).apply {
- isSwitchEnabled = isEnabled
- // "true" is not the real default value (it is provided by AutoBrightnessDataStore)
- isChecked = preferenceDataStore!!.getBoolean(key, true)
+ override fun bind(preference: Preference, metadata: PreferenceMetadata) =
+ when (preference) {
+ is PreferenceScreen -> super<PreferenceScreenBinding>.bind(preference, metadata)
+ else -> super<PrimarySwitchPreferenceBinding>.bind(preference, metadata)
}
- }
/**
* The datastore for brightness, which is persisted as integer but the external type is boolean.
diff --git a/src/com/android/settings/display/darkmode/DarkModeScreen.kt b/src/com/android/settings/display/darkmode/DarkModeScreen.kt
index f1a95f5..527cd19 100644
--- a/src/com/android/settings/display/darkmode/DarkModeScreen.kt
+++ b/src/com/android/settings/display/darkmode/DarkModeScreen.kt
@@ -20,12 +20,13 @@
import android.content.Context
import android.os.PowerManager
import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.flags.Flags
-import com.android.settingslib.PrimarySwitchPreference
+import com.android.settingslib.PrimarySwitchPreferenceBinding
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.Permissions
-import com.android.settingslib.metadata.BooleanPreference
+import com.android.settingslib.metadata.BooleanValuePreference
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.ProvidePreferenceScreen
@@ -39,8 +40,9 @@
@ProvidePreferenceScreen(DarkModeScreen.KEY)
class DarkModeScreen(context: Context) :
PreferenceScreenCreator,
- PreferenceScreenBinding,
- BooleanPreference,
+ PreferenceScreenBinding, // binding for screen page
+ PrimarySwitchPreferenceBinding, // binding for screen entry point widget
+ BooleanValuePreference,
PreferenceSummaryProvider {
private val darkModeStorage = DarkModeStorage(context)
@@ -82,14 +84,11 @@
override fun storage(context: Context): KeyValueStore = darkModeStorage
- override fun createWidget(context: Context) = PrimarySwitchPreference(context)
-
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
- super.bind(preference, metadata)
if (preference is DarkModePreference) preference.setCatalystEnabled(true)
- (preference as? PrimarySwitchPreference)?.apply {
- isSwitchEnabled = isEnabled
- isChecked = darkModeStorage.getBoolean(KEY) == true
+ when (preference) {
+ is PreferenceScreen -> super<PreferenceScreenBinding>.bind(preference, metadata)
+ else -> super<PrimarySwitchPreferenceBinding>.bind(preference, metadata)
}
}
diff --git a/src/com/android/settings/gestures/OneHandedSettingsUtils.java b/src/com/android/settings/gestures/OneHandedSettingsUtils.java
index fe7db4f..cba40bd 100644
--- a/src/com/android/settings/gestures/OneHandedSettingsUtils.java
+++ b/src/com/android/settings/gestures/OneHandedSettingsUtils.java
@@ -257,16 +257,10 @@
return true;
}
- if (android.view.accessibility.Flags.a11yQsShortcut()) {
- // Checks QS_SHORTCUT_KEY
- final String targetsQs = Settings.Secure.getStringForUser(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_QS_TARGETS, sCurrentUserId);
- if (!TextUtils.isEmpty(targetsQs) && targetsQs.contains(ONE_HANDED_MODE_TARGET_NAME)) {
- return true;
- }
- }
-
- return false;
+ // Checks QS_SHORTCUT_KEY
+ final String targetsQs = Settings.Secure.getStringForUser(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS, sCurrentUserId);
+ return !TextUtils.isEmpty(targetsQs) && targetsQs.contains(ONE_HANDED_MODE_TARGET_NAME);
}
/**
diff --git a/src/com/android/settings/inputmethod/MouseScrollingSpeedPreferenceController.java b/src/com/android/settings/inputmethod/MouseScrollingSpeedPreferenceController.java
index 0a57085..a047e56 100644
--- a/src/com/android/settings/inputmethod/MouseScrollingSpeedPreferenceController.java
+++ b/src/com/android/settings/inputmethod/MouseScrollingSpeedPreferenceController.java
@@ -16,29 +16,55 @@
package com.android.settings.inputmethod;
+import android.content.ContentResolver;
import android.content.Context;
+import android.database.ContentObserver;
import android.hardware.input.InputSettings;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.SliderPreferenceController;
import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
-public class MouseScrollingSpeedPreferenceController extends SliderPreferenceController {
+
+public class MouseScrollingSpeedPreferenceController extends SliderPreferenceController implements
+ Preference.OnPreferenceChangeListener, LifecycleObserver, OnStop, OnStart {
+
+ private final ContentResolver mContentResolver;
+ private final ContentObserver mContentObserver;
+
+ @Nullable
+ private SeekBarPreference mPreference;
public MouseScrollingSpeedPreferenceController(@NonNull Context context, @NonNull String key) {
super(context, key);
+
+ mContentResolver = context.getContentResolver();
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAvailabilityStatus();
+ }
+ };
}
@Override
public void displayPreference(@NonNull PreferenceScreen screen) {
super.displayPreference(screen);
- SeekBarPreference preference = screen.findPreference(getPreferenceKey());
- preference.setMax(getMax());
- preference.setMin(getMin());
- preference.setProgress(getSliderPosition());
- updateState(preference);
+ mPreference = screen.findPreference(getPreferenceKey());
+ mPreference.setMax(getMax());
+ mPreference.setMin(getMin());
+ mPreference.setProgress(getSliderPosition());
+ updateState(mPreference);
}
@Override
@@ -46,7 +72,7 @@
if (!InputSettings.isMouseScrollingAccelerationFeatureFlagEnabled()) {
return UNSUPPORTED_ON_DEVICE;
}
- return AVAILABLE;
+ return shouldEnableSlideBar() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
}
@Override
@@ -73,4 +99,30 @@
public int getMax() {
return InputSettings.MAX_MOUSE_SCROLLING_SPEED;
}
+
+ /**
+ * Returns whether the mouse scrolling speed slide bar should allow users to customize or not.
+ */
+ public boolean shouldEnableSlideBar() {
+ return !InputSettings.isMouseScrollingAccelerationEnabled(mContext);
+ }
+
+ @Override
+ public void onStart() {
+ mContentResolver.registerContentObserver(
+ Settings.System.getUriFor(
+ Settings.System.MOUSE_SCROLLING_ACCELERATION),
+ /* notifyForDescendants= */ false, mContentObserver);
+ }
+
+ @Override
+ public void onStop() {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+
+ private void updateAvailabilityStatus() {
+ if (mPreference != null) {
+ mPreference.setEnabled(shouldEnableSlideBar());
+ }
+ }
}
diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java
index e8a645f..d3b23a7 100644
--- a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java
+++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java
@@ -57,8 +57,8 @@
private KeyboardLayout[] mKeyboardLayouts;
private PreferenceScreen mScreen;
private String mPreviousSelection;
- private String mFinalSelectedLayout;
- private String mLayout;
+ private String mFinalSelectedLayoutDescriptor;
+ private String mSelectedLayoutDescriptor;
private MetricsFeatureProvider mMetricsFeatureProvider;
private KeyboardLayoutSelectedCallback mKeyboardLayoutSelectedCallback;
@@ -83,8 +83,8 @@
mInputMethodSubtype =
arguments.getParcelable(
InputPeripheralsSettingsUtils.EXTRA_INPUT_METHOD_SUBTYPE);
- mLayout = getSelectedLayoutLabel();
- mFinalSelectedLayout = mLayout;
+ mSelectedLayoutDescriptor = getSelectedLayoutDescriptor();
+ mFinalSelectedLayoutDescriptor = mSelectedLayoutDescriptor;
mKeyboardLayouts = mIm.getKeyboardLayoutListForInputDevice(
mInputDeviceIdentifier, mUserId, mInputMethodInfo, mInputMethodSubtype);
InputPeripheralsSettingsUtils.sortKeyboardLayoutsByLabel(mKeyboardLayouts);
@@ -106,8 +106,12 @@
@Override
public void onStop() {
- if (mLayout != null && !mLayout.equals(mFinalSelectedLayout)) {
- String change = "From:" + mLayout + ", to:" + mFinalSelectedLayout;
+ if (mSelectedLayoutDescriptor != null
+ && !mSelectedLayoutDescriptor.equals(mFinalSelectedLayoutDescriptor)) {
+ String change = "From:"
+ + getLayoutLabel(mSelectedLayoutDescriptor)
+ + ", to:"
+ + getLayoutLabel(mFinalSelectedLayoutDescriptor);
mMetricsFeatureProvider.action(
mContext, SettingsEnums.ACTION_PK_LAYOUT_CHANGED, change);
}
@@ -152,7 +156,7 @@
}
setLayout(pref);
mPreviousSelection = preference.getKey();
- mFinalSelectedLayout = pref.getTitle().toString();
+ mFinalSelectedLayoutDescriptor = mPreviousSelection;
return true;
}
@@ -182,12 +186,12 @@
pref = new TickButtonPreference(mScreen.getContext());
pref.setTitle(layout.getLabel());
- if (mLayout.equals(layout.getLabel())) {
+ if (mSelectedLayoutDescriptor.equals(layout.getDescriptor())) {
if (mKeyboardLayoutSelectedCallback != null) {
mKeyboardLayoutSelectedCallback.onSelected(layout);
}
pref.setSelected(true);
- mPreviousSelection = layout.getDescriptor();
+ mPreviousSelection = mSelectedLayoutDescriptor;
}
pref.setKey(layout.getDescriptor());
mScreen.addPreference(pref);
@@ -204,15 +208,19 @@
mPreferenceMap.get(preference).getDescriptor());
}
- private String getSelectedLayoutLabel() {
- String label = mContext.getString(R.string.keyboard_default_layout);
+ private String getSelectedLayoutDescriptor() {
KeyboardLayoutSelectionResult result = InputPeripheralsSettingsUtils.getKeyboardLayout(
mIm, mUserId, mInputDeviceIdentifier, mInputMethodInfo, mInputMethodSubtype);
+ return result.getLayoutDescriptor();
+ }
+
+ private String getLayoutLabel(String descriptor) {
+ String label = mContext.getString(R.string.keyboard_default_layout);
KeyboardLayout[] keyboardLayouts = InputPeripheralsSettingsUtils.getKeyboardLayouts(
mIm, mUserId, mInputDeviceIdentifier, mInputMethodInfo, mInputMethodSubtype);
- if (result.getLayoutDescriptor() != null) {
+ if (descriptor != null) {
for (KeyboardLayout keyboardLayout : keyboardLayouts) {
- if (keyboardLayout.getDescriptor().equals(result.getLayoutDescriptor())) {
+ if (keyboardLayout.getDescriptor().equals(descriptor)) {
label = keyboardLayout.getLabel();
break;
}
diff --git a/src/com/android/settings/localepicker/AppLocaleSuggestedListPreferenceController.java b/src/com/android/settings/localepicker/AppLocaleSuggestedListPreferenceController.java
new file mode 100644
index 0000000..3fe9550
--- /dev/null
+++ b/src/com/android/settings/localepicker/AppLocaleSuggestedListPreferenceController.java
@@ -0,0 +1,203 @@
+/**
+ * Copyright (C) 2025 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.localepicker;
+
+import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_APPS_LOCALE;
+
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationCompat;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.app.AppLocaleCollector;
+import com.android.internal.app.LocaleStore;
+import com.android.settings.R;
+import com.android.settings.applications.manageapplications.ManageApplicationsUtil;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** A controller for handling suggested locale of app. */
+public class AppLocaleSuggestedListPreferenceController extends
+ BasePreferenceController implements LocaleListSearchCallback {
+ private static final String TAG = "AppLocaleSuggestedListPreferenceController";
+ private static final String KEY_PREFERENCE_CATEGORY_APP_LANGUAGE_SUGGESTED =
+ "app_language_suggested_category";
+ private static final String KEY_PREFERENCE_APP_LOCALE_SUGGESTED_LIST =
+ "app_locale_suggested_list";
+ private static final String KEY_PREFERENCE_CATEGORY_ADD_A_LANGUAGE_SUGGESTED =
+ "system_language_suggested_category";
+
+ @SuppressWarnings("NullAway")
+ private PreferenceCategory mPreferenceCategory;
+ private Set<LocaleStore.LocaleInfo> mLocaleList;
+ private List<LocaleStore.LocaleInfo> mLocaleOptions;
+ private Map<String, Preference> mSuggestedPreferences;
+ private boolean mIsCountryMode;
+ @Nullable private LocaleStore.LocaleInfo mParentLocale;
+ private AppLocaleCollector mAppLocaleCollector;
+ @SuppressWarnings("NullAway")
+ private String mPackageName;
+ private boolean mIsNumberingSystemMode;
+
+ @SuppressWarnings("NullAway")
+ public AppLocaleSuggestedListPreferenceController(@NonNull Context context,
+ @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @SuppressWarnings("NullAway")
+ public AppLocaleSuggestedListPreferenceController(@NonNull Context context,
+ @NonNull String preferenceKey, @Nullable String packageName,
+ boolean isNumberingSystemMode, @NonNull LocaleStore.LocaleInfo parentLocale) {
+ super(context, preferenceKey);
+ mPackageName = packageName;
+ mIsNumberingSystemMode = isNumberingSystemMode;
+ mParentLocale = parentLocale;
+ mIsCountryMode = mParentLocale != null;
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreferenceCategory = screen.findPreference(
+ (mIsNumberingSystemMode || mIsCountryMode)
+ ? KEY_PREFERENCE_CATEGORY_ADD_A_LANGUAGE_SUGGESTED
+ : KEY_PREFERENCE_CATEGORY_APP_LANGUAGE_SUGGESTED);
+
+ mAppLocaleCollector = new AppLocaleCollector(mContext, mPackageName);
+ mSuggestedPreferences = new ArrayMap<>();
+ mLocaleOptions = new ArrayList<>();
+ updatePreferences();
+ }
+
+ private void updatePreferences() {
+ if (mPreferenceCategory == null) {
+ Log.d(TAG, "updatePreferences, mPreferenceCategory is null");
+ return;
+ }
+
+ List<LocaleStore.LocaleInfo> result = LocaleUtils.getSortedLocaleList(
+ getSuggestedLocaleList(), mIsCountryMode);
+ final Map<String, Preference> existingSuggestedPreferences = mSuggestedPreferences;
+ mSuggestedPreferences = new ArrayMap<>();
+ setupSuggestedPreference(result, existingSuggestedPreferences);
+ for (Preference pref : existingSuggestedPreferences.values()) {
+ mPreferenceCategory.removePreference(pref);
+ }
+ }
+
+ @Override
+ public void onSearchListChanged(@NonNull List<LocaleStore.LocaleInfo> newList,
+ @Nullable CharSequence prefix) {
+ if (mPreferenceCategory == null) {
+ Log.d(TAG, "onSearchListChanged, mPreferenceCategory is null");
+ return;
+ }
+
+ mPreferenceCategory.removeAll();
+ final Map<String, Preference> existingSuggestedPreferences = mSuggestedPreferences;
+ List<LocaleStore.LocaleInfo> sortedList = getSuggestedLocaleList();
+ newList = LocaleUtils.getSortedLocaleFromSearchList(newList, sortedList, mIsCountryMode);
+ setupSuggestedPreference(newList, existingSuggestedPreferences);
+ }
+
+ private void setupSuggestedPreference(List<LocaleStore.LocaleInfo> localeInfoList,
+ Map<String, Preference> existingSuggestedPreferences) {
+ for (LocaleStore.LocaleInfo locale : localeInfoList) {
+ if (mIsNumberingSystemMode || mIsCountryMode) {
+ Preference pref = existingSuggestedPreferences.remove(locale.getId());
+ if (pref == null) {
+ pref = new Preference(mContext);
+ setupPreference(pref, locale);
+ mPreferenceCategory.addPreference(pref);
+ }
+ } else {
+ SelectorWithWidgetPreference pref =
+ (SelectorWithWidgetPreference) existingSuggestedPreferences.remove(
+ locale.getId());
+ if (pref == null) {
+ pref = new SelectorWithWidgetPreference(mContext);
+ setupPreference(pref, locale);
+ mPreferenceCategory.addPreference(pref);
+ }
+ }
+ }
+ Log.d(TAG, "setupSuggestedPreference, mPreferenceCategory setVisible"
+ + (mPreferenceCategory.getPreferenceCount() > 0));
+ mPreferenceCategory.setVisible(mPreferenceCategory.getPreferenceCount() > 0);
+ }
+
+ private void setupPreference(Preference pref, LocaleStore.LocaleInfo locale) {
+ String localeName = mIsCountryMode ? locale.getFullCountryNameNative()
+ : locale.getFullNameNative();
+ if (pref instanceof SelectorWithWidgetPreference) {
+ ((SelectorWithWidgetPreference) pref).setChecked(locale.isAppCurrentLocale());
+ }
+ pref.setTitle(locale.isSystemLocale()
+ ? mContext.getString(R.string.preference_of_system_locale_summary)
+ : localeName);
+ pref.setKey(locale.toString());
+ pref.setOnPreferenceClickListener(clickedPref -> {
+ LocaleUtils.onLocaleSelected(mContext, locale, mPackageName);
+ ((Activity) mContext).finish();
+ return true;
+ });
+ mSuggestedPreferences.put(locale.getId(), pref);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ protected List<LocaleStore.LocaleInfo> getSuggestedLocaleList() {
+ setupLocaleList();
+ if (mLocaleList != null && !mLocaleList.isEmpty()) {
+ mLocaleOptions.addAll(
+ mLocaleList.stream().filter(localeInfo -> (localeInfo.isSuggested())).collect(
+ Collectors.toList()));
+ } else {
+ Log.d(TAG, "Can not get suggested locales because the locale list is null or empty.");
+ }
+ return mLocaleOptions;
+ }
+
+ private void setupLocaleList() {
+ mLocaleList = mAppLocaleCollector.getSupportedLocaleList(mParentLocale,
+ false, mIsCountryMode);
+ mLocaleOptions.clear();
+ }
+
+ @Override
+ public @NonNull String getPreferenceKey() {
+ return KEY_PREFERENCE_APP_LOCALE_SUGGESTED_LIST;
+ }
+}
diff --git a/src/com/android/settings/localepicker/LocaleUtils.java b/src/com/android/settings/localepicker/LocaleUtils.java
index a84d0be..b650218 100644
--- a/src/com/android/settings/localepicker/LocaleUtils.java
+++ b/src/com/android/settings/localepicker/LocaleUtils.java
@@ -16,16 +16,53 @@
package com.android.settings.localepicker;
+import static com.android.settings.flags.Flags.localeNotificationEnabled;
+import static com.android.settings.localepicker.LocaleListEditor.EXTRA_RESULT_LOCALE;
+import static com.android.settings.localepicker.RegionAndNumberingSystemPickerFragment.EXTRA_IS_NUMBERING_SYSTEM;
+import static com.android.settings.localepicker.RegionAndNumberingSystemPickerFragment.EXTRA_TARGET_LOCALE;
+
+import android.app.Dialog;
+import android.app.LocaleManager;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.LocaleList;
+import android.os.SystemClock;
+import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+import com.android.internal.app.LocaleHelper;
+import com.android.internal.app.LocaleStore;
+import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Locale;
+import java.util.stream.Collectors;
/**
* A locale utility class.
*/
public class LocaleUtils {
+ private static final String TAG = "LocaleUtils";
+ private static final String CHANNEL_ID_SUGGESTION = "suggestion";
+ private static final String CHANNEL_ID_SUGGESTION_TO_USER = "Locale suggestion";
+ private static final String EXTRA_APP_LOCALE = "app_locale";
+ private static final String EXTRA_NOTIFICATION_ID = "notification_id";
+ private static final int SIM_LOCALE = 1 << 0;
+ private static final int SYSTEM_LOCALE = 1 << 1;
+ private static final int APP_LOCALE = 1 << 2;
+ private static final int IME_LOCALE = 1 << 3;
+
/**
* Checks if the languageTag is in the system locale. Since in the current design, the system
* language list would not show two locales with the same language and region but different
@@ -50,4 +87,191 @@
}
return false;
}
+
+ /**
+ * Logs the locale, sets the default locale for the app then broadcasts it.
+ *
+ * @param context Context
+ * @param localeInfo locale info
+ */
+ public static void onLocaleSelected(@NonNull Context context,
+ @NonNull LocaleStore.LocaleInfo localeInfo,
+ @NonNull String packageName) {
+ if (localeInfo.getLocale() == null || localeInfo.isSystemLocale()) {
+ setAppDefaultLocale(context, "", packageName);
+ } else {
+ logLocaleSource(context, localeInfo);
+ setAppDefaultLocale(context, localeInfo.getLocale().toLanguageTag(),
+ packageName);
+ broadcastAppLocaleChange(context, localeInfo, packageName);
+ }
+ }
+
+ private static void logLocaleSource(Context context, LocaleStore.LocaleInfo localeInfo) {
+ if (!localeInfo.isSuggested() || localeInfo.isAppCurrentLocale()) {
+ return;
+ }
+
+ int localeSource = 0;
+ if (hasSuggestionType(localeInfo,
+ LocaleStore.LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE)) {
+ localeSource |= SYSTEM_LOCALE;
+ }
+ if (hasSuggestionType(localeInfo,
+ LocaleStore.LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE)) {
+ localeSource |= APP_LOCALE;
+ }
+ if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE)) {
+ localeSource |= IME_LOCALE;
+ }
+ if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_SIM)) {
+ localeSource |= SIM_LOCALE;
+ }
+ MetricsFeatureProvider metricsFeatureProvider =
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
+ metricsFeatureProvider.action(context,
+ SettingsEnums.ACTION_CHANGE_APP_LANGUAGE_FROM_SUGGESTED, localeSource);
+ }
+
+ private static boolean hasSuggestionType(LocaleStore.LocaleInfo localeInfo,
+ int suggestionType) {
+ return localeInfo.isSuggestionOfType(suggestionType);
+ }
+
+ private static void setAppDefaultLocale(Context context, String languageTag,
+ String packageName) {
+ LocaleManager localeManager = context.getSystemService(LocaleManager.class);
+ if (localeManager == null) {
+ Log.w(TAG, "LocaleManager is null, cannot set default app locale");
+ return;
+ }
+ localeManager.setApplicationLocales(packageName,
+ LocaleList.forLanguageTags(languageTag));
+ }
+
+ private static void broadcastAppLocaleChange(Context context, LocaleStore.LocaleInfo localeInfo,
+ String packageName) {
+ if (!localeNotificationEnabled()) {
+ Log.w(TAG, "Locale notification is not enabled");
+ return;
+ }
+ if (localeInfo.isAppCurrentLocale()) {
+ return;
+ }
+ try {
+ NotificationController notificationController = NotificationController.getInstance(
+ context);
+ String localeTag = localeInfo.getLocale().toLanguageTag();
+ int uid = context.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_META_DATA).uid;
+ boolean launchNotification = notificationController.shouldTriggerNotification(
+ uid, localeTag);
+ if (launchNotification) {
+ triggerNotification(
+ context,
+ notificationController.getNotificationId(localeTag),
+ context.getString(R.string.title_system_locale_addition,
+ localeInfo.getFullNameNative()),
+ context.getString(R.string.desc_system_locale_addition),
+ localeTag);
+ MetricsFeatureProvider metricsFeatureProvider =
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
+ metricsFeatureProvider.action(context,
+ SettingsEnums.ACTION_NOTIFICATION_FOR_SYSTEM_LOCALE);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to find info for package: " + packageName);
+ }
+ }
+
+ private static void triggerNotification(
+ Context context,
+ int notificationId,
+ String title,
+ String description,
+ String localeTag) {
+ NotificationManager notificationManager = context.getSystemService(
+ NotificationManager.class);
+ final boolean channelExist =
+ notificationManager.getNotificationChannel(CHANNEL_ID_SUGGESTION) != null;
+
+ // Create an alert channel if it does not exist
+ if (!channelExist) {
+ NotificationChannel channel =
+ new NotificationChannel(
+ CHANNEL_ID_SUGGESTION,
+ CHANNEL_ID_SUGGESTION_TO_USER,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(/* sound */ null, /* audioAttributes */ null); // silent notification
+ notificationManager.createNotificationChannel(channel);
+ }
+ final NotificationCompat.Builder builder =
+ new NotificationCompat.Builder(context, CHANNEL_ID_SUGGESTION)
+ .setSmallIcon(R.drawable.ic_settings_language)
+ .setAutoCancel(true)
+ .setContentTitle(title)
+ .setContentText(description)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setContentIntent(
+ createPendingIntent(context, localeTag, notificationId, false))
+ .setDeleteIntent(
+ createPendingIntent(context, localeTag, notificationId, true));
+ notificationManager.notify(notificationId, builder.build());
+ }
+
+ private static PendingIntent createPendingIntent(Context context, String locale,
+ int notificationId,
+ boolean isDeleteIntent) {
+ Intent intent = isDeleteIntent
+ ? new Intent(context, NotificationCancelReceiver.class)
+ : new Intent(context, NotificationActionActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+ intent.putExtra(EXTRA_APP_LOCALE, locale)
+ .putExtra(EXTRA_NOTIFICATION_ID, notificationId);
+ int flag = PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
+ int elapsedTime = (int) SystemClock.elapsedRealtimeNanos();
+
+ return isDeleteIntent
+ ? PendingIntent.getBroadcast(context, elapsedTime, intent, flag)
+ : PendingIntent.getActivity(context, elapsedTime, intent, flag);
+ }
+
+ /**
+ * Sort the locale's list.
+ *
+ * @param localeInfos list of locale Infos
+ * @param isCountryMode Whether the locale page is in country mode or not.
+ * @return localeInfos list of locale Infos
+ */
+ public static @NonNull List<LocaleStore.LocaleInfo> getSortedLocaleList(
+ @NonNull List<LocaleStore.LocaleInfo> localeInfos, boolean isCountryMode) {
+ final Locale sortingLocale = Locale.getDefault();
+ final LocaleHelper.LocaleInfoComparator comp = new LocaleHelper.LocaleInfoComparator(
+ sortingLocale, isCountryMode);
+ Collections.sort(localeInfos, comp);
+ return localeInfos;
+ }
+
+ /**
+ * Sort the locale's list by keywords in search.
+ *
+ * @param searchList locale Infos in search bar
+ * @param localeList list of locale Infos
+ * @param isCountryMode Whether the locale page is in country mode or not.
+ * @return localeInfos list of locale Infos
+ */
+ public static @NonNull List<LocaleStore.LocaleInfo> getSortedLocaleFromSearchList(
+ @NonNull List<LocaleStore.LocaleInfo> searchList,
+ @NonNull List<LocaleStore.LocaleInfo> localeList,
+ boolean isCountryMode) {
+ List<LocaleStore.LocaleInfo> searchItem = localeList.stream()
+ .filter(suggested -> searchList.stream()
+ .anyMatch(option -> option.getLocale() != null
+ && option.getLocale().getLanguage().equals(
+ suggested.getLocale().getLanguage())))
+ .distinct()
+ .collect(Collectors.toList());
+ return getSortedLocaleList(searchItem, isCountryMode);
+ }
}
diff --git a/src/com/android/settings/network/AirplaneModePreference.kt b/src/com/android/settings/network/AirplaneModePreference.kt
index b870f30..3b2b58a 100644
--- a/src/com/android/settings/network/AirplaneModePreference.kt
+++ b/src/com/android/settings/network/AirplaneModePreference.kt
@@ -30,11 +30,11 @@
import androidx.annotation.DrawableRes
import androidx.preference.Preference
import com.android.settings.AirplaneModeEnabler
+import com.android.settings.PreferenceActionMetricsProvider
import com.android.settings.PreferenceRestrictionMixin
import com.android.settings.R
import com.android.settings.Utils
import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn
-import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settingslib.RestrictedSwitchPreference
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.KeyValueStore
@@ -51,6 +51,7 @@
// LINT.IfChange
class AirplaneModePreference :
SwitchPreference(KEY, R.string.airplane_mode),
+ PreferenceActionMetricsProvider,
PreferenceAvailabilityProvider,
PreferenceLifecycleProvider,
PreferenceRestrictionMixin {
@@ -88,6 +89,9 @@
override val sensitivityLevel
get() = SensitivityLevel.HIGH_SENSITIVITY
+ override val preferenceActionMetrics: Int
+ get() = ACTION_AIRPLANE_TOGGLE
+
override fun storage(context: Context): KeyValueStore =
AirplaneModeStorage(context, SettingsGlobalStore.get(context))
@@ -109,16 +113,12 @@
(settingsStore.getBoolean(key) ?: DEFAULT_VALUE) as T
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
- if (value is Boolean) {
- settingsStore.setBoolean(key, value)
+ if (value !is Boolean) return
+ settingsStore.setBoolean(key, value)
- val intent = Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
- intent.putExtra("state", value)
- context.sendBroadcastAsUser(intent, UserHandle.ALL)
-
- val metricsFeature = featureFactory.metricsFeatureProvider
- metricsFeature.action(context, ACTION_AIRPLANE_TOGGLE, value)
- }
+ val intent = Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+ intent.putExtra("state", value)
+ context.sendBroadcastAsUser(intent, UserHandle.ALL)
}
override fun onFirstObserverAdded() {
diff --git a/src/com/android/settings/notification/SettingsEnableZenModeDialog.java b/src/com/android/settings/notification/EnableDndDialogFragment.java
similarity index 77%
rename from src/com/android/settings/notification/SettingsEnableZenModeDialog.java
rename to src/com/android/settings/notification/EnableDndDialogFragment.java
index d851caf..d093ca4 100644
--- a/src/com/android/settings/notification/SettingsEnableZenModeDialog.java
+++ b/src/com/android/settings/notification/EnableDndDialogFragment.java
@@ -21,17 +21,17 @@
import android.os.Bundle;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settingslib.notification.modes.EnableZenModeDialog;
+import com.android.settingslib.notification.modes.EnableDndDialogFactory;
-public class SettingsEnableZenModeDialog extends InstrumentedDialogFragment {
+public class EnableDndDialogFragment extends InstrumentedDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- return new EnableZenModeDialog(getContext()).createDialog();
+ return new EnableDndDialogFactory(getContext()).createDialog();
}
@Override
public int getMetricsCategory() {
- return SettingsEnums.NOTIFICATION_ZEN_MODE_ENABLE_DIALOG;
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_ENABLE_DIALOG;
}
}
diff --git a/src/com/android/settings/notification/history/NotificationHistoryActivity.java b/src/com/android/settings/notification/history/NotificationHistoryActivity.java
index 156df96..709eb7f 100644
--- a/src/com/android/settings/notification/history/NotificationHistoryActivity.java
+++ b/src/com/android/settings/notification/history/NotificationHistoryActivity.java
@@ -17,11 +17,14 @@
package com.android.settings.notification.history;
import static android.provider.Settings.Secure.NOTIFICATION_HISTORY_ENABLED;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
import static androidx.core.view.accessibility.AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
import android.annotation.AttrRes;
import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
import android.app.ActionBar;
import android.app.ActivityManager;
import android.app.INotificationManager;
@@ -30,7 +33,6 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.Outline;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -41,12 +43,10 @@
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Slog;
-import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.TextView;
@@ -95,22 +95,7 @@
private PackageManager mPm;
private CountDownLatch mCountdownLatch;
private Future mCountdownFuture;
- private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- final TypedArray ta = NotificationHistoryActivity.this.obtainStyledAttributes(
- new int[]{android.R.attr.dialogCornerRadius});
- final float dialogCornerRadius = ta.getDimension(0, 0);
- ta.recycle();
- TypedValue v = new TypedValue();
- NotificationHistoryActivity.this.getTheme().resolveAttribute(
- com.android.internal.R.attr.listDivider, v, true);
- int bottomPadding = NotificationHistoryActivity.this.getDrawable(v.resourceId)
- .getIntrinsicHeight();
- outline.setRoundRect(0, 0, view.getWidth(), (view.getHeight() - bottomPadding),
- dialogCornerRadius);
- }
- };
+
private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
enum NotificationHistoryEvent implements UiEventLogger.UiEventEnum {
@@ -158,20 +143,28 @@
private HistoryLoader.OnHistoryLoaderListener mOnHistoryLoaderListener = notifications -> {
findViewById(R.id.today_list).setVisibility(
- notifications.isEmpty() ? View.GONE : View.VISIBLE);
+ notifications.isEmpty() ? GONE : VISIBLE);
mCountdownLatch.countDown();
View recyclerView = mTodayView.findViewById(R.id.apps);
recyclerView.setClipToOutline(true);
- mTodayView.setOutlineProvider(mOutlineProvider);
- mSnoozeView.setOutlineProvider(mOutlineProvider);
// for each package, new header and recycler view
for (int i = 0, notificationsSize = notifications.size(); i < notificationsSize; i++) {
NotificationHistoryPackage nhp = notifications.get(i);
View viewForPackage = LayoutInflater.from(this)
.inflate(R.layout.notification_history_app_layout, null);
+ int cornerType = ROUND_CORNER_CENTER;
+ if (i == (notificationsSize - 1)) {
+ cornerType |= ROUND_CORNER_BOTTOM;
+ }
+ if (i == 0) {
+ cornerType |= ROUND_CORNER_TOP;
+ }
+ int backgroundRes = NotificationHistoryActivity.getRoundCornerDrawableRes(cornerType);
+ viewForPackage.setBackgroundResource(backgroundRes);
+
final View container = viewForPackage.findViewById(R.id.notification_list_wrapper);
- container.setVisibility(View.GONE);
+ container.setVisibility(GONE);
View header = viewForPackage.findViewById(R.id.app_header);
NotificationExpandButton expand = viewForPackage.findViewById(
com.android.internal.R.id.expand_button);
@@ -181,19 +174,19 @@
expand.setDefaultPillColor(pillColor);
expand.setDefaultTextColor(textColor);
expand.setExpanded(false);
- header.setStateDescription(container.getVisibility() == View.VISIBLE
+ header.setStateDescription(container.getVisibility() == VISIBLE
? getString(R.string.condition_expand_hide)
: getString(R.string.condition_expand_show));
int finalI = i;
header.setOnClickListener(v -> {
- container.setVisibility(container.getVisibility() == View.VISIBLE
- ? View.GONE : View.VISIBLE);
- expand.setExpanded(container.getVisibility() == View.VISIBLE);
- header.setStateDescription(container.getVisibility() == View.VISIBLE
+ container.setVisibility(container.getVisibility() == VISIBLE
+ ? GONE : VISIBLE);
+ expand.setExpanded(container.getVisibility() == VISIBLE);
+ header.setStateDescription(container.getVisibility() == VISIBLE
? getString(R.string.condition_expand_hide)
: getString(R.string.condition_expand_show));
header.sendAccessibilityEvent(TYPE_VIEW_ACCESSIBILITY_FOCUSED);
- mUiEventLogger.logWithPosition((container.getVisibility() == View.VISIBLE)
+ mUiEventLogger.logWithPosition((container.getVisibility() == VISIBLE)
? NotificationHistoryEvent.NOTIFICATION_HISTORY_PACKAGE_HISTORY_OPEN
: NotificationHistoryEvent.NOTIFICATION_HISTORY_PACKAGE_HISTORY_CLOSE,
nhp.uid, nhp.pkgName, finalI);
@@ -217,7 +210,7 @@
count.setText(StringUtil.getIcuPluralsString(this, newCount,
R.string.notification_history_count));
if (newCount == 0) {
- viewForPackage.setVisibility(View.GONE);
+ viewForPackage.setVisibility(GONE);
}
}, mUiEventLogger));
((NotificationHistoryAdapter) rv.getAdapter()).onRebuildComplete(
@@ -227,11 +220,6 @@
}
};
- private void configureNotificationList(View recyclerView) {
- recyclerView.setClipToOutline(true);
- recyclerView.setOutlineProvider(mOutlineProvider);
- }
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -240,8 +228,6 @@
mTodayView = findViewById(R.id.apps);
mSnoozeView = findViewById(R.id.snoozed_list);
mDismissView = findViewById(R.id.recently_dismissed_list);
- configureNotificationList(mDismissView.findViewById(R.id.notification_list));
- configureNotificationList(mSnoozeView.findViewById(R.id.notification_list));
mHistoryOff = findViewById(R.id.history_off);
mHistoryOn = findViewById(R.id.history_on);
mHistoryEmpty = findViewById(R.id.history_on_empty);
@@ -289,11 +275,11 @@
}
ThreadUtils.postOnMainThread(() -> {
if (mSwitchBar.isChecked()
- && findViewById(R.id.today_list).getVisibility() == View.GONE
- && mSnoozeView.getVisibility() == View.GONE
- && mDismissView.getVisibility() == View.GONE) {
- mHistoryOn.setVisibility(View.GONE);
- mHistoryEmpty.setVisibility(View.VISIBLE);
+ && findViewById(R.id.today_list).getVisibility() == GONE
+ && mSnoozeView.getVisibility() == GONE
+ && mDismissView.getVisibility() == GONE) {
+ mHistoryOn.setVisibility(GONE);
+ mHistoryEmpty.setVisibility(VISIBLE);
}
});
});
@@ -320,6 +306,33 @@
super.onDestroy();
}
+ public static final int ROUND_CORNER_CENTER = 1;
+ public static final int ROUND_CORNER_TOP = 1 << 1;
+ public static final int ROUND_CORNER_BOTTOM = 1 << 2;
+
+ public static @DrawableRes int getRoundCornerDrawableRes(int cornerType) {
+
+ if ((cornerType & ROUND_CORNER_CENTER) == 0) {
+ return 0;
+ }
+
+ if (((cornerType & ROUND_CORNER_TOP) != 0) && ((cornerType & ROUND_CORNER_BOTTOM) == 0)) {
+ // the first
+ return com.android.settingslib.widget.theme.R.drawable.settingslib_round_background_top;
+ } else if (((cornerType & ROUND_CORNER_BOTTOM) != 0)
+ && ((cornerType & ROUND_CORNER_TOP) == 0)) {
+ // the last
+ return com.android.settingslib.widget.theme.R.drawable.settingslib_round_background_bottom;
+ } else if (((cornerType & ROUND_CORNER_TOP) != 0)
+ && ((cornerType & ROUND_CORNER_BOTTOM) != 0)) {
+ // the only one preference
+ return com.android.settingslib.widget.theme.R.drawable.settingslib_round_background;
+ } else {
+ // in the center
+ return com.android.settingslib.widget.theme.R.drawable.settingslib_round_background_center;
+ }
+ }
+
private @ColorInt int obtainThemeColor(@AttrRes int attrRes) {
Resources.Theme theme = new ContextThemeWrapper(this,
android.R.style.Theme_DeviceDefault_DayNight).getTheme();
@@ -345,14 +358,14 @@
private void toggleViews(boolean isChecked) {
if (isChecked) {
- mHistoryOff.setVisibility(View.GONE);
- mHistoryOn.setVisibility(View.VISIBLE);
+ mHistoryOff.setVisibility(GONE);
+ mHistoryOn.setVisibility(VISIBLE);
} else {
- mHistoryOn.setVisibility(View.GONE);
- mHistoryOff.setVisibility(View.VISIBLE);
+ mHistoryOn.setVisibility(GONE);
+ mHistoryOff.setVisibility(VISIBLE);
mTodayView.removeAllViews();
}
- mHistoryEmpty.setVisibility(View.GONE);
+ mHistoryEmpty.setVisibility(GONE);
}
private final OnCheckedChangeListener mOnSwitchClickListener =
@@ -372,13 +385,13 @@
Log.d(TAG, "onSwitchChange history to " + isChecked);
}
// Reset UI visibility to ensure it matches real state.
- mHistoryOn.setVisibility(View.GONE);
+ mHistoryOn.setVisibility(GONE);
if (isChecked) {
- mHistoryEmpty.setVisibility(View.VISIBLE);
- mHistoryOff.setVisibility(View.GONE);
+ mHistoryEmpty.setVisibility(VISIBLE);
+ mHistoryOff.setVisibility(GONE);
} else {
- mHistoryOff.setVisibility(View.VISIBLE);
- mHistoryEmpty.setVisibility(View.GONE);
+ mHistoryOff.setVisibility(VISIBLE);
+ mHistoryEmpty.setVisibility(GONE);
}
mTodayView.removeAllViews();
};
@@ -410,7 +423,7 @@
mSnoozedRv.setNestedScrollingEnabled(false);
if (snoozed == null || snoozed.length == 0) {
- mSnoozeView.setVisibility(View.GONE);
+ mSnoozeView.setVisibility(GONE);
} else {
((NotificationSbnAdapter) mSnoozedRv.getAdapter()).onRebuildComplete(
new ArrayList<>(Arrays.asList(snoozed)));
@@ -426,9 +439,9 @@
mDismissedRv.setNestedScrollingEnabled(false);
if (dismissed == null || dismissed.length == 0) {
- mDismissView.setVisibility(View.GONE);
+ mDismissView.setVisibility(GONE);
} else {
- mDismissView.setVisibility(View.VISIBLE);
+ mDismissView.setVisibility(VISIBLE);
((NotificationSbnAdapter) mDismissedRv.getAdapter()).onRebuildComplete(
new ArrayList<>(Arrays.asList(dismissed)));
}
@@ -446,10 +459,10 @@
int reason) {
if (reason == REASON_SNOOZED) {
((NotificationSbnAdapter) mSnoozedRv.getAdapter()).addSbn(sbn);
- mSnoozeView.setVisibility(View.VISIBLE);
+ mSnoozeView.setVisibility(VISIBLE);
} else {
((NotificationSbnAdapter) mDismissedRv.getAdapter()).addSbn(sbn);
- mDismissView.setVisibility(View.VISIBLE);
+ mDismissView.setVisibility(VISIBLE);
}
}
};
diff --git a/src/com/android/settings/notification/history/NotificationHistoryRecyclerView.java b/src/com/android/settings/notification/history/NotificationHistoryRecyclerView.java
index 5923a4e..ae7590c 100644
--- a/src/com/android/settings/notification/history/NotificationHistoryRecyclerView.java
+++ b/src/com/android/settings/notification/history/NotificationHistoryRecyclerView.java
@@ -9,7 +9,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -36,7 +35,6 @@
super(context, attrs, defStyle);
setLayoutManager(new LinearLayoutManager(getContext()));
- addItemDecoration(new DividerItemDecoration(getContext(), LinearLayoutManager.VERTICAL));
ItemTouchHelper touchHelper = new ItemTouchHelper(
new DismissTouchHelper(0, ItemTouchHelper.START | ItemTouchHelper.END));
touchHelper.attachToRecyclerView(this);
diff --git a/src/com/android/settings/notification/history/NotificationSbnAdapter.java b/src/com/android/settings/notification/history/NotificationSbnAdapter.java
index 0301d7b..3262ec4 100644
--- a/src/com/android/settings/notification/history/NotificationSbnAdapter.java
+++ b/src/com/android/settings/notification/history/NotificationSbnAdapter.java
@@ -24,6 +24,10 @@
import static android.provider.Settings.EXTRA_CHANNEL_ID;
import static android.provider.Settings.EXTRA_CONVERSATION_ID;
+import static com.android.settings.notification.history.NotificationHistoryActivity.ROUND_CORNER_BOTTOM;
+import static com.android.settings.notification.history.NotificationHistoryActivity.ROUND_CORNER_CENTER;
+import static com.android.settings.notification.history.NotificationHistoryActivity.ROUND_CORNER_TOP;
+
import android.annotation.ColorInt;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -111,13 +115,22 @@
int position) {
final StatusBarNotification sbn = mValues.get(position);
if (sbn != null) {
+ int cornerType = ROUND_CORNER_CENTER;
+ if (position == (getItemCount() - 1)) {
+ cornerType |= ROUND_CORNER_BOTTOM;
+ }
+ if (position == 0) {
+ cornerType |= ROUND_CORNER_TOP;
+ }
+ int backgroundRes = NotificationHistoryActivity.getRoundCornerDrawableRes(cornerType);
+ holder.itemView.setBackgroundResource(backgroundRes);
+
holder.setIconBackground(loadBackground(sbn));
holder.setIcon(loadIcon(sbn));
holder.setPackageLabel(loadPackageLabel(sbn.getPackageName()).toString());
holder.setTitle(getTitleString(sbn.getNotification()));
holder.setSummary(getTextString(mContext, sbn.getNotification()));
holder.setPostedTime(sbn.getPostTime());
- holder.setDividerVisible(position < (mValues.size() -1));
int userId = normalizeUserId(sbn);
if (!mUserBadgeCache.containsKey(userId)) {
Drawable profile = mContext.getPackageManager().getUserBadgeForDensityNoBackground(
diff --git a/src/com/android/settings/notification/history/NotificationSbnViewHolder.java b/src/com/android/settings/notification/history/NotificationSbnViewHolder.java
index df8aec4..36a8720 100644
--- a/src/com/android/settings/notification/history/NotificationSbnViewHolder.java
+++ b/src/com/android/settings/notification/history/NotificationSbnViewHolder.java
@@ -49,7 +49,6 @@
private final TextView mTitle;
private final TextView mSummary;
private final ImageView mProfileBadge;
- private final View mDivider;
NotificationSbnViewHolder(View itemView) {
super(itemView);
@@ -59,7 +58,6 @@
mTitle = itemView.findViewById(R.id.title);
mSummary = itemView.findViewById(R.id.text);
mProfileBadge = itemView.findViewById(R.id.profile_badge);
- mDivider = itemView.findViewById(R.id.divider);
}
void setSummary(CharSequence summary) {
@@ -92,10 +90,6 @@
mProfileBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
}
- void setDividerVisible(boolean visible) {
- mDivider.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
-
void addOnClick(int position, String pkg, int uid, int userId, PendingIntent pi,
InstanceId instanceId,
boolean isSnoozed, UiEventLogger uiEventLogger) {
diff --git a/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java
index bb9d23c..72ff524 100644
--- a/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java
@@ -27,7 +27,7 @@
import androidx.preference.Preference;
import com.android.settings.R;
-import com.android.settings.notification.SettingsEnableZenModeDialog;
+import com.android.settings.notification.EnableDndDialogFragment;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
@@ -69,7 +69,7 @@
int zenDuration = mDurationHelper.getZenDuration();
switch (zenDuration) {
case Settings.Secure.ZEN_DURATION_PROMPT:
- new SettingsEnableZenModeDialog().show(
+ new EnableDndDialogFragment().show(
mParent.getParentFragmentManager(), TAG);
break;
case Settings.Secure.ZEN_DURATION_FOREVER:
diff --git a/src/com/android/settings/notification/zen/SettingsZenDurationDialog.java b/src/com/android/settings/notification/zen/SettingsZenDurationDialog.java
index bb83a73..ce7d7be 100644
--- a/src/com/android/settings/notification/zen/SettingsZenDurationDialog.java
+++ b/src/com/android/settings/notification/zen/SettingsZenDurationDialog.java
@@ -21,13 +21,13 @@
import android.os.Bundle;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settingslib.notification.modes.ZenDurationDialog;
+import com.android.settingslib.notification.modes.DndDurationDialogFactory;
public class SettingsZenDurationDialog extends InstrumentedDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- return new ZenDurationDialog(getContext()).createDialog();
+ return new DndDurationDialogFactory(getContext()).createDialog();
}
@Override
diff --git a/src/com/android/settings/notification/zen/ZenDurationDialogPreference.java b/src/com/android/settings/notification/zen/ZenDurationDialogPreference.java
index f243a51..a37ac5d 100644
--- a/src/com/android/settings/notification/zen/ZenDurationDialogPreference.java
+++ b/src/com/android/settings/notification/zen/ZenDurationDialogPreference.java
@@ -23,7 +23,7 @@
import androidx.appcompat.app.AlertDialog;
import com.android.settingslib.CustomDialogPreferenceCompat;
-import com.android.settingslib.notification.modes.ZenDurationDialog;
+import com.android.settingslib.notification.modes.DndDurationDialogFactory;
public class ZenDurationDialogPreference extends CustomDialogPreferenceCompat {
@@ -48,7 +48,7 @@
DialogInterface.OnClickListener listener) {
super.onPrepareDialogBuilder(builder, listener);
- ZenDurationDialog zenDialog = new ZenDurationDialog(getContext());
+ DndDurationDialogFactory zenDialog = new DndDurationDialogFactory(getContext());
zenDialog.setupDialog(builder);
}
}
diff --git a/src/com/android/settings/notification/zen/ZenModeButtonPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeButtonPreferenceController.java
index dc338bd..e427ca2 100644
--- a/src/com/android/settings/notification/zen/ZenModeButtonPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModeButtonPreferenceController.java
@@ -30,7 +30,7 @@
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.notification.SettingsEnableZenModeDialog;
+import com.android.settings.notification.EnableDndDialogFragment;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.LayoutPreference;
@@ -118,7 +118,7 @@
int zenDuration = getZenDuration();
switch (zenDuration) {
case Settings.Secure.ZEN_DURATION_PROMPT:
- new SettingsEnableZenModeDialog().show(mFragment, TAG);
+ new EnableDndDialogFragment().show(mFragment, TAG);
break;
case Settings.Secure.ZEN_DURATION_FOREVER:
mBackend.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java
index 8c4c621..9680d5a 100644
--- a/src/com/android/settings/password/ChooseLockPattern.java
+++ b/src/com/android/settings/password/ChooseLockPattern.java
@@ -815,8 +815,6 @@
if (stage == Stage.NeedToConfirm) {
// If the Stage is NeedToConfirm, move the a11y focus to the header.
mHeaderText.requestAccessibilityFocus();
- } else {
- mHeaderText.announceForAccessibility(mHeaderText.getText());
}
}
}
diff --git a/src/com/android/settings/widget/RadioButtonPickerFragment.java b/src/com/android/settings/widget/RadioButtonPickerFragment.java
index 121458c..94b5912 100644
--- a/src/com/android/settings/widget/RadioButtonPickerFragment.java
+++ b/src/com/android/settings/widget/RadioButtonPickerFragment.java
@@ -89,7 +89,6 @@
if (isCatalystEnabled()) {
PreferenceScreen preferenceScreen = createPreferenceScreen();
setPreferenceScreen(preferenceScreen);
- updateActivityTitleWithScreenTitle(preferenceScreen);
} else {
super.onCreatePreferences(savedInstanceState, rootKey);
}
diff --git a/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt b/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt
index 3877a02..21b0e88 100644
--- a/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt
+++ b/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt
@@ -27,17 +27,17 @@
import android.os.UserManager
import android.text.BidiFormatter
import android.util.Log
-import androidx.preference.Preference
import com.android.settings.PreferenceRestrictionMixin
import com.android.settings.R
import com.android.settings.Utils
import com.android.settings.core.SubSettingLauncher
+import com.android.settings.datausage.DataSaverMainSwitchPreference.Companion.KEY as DATA_SAVER_KEY
import com.android.settings.wifi.WifiUtils.canShowWifiHotspot
import com.android.settings.wifi.utils.tetheringManager
import com.android.settings.wifi.utils.wifiApState
import com.android.settings.wifi.utils.wifiManager
import com.android.settings.wifi.utils.wifiSoftApSsid
-import com.android.settingslib.PrimarySwitchPreference
+import com.android.settingslib.PrimarySwitchPreferenceBinding
import com.android.settingslib.TetherUtil
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.HandlerExecutor
@@ -46,19 +46,16 @@
import com.android.settingslib.datastore.Permissions
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceChangeReason
-import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference
-import com.android.settingslib.preference.PreferenceBinding
import com.android.settingslib.wifi.WifiUtils.Companion.getWifiTetherSummaryForConnectedDevices
-import com.android.settings.datausage.DataSaverMainSwitchPreference.Companion.KEY as DATA_SAVER_KEY
// LINT.IfChange
class WifiHotspotSwitchPreference(context: Context, dataSaverStore: KeyValueStore) :
SwitchPreference(KEY, R.string.wifi_hotspot_checkbox_text),
- PreferenceBinding,
+ PrimarySwitchPreferenceBinding,
PreferenceAvailabilityProvider,
PreferenceSummaryProvider,
PreferenceRestrictionMixin {
@@ -130,8 +127,6 @@
override val sensitivityLevel
get() = SensitivityLevel.HIGH_SENSITIVITY
- override fun createWidget(context: Context) = PrimarySwitchPreference(context)
-
override fun storage(context: Context): KeyValueStore = wifiHotspotStore
@Suppress("UNCHECKED_CAST")
@@ -201,16 +196,7 @@
Log.e(TAG, "onTetheringFailed(),error=$error")
}
- override fun onKeyChanged(key: String, reason: Int) =
- notifyChange(KEY, reason)
- }
-
- override fun bind(preference: Preference, metadata: PreferenceMetadata) {
- super.bind(preference, metadata)
- (preference as PrimarySwitchPreference).apply {
- isChecked = preferenceDataStore!!.getBoolean(key, false)
- isSwitchEnabled = isEnabled
- }
+ override fun onKeyChanged(key: String, reason: Int) = notifyChange(KEY, reason)
}
companion object {
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index 935c687..464d970 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -58,7 +58,6 @@
"Settings-robo-testutils",
"Settings-testutils2",
"SettingsLib-robo-testutils",
- "SettingsLibPreference-testutils",
"Settings_robolectric_meta_service_file",
"aconfig_settings_flags_lib",
"android.webkit.flags-aconfig-java",
@@ -73,6 +72,7 @@
"kotlin-test",
"mockito-robolectric-prebuilt", // mockito deps order matters!
"mockito-kotlin2",
+ "SettingsLibPreference-testutils", // order matters because it depends on mockito-kotlin2
"notification_flags_lib",
"platform-test-annotations",
"testables",
@@ -115,6 +115,7 @@
libs: [
"Robolectric_all-target",
"Settings-core",
+ "androidx.test.core",
"mockito-robolectric-prebuilt",
"truth",
],
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingControllerTest.java
index 2ce68e4..baf95fd 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingControllerTest.java
@@ -46,6 +46,7 @@
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
+import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@@ -143,9 +144,12 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void connected_hasConnectedBroadcastSource_showTwoPreference() {
+ public void connected_hasBroadcastSource_showTwoPreference() {
when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mCachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(false);
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
when(mLocalManager
.getProfileManager()
.getLeAudioBroadcastAssistantProfile()
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 4c25c11..6f03209 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.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
@@ -71,6 +73,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
@RunWith(RobolectricTestRunner.class)
@@ -164,9 +167,9 @@
}
@Test
- public void testDisplayPreference_sourceConnected_setDisconnectButton() {
- when(mAudioStreamsHelper.getAllConnectedSources())
- .thenReturn(List.of(mBroadcastReceiveState));
+ public void testDisplayPreference_sourceStreaming_setDisconnectButton() {
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Map.of(BROADCAST_ID, STREAMING));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
mController.displayPreference(mScreen);
@@ -190,7 +193,8 @@
@Test
public void testDisplayPreference_sourceNotConnected_setConnectButton() {
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Collections.emptyMap());
mController.setAudioStreamsRepositoryForTesting(mRepository);
var metadataToRejoin = mock(BluetoothLeBroadcastMetadata.class);
when(mRepository.getSavedMetadata(any(), anyInt())).thenReturn(metadataToRejoin);
@@ -216,7 +220,8 @@
@Test
public void testCallback_onSourceRemoved_updateButton() {
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceRemoved(
@@ -230,9 +235,8 @@
@Test
public void testCallback_onSourceRemovedFailed_updateButton() {
- when(mAudioStreamsHelper.getAllConnectedSources())
- .thenReturn(List.of(mBroadcastReceiveState));
- when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Map.of(BROADCAST_ID, STREAMING));
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceRemoveFailed(
@@ -250,9 +254,8 @@
@Test
public void testCallback_onReceiveStateChanged_updateButton() {
- when(mAudioStreamsHelper.getAllConnectedSources())
- .thenReturn(List.of(mBroadcastReceiveState));
- when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Map.of(BROADCAST_ID, STREAMING));
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
@@ -273,7 +276,7 @@
}
@Test
- public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() {
+ public void testCallback_onReceiveStateChangedWithSourcePaused_updateButton() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
@@ -284,13 +287,16 @@
when(mSourceDevice.getAddress()).thenReturn(address);
List<Long> bisSyncState = new ArrayList<>();
when(state.getBisSyncState()).thenReturn(bisSyncState);
- when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(List.of(state));
-
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Map.of(BROADCAST_ID, PAUSED));
+ // Create new controller to enable hysteresis mode
+ mController = new AudioStreamButtonController(mContext, KEY);
+ mController.init(BROADCAST_ID);
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onReceiveStateChanged(
mock(BluetoothDevice.class), /* sourceId= */ 0, state);
- verify(mFeatureFactory.metricsFeatureProvider, never())
+ verify(mFeatureFactory.metricsFeatureProvider)
.action(any(), eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED), anyInt());
// Called twice, once in displayPreference, the other one in callback
@@ -302,7 +308,8 @@
@Test
public void testCallback_onSourceAddFailed_updateButton() {
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceAddFailed(
@@ -321,7 +328,8 @@
@Test
public void testCallback_onSourceLost_updateButton() {
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 0);
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 54b26ec..0565146 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
@@ -19,10 +19,13 @@
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.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -66,6 +69,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
@RunWith(RobolectricTestRunner.class)
@@ -160,10 +164,9 @@
}
@Test
- public void testDisplayPreference_sourceConnected_setSummary() {
- when(mAudioStreamsHelper.getAllConnectedSources())
- .thenReturn(List.of(mBroadcastReceiveState));
- when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
+ public void testDisplayPreference_sourceStreaming_setSummary() {
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Map.of(BROADCAST_ID, STREAMING));
mController.displayPreference(mScreen);
@@ -176,8 +179,9 @@
}
@Test
- public void testDisplayPreference_sourceNotConnected_setSummary() {
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
+ public void testDisplayPreference_sourceNotStreaming_setSummary() {
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen);
@@ -189,18 +193,14 @@
}
@Test
- public void testDisplayPreference_sourcePresent_setSummary() {
+ public void testDisplayPreference_sourcePaused_setSummary() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
- String address = "11:22:33:44:55:66";
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Map.of(BROADCAST_ID, PAUSED));
- 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));
-
+ // Create new controller to enable hysteresis mode
+ mController = new AudioStreamHeaderController(mContext, KEY);
+ mController.init(mFragment, BROADCAST_NAME, BROADCAST_ID);
mController.displayPreference(mScreen);
verify(mHeaderController).setLabel(BROADCAST_NAME);
@@ -212,10 +212,10 @@
}
@Test
- public void testDisplayPreference_sourceNotPresent_setSummary() {
+ public void testDisplayPreference_sourceNotPaused_setSummary() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
-
- when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(Collections.emptyList());
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen);
@@ -228,7 +228,8 @@
@Test
public void testCallback_onSourceRemoved_updateButton() {
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceRemoved(
@@ -241,7 +242,8 @@
@Test
public void testCallback_onSourceLost_updateButton() {
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 1);
@@ -253,8 +255,8 @@
@Test
public void testCallback_onReceiveStateChanged_updateButton() {
- when(mAudioStreamsHelper.getAllConnectedSources())
- .thenReturn(List.of(mBroadcastReceiveState));
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Map.of(BROADCAST_ID, STREAMING));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
List<Long> bisSyncState = new ArrayList<>();
@@ -272,17 +274,20 @@
}
@Test
- public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() {
+ public void testCallback_onReceiveStateChangedWithSourcePaused_updateButton() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
- when(mAudioStreamsHelper.getAllPresentSources())
- .thenReturn(List.of(mBroadcastReceiveState));
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Map.of(BROADCAST_ID, PAUSED));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getAddress()).thenReturn(address);
+ // Create new controller to enable hysteresis mode
+ mController = new AudioStreamHeaderController(mContext, KEY);
+ mController.init(mFragment, BROADCAST_NAME, BROADCAST_ID);
mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onReceiveStateChanged(
mock(BluetoothDevice.class), /* sourceId= */ 0, mBroadcastReceiveState);
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 abfc4b7..ba37c83 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.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
@@ -149,11 +151,14 @@
@Test
public void removeSource_noConnectedSource_doNothing() {
+ 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(source.getBroadcastId()).thenReturn(BROADCAST_ID_2);
+ when(source.getSourceDevice()).thenReturn(mSourceDevice);
+ when(mSourceDevice.getAddress()).thenReturn(address);
when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID);
@@ -214,15 +219,16 @@
}
@Test
- public void getAllConnectedSources_noAssistant() {
+ public void getConnectedBroadcastIdAndState_noAssistant() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null);
mHelper = new AudioStreamsHelper(mLocalBluetoothManager);
- assertThat(mHelper.getAllConnectedSources()).isEmpty();
+ assertThat(mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */
+ false)).isEmpty();
}
@Test
- public void getAllConnectedSources_returnSource() {
+ public void getConnectedBroadcastIdAndState_returnStreamingSource() {
List<BluetoothDevice> devices = new ArrayList<>();
devices.add(mDevice);
when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
@@ -234,14 +240,15 @@
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
when(source.getBisSyncState()).thenReturn(bisSyncState);
+ when(source.getBroadcastId()).thenReturn(BROADCAST_ID_1);
- var list = mHelper.getAllConnectedSources();
- assertThat(list).isNotEmpty();
- assertThat(list.get(0)).isEqualTo(source);
+ var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ false);
+ assertThat(map).isNotEmpty();
+ assertThat(map.get(BROADCAST_ID_1)).isEqualTo(STREAMING);
}
@Test
- public void getAllPresentSources_noSource() {
+ public void getConnectedBroadcastIdAndState_noSource() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
@@ -259,12 +266,12 @@
when(source.getSourceDevice()).thenReturn(mSourceDevice);
when(mSourceDevice.getAddress()).thenReturn(address);
- var list = mHelper.getAllPresentSources();
- assertThat(list).isEmpty();
+ var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ true);
+ assertThat(map).isEmpty();
}
@Test
- public void getAllPresentSources_returnSource() {
+ public void getConnectedBroadcastIdAndState_returnPausedSource() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
@@ -282,10 +289,11 @@
when(mSourceDevice.getAddress()).thenReturn(address);
List<Long> bisSyncState = new ArrayList<>();
when(source.getBisSyncState()).thenReturn(bisSyncState);
+ when(source.getBroadcastId()).thenReturn(BROADCAST_ID_1);
- var list = mHelper.getAllPresentSources();
- assertThat(list).isNotEmpty();
- assertThat(list.get(0)).isEqualTo(source);
+ var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ true);
+ assertThat(map).isNotEmpty();
+ assertThat(map.get(BROADCAST_ID_1)).isEqualTo(PAUSED);
}
@Test
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 6aff8c3..1992846 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
@@ -77,7 +77,7 @@
BluetoothStatusCodes.FEATURE_SUPPORTED);
shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- mCallback = new AudioStreamsProgressCategoryCallback(mContext, mController);
+ mCallback = new AudioStreamsProgressCategoryCallback(mController);
}
@Test
@@ -87,7 +87,7 @@
when(mState.getBisSyncState()).thenReturn(bisSyncState);
mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
- verify(mController).handleSourceConnected(any(), any());
+ verify(mController).handleSourceStreaming(any(), any());
}
@Test
@@ -102,7 +102,7 @@
when(mSourceDevice.getAddress()).thenReturn(address);
mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
- verify(mController).handleSourcePresent(any(), any());
+ verify(mController).handleSourcePaused(any(), any());
}
@Test
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 f042329..8cccbd4 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
@@ -25,6 +25,7 @@
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.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
@@ -355,7 +356,7 @@
}
@Test
- public void testOnStart_handleSourceAlreadyConnected_useNameFromMetadata() {
+ public void testOnStart_handleSourceAlreadyStreaming_useNameFromMetadata() {
// Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
@@ -665,8 +666,8 @@
shadowOf(Looper.getMainLooper()).idle();
// A new source found is lost, but the source is still connected
- BluetoothLeBroadcastReceiveState connected = createConnectedMock(NEWLY_FOUND_BROADCAST_ID);
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())).thenReturn(
+ Map.of(NEWLY_FOUND_BROADCAST_ID, STREAMING));
mController.handleSourceLost(NEWLY_FOUND_BROADCAST_ID);
shadowOf(Looper.getMainLooper()).idle();
@@ -819,13 +820,15 @@
}
@Test
- public void testHandleSourcePresent_updateState() {
+ public void testHandleSourcePaused_updateState() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66";
// Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+ // Create new controller to enable hysteresis mode
+ mController = spy(new TestController(mContext, KEY));
// Setup mPreference so it's not null
mController.displayPreference(mScreen);
@@ -844,7 +847,7 @@
when(receiveState.getBisSyncState()).thenReturn(bisSyncState);
// The new found source is identified as failed to connect
- mController.handleSourcePresent(mSourceDevice, receiveState);
+ mController.handleSourcePaused(mSourceDevice, receiveState);
shadowOf(Looper.getMainLooper()).idle();
ArgumentCaptor<AudioStreamPreference> preference =
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 e5e51fc..1d3c7a0 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
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
+
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -57,8 +59,9 @@
}
@Implementation
- public List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() {
- return sMockHelper.getAllConnectedSources();
+ public Map<Integer, LocalBluetoothLeBroadcastSourceState> getConnectedBroadcastIdAndState(
+ boolean hysteresisModeFixAvailable) {
+ return sMockHelper.getConnectedBroadcastIdAndState(hysteresisModeFixAvailable);
}
@Implementation
@@ -66,11 +69,6 @@
return sMockHelper.getAllSourcesByDevice();
}
- @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/core/InstrumentedPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/core/InstrumentedPreferenceFragmentTest.java
index 838edc6..3af89f4 100644
--- a/tests/robotests/src/com/android/settings/core/InstrumentedPreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/core/InstrumentedPreferenceFragmentTest.java
@@ -16,7 +16,6 @@
package com.android.settings.core;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -84,7 +83,6 @@
mFragment.onCreatePreferences(Bundle.EMPTY, null /* rootKey */);
verify(mFragment).addPreferencesFromResource(R.xml.screen_pinning_settings);
- verify(mActivity, never()).setTitle(any());
}
@Test
@@ -97,7 +95,6 @@
mFragment.onCreatePreferences(Bundle.EMPTY, null /* rootKey */);
verify(mFragment).addPreferencesFromResource(R.xml.screen_pinning_settings);
- verify(mActivity).setTitle(title);
}
public static class InstrumentedPreferenceFragmentTestable
diff --git a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsUtilsTest.java b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsUtilsTest.java
index ee5f72e..ab1682a 100644
--- a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsUtilsTest.java
@@ -22,14 +22,9 @@
import android.content.Context;
import android.os.UserHandle;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
-import android.view.accessibility.Flags;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -37,8 +32,6 @@
@RunWith(RobolectricTestRunner.class)
public class OneHandedSettingsUtilsTest {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int OFF = 0;
private static final int ON = 1;
@@ -162,7 +155,6 @@
}
@Test
- @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void getShortcutEnabled_qsShortcutEnabled_returnTrue() {
setupShortcuts(
/* enableFab= */ false, /* enableVolumeKeys= */ false, /* enableQs=*/ true);
@@ -170,15 +162,6 @@
assertThat(OneHandedSettingsUtils.getShortcutEnabled(mContext)).isTrue();
}
- @Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
- public void getShortcutEnabled_flagDisabled_qsShortcutEnabled_returnFalse() {
- setupShortcuts(
- /* enableFab= */ false, /* enableVolumeKeys= */ false, /* enableQs=*/ true);
-
- assertThat(OneHandedSettingsUtils.getShortcutEnabled(mContext)).isFalse();
- }
-
private void setupShortcuts(boolean enableFab, boolean enableVolumeKeys, boolean enableQs) {
setupShortcut(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, enableFab);
setupShortcut(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, enableVolumeKeys);
diff --git a/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceTest.kt b/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceTest.kt
index 75b843d..5b39a26 100644
--- a/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceTest.kt
+++ b/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceTest.kt
@@ -27,10 +27,11 @@
import androidx.preference.SwitchPreferenceCompat
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settings.testutils.FakeFeatureFactory
+import com.android.settings.testutils.MetricsRule
import com.android.settingslib.datastore.SettingsGlobalStore
import com.android.settingslib.preference.createAndBindWidget
import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -41,6 +42,7 @@
@RunWith(AndroidJUnit4::class)
class AirplaneModePreferenceTest {
+ @Rule(order = 0) @JvmField val metricsRule = MetricsRule()
private val mockResources = mock<Resources>()
private val mockPackageManager = mock<PackageManager>()
@@ -107,30 +109,13 @@
}
@Test
- fun setValue_valueTrue_metricsActionAirplaneToggleTrue() {
- val metricsFeatureProvider = FakeFeatureFactory.setupForTest().metricsFeatureProvider
-
- airplaneModePreference.storage(context).setBoolean(AirplaneModePreference.KEY, true)
-
- verify(metricsFeatureProvider).action(context, ACTION_AIRPLANE_TOGGLE, true)
- }
-
- @Test
- fun setValue_valueFalse_metricsActionAirplaneToggleFalse() {
- val metricsFeatureProvider = FakeFeatureFactory.setupForTest().metricsFeatureProvider
-
- airplaneModePreference.storage(context).setBoolean(AirplaneModePreference.KEY, false)
-
- verify(metricsFeatureProvider).action(context, ACTION_AIRPLANE_TOGGLE, false)
- }
-
- @Test
fun performClick_defaultOn_checkedIsFalse() {
SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 1)
val preference = getSwitchPreference().apply { performClick() }
assertThat(preference.isChecked).isFalse()
+ verify(metricsRule.metricsFeatureProvider).action(context, ACTION_AIRPLANE_TOGGLE, false)
}
@Test
@@ -140,6 +125,7 @@
val preference = getSwitchPreference().apply { performClick() }
assertThat(preference.isChecked).isTrue()
+ verify(metricsRule.metricsFeatureProvider).action(context, ACTION_AIRPLANE_TOGGLE, true)
}
private fun getSwitchPreference(): SwitchPreferenceCompat =
diff --git a/tests/robotests/testutils/com/android/settings/testutils/MetricsRule.kt b/tests/robotests/testutils/com/android/settings/testutils/MetricsRule.kt
new file mode 100644
index 0000000..44fa4bf
--- /dev/null
+++ b/tests/robotests/testutils/com/android/settings/testutils/MetricsRule.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 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.testutils
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import com.android.settings.SettingsMetricsLogger
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider
+import com.android.settingslib.metadata.PreferenceScreenRegistry
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/** Test rule for metrics. */
+class MetricsRule : TestWatcher() {
+ val metricsFeatureProvider: MetricsFeatureProvider =
+ FakeFeatureFactory.setupForTest().metricsFeatureProvider
+
+ override fun starting(description: Description) {
+ val context: Context = ApplicationProvider.getApplicationContext()
+ PreferenceScreenRegistry.preferenceUiActionMetricsLogger =
+ SettingsMetricsLogger(context, metricsFeatureProvider)
+ }
+
+ override fun finished(description: Description) {
+ PreferenceScreenRegistry.preferenceUiActionMetricsLogger = null
+ }
+}