Snap for 12930767 from 878f860fd3 to 25Q2-release
Change-Id: I4f9ade7440b5adeb5d75895c8cc5874ffa301c24
This commit is contained in:
@@ -58,6 +58,13 @@ flag {
|
||||
bug: "301198830"
|
||||
}
|
||||
|
||||
flag {
|
||||
name: "enable_magnification_cursor_following_dialog"
|
||||
namespace: "accessibility"
|
||||
description: "Decides whether to show the magnification cursor following dialog in Settings app."
|
||||
bug: "388335935"
|
||||
}
|
||||
|
||||
flag {
|
||||
name: "enable_magnification_focus_following_dialog"
|
||||
namespace: "accessibility"
|
||||
|
||||
21
res/drawable/ic_settings_globe.xml
Normal file
21
res/drawable/ic_settings_globe.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<!--
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp"
|
||||
android:height="24dp" android:viewportWidth="960" android:viewportHeight="960"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path android:fillColor="@android:color/white"
|
||||
android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,473 799.5,465.5Q799,458 799,453Q794,482 772,501Q750,520 720,520L640,520Q607,520 583.5,496.5Q560,473 560,440L560,400L400,400L400,320Q400,287 423.5,263.5Q447,240 480,240L520,240L520,240Q520,217 532.5,199.5Q545,182 563,171Q543,166 522.5,163Q502,160 480,160Q346,160 253,253Q160,346 160,480Q160,480 160,480Q160,480 160,480L360,480Q426,480 473,527Q520,574 520,640L520,680L400,680L400,790Q420,795 439.5,797.5Q459,800 480,800Z"/>
|
||||
</vector>
|
||||
@@ -17,7 +17,6 @@
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/scroll"
|
||||
android:background="?android:attr/colorBackgroundFloating"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@@ -132,7 +131,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/snoozed_list"
|
||||
@@ -145,12 +145,12 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/notification_history_snooze"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:textAppearance="@style/TextAppearance.HomepageCardTitle"
|
||||
android:paddingBottom="16dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/notification_list"
|
||||
android:background="@drawable/rounded_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="true"
|
||||
@@ -170,10 +170,10 @@
|
||||
android:text="@string/notification_history_dismiss"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textAppearance="@style/TextAppearance.HomepageCardTitle"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingBottom="16dp" />
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/notification_list"
|
||||
android:background="@drawable/rounded_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="true"
|
||||
@@ -191,13 +191,13 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textAppearance="@style/TextAppearance.HomepageCardTitle"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingBottom="16dp" />
|
||||
<LinearLayout
|
||||
android:id="@+id/apps"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_bg">
|
||||
android:orientation="vertical">
|
||||
<!-- app based recycler views added here -->
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
android:id="@+id/app_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="18dp"
|
||||
android:paddingStart="16dp"
|
||||
@@ -84,15 +86,12 @@
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/notification_list_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<com.android.settings.notification.history.NotificationHistoryRecyclerView
|
||||
android:id="@+id/notification_list"
|
||||
|
||||
@@ -17,17 +17,22 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="vertical"
|
||||
android:foreground="?android:attr/selectableItemBackground">
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="@*android:dimen/status_bar_icon_size"
|
||||
android:layout_marginStart="54dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
@@ -36,7 +41,6 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="32dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
|
||||
@@ -77,11 +81,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left|center_vertical"
|
||||
android:ellipsize="end"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginStart="54dp"
|
||||
android:maxLines="7"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification"
|
||||
android:textAlignment="viewStart" />
|
||||
android:textAlignment="viewStart"
|
||||
android:paddingBottom="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -22,8 +22,10 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
@@ -128,11 +130,5 @@
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/listDivider" />
|
||||
</LinearLayout>
|
||||
@@ -527,6 +527,10 @@
|
||||
<string name="top_intro_region_title">The region you choose affects how your phone displays time, dates, temperature, and more</string>
|
||||
<!-- Category for more language settings. [CHAR LIMIT=NONE]-->
|
||||
<string name="more_language_settings_category">More language settings</string>
|
||||
<!-- Title for asking to change system locale region or not. [CHAR LIMIT=50]-->
|
||||
<string name="title_change_system_locale_region">Change region to %s ?</string>
|
||||
<!-- Message for asking to change system locale region or not. [CHAR LIMIT=50]-->
|
||||
<string name="body_change_system_locale_region">Your device will keep %s as a system language</string>
|
||||
|
||||
<!-- Regional Preferences begin -->
|
||||
<!-- The title of the menu entry of regional preferences. [CHAR LIMIT=50] -->
|
||||
@@ -11326,11 +11330,11 @@
|
||||
<!-- Double Tap Power Gesture camera launch action title [CHAR_LIMIT=60] -->
|
||||
<string name="double_tap_power_camera_action_title">Camera</string>
|
||||
<!-- Setting summary to describe double tap power button will open camera. [CHAR LIMIT=NONE] -->
|
||||
<string name="double_tap_power_camera_action_summary">Access Camera</string>
|
||||
<string name="double_tap_power_camera_action_summary">Open Camera</string>
|
||||
<!-- Double Tap Power Gesture wallet launch action title [CHAR_LIMIT=60] -->
|
||||
<string name="double_tap_power_wallet_action_title">Wallet</string>
|
||||
<!-- Setting summary to describe double tap power button will open wallet. [CHAR LIMIT=NONE] -->
|
||||
<string name="double_tap_power_wallet_action_summary">Access Wallet</string>
|
||||
<string name="double_tap_power_wallet_action_summary">Open Wallet</string>
|
||||
|
||||
<!-- Title text for double twist for camera mode [CHAR LIMIT=60]-->
|
||||
<string name="double_twist_for_camera_mode_title">Flip camera for selfie</string>
|
||||
|
||||
54
src/com/android/settings/Metrics.kt
Normal file
54
src/com/android/settings/Metrics.kt
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings
|
||||
|
||||
import android.content.Context
|
||||
import com.android.settings.overlay.FeatureFactory
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider
|
||||
import com.android.settingslib.metadata.PreferenceUiActionMetricsLogger
|
||||
import com.android.settingslib.metadata.PreferenceMetadata
|
||||
import com.android.settingslib.metadata.PreferenceScreenMetadata
|
||||
|
||||
/** Provides metrics for preference action. */
|
||||
interface PreferenceActionMetricsProvider {
|
||||
|
||||
/** Metrics action id for the preference. */
|
||||
val preferenceActionMetrics: Int
|
||||
}
|
||||
|
||||
/** [PreferenceUiActionMetricsLogger] of Settings app. */
|
||||
class SettingsMetricsLogger
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
private val context: Context,
|
||||
private val metricsFeatureProvider: MetricsFeatureProvider =
|
||||
FeatureFactory.featureFactory.metricsFeatureProvider,
|
||||
) : PreferenceUiActionMetricsLogger {
|
||||
|
||||
override fun logPreferenceValueChange(
|
||||
screen: PreferenceScreenMetadata,
|
||||
preference: PreferenceMetadata,
|
||||
value: Any?,
|
||||
) {
|
||||
if (preference !is PreferenceActionMetricsProvider) return
|
||||
when (value) {
|
||||
is Boolean ->
|
||||
metricsFeatureProvider.action(context, preference.preferenceActionMetrics, value)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,8 @@ public class SettingsApplication extends Application {
|
||||
if (Flags.catalyst()) {
|
||||
PreferenceScreenRegistry.INSTANCE.setPreferenceScreenMetadataFactories(
|
||||
preferenceScreenFactories());
|
||||
PreferenceScreenRegistry.INSTANCE.setPreferenceUiActionMetricsLogger(
|
||||
new SettingsMetricsLogger(this));
|
||||
PreferenceBindingFactory.setDefaultFactory(new SettingsPreferenceBindingFactory());
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ public class BluetoothDetailsAudioSharingController extends BluetoothDetailsCont
|
||||
mProfilesContainer.removeAll();
|
||||
mProfilesContainer.addPreference(createAudioSharingPreference());
|
||||
if ((BluetoothUtils.isActiveLeAudioDevice(mCachedDevice)
|
||||
|| AudioStreamsHelper.hasConnectedBroadcastSource(
|
||||
|| AudioStreamsHelper.hasBroadcastSource(
|
||||
mCachedDevice, mLocalBluetoothManager))
|
||||
&& !BluetoothUtils.isBroadcasting(mLocalBluetoothManager)) {
|
||||
mProfilesContainer.addPreference(createFindAudioStreamPreference());
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothLeBroadcastAssistant;
|
||||
@@ -42,7 +46,6 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
import com.android.settingslib.widget.ActionButtonsPreference;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@@ -75,20 +78,19 @@ public class AudioStreamButtonController extends BasePreferenceController
|
||||
int sourceId,
|
||||
BluetoothLeBroadcastReceiveState state) {
|
||||
super.onReceiveStateChanged(sink, sourceId, state);
|
||||
boolean shouldUpdateButton =
|
||||
BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)
|
||||
? AudioStreamsHelper.hasSourcePresent(state)
|
||||
: AudioStreamsHelper.isConnected(state);
|
||||
var localSourceState = getLocalSourceState(state);
|
||||
boolean shouldUpdateButton = mHysteresisModeFixAvailable
|
||||
? (localSourceState == PAUSED || localSourceState == STREAMING)
|
||||
: localSourceState == STREAMING;
|
||||
if (shouldUpdateButton) {
|
||||
updateButton();
|
||||
if (AudioStreamsHelper.isConnected(state)) {
|
||||
// TODO(b/308368124): Verify if this log is too noisy.
|
||||
mMetricsFeatureProvider.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
|
||||
SOURCE_ORIGIN_REPOSITORY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSourceAddFailed(
|
||||
@@ -113,6 +115,7 @@ public class AudioStreamButtonController extends BasePreferenceController
|
||||
private final AudioStreamsHelper mAudioStreamsHelper;
|
||||
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
|
||||
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
||||
private final boolean mHysteresisModeFixAvailable;
|
||||
private @Nullable ActionButtonsPreference mPreference;
|
||||
private int mBroadcastId = -1;
|
||||
|
||||
@@ -121,6 +124,8 @@ public class AudioStreamButtonController extends BasePreferenceController
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
|
||||
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
|
||||
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
|
||||
context);
|
||||
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||
}
|
||||
|
||||
@@ -155,14 +160,8 @@ public class AudioStreamButtonController extends BasePreferenceController
|
||||
return;
|
||||
}
|
||||
|
||||
List<BluetoothLeBroadcastReceiveState> sources =
|
||||
BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)
|
||||
? mAudioStreamsHelper.getAllPresentSources()
|
||||
: mAudioStreamsHelper.getAllConnectedSources();
|
||||
boolean isConnected =
|
||||
sources.stream()
|
||||
.map(BluetoothLeBroadcastReceiveState::getBroadcastId)
|
||||
.anyMatch(connectedBroadcastId -> connectedBroadcastId == mBroadcastId);
|
||||
boolean isConnected = mAudioStreamsHelper.getConnectedBroadcastIdAndState(
|
||||
mHysteresisModeFixAvailable).containsKey(mBroadcastId);
|
||||
|
||||
View.OnClickListener onClickListener;
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothLeBroadcastAssistant;
|
||||
@@ -61,6 +62,7 @@ public class AudioStreamHeaderController extends BasePreferenceController
|
||||
private final Executor mExecutor;
|
||||
private final AudioStreamsHelper mAudioStreamsHelper;
|
||||
@Nullable private final LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
|
||||
private final boolean mHysteresisModeFixAvailable;
|
||||
|
||||
@VisibleForTesting
|
||||
final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
|
||||
@@ -83,13 +85,13 @@ public class AudioStreamHeaderController extends BasePreferenceController
|
||||
int sourceId,
|
||||
BluetoothLeBroadcastReceiveState state) {
|
||||
super.onReceiveStateChanged(sink, sourceId, state);
|
||||
if (AudioStreamsHelper.isConnected(state)) {
|
||||
var localSourceState = getLocalSourceState(state);
|
||||
if (localSourceState == STREAMING) {
|
||||
updateSummary();
|
||||
mAudioStreamsHelper.startMediaService(
|
||||
mContext, mBroadcastId, mBroadcastName);
|
||||
} else if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)
|
||||
&& AudioStreamsHelper.hasSourcePresent(state)) {
|
||||
// if source present but not connected, only update the summary
|
||||
} else if (mHysteresisModeFixAvailable && localSourceState == PAUSED) {
|
||||
// if source paused, only update the summary
|
||||
updateSummary();
|
||||
}
|
||||
}
|
||||
@@ -105,6 +107,8 @@ public class AudioStreamHeaderController extends BasePreferenceController
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
|
||||
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
|
||||
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
|
||||
context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,38 +155,9 @@ public class AudioStreamHeaderController extends BasePreferenceController
|
||||
var unused =
|
||||
ThreadUtils.postOnBackgroundThread(
|
||||
() -> {
|
||||
var connectedSourceList =
|
||||
mAudioStreamsHelper.getAllPresentSources().stream()
|
||||
.filter(
|
||||
state ->
|
||||
(state.getBroadcastId()
|
||||
== mBroadcastId))
|
||||
.collect(toList());
|
||||
|
||||
var latestSummary =
|
||||
audioSharingHysteresisModeFix()
|
||||
? connectedSourceList.isEmpty()
|
||||
? AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY
|
||||
: (connectedSourceList.stream()
|
||||
.anyMatch(
|
||||
AudioStreamsHelper
|
||||
::isConnected)
|
||||
? mContext.getString(
|
||||
AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
|
||||
: mContext.getString(
|
||||
AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY))
|
||||
: mAudioStreamsHelper.getAllConnectedSources().stream()
|
||||
.map(
|
||||
BluetoothLeBroadcastReceiveState
|
||||
::getBroadcastId)
|
||||
.anyMatch(
|
||||
connectedBroadcastId ->
|
||||
connectedBroadcastId
|
||||
== mBroadcastId)
|
||||
? mContext.getString(
|
||||
AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
|
||||
: AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
|
||||
|
||||
var sourceState = mAudioStreamsHelper.getConnectedBroadcastIdAndState(
|
||||
mHysteresisModeFixAvailable).get(mBroadcastId);
|
||||
var latestSummary = getLatestSummary(sourceState);
|
||||
ThreadUtils.postOnMainThread(
|
||||
() -> {
|
||||
if (mHeaderController != null) {
|
||||
@@ -212,4 +187,16 @@ public class AudioStreamHeaderController extends BasePreferenceController
|
||||
mBroadcastName = broadcastName;
|
||||
mBroadcastId = broadcastId;
|
||||
}
|
||||
|
||||
private String getLatestSummary(@Nullable LocalBluetoothLeBroadcastSourceState state) {
|
||||
if (state == null) {
|
||||
return AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
|
||||
}
|
||||
if (mHysteresisModeFixAvailable) {
|
||||
return state == STREAMING
|
||||
? mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
|
||||
: mContext.getString(AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY);
|
||||
}
|
||||
return mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -25,7 +23,6 @@ import android.app.Service;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothVolumeControl;
|
||||
import android.content.Intent;
|
||||
@@ -107,6 +104,7 @@ public class AudioStreamMediaService extends Service {
|
||||
// override this value. Otherwise, we raise the volume to 25 when the play button is clicked.
|
||||
private final AtomicInteger mLatestPositiveVolume = new AtomicInteger(25);
|
||||
private final Object mLocalSessionLock = new Object();
|
||||
private boolean mHysteresisModeFixAvailable;
|
||||
private int mBroadcastId;
|
||||
@Nullable private List<BluetoothDevice> mDevices;
|
||||
@Nullable private LocalBluetoothManager mLocalBtManager;
|
||||
@@ -139,6 +137,7 @@ public class AudioStreamMediaService extends Service {
|
||||
Log.w(TAG, "onCreate() : mLeBroadcastAssistant is null!");
|
||||
return;
|
||||
}
|
||||
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(this);
|
||||
|
||||
mNotificationManager = getSystemService(NotificationManager.class);
|
||||
if (mNotificationManager == null) {
|
||||
@@ -309,13 +308,9 @@ public class AudioStreamMediaService extends Service {
|
||||
}
|
||||
|
||||
private void handleRemoveSource() {
|
||||
List<BluetoothLeBroadcastReceiveState> connected =
|
||||
mAudioStreamsHelper == null
|
||||
? emptyList()
|
||||
: mAudioStreamsHelper.getAllConnectedSources();
|
||||
if (connected.stream()
|
||||
.map(BluetoothLeBroadcastReceiveState::getBroadcastId)
|
||||
.noneMatch(id -> id == mBroadcastId)) {
|
||||
if (mAudioStreamsHelper != null
|
||||
&& !mAudioStreamsHelper.getConnectedBroadcastIdAndState(
|
||||
mHysteresisModeFixAvailable).containsKey(mBroadcastId)) {
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_TITLE;
|
||||
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES;
|
||||
import static com.android.settingslib.bluetooth.BluetoothUtils.isAudioSharingHysteresisModeFixAvailable;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
@@ -32,6 +38,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
@@ -67,12 +74,6 @@ public class AudioStreamsHelper {
|
||||
|
||||
private final @Nullable LocalBluetoothManager mBluetoothManager;
|
||||
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
|
||||
// Referring to Broadcast Audio Scan Service 1.0
|
||||
// Table 3.9: Broadcast Receive State characteristic format
|
||||
// 0x00000000: 0b0 = Not synchronized to BIS_index[x]
|
||||
// 0xFFFFFFFF: Failed to sync to BIG
|
||||
private static final long BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L;
|
||||
private static final long BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL;
|
||||
|
||||
AudioStreamsHelper(@Nullable LocalBluetoothManager bluetoothManager) {
|
||||
mBluetoothManager = bluetoothManager;
|
||||
@@ -141,16 +142,31 @@ public class AudioStreamsHelper {
|
||||
});
|
||||
}
|
||||
|
||||
/** Retrieves a list of all LE broadcast receive states from active sinks. */
|
||||
public List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() {
|
||||
if (mLeBroadcastAssistant == null) {
|
||||
Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!");
|
||||
return emptyList();
|
||||
/**
|
||||
* Gets a map of connected broadcast IDs to their corresponding local broadcast source states.
|
||||
*
|
||||
* <p>If multiple sources have the same broadcast ID, the state of the source that is
|
||||
* {@code STREAMING} is preferred.
|
||||
*/
|
||||
public Map<Integer, LocalBluetoothLeBroadcastSourceState> getConnectedBroadcastIdAndState(
|
||||
boolean hysteresisModeFixAvailable) {
|
||||
if (mBluetoothManager == null || mLeBroadcastAssistant == null) {
|
||||
Log.w(TAG,
|
||||
"getConnectedBroadcastIdAndState(): BluetoothManager or LeBroadcastAssistant "
|
||||
+ "is null!");
|
||||
return emptyMap();
|
||||
}
|
||||
return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream()
|
||||
.flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
|
||||
.filter(AudioStreamsHelper::isConnected)
|
||||
.toList();
|
||||
.map(state -> new Pair<>(state.getBroadcastId(), getLocalSourceState(state)))
|
||||
.filter(pair -> pair.second == STREAMING
|
||||
|| (hysteresisModeFixAvailable && pair.second == PAUSED))
|
||||
.collect(toMap(
|
||||
p -> p.first,
|
||||
p -> p.second,
|
||||
(existingState, newState) -> existingState == STREAMING ? existingState
|
||||
: newState
|
||||
));
|
||||
}
|
||||
|
||||
/** Retrieves a list of all LE broadcast receive states keyed by each active device. */
|
||||
@@ -163,47 +179,12 @@ 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<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
|
||||
if (mLeBroadcastAssistant == null) {
|
||||
Log.w(TAG, "getAllPresentSources(): LeBroadcastAssistant is null!");
|
||||
return emptyList();
|
||||
}
|
||||
return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream()
|
||||
.flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
|
||||
.filter(AudioStreamsHelper::hasSourcePresent)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/** Retrieves LocalBluetoothLeBroadcastAssistant. */
|
||||
@Nullable
|
||||
public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
|
||||
return mLeBroadcastAssistant;
|
||||
}
|
||||
|
||||
/** Checks the connectivity status based on the provided broadcast receive state. */
|
||||
public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
|
||||
return state.getBisSyncState().stream()
|
||||
.anyMatch(
|
||||
bitmap ->
|
||||
(bitmap != BIS_SYNC_NOT_SYNC_TO_BIS
|
||||
&& bitmap != BIS_SYNC_FAILED_SYNC_TO_BIG));
|
||||
}
|
||||
|
||||
/** Checks the connectivity status based on the provided broadcast receive state. */
|
||||
public static boolean hasSourcePresent(BluetoothLeBroadcastReceiveState state) {
|
||||
// Referring to Broadcast Audio Scan Service 1.0
|
||||
// All zero address means no source on the sink device
|
||||
return !state.getSourceDevice().getAddress().equals("00:00:00:00:00:00");
|
||||
}
|
||||
|
||||
static boolean isBadCode(BluetoothLeBroadcastReceiveState state) {
|
||||
return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
|
||||
&& state.getBigEncryptionState()
|
||||
== BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code CachedBluetoothDevice} that is either connected to a broadcast source or is
|
||||
* a connected LE device.
|
||||
@@ -226,7 +207,7 @@ 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 localBtManager The BT manager to provide BT functions.
|
||||
* @return Whether the device has connected to a broadcast source.
|
||||
* @return Whether the device has a broadcast source.
|
||||
*/
|
||||
public static boolean hasConnectedBroadcastSource(
|
||||
public static boolean hasBroadcastSource(
|
||||
CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
|
||||
if (localBtManager == null) {
|
||||
Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null");
|
||||
Log.d(TAG, "Skip check hasBroadcastSource due to bt manager is null");
|
||||
return false;
|
||||
}
|
||||
LocalBluetoothLeBroadcastAssistant assistant =
|
||||
localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
|
||||
if (assistant == null) {
|
||||
Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null");
|
||||
Log.d(TAG, "Skip check hasBroadcastSource due to assistant profile is null");
|
||||
return false;
|
||||
}
|
||||
List<BluetoothLeBroadcastReceiveState> sourceList =
|
||||
assistant.getAllSources(cachedDevice.getDevice());
|
||||
if (!sourceList.isEmpty()
|
||||
&& (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
|
||||
localBtManager.getContext())
|
||||
|| sourceList.stream().anyMatch(AudioStreamsHelper::isConnected))) {
|
||||
boolean hysteresisModeFixAvailable = isAudioSharingHysteresisModeFixAvailable(
|
||||
localBtManager.getContext());
|
||||
if (hasReceiveState(sourceList, hysteresisModeFixAvailable)) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"Lead device has connected broadcast source, device = "
|
||||
"Lead device has broadcast source, device = "
|
||||
+ cachedDevice.getDevice().getAnonymizedAddress());
|
||||
return true;
|
||||
}
|
||||
@@ -297,13 +278,10 @@ public class AudioStreamsHelper {
|
||||
for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
|
||||
List<BluetoothLeBroadcastReceiveState> list =
|
||||
assistant.getAllSources(device.getDevice());
|
||||
if (!list.isEmpty()
|
||||
&& (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
|
||||
localBtManager.getContext())
|
||||
|| list.stream().anyMatch(AudioStreamsHelper::isConnected))) {
|
||||
if (hasReceiveState(list, hysteresisModeFixAvailable)) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"Member device has connected broadcast source, device = "
|
||||
"Member device has broadcast source, device = "
|
||||
+ device.getDevice().getAnonymizedAddress());
|
||||
return true;
|
||||
}
|
||||
@@ -311,6 +289,18 @@ public class AudioStreamsHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasReceiveState(List<BluetoothLeBroadcastReceiveState> states,
|
||||
boolean hysteresisModeFixAvailable) {
|
||||
return states.stream().anyMatch(state -> {
|
||||
var localSourceState = getLocalSourceState(state);
|
||||
if (hysteresisModeFixAvailable) {
|
||||
return localSourceState == STREAMING || localSourceState == DECRYPTION_FAILED
|
||||
|| localSourceState == PAUSED;
|
||||
}
|
||||
return localSourceState == STREAMING || localSourceState == DECRYPTION_FAILED;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of connected Bluetooth devices that belongs to one {@link
|
||||
* CachedBluetoothDevice} that's either connected to a broadcast source or is a connected LE
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Integer, AudioStreamPreference> 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,7 +269,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
||||
// change it's state.
|
||||
existingPreference.setAudioStreamMetadata(source);
|
||||
if (fromState != AudioStreamState.SOURCE_ADDED
|
||||
&& (!isAudioSharingHysteresisModeFixAvailable(mContext)
|
||||
&& (!mHysteresisModeFixAvailable
|
||||
|| fromState != AudioStreamState.SOURCE_PRESENT)) {
|
||||
Log.w(
|
||||
TAG,
|
||||
@@ -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<BluetoothLeBroadcastMetadata> metadata =
|
||||
getMetadataMatchingByBroadcastId(
|
||||
device, receiveState.getSourceId(), broadcastIdConnected);
|
||||
device, receiveState.getSourceId(), broadcastIdStreaming);
|
||||
handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState);
|
||||
mBroadcastIdToPreferenceMap.compute(
|
||||
broadcastIdConnected,
|
||||
broadcastIdStreaming,
|
||||
(k, existingPreference) -> {
|
||||
if (existingPreference == null) {
|
||||
// No existing preference for this source even if it's already connected,
|
||||
// No existing preference for this source even if it's already streaming,
|
||||
// add one and set initial state to SOURCE_ADDED. This could happen because
|
||||
// we retrieves the connected source during onStart() from
|
||||
// AudioStreamsHelper#getAllConnectedSources() even before the source is
|
||||
// we retrieves the streaming source during onStart() from
|
||||
// AudioStreamsHelper#getAllStreamingSources() even before the source is
|
||||
// founded by scanning.
|
||||
return metadata.isPresent()
|
||||
? addNewPreference(
|
||||
@@ -440,7 +447,7 @@ 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<BluetoothLeBroadcastMetadata> metadata =
|
||||
getMetadataMatchingByBroadcastId(
|
||||
device, receiveState.getSourceId(), broadcastIdConnected);
|
||||
device, receiveState.getSourceId(), broadcastIdPaused);
|
||||
handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState);
|
||||
mBroadcastIdToPreferenceMap.compute(
|
||||
broadcastIdConnected,
|
||||
broadcastIdPaused,
|
||||
(k, existingPreference) -> {
|
||||
if (existingPreference == null) {
|
||||
// No existing preference for this source even if it's already connected,
|
||||
// add one and set initial state to SOURCE_PRESENT. This could happen
|
||||
// because
|
||||
// we retrieves the connected source during onStart() from
|
||||
// AudioStreamsHelper#getAllPresentSources() even before the source is
|
||||
// No existing preference for this source even if it's already existed but
|
||||
// currently paused, add one and set initial state to SOURCE_PRESENT. This
|
||||
// could happen because we retrieves the paused source during onStart() from
|
||||
// AudioStreamsHelper#getAllPausedSources() even before the source is
|
||||
// founded by scanning.
|
||||
return metadata.isPresent()
|
||||
? addNewPreference(
|
||||
@@ -580,56 +586,43 @@ 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<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources =
|
||||
mAudioStreamsHelper.getAllSourcesByDevice();
|
||||
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> connectedSources =
|
||||
getConnectedSources(sources);
|
||||
if (isAudioSharingHysteresisModeFixAvailable(mContext)) {
|
||||
// With hysteresis mode, we prioritize showing connected sources first.
|
||||
// If no connected sources are found, we then show present sources.
|
||||
if (!connectedSources.isEmpty()) {
|
||||
connectedSources.forEach(
|
||||
getStreamSourcesByDevice(sources).forEach(
|
||||
(device, stateList) ->
|
||||
stateList.forEach(
|
||||
state -> handleSourceConnected(device, state)));
|
||||
} else {
|
||||
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>>
|
||||
presentSources = getPresentSources(sources);
|
||||
presentSources.forEach(
|
||||
state -> handleSourceStreaming(device, state)));
|
||||
if (mHysteresisModeFixAvailable) {
|
||||
getPausedSourcesByDevice(sources).forEach(
|
||||
(device, stateList) ->
|
||||
stateList.forEach(
|
||||
state -> handleSourcePresent(device, state)));
|
||||
}
|
||||
} else {
|
||||
connectedSources.forEach(
|
||||
(device, stateList) ->
|
||||
stateList.forEach(
|
||||
state -> handleSourceConnected(device, state)));
|
||||
state -> handleSourcePaused(device, state)));
|
||||
}
|
||||
mLeBroadcastAssistant.startSearchingForSources(emptyList());
|
||||
mMediaControlHelper.start();
|
||||
});
|
||||
}
|
||||
|
||||
private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getConnectedSources(
|
||||
private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getStreamSourcesByDevice(
|
||||
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) {
|
||||
return sources.entrySet().stream()
|
||||
.filter(
|
||||
entry ->
|
||||
entry.getValue().stream().anyMatch(AudioStreamsHelper::isConnected))
|
||||
entry.getValue().stream().anyMatch(
|
||||
state -> getLocalSourceState(state) == STREAMING))
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPresentSources(
|
||||
private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPausedSourcesByDevice(
|
||||
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) {
|
||||
return sources.entrySet().stream()
|
||||
.filter(
|
||||
entry ->
|
||||
entry.getValue().stream()
|
||||
.anyMatch(AudioStreamsHelper::hasSourcePresent))
|
||||
.anyMatch(state -> getLocalSourceState(state) == PAUSED))
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
@@ -742,8 +735,4 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isAudioSharingHysteresisModeFixAvailable(Context context) {
|
||||
return BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 extends Preference> 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;
|
||||
|
||||
@@ -415,7 +415,6 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
|
||||
removeControllersForHybridMode();
|
||||
}
|
||||
setPreferenceScreen(screen);
|
||||
updateActivityTitleWithScreenTitle(screen);
|
||||
} else {
|
||||
addPreferencesFromResource(resId);
|
||||
screen = getPreferenceScreen();
|
||||
|
||||
@@ -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,15 +95,10 @@ 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<PreferenceScreenBinding>.bind(preference, metadata)
|
||||
else -> super<PrimarySwitchPreferenceBinding>.bind(preference, metadata)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<PreferenceScreenBinding>.bind(preference, metadata)
|
||||
else -> super<PrimarySwitchPreferenceBinding>.bind(preference, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.localepicker;
|
||||
|
||||
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_APPS_LOCALE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.internal.app.AppLocaleCollector;
|
||||
import com.android.internal.app.LocaleStore;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.applications.manageapplications.ManageApplicationsUtil;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.widget.SelectorWithWidgetPreference;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** A controller for handling suggested locale of app. */
|
||||
public class AppLocaleSuggestedListPreferenceController extends
|
||||
BasePreferenceController implements LocaleListSearchCallback {
|
||||
private static final String TAG = "AppLocaleSuggestedListPreferenceController";
|
||||
private static final String KEY_PREFERENCE_CATEGORY_APP_LANGUAGE_SUGGESTED =
|
||||
"app_language_suggested_category";
|
||||
private static final String KEY_PREFERENCE_APP_LOCALE_SUGGESTED_LIST =
|
||||
"app_locale_suggested_list";
|
||||
private static final String KEY_PREFERENCE_CATEGORY_ADD_A_LANGUAGE_SUGGESTED =
|
||||
"system_language_suggested_category";
|
||||
|
||||
@SuppressWarnings("NullAway")
|
||||
private PreferenceCategory mPreferenceCategory;
|
||||
private Set<LocaleStore.LocaleInfo> mLocaleList;
|
||||
private List<LocaleStore.LocaleInfo> mLocaleOptions;
|
||||
private Map<String, Preference> mSuggestedPreferences;
|
||||
private boolean mIsCountryMode;
|
||||
@Nullable private LocaleStore.LocaleInfo mParentLocale;
|
||||
private AppLocaleCollector mAppLocaleCollector;
|
||||
@SuppressWarnings("NullAway")
|
||||
private String mPackageName;
|
||||
private boolean mIsNumberingSystemMode;
|
||||
|
||||
@SuppressWarnings("NullAway")
|
||||
public AppLocaleSuggestedListPreferenceController(@NonNull Context context,
|
||||
@NonNull String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullAway")
|
||||
public AppLocaleSuggestedListPreferenceController(@NonNull Context context,
|
||||
@NonNull String preferenceKey, @Nullable String packageName,
|
||||
boolean isNumberingSystemMode, @NonNull LocaleStore.LocaleInfo parentLocale) {
|
||||
super(context, preferenceKey);
|
||||
mPackageName = packageName;
|
||||
mIsNumberingSystemMode = isNumberingSystemMode;
|
||||
mParentLocale = parentLocale;
|
||||
mIsCountryMode = mParentLocale != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
mPreferenceCategory = screen.findPreference(
|
||||
(mIsNumberingSystemMode || mIsCountryMode)
|
||||
? KEY_PREFERENCE_CATEGORY_ADD_A_LANGUAGE_SUGGESTED
|
||||
: KEY_PREFERENCE_CATEGORY_APP_LANGUAGE_SUGGESTED);
|
||||
|
||||
mAppLocaleCollector = new AppLocaleCollector(mContext, mPackageName);
|
||||
mSuggestedPreferences = new ArrayMap<>();
|
||||
mLocaleOptions = new ArrayList<>();
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
private void updatePreferences() {
|
||||
if (mPreferenceCategory == null) {
|
||||
Log.d(TAG, "updatePreferences, mPreferenceCategory is null");
|
||||
return;
|
||||
}
|
||||
|
||||
List<LocaleStore.LocaleInfo> result = LocaleUtils.getSortedLocaleList(
|
||||
getSuggestedLocaleList(), mIsCountryMode);
|
||||
final Map<String, Preference> existingSuggestedPreferences = mSuggestedPreferences;
|
||||
mSuggestedPreferences = new ArrayMap<>();
|
||||
setupSuggestedPreference(result, existingSuggestedPreferences);
|
||||
for (Preference pref : existingSuggestedPreferences.values()) {
|
||||
mPreferenceCategory.removePreference(pref);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchListChanged(@NonNull List<LocaleStore.LocaleInfo> newList,
|
||||
@Nullable CharSequence prefix) {
|
||||
if (mPreferenceCategory == null) {
|
||||
Log.d(TAG, "onSearchListChanged, mPreferenceCategory is null");
|
||||
return;
|
||||
}
|
||||
|
||||
mPreferenceCategory.removeAll();
|
||||
final Map<String, Preference> existingSuggestedPreferences = mSuggestedPreferences;
|
||||
List<LocaleStore.LocaleInfo> sortedList = getSuggestedLocaleList();
|
||||
newList = LocaleUtils.getSortedLocaleFromSearchList(newList, sortedList, mIsCountryMode);
|
||||
setupSuggestedPreference(newList, existingSuggestedPreferences);
|
||||
}
|
||||
|
||||
private void setupSuggestedPreference(List<LocaleStore.LocaleInfo> localeInfoList,
|
||||
Map<String, Preference> existingSuggestedPreferences) {
|
||||
for (LocaleStore.LocaleInfo locale : localeInfoList) {
|
||||
if (mIsNumberingSystemMode || mIsCountryMode) {
|
||||
Preference pref = existingSuggestedPreferences.remove(locale.getId());
|
||||
if (pref == null) {
|
||||
pref = new Preference(mContext);
|
||||
setupPreference(pref, locale);
|
||||
mPreferenceCategory.addPreference(pref);
|
||||
}
|
||||
} else {
|
||||
SelectorWithWidgetPreference pref =
|
||||
(SelectorWithWidgetPreference) existingSuggestedPreferences.remove(
|
||||
locale.getId());
|
||||
if (pref == null) {
|
||||
pref = new SelectorWithWidgetPreference(mContext);
|
||||
setupPreference(pref, locale);
|
||||
mPreferenceCategory.addPreference(pref);
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "setupSuggestedPreference, mPreferenceCategory setVisible"
|
||||
+ (mPreferenceCategory.getPreferenceCount() > 0));
|
||||
mPreferenceCategory.setVisible(mPreferenceCategory.getPreferenceCount() > 0);
|
||||
}
|
||||
|
||||
private void setupPreference(Preference pref, LocaleStore.LocaleInfo locale) {
|
||||
String localeName = mIsCountryMode ? locale.getFullCountryNameNative()
|
||||
: locale.getFullNameNative();
|
||||
if (pref instanceof SelectorWithWidgetPreference) {
|
||||
((SelectorWithWidgetPreference) pref).setChecked(locale.isAppCurrentLocale());
|
||||
}
|
||||
pref.setTitle(locale.isSystemLocale()
|
||||
? mContext.getString(R.string.preference_of_system_locale_summary)
|
||||
: localeName);
|
||||
pref.setKey(locale.toString());
|
||||
pref.setOnPreferenceClickListener(clickedPref -> {
|
||||
LocaleUtils.onLocaleSelected(mContext, locale, mPackageName);
|
||||
((Activity) mContext).finish();
|
||||
return true;
|
||||
});
|
||||
mSuggestedPreferences.put(locale.getId(), pref);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
|
||||
protected List<LocaleStore.LocaleInfo> getSuggestedLocaleList() {
|
||||
setupLocaleList();
|
||||
if (mLocaleList != null && !mLocaleList.isEmpty()) {
|
||||
mLocaleOptions.addAll(
|
||||
mLocaleList.stream().filter(localeInfo -> (localeInfo.isSuggested())).collect(
|
||||
Collectors.toList()));
|
||||
} else {
|
||||
Log.d(TAG, "Can not get suggested locales because the locale list is null or empty.");
|
||||
}
|
||||
return mLocaleOptions;
|
||||
}
|
||||
|
||||
private void setupLocaleList() {
|
||||
mLocaleList = mAppLocaleCollector.getSupportedLocaleList(mParentLocale,
|
||||
false, mIsCountryMode);
|
||||
mLocaleOptions.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getPreferenceKey() {
|
||||
return KEY_PREFERENCE_APP_LOCALE_SUGGESTED_LIST;
|
||||
}
|
||||
}
|
||||
@@ -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<LocaleStore.LocaleInfo> getSortedLocaleList(
|
||||
@NonNull List<LocaleStore.LocaleInfo> localeInfos, boolean isCountryMode) {
|
||||
final Locale sortingLocale = Locale.getDefault();
|
||||
final LocaleHelper.LocaleInfoComparator comp = new LocaleHelper.LocaleInfoComparator(
|
||||
sortingLocale, isCountryMode);
|
||||
Collections.sort(localeInfos, comp);
|
||||
return localeInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the locale's list by keywords in search.
|
||||
*
|
||||
* @param searchList locale Infos in search bar
|
||||
* @param localeList list of locale Infos
|
||||
* @param isCountryMode Whether the locale page is in country mode or not.
|
||||
* @return localeInfos list of locale Infos
|
||||
*/
|
||||
public static @NonNull List<LocaleStore.LocaleInfo> getSortedLocaleFromSearchList(
|
||||
@NonNull List<LocaleStore.LocaleInfo> searchList,
|
||||
@NonNull List<LocaleStore.LocaleInfo> localeList,
|
||||
boolean isCountryMode) {
|
||||
List<LocaleStore.LocaleInfo> searchItem = localeList.stream()
|
||||
.filter(suggested -> searchList.stream()
|
||||
.anyMatch(option -> option.getLocale() != null
|
||||
&& option.getLocale().getLanguage().equals(
|
||||
suggested.getLocale().getLanguage())))
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
return getSortedLocaleList(searchItem, isCountryMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
|
||||
if (value is Boolean) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFirstObserverAdded() {
|
||||
|
||||
@@ -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.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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ public abstract class RadioButtonPickerFragment extends SettingsPreferenceFragme
|
||||
if (isCatalystEnabled()) {
|
||||
PreferenceScreen preferenceScreen = createPreferenceScreen();
|
||||
setPreferenceScreen(preferenceScreen);
|
||||
updateActivityTitleWithScreenTitle(preferenceScreen);
|
||||
} else {
|
||||
super.onCreatePreferences(savedInstanceState, rootKey);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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<Long> bisSyncState = new ArrayList<>();
|
||||
bisSyncState.add(1L);
|
||||
when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
|
||||
when(mLocalManager
|
||||
.getProfileManager()
|
||||
.getLeAudioBroadcastAssistantProfile()
|
||||
|
||||
@@ -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<Long> 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<Long> bisSyncState = new ArrayList<>();
|
||||
when(state.getBisSyncState()).thenReturn(bisSyncState);
|
||||
when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(List.of(state));
|
||||
|
||||
when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
|
||||
.thenReturn(Map.of(BROADCAST_ID, PAUSED));
|
||||
// Create new controller to enable hysteresis mode
|
||||
mController = new AudioStreamButtonController(mContext, KEY);
|
||||
mController.init(BROADCAST_ID);
|
||||
mController.displayPreference(mScreen);
|
||||
mController.mBroadcastAssistantCallback.onReceiveStateChanged(
|
||||
mock(BluetoothDevice.class), /* sourceId= */ 0, state);
|
||||
|
||||
verify(mFeatureFactory.metricsFeatureProvider, never())
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(any(), eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED), anyInt());
|
||||
|
||||
// Called twice, once in displayPreference, the other one in callback
|
||||
@@ -302,7 +308,8 @@ 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);
|
||||
|
||||
@@ -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<Long> 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<Long> 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);
|
||||
|
||||
@@ -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<BluetoothDevice> devices = new ArrayList<>();
|
||||
devices.add(mDevice);
|
||||
when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
|
||||
BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class);
|
||||
when(source.getBroadcastId()).thenReturn(BROADCAST_ID_2);
|
||||
when(source.getSourceDevice()).thenReturn(mSourceDevice);
|
||||
when(mSourceDevice.getAddress()).thenReturn(address);
|
||||
when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice);
|
||||
when(mCachedDevice.getDevice()).thenReturn(mDevice);
|
||||
when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID);
|
||||
@@ -214,15 +219,16 @@ 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<BluetoothDevice> devices = new ArrayList<>();
|
||||
devices.add(mDevice);
|
||||
when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
|
||||
@@ -234,14 +240,15 @@ public class AudioStreamsHelperTest {
|
||||
List<Long> bisSyncState = new ArrayList<>();
|
||||
bisSyncState.add(1L);
|
||||
when(source.getBisSyncState()).thenReturn(bisSyncState);
|
||||
when(source.getBroadcastId()).thenReturn(BROADCAST_ID_1);
|
||||
|
||||
var list = mHelper.getAllConnectedSources();
|
||||
assertThat(list).isNotEmpty();
|
||||
assertThat(list.get(0)).isEqualTo(source);
|
||||
var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ false);
|
||||
assertThat(map).isNotEmpty();
|
||||
assertThat(map.get(BROADCAST_ID_1)).isEqualTo(STREAMING);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllPresentSources_noSource() {
|
||||
public void getConnectedBroadcastIdAndState_noSource() {
|
||||
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
|
||||
|
||||
@@ -259,12 +266,12 @@ 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<Long> bisSyncState = new ArrayList<>();
|
||||
when(source.getBisSyncState()).thenReturn(bisSyncState);
|
||||
when(source.getBroadcastId()).thenReturn(BROADCAST_ID_1);
|
||||
|
||||
var list = mHelper.getAllPresentSources();
|
||||
assertThat(list).isNotEmpty();
|
||||
assertThat(list.get(0)).isEqualTo(source);
|
||||
var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ true);
|
||||
assertThat(map).isNotEmpty();
|
||||
assertThat(map.get(BROADCAST_ID_1)).isEqualTo(PAUSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<AudioStreamPreference> preference =
|
||||
|
||||
@@ -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<BluetoothLeBroadcastReceiveState> getAllConnectedSources() {
|
||||
return sMockHelper.getAllConnectedSources();
|
||||
public Map<Integer, LocalBluetoothLeBroadcastSourceState> getConnectedBroadcastIdAndState(
|
||||
boolean hysteresisModeFixAvailable) {
|
||||
return sMockHelper.getConnectedBroadcastIdAndState(hysteresisModeFixAvailable);
|
||||
}
|
||||
|
||||
@Implementation
|
||||
@@ -66,11 +69,6 @@ public class ShadowAudioStreamsHelper {
|
||||
return sMockHelper.getAllSourcesByDevice();
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
|
||||
return sMockHelper.getAllPresentSources();
|
||||
}
|
||||
|
||||
/** Gets {@link CachedBluetoothDevice} in sharing or le connected */
|
||||
@Implementation
|
||||
public static Optional<CachedBluetoothDevice> getCachedBluetoothDeviceInSharingOrLeConnected(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Resources>()
|
||||
private val mockPackageManager = mock<PackageManager>()
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user