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.
+ *
+ * If multiple sources have the same broadcast ID, the state of the source that is
+ * {@code STREAMING} is preferred.
+ */
+ public Map 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 @@ public class AudioStreamsHelper {
.collect(toMap(Function.identity(), mLeBroadcastAssistant::getAllSources));
}
- /** Retrieves a list of all LE broadcast receive states from sinks with source present. */
- @VisibleForTesting
- public List 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 @@ public class AudioStreamsHelper {
}
var deviceHasSource =
leadDevices.stream()
- .filter(device -> hasConnectedBroadcastSource(device, manager))
+ .filter(device -> hasBroadcastSource(device, manager))
.findFirst();
if (deviceHasSource.isPresent()) {
Log.d(
@@ -258,38 +239,38 @@ public class AudioStreamsHelper {
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 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 @@ public class AudioStreamsHelper {
for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
List 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 @@ public class AudioStreamsHelper {
return false;
}
+ private static boolean hasReceiveState(List 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 87cea2c1e94..f0d0bebcddb 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 class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA
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 6831c5a12e3..24978c6a753 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 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
private final @Nullable LocalBluetoothManager mBluetoothManager;
private final ConcurrentHashMap mBroadcastIdToPreferenceMap =
new ConcurrentHashMap<>();
+ private final boolean mHysteresisModeFixAvailable;
private @Nullable BluetoothLeBroadcastMetadata mSourceFromQrCode;
private SourceOriginForLogging mSourceFromQrCodeOriginForLogging;
@Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference;
@@ -149,7 +156,9 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
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 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
// 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 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
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 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
// 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 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
// 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 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 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
if (DEBUG) {
Log.d(TAG, "handleSourceConnectBadCode()");
}
- if (!AudioStreamsHelper.isBadCode(receiveState)) {
+ if (getLocalSourceState(receiveState) != DECRYPTION_FAILED) {
return;
}
mBroadcastIdToPreferenceMap.computeIfPresent(
@@ -467,29 +474,28 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
// 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 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 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
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> sources =
mAudioStreamsHelper.getAllSourcesByDevice();
- Map> 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>
- 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> getConnectedSources(
+ private Map> getStreamSourcesByDevice(
Map> 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> getPresentSources(
+ private Map> getPausedSourcesByDevice(
Map> 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 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
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 ac87ea5abc6..2ec93b6e75e 100644
--- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java
+++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
@@ -21,12 +21,8 @@ import static com.android.internal.jank.InteractionJankMonitor.Configuration;
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;
@@ -108,12 +104,6 @@ public abstract class InstrumentedPreferenceFragment extends ObservablePreferenc
}
}
- @Override
- public void addPreferencesFromResource(@XmlRes int preferencesResId) {
- super.addPreferencesFromResource(preferencesResId);
- updateActivityTitleWithScreenTitle(getPreferenceScreen());
- }
-
@Override
public T findPreference(CharSequence key) {
if (key == null) {
@@ -147,17 +137,6 @@ public abstract class InstrumentedPreferenceFragment extends ObservablePreferenc
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 c79cc65bbfa..d163bda8d9f 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -415,7 +415,6 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
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 48ef210e360..c42588853c6 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
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 @@ import com.android.settingslib.preference.PreferenceScreenCreator
@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 @@ class AutoBrightnessScreen :
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.bind(preference, metadata)
+ else -> super.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 f1a95f59874..527cd192b79 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.Manifest
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 @@ import com.android.settingslib.preference.PreferenceScreenCreator
@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 @@ class DarkModeScreen(context: Context) :
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.bind(preference, metadata)
+ else -> super.bind(preference, metadata)
}
}
diff --git a/src/com/android/settings/inputmethod/MouseScrollingSpeedPreferenceController.java b/src/com/android/settings/inputmethod/MouseScrollingSpeedPreferenceController.java
index 0a57085b7d2..a047e5699a7 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 @@ public class MouseScrollingSpeedPreferenceController extends SliderPreferenceCon
if (!InputSettings.isMouseScrollingAccelerationFeatureFlagEnabled()) {
return UNSUPPORTED_ON_DEVICE;
}
- return AVAILABLE;
+ return shouldEnableSlideBar() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
}
@Override
@@ -73,4 +99,30 @@ public class MouseScrollingSpeedPreferenceController extends SliderPreferenceCon
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 e8a645f2b5e..d3b23a7128a 100644
--- a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java
+++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java
@@ -57,8 +57,8 @@ public class NewKeyboardLayoutPickerController extends BasePreferenceController
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 @@ public class NewKeyboardLayoutPickerController extends BasePreferenceController
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 @@ public class NewKeyboardLayoutPickerController extends BasePreferenceController
@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 @@ public class NewKeyboardLayoutPickerController extends BasePreferenceController
}
setLayout(pref);
mPreviousSelection = preference.getKey();
- mFinalSelectedLayout = pref.getTitle().toString();
+ mFinalSelectedLayoutDescriptor = mPreviousSelection;
return true;
}
@@ -182,12 +186,12 @@ public class NewKeyboardLayoutPickerController extends BasePreferenceController
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 @@ public class NewKeyboardLayoutPickerController extends BasePreferenceController
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 00000000000..3fe95508a54
--- /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 mLocaleList;
+ private List mLocaleOptions;
+ private Map 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 result = LocaleUtils.getSortedLocaleList(
+ getSuggestedLocaleList(), mIsCountryMode);
+ final Map existingSuggestedPreferences = mSuggestedPreferences;
+ mSuggestedPreferences = new ArrayMap<>();
+ setupSuggestedPreference(result, existingSuggestedPreferences);
+ for (Preference pref : existingSuggestedPreferences.values()) {
+ mPreferenceCategory.removePreference(pref);
+ }
+ }
+
+ @Override
+ public void onSearchListChanged(@NonNull List newList,
+ @Nullable CharSequence prefix) {
+ if (mPreferenceCategory == null) {
+ Log.d(TAG, "onSearchListChanged, mPreferenceCategory is null");
+ return;
+ }
+
+ mPreferenceCategory.removeAll();
+ final Map existingSuggestedPreferences = mSuggestedPreferences;
+ List sortedList = getSuggestedLocaleList();
+ newList = LocaleUtils.getSortedLocaleFromSearchList(newList, sortedList, mIsCountryMode);
+ setupSuggestedPreference(newList, existingSuggestedPreferences);
+ }
+
+ private void setupSuggestedPreference(List localeInfoList,
+ Map 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 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 a84d0beb7a8..b6502180aa0 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 @@ public class LocaleUtils {
}
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 getSortedLocaleList(
+ @NonNull List 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 getSortedLocaleFromSearchList(
+ @NonNull List searchList,
+ @NonNull List localeList,
+ boolean isCountryMode) {
+ List 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 b870f30eaf1..3b2b58a5148 100644
--- a/src/com/android/settings/network/AirplaneModePreference.kt
+++ b/src/com/android/settings/network/AirplaneModePreference.kt
@@ -30,11 +30,11 @@ import android.telephony.TelephonyManager
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 @@ import com.android.settingslib.metadata.SwitchPreference
// LINT.IfChange
class AirplaneModePreference :
SwitchPreference(KEY, R.string.airplane_mode),
+ PreferenceActionMetricsProvider,
PreferenceAvailabilityProvider,
PreferenceLifecycleProvider,
PreferenceRestrictionMixin {
@@ -88,6 +89,9 @@ class AirplaneModePreference :
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 @@ class AirplaneModePreference :
(settingsStore.getBoolean(key) ?: DEFAULT_VALUE) as T
override fun setValue(key: String, valueType: Class, 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 d851caf3bdc..d093ca43208 100644
--- a/src/com/android/settings/notification/SettingsEnableZenModeDialog.java
+++ b/src/com/android/settings/notification/EnableDndDialogFragment.java
@@ -21,17 +21,17 @@ import android.app.settings.SettingsEnums;
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 156df96e04e..709eb7fc9e0 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.Context;
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.NotificationListenerService;
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
}
};
- private void configureNotificationList(View recyclerView) {
- recyclerView.setClipToOutline(true);
- recyclerView.setOutlineProvider(mOutlineProvider);
- }
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -240,8 +228,6 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
}
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity {
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 5923a4e8dbf..ae7590c9d01 100644
--- a/src/com/android/settings/notification/history/NotificationHistoryRecyclerView.java
+++ b/src/com/android/settings/notification/history/NotificationHistoryRecyclerView.java
@@ -9,7 +9,6 @@ import android.view.View;
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 @@ public class NotificationHistoryRecyclerView extends RecyclerView {
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 0301d7b7fd5..3262ec4aea9 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_APP_PACKAGE;
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 @@ public class NotificationSbnAdapter extends
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 df8aec44c0d..36a8720a859 100644
--- a/src/com/android/settings/notification/history/NotificationSbnViewHolder.java
+++ b/src/com/android/settings/notification/history/NotificationSbnViewHolder.java
@@ -49,7 +49,6 @@ public class NotificationSbnViewHolder extends RecyclerView.ViewHolder {
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 @@ public class NotificationSbnViewHolder extends RecyclerView.ViewHolder {
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 @@ public class NotificationSbnViewHolder extends RecyclerView.ViewHolder {
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 bb9d23cd558..72ff5249bb4 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.fragment.app.Fragment;
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 @@ class ZenModeButtonPreferenceController extends AbstractZenModePreferenceControl
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 bb83a730837..ce7d7be9ea0 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.app.settings.SettingsEnums;
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 f243a5170b6..a37ac5d21a0 100644
--- a/src/com/android/settings/notification/zen/ZenDurationDialogPreference.java
+++ b/src/com/android/settings/notification/zen/ZenDurationDialogPreference.java
@@ -23,7 +23,7 @@ import android.util.AttributeSet;
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 @@ public class ZenDurationDialogPreference extends CustomDialogPreferenceCompat {
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 dc338bd5ac2..e427ca224a3 100644
--- a/src/com/android/settings/notification/zen/ZenModeButtonPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModeButtonPreferenceController.java
@@ -30,7 +30,7 @@ import androidx.preference.Preference;
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 @@ public class ZenModeButtonPreferenceController extends AbstractZenModePreference
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 8c4c621242f..9680d5a2748 100644
--- a/src/com/android/settings/password/ChooseLockPattern.java
+++ b/src/com/android/settings/password/ChooseLockPattern.java
@@ -815,8 +815,6 @@ public class ChooseLockPattern extends SettingsActivity {
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 121458c714a..94b59121110 100644
--- a/src/com/android/settings/widget/RadioButtonPickerFragment.java
+++ b/src/com/android/settings/widget/RadioButtonPickerFragment.java
@@ -89,7 +89,6 @@ public abstract class RadioButtonPickerFragment extends SettingsPreferenceFragme
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 3877a026480..21b0e88d7ac 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.net.wifi.WifiManager
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.KeyedObserver
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 @@ class WifiHotspotSwitchPreference(context: Context, dataSaverStore: KeyValueStor
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 @@ class WifiHotspotSwitchPreference(context: Context, dataSaverStore: KeyValueStor
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 935c687ade3..464d9708c0a 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -58,7 +58,6 @@ android_robolectric_test {
"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 @@ android_robolectric_test {
"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 @@ java_library {
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 2ce68e486de..baf95fd4181 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.RobolectricTestRunner;
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 @@ public class BluetoothDetailsAudioSharingControllerTest extends BluetoothDetails
@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 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 4c25c11db70..6f032097118 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 org.robolectric.shadow.api.Shadow;
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 @@ public class AudioStreamButtonControllerTest {
}
@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 @@ public class AudioStreamButtonControllerTest {
@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 @@ public class AudioStreamButtonControllerTest {
@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 @@ public class AudioStreamButtonControllerTest {
@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 @@ public class AudioStreamButtonControllerTest {
@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 bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
@@ -273,7 +276,7 @@ public class AudioStreamButtonControllerTest {
}
@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 @@ public class AudioStreamButtonControllerTest {
when(mSourceDevice.getAddress()).thenReturn(address);
List 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 @@ public class AudioStreamButtonControllerTest {
@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 @@ public class AudioStreamButtonControllerTest {
@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 54b26ec228a..056514620d2 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 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
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 org.robolectric.shadow.api.Shadow;
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 @@ public class AudioStreamHeaderControllerTest {
}
@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 @@ public class AudioStreamHeaderControllerTest {
}
@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 @@ public class AudioStreamHeaderControllerTest {
}
@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(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
- when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
- when(mBluetoothDevice.getAddress()).thenReturn(address);
- List bisSyncState = new ArrayList<>();
- when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
- when(mAudioStreamsHelper.getAllPresentSources())
- .thenReturn(List.of(mBroadcastReceiveState));
+ when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
+ .thenReturn(Map.of(BROADCAST_ID, PAUSED));
+ // 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 @@ public class AudioStreamHeaderControllerTest {
}
@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 @@ public class AudioStreamHeaderControllerTest {
@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 @@ public class AudioStreamHeaderControllerTest {
@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 @@ public class AudioStreamHeaderControllerTest {
@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 bisSyncState = new ArrayList<>();
@@ -272,17 +274,20 @@ public class AudioStreamHeaderControllerTest {
}
@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 abfc4b7cc80..ba37c83055a 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 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
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 @@ public class AudioStreamsHelperTest {
@Test
public void removeSource_noConnectedSource_doNothing() {
+ String address = "11:22:33:44:55:66";
List 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 @@ public class AudioStreamsHelperTest {
}
@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 devices = new ArrayList<>();
devices.add(mDevice);
when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
@@ -234,14 +240,15 @@ public class AudioStreamsHelperTest {
List 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 @@ public class AudioStreamsHelperTest {
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 @@ public class AudioStreamsHelperTest {
when(mSourceDevice.getAddress()).thenReturn(address);
List 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 6aff8c38d7e..199284657e3 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 @@ public class AudioStreamsProgressCategoryCallbackTest {
BluetoothStatusCodes.FEATURE_SUPPORTED);
shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- mCallback = new AudioStreamsProgressCategoryCallback(mContext, mController);
+ mCallback = new AudioStreamsProgressCategoryCallback(mController);
}
@Test
@@ -87,7 +87,7 @@ public class AudioStreamsProgressCategoryCallbackTest {
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 @@ public class AudioStreamsProgressCategoryCallbackTest {
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 f042329200a..8cccbd443f1 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.Aud
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 @@ public class AudioStreamsProgressCategoryControllerTest {
}
@Test
- public void testOnStart_handleSourceAlreadyConnected_useNameFromMetadata() {
+ public void testOnStart_handleSourceAlreadyStreaming_useNameFromMetadata() {
// Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
@@ -665,8 +666,8 @@ public class AudioStreamsProgressCategoryControllerTest {
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 @@ public class AudioStreamsProgressCategoryControllerTest {
}
@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 @@ public class AudioStreamsProgressCategoryControllerTest {
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 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 e5e51fce717..1d3c7a0ddd6 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 @@ public class ShadowAudioStreamsHelper {
}
@Implementation
- public List getAllConnectedSources() {
- return sMockHelper.getAllConnectedSources();
+ public Map getConnectedBroadcastIdAndState(
+ boolean hysteresisModeFixAvailable) {
+ return sMockHelper.getConnectedBroadcastIdAndState(hysteresisModeFixAvailable);
}
@Implementation
@@ -66,11 +69,6 @@ public class ShadowAudioStreamsHelper {
return sMockHelper.getAllSourcesByDevice();
}
- @Implementation
- public List getAllPresentSources() {
- return sMockHelper.getAllPresentSources();
- }
-
/** Gets {@link CachedBluetoothDevice} in sharing or le connected */
@Implementation
public static Optional getCachedBluetoothDeviceInSharingOrLeConnected(
diff --git a/tests/robotests/src/com/android/settings/core/InstrumentedPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/core/InstrumentedPreferenceFragmentTest.java
index 838edc618a3..3af89f46481 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 @@ public class InstrumentedPreferenceFragmentTest {
mFragment.onCreatePreferences(Bundle.EMPTY, null /* rootKey */);
verify(mFragment).addPreferencesFromResource(R.xml.screen_pinning_settings);
- verify(mActivity, never()).setTitle(any());
}
@Test
@@ -97,7 +95,6 @@ public class InstrumentedPreferenceFragmentTest {
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/network/AirplaneModePreferenceTest.kt b/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceTest.kt
index 75b843d8b46..5b39a269e97 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 android.telephony.TelephonyManager
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 @@ import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class AirplaneModePreferenceTest {
+ @Rule(order = 0) @JvmField val metricsRule = MetricsRule()
private val mockResources = mock()
private val mockPackageManager = mock()
@@ -106,24 +108,6 @@ class AirplaneModePreferenceTest {
assertThat(getValue).isFalse()
}
- @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)
@@ -131,6 +115,7 @@ class AirplaneModePreferenceTest {
val preference = getSwitchPreference().apply { performClick() }
assertThat(preference.isChecked).isFalse()
+ verify(metricsRule.metricsFeatureProvider).action(context, ACTION_AIRPLANE_TOGGLE, false)
}
@Test
@@ -140,6 +125,7 @@ class AirplaneModePreferenceTest {
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 00000000000..44fa4bf90c6
--- /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
+ }
+}