Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3e097b8..bc57322 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1262,6 +1262,8 @@
<string name="private_space_face_settings_require_attention_details">To unlock private space, your eyes must be open. For best results, take off sunglasses.</string>
<!-- Text shown in face settings in private space explaining what your face can be used for. [CHAR LIMIT=NONE] -->
<string name="private_space_face_settings_footer">Use your face to unlock your private space.\n\nKeep in mind:\nYou can only have one face set up at a time. To add another face, delete the current one.\n\nLooking at the phone can unlock it when you don\u2019t intend to.\n\nYour private space can be unlocked by someone else if your device is held up to your face.\n\nYour private space can be unlocked by someone who looks a lot like you, like an identical sibling.</string>
+ <!-- Text shown on the details of a toggle which enables/disables confirmation (i.e. confirm button) each time face unlock is used for private space. [CHAR LIMIT=90] -->
+ <string name="private_space_face_settings_require_confirmation_details">When using Face Unlock, always require confirmation step</string>
<!-- Biometric category title - biometric options for unlocking the device. [CHAR LIMIT=50] -->
<string name="private_space_category_ways_to_unlock">Ways to unlock</string>
<!-- Summary for one lock when device screen lock is used as private profile lock. [CHAR LIMIT=40] -->
@@ -6480,7 +6482,7 @@
<!-- Message when there are no available trust agents to display -->
<string name="no_trust_agents">No available trust agents</string>
- <!-- Title for screen to add a device admin app [CHAR LIMIT=40] -->
+ <!-- Title for screen to add a device admin app [CHAR LIMIT=50] -->
<string name="add_device_admin_msg">Activate device admin app?</string>
<!-- Label for button to set the active device admin [CHAR_LIMIT=80] -->
<string name="add_device_admin">Activate this device admin app</string>
@@ -7718,7 +7720,7 @@
<string name="keywords_wallpaper">background, theme, grid, customize, personalize</string>
<!-- List of synonyms for the Styles picker setting, used to match in settings search [CHAR LIMIT=NONE] -->
- <string name="keywords_styles">icon, accent, color, home screen, lock screen, shortcut, clock size</string>
+ <string name="keywords_styles">icon, icons, accent, color, home screen, lock screen, shortcut, clock size</string>
<!-- List of synonyms for the Default Assist and Voice input setting, used to match in settings search [CHAR LIMIT=NONE] -->
<string name="keywords_assist_input">default, assistant</string>
@@ -8018,6 +8020,7 @@
}
</string>
+
<!-- Do not disturb: restrict notifications settings title [CHAR LIMIT=80] -->
<string name="zen_mode_restrict_notifications_title">Display options for filtered
notifications</string>
@@ -9173,6 +9176,17 @@
<!-- [CHAR LIMIT=50] Zen mode settings: Events (ie: calendar events) category in a list of sounds when events is the first or only element in the list. For example “Events can interrupt" or “Events and touch sounds can interrupt" -->
<string name="zen_mode_events_list_first">Events</string>
+ <!-- [CHAR LIMIT=80] Zen mode settings: Title for page to select which apps allowed to interrupt dnd -->
+ <string name="zen_mode_apps_title">Apps</string>
+ <!-- [CHAR LIMIT=100] Zen mode settings: subtitle for page to select which apps allowed to interrupt dnd -->
+ <string name="zen_mode_apps_category">Apps that can interrupt</string>
+ <!-- [CHAR LIMIT=60] Zen mode settings: selected apps will be able to bypass dnd -->
+ <string name="zen_mode_apps_priority_apps">Selected apps</string>
+ <!-- [CHAR LIMIT=60] Zen mode settings: no apps will be able to bypass dnd -->
+ <string name="zen_mode_apps_none_apps">None</string>
+ <!-- [CHAR LIMIT=60] Zen mode settings: all apps will be able to bypass dnd -->
+ <string name="zen_mode_apps_all_apps">All</string>
+
<!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND -->
<string name="zen_mode_bypassing_apps">Allow apps to override</string>
<!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND header -->
diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml
index f1ff977..df56095 100644
--- a/res/xml/modes_rule_settings.xml
+++ b/res/xml/modes_rule_settings.xml
@@ -35,6 +35,10 @@
android:title="@string/zen_category_people"/>
<Preference
+ android:key="zen_mode_apps"
+ android:title="@string/zen_category_apps" />
+
+ <Preference
android:key="zen_other_settings"
android:title="@string/zen_category_exceptions" />
</PreferenceCategory>
diff --git a/res/xml/zen_mode_apps_settings.xml b/res/xml/zen_mode_apps_settings.xml
new file mode 100644
index 0000000..4ee14e4
--- /dev/null
+++ b/res/xml/zen_mode_apps_settings.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/zen_mode_apps_title" >
+
+ <PreferenceCategory
+ android:key="zen_mode_apps_category"
+ android:title="@string/zen_mode_apps_category">
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="zen_mode_apps_priority"
+ android:title="@string/zen_mode_apps_priority_apps"
+ settings:searchable="false"/>
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="zen_mode_apps_none"
+ android:title="@string/zen_mode_apps_none_apps"
+ settings:searchable="false"/>
+
+ <com.android.settingslib.widget.SelectorWithWidgetPreference
+ android:key="zen_mode_apps_all"
+ android:title="@string/zen_mode_apps_all_apps"
+ settings:searchable="false"/>
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/zen_mode_select_bypassing_apps.xml b/res/xml/zen_mode_select_bypassing_apps.xml
new file mode 100644
index 0000000..7b5a84d
--- /dev/null
+++ b/res/xml/zen_mode_select_bypassing_apps.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/zen_mode_bypassing_apps_title">
+
+ <PreferenceCategory
+ android:key="zen_mode_bypassing_apps_list"
+ android:title="@string/zen_mode_bypassing_apps_header">
+ <!-- apps that have notifications that can bypass DND are added here -->
+ </PreferenceCategory>
+
+ <Preference
+ android:key="zen_mode_bypassing_apps_add"
+ android:title="@string/zen_mode_bypassing_apps_add"
+ android:icon="@drawable/ic_add_24dp"
+ settings:allowDividerAbove="true" />
+
+ <com.android.settingslib.widget.FooterPreference
+ android:title="@string/zen_mode_bypassing_apps_footer" />
+</PreferenceScreen>
diff --git a/src/com/android/settings/MainClearConfirm.java b/src/com/android/settings/MainClearConfirm.java
index b5ddd1a..042c5c2 100644
--- a/src/com/android/settings/MainClearConfirm.java
+++ b/src/com/android/settings/MainClearConfirm.java
@@ -151,15 +151,20 @@
if (pdbManager == null) {
return false;
}
+
// The persistent data block will persist if the device is still being provisioned.
if (isDeviceStillBeingProvisioned()) {
return false;
}
- // If OEM unlock is allowed, the persistent data block will be wiped during FR
- // process. If disabled, it will be wiped here instead.
- if (isOemUnlockedAllowed()) {
+
+ // If OEM unlock is allowed, the persistent data block will be wiped during the FR
+ // process on devices without FRP Hardening. If disabled, it will be wiped here instead.
+ // On devices with FRP Hardening, the persistent data block should always be wiped,
+ // regardless of the OEM Unlocking state.
+ if (!android.security.Flags.frpEnforcement() && isOemUnlockedAllowed()) {
return false;
}
+
final DevicePolicyManager dpm = (DevicePolicyManager) getActivity()
.getSystemService(Context.DEVICE_POLICY_SERVICE);
// Do not erase the factory reset protection data (from Settings) if factory reset
@@ -167,6 +172,7 @@
if (!dpm.isFactoryResetProtectionPolicySupported()) {
return false;
}
+
// Do not erase the factory reset protection data (from Settings) if the
// device is an organization-owned managed profile device and a factory
// reset protection policy has been set.
@@ -175,6 +181,7 @@
&& frpPolicy.isNotEmpty()) {
return false;
}
+
return true;
}
diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
index cad2186..0f551b0 100644
--- a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
@@ -97,13 +97,12 @@
@Override
protected ComponentName getTileComponentName() {
- // Don't have quick settings tile for now.
- return null;
+ return AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME;
}
@Override
protected CharSequence getTileTooltipContent(int type) {
- // Don't have quick settings tile for now.
+ // No tooltip to be shown
return null;
}
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index 2295dee..4e77243 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -210,7 +210,6 @@
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
initializeAllPreferences();
- updateAllPreferences();
registerContentMonitors();
registerInputDeviceListener();
}
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java
index f71e2fe..d820533 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java
@@ -26,6 +26,7 @@
import androidx.preference.Preference;
+import com.android.settings.R;
import com.android.settings.Utils;
import java.util.List;
@@ -76,6 +77,11 @@
preference.setEnabled(false);
} else {
preference.setEnabled(true);
+ // Update summary for private space face settings toggle
+ if (Utils.isPrivateProfile(getUserId(), mContext)) {
+ preference.setSummary(mContext.getString(
+ R.string.private_space_face_settings_require_confirmation_details));
+ }
}
}
diff --git a/src/com/android/settings/deviceinfo/simstatus/ImsRegistrationStateController.kt b/src/com/android/settings/deviceinfo/simstatus/ImsRegistrationStateController.kt
deleted file mode 100644
index 3d17ec0..0000000
--- a/src/com/android/settings/deviceinfo/simstatus/ImsRegistrationStateController.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.deviceinfo.simstatus
-
-import android.content.Context
-import android.telephony.SubscriptionManager
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.settings.network.telephony.SimSlotRepository
-import com.android.settings.network.telephony.ims.ImsMmTelRepository
-import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
-
-@OptIn(ExperimentalCoroutinesApi::class)
-class ImsRegistrationStateController @JvmOverloads constructor(
- private val context: Context,
- private val simSlotRepository: SimSlotRepository = SimSlotRepository(context),
- private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId ->
- ImsMmTelRepositoryImpl(context, subId)
- },
-) {
- fun collectImsRegistered(
- lifecycleOwner: LifecycleOwner,
- simSlotIndex: Int,
- action: (imsRegistered: Boolean) -> Unit,
- ) {
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- imsRegisteredFlow(simSlotIndex).collect(action)
- }
- }
- }
-
- private fun imsRegisteredFlow(simSlotIndex: Int): Flow<Boolean> =
- simSlotRepository.subIdInSimSlotFlow(simSlotIndex)
- .flatMapLatest { subId ->
- if (SubscriptionManager.isValidSubscriptionId(subId)) {
- imsMmTelRepositoryFactory(subId).imsRegisteredFlow()
- } else {
- flowOf(false)
- }
- }
- .conflate()
- .flowOn(Dispatchers.Default)
-}
diff --git a/src/com/android/settings/deviceinfo/simstatus/SignalStrengthRepository.kt b/src/com/android/settings/deviceinfo/simstatus/SignalStrengthRepository.kt
new file mode 100644
index 0000000..8b062e7
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/simstatus/SignalStrengthRepository.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.simstatus
+
+import android.content.Context
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyCallback
+import android.util.Log
+import com.android.settings.R
+import com.android.settings.network.telephony.serviceStateFlow
+import com.android.settings.network.telephony.telephonyCallbackFlow
+import com.android.settingslib.Utils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class SignalStrengthRepository(
+ private val context: Context,
+ private val serviceStateFlowFactory: (subId: Int) -> Flow<ServiceState> = {
+ context.serviceStateFlow(it)
+ },
+) {
+ fun signalStrengthDisplayFlow(subId: Int): Flow<String> =
+ serviceStateFlowFactory(subId).flatMapLatest { serviceState ->
+ if (Utils.isInService(serviceState)) {
+ signalStrengthFlow(subId).map { it.displayString() }
+ } else {
+ flowOf("0")
+ }
+ }.conflate().flowOn(Dispatchers.Default)
+
+ /** Creates an instance of a cold Flow for [SignalStrength] of given [subId]. */
+ private fun signalStrengthFlow(subId: Int): Flow<SignalStrength> =
+ context.telephonyCallbackFlow(subId) {
+ object : TelephonyCallback(), TelephonyCallback.SignalStrengthsListener {
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ trySend(signalStrength)
+ val cellSignalStrengths = signalStrength.cellSignalStrengths
+ Log.d(TAG, "[$subId] onSignalStrengthsChanged: $cellSignalStrengths")
+ }
+ }
+ }
+
+ private fun SignalStrength.displayString() =
+ context.getString(R.string.sim_signal_strength, signalDbm(), signalAsu())
+
+ private companion object {
+ private const val TAG = "SignalStrengthRepo"
+
+
+ private fun SignalStrength.signalDbm(): Int =
+ cellSignalStrengths.firstOrNull { it.dbm != -1 }?.dbm ?: 0
+
+ private fun SignalStrength.signalAsu(): Int =
+ cellSignalStrengths.firstOrNull { it.asuLevel != -1 }?.asuLevel ?: 0
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java
index e5882dd..b5ee1d8 100644
--- a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java
+++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java
@@ -32,10 +32,8 @@
import android.telephony.CarrierConfigManager;
import android.telephony.CellBroadcastIntents;
import android.telephony.CellBroadcastService;
-import android.telephony.CellSignalStrength;
import android.telephony.ICellBroadcastService;
import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
@@ -113,7 +111,6 @@
private SubscriptionInfo mSubscriptionInfo;
private TelephonyDisplayInfo mTelephonyDisplayInfo;
- private ServiceState mPreviousServiceState;
private final int mSlotIndex;
private TelephonyManager mTelephonyManager;
@@ -219,15 +216,12 @@
// getServiceState() may return null when the subscription is inactive
// or when there was an error communicating with the phone process.
final ServiceState serviceState = getTelephonyManager().getServiceState();
- final SignalStrength signalStrength = getTelephonyManager().getSignalStrength();
updatePhoneNumber();
updateServiceState(serviceState);
- updateSignalStrength(signalStrength);
updateNetworkType();
updateRoamingStatus(serviceState);
updateIccidNumber();
- updateImsRegistrationState();
}
/**
@@ -257,7 +251,7 @@
.registerTelephonyCallback(mContext.getMainExecutor(), mTelephonyCallback);
mSubscriptionManager.addOnSubscriptionsChangedListener(
mContext.getMainExecutor(), mOnSubscriptionsChangedListener);
- collectImsRegistered(owner);
+ collectSimStatusDialogInfo(owner);
if (mShowLatestAreaInfo) {
updateAreaInfoText();
@@ -420,12 +414,6 @@
private void updateServiceState(ServiceState serviceState) {
final int state = Utils.getCombinedServiceState(serviceState);
- if (!Utils.isInService(serviceState)) {
- resetSignalStrength();
- } else if (!Utils.isInService(mPreviousServiceState)) {
- // If ServiceState changed from out of service -> in service, update signal strength.
- updateSignalStrength(getTelephonyManager().getSignalStrength());
- }
String serviceStateValue;
@@ -450,49 +438,11 @@
mDialog.setText(SERVICE_STATE_VALUE_ID, serviceStateValue);
}
- private void updateSignalStrength(SignalStrength signalStrength) {
- if (signalStrength == null) {
- return;
- }
- // by default we show the signal strength
- boolean showSignalStrength = true;
- if (mSubscriptionInfo != null) {
- final int subscriptionId = mSubscriptionInfo.getSubscriptionId();
- final PersistableBundle carrierConfig =
- mCarrierConfigManager.getConfigForSubId(subscriptionId);
- if (carrierConfig != null) {
- showSignalStrength = carrierConfig.getBoolean(
- CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL);
- }
- }
- if (!showSignalStrength) {
- mDialog.removeSettingFromScreen(SIGNAL_STRENGTH_LABEL_ID);
- mDialog.removeSettingFromScreen(SIGNAL_STRENGTH_VALUE_ID);
- return;
- }
-
- ServiceState serviceState = getTelephonyManager().getServiceState();
- if (!Utils.isInService(serviceState)) {
- return;
- }
-
- int signalDbm = getDbm(signalStrength);
- int signalAsu = getAsuLevel(signalStrength);
-
- if (signalDbm == -1) {
- signalDbm = 0;
- }
-
- if (signalAsu == -1) {
- signalAsu = 0;
- }
-
- mDialog.setText(SIGNAL_STRENGTH_VALUE_ID, mRes.getString(R.string.sim_signal_strength,
- signalDbm, signalAsu));
- }
-
- private void resetSignalStrength() {
- mDialog.setText(SIGNAL_STRENGTH_VALUE_ID, "0");
+ private void updateSignalStrength(@Nullable String signalStrength) {
+ boolean isVisible = signalStrength != null;
+ mDialog.setSettingVisibility(SIGNAL_STRENGTH_LABEL_ID, isVisible);
+ mDialog.setSettingVisibility(SIGNAL_STRENGTH_VALUE_ID, isVisible);
+ mDialog.setText(SIGNAL_STRENGTH_VALUE_ID, signalStrength);
}
private void updateNetworkType() {
@@ -581,39 +531,21 @@
}
}
- private boolean isImsRegistrationStateShowUp() {
- if (mSubscriptionInfo == null) {
- return false;
- }
- final int subscriptionId = mSubscriptionInfo.getSubscriptionId();
- final PersistableBundle carrierConfig =
- mCarrierConfigManager.getConfigForSubId(subscriptionId);
- return carrierConfig == null ? false :
- carrierConfig.getBoolean(
- CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL);
+ private void updateImsRegistrationState(@Nullable Boolean imsRegistered) {
+ boolean isVisible = imsRegistered != null;
+ mDialog.setSettingVisibility(IMS_REGISTRATION_STATE_LABEL_ID, isVisible);
+ mDialog.setSettingVisibility(IMS_REGISTRATION_STATE_VALUE_ID, isVisible);
+ int stringId = Boolean.TRUE.equals(imsRegistered)
+ ? com.android.settingslib.R.string.ims_reg_status_registered
+ : com.android.settingslib.R.string.ims_reg_status_not_registered;
+ mDialog.setText(IMS_REGISTRATION_STATE_VALUE_ID, mRes.getString(stringId));
}
- private void updateImsRegistrationState() {
- if (isImsRegistrationStateShowUp()) {
- return;
- }
- mDialog.removeSettingFromScreen(IMS_REGISTRATION_STATE_LABEL_ID);
- mDialog.removeSettingFromScreen(IMS_REGISTRATION_STATE_VALUE_ID);
- }
-
- private void collectImsRegistered(@NonNull LifecycleOwner owner) {
- if (!isImsRegistrationStateShowUp()) {
- return;
- }
- new ImsRegistrationStateController(mContext).collectImsRegistered(
- owner, mSlotIndex, (Boolean imsRegistered) -> {
- if (imsRegistered) {
- mDialog.setText(IMS_REGISTRATION_STATE_VALUE_ID, mRes.getString(
- com.android.settingslib.R.string.ims_reg_status_registered));
- } else {
- mDialog.setText(IMS_REGISTRATION_STATE_VALUE_ID, mRes.getString(
- com.android.settingslib.R.string.ims_reg_status_not_registered));
- }
+ private void collectSimStatusDialogInfo(@NonNull LifecycleOwner owner) {
+ new SimStatusDialogRepository(mContext).collectSimStatusDialogInfo(
+ owner, mSlotIndex, (simStatusDialogInfo) -> {
+ updateSignalStrength(simStatusDialogInfo.getSignalStrength());
+ updateImsRegistrationState(simStatusDialogInfo.getImsRegistered());
return Unit.INSTANCE;
}
);
@@ -623,44 +555,9 @@
return SubscriptionManager.from(mContext).getActiveSubscriptionInfoForSimSlotIndex(slotId);
}
- private int getDbm(SignalStrength signalStrength) {
- List<CellSignalStrength> cellSignalStrengthList = signalStrength.getCellSignalStrengths();
- int dbm = -1;
- if (cellSignalStrengthList == null) {
- return dbm;
- }
-
- for (CellSignalStrength cell : cellSignalStrengthList) {
- if (cell.getDbm() != -1) {
- dbm = cell.getDbm();
- break;
- }
- }
-
- return dbm;
- }
-
- private int getAsuLevel(SignalStrength signalStrength) {
- List<CellSignalStrength> cellSignalStrengthList = signalStrength.getCellSignalStrengths();
- int asu = -1;
- if (cellSignalStrengthList == null) {
- return asu;
- }
-
- for (CellSignalStrength cell : cellSignalStrengthList) {
- if (cell.getAsuLevel() != -1) {
- asu = cell.getAsuLevel();
- break;
- }
- }
-
- return asu;
- }
-
@VisibleForTesting
class SimStatusDialogTelephonyCallback extends TelephonyCallback implements
TelephonyCallback.DataConnectionStateListener,
- TelephonyCallback.SignalStrengthsListener,
TelephonyCallback.ServiceStateListener,
TelephonyCallback.DisplayInfoListener {
@Override
@@ -670,16 +567,10 @@
}
@Override
- public void onSignalStrengthsChanged(SignalStrength signalStrength) {
- updateSignalStrength(signalStrength);
- }
-
- @Override
public void onServiceStateChanged(ServiceState serviceState) {
updateNetworkProvider();
updateServiceState(serviceState);
updateRoamingStatus(serviceState);
- mPreviousServiceState = serviceState;
}
@Override
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogFragment.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogFragment.java
index f212eea..c51417c 100644
--- a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogFragment.java
+++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogFragment.java
@@ -26,6 +26,7 @@
import android.view.WindowManager;
import android.widget.TextView;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@@ -91,6 +92,13 @@
super.onDestroy();
}
+ public void setSettingVisibility(int viewId, boolean isVisible) {
+ final View view = mRootView.findViewById(viewId);
+ if (view != null) {
+ view.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ }
+ }
+
public void removeSettingFromScreen(int viewId) {
final View view = mRootView.findViewById(viewId);
if (view != null) {
@@ -106,7 +114,7 @@
SimStatusDialogController.PHONE_NUMBER_VALUE_ID)
.sorted().toArray();
- public void setText(int viewId, CharSequence text) {
+ public void setText(int viewId, @Nullable CharSequence text) {
if (!isAdded()) {
Log.d(TAG, "Fragment not attached yet.");
return;
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepository.kt b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepository.kt
new file mode 100644
index 0000000..5ed6993
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepository.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.simstatus
+
+import android.content.Context
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settings.network.telephony.SimSlotRepository
+import com.android.settings.network.telephony.ims.ImsMmTelRepository
+import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
+import com.android.settings.network.telephony.safeGetConfig
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class SimStatusDialogRepository @JvmOverloads constructor(
+ private val context: Context,
+ private val simSlotRepository: SimSlotRepository = SimSlotRepository(context),
+ private val signalStrengthRepository: SignalStrengthRepository =
+ SignalStrengthRepository(context),
+ private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId ->
+ ImsMmTelRepositoryImpl(context, subId)
+ },
+) {
+ private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
+
+ data class SimStatusDialogInfo(
+ val signalStrength: String? = null,
+ val imsRegistered: Boolean? = null,
+ )
+
+ private data class SimStatusDialogVisibility(
+ val signalStrengthShowUp: Boolean,
+ val imsRegisteredShowUp: Boolean,
+ )
+
+ fun collectSimStatusDialogInfo(
+ lifecycleOwner: LifecycleOwner,
+ simSlotIndex: Int,
+ action: (info: SimStatusDialogInfo) -> Unit,
+ ) {
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ simStatusDialogInfoBySlotFlow(simSlotIndex).collect(action)
+ }
+ }
+ }
+
+ private fun simStatusDialogInfoBySlotFlow(simSlotIndex: Int): Flow<SimStatusDialogInfo> =
+ simSlotRepository.subIdInSimSlotFlow(simSlotIndex)
+ .flatMapLatest { subId ->
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ simStatusDialogInfoFlow(subId)
+ } else {
+ flowOf(SimStatusDialogInfo())
+ }
+ }
+ .conflate()
+ .flowOn(Dispatchers.Default)
+
+ private fun simStatusDialogInfoFlow(subId: Int): Flow<SimStatusDialogInfo> =
+ showUpFlow(subId).flatMapLatest { visibility ->
+ combine(
+ if (visibility.signalStrengthShowUp) {
+ signalStrengthRepository.signalStrengthDisplayFlow(subId)
+ } else flowOf(null),
+ if (visibility.imsRegisteredShowUp) {
+ imsMmTelRepositoryFactory(subId).imsRegisteredFlow()
+ } else flowOf(null),
+ ) { signalStrength, imsRegistered ->
+ SimStatusDialogInfo(signalStrength = signalStrength, imsRegistered = imsRegistered)
+ }
+ }
+
+ private fun showUpFlow(subId: Int) = flow {
+ val config = carrierConfigManager.safeGetConfig(
+ keys = listOf(
+ CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL,
+ CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL,
+ ),
+ subId = subId,
+ )
+ val visibility = SimStatusDialogVisibility(
+ signalStrengthShowUp = config.getBoolean(
+ CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL,
+ true, // by default we show the signal strength in sim status
+ ),
+ imsRegisteredShowUp = config.getBoolean(
+ CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL
+ ),
+ )
+ emit(visibility)
+ }
+}
diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java
index 6642861..99441b0 100644
--- a/src/com/android/settings/homepage/TopLevelSettings.java
+++ b/src/com/android/settings/homepage/TopLevelSettings.java
@@ -33,7 +33,9 @@
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import androidx.window.embedding.ActivityEmbeddingController;
@@ -210,6 +212,9 @@
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
+ if (Flags.homepageRevamp()) {
+ return;
+ }
int tintColor = Utils.getHomepageIconColor(getContext());
iteratePreferences(preference -> {
Drawable icon = preference.getIcon();
@@ -364,13 +369,17 @@
}
job.init();
- int count = screen.getPreferenceCount();
+ iteratePreferences(screen, job);
+ }
+
+ private void iteratePreferences(PreferenceGroup group, PreferenceJob job) {
+ int count = group.getPreferenceCount();
for (int i = 0; i < count; i++) {
- Preference preference = screen.getPreference(i);
- if (preference == null) {
- break;
- }
+ Preference preference = group.getPreference(i);
job.doForEach(preference);
+ if (preference instanceof PreferenceCategory) {
+ iteratePreferences((PreferenceCategory) preference, job);
+ }
}
}
diff --git a/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt b/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt
index b421185..05b4c07 100644
--- a/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt
+++ b/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt
@@ -36,4 +36,7 @@
// Settings should not assume Carrier config loader (and any other system services as well) are
// always available. If not available, use default value instead.
persistableBundleOf()
+} catch (e: RuntimeException) {
+ // The reason is same with above.
+ persistableBundleOf()
}
diff --git a/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java
new file mode 100644
index 0000000..ccd35ec
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import android.app.Application;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.text.BidiFormatter;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.notification.NotificationBackend;
+import com.android.settings.notification.app.AppChannelsBypassingDndSettings;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.utils.ThreadUtils;
+import com.android.settingslib.widget.AppPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * When clicked, populates the PreferenceScreen with apps that aren't already bypassing DND. The
+ * user can click on these Preferences to allow notification channels from the app to bypass DND.
+ */
+public class ZenModeAddBypassingAppsPreferenceController extends AbstractPreferenceController
+ implements PreferenceControllerMixin {
+
+ public static final String KEY_NO_APPS = "add_none";
+ private static final String KEY = "zen_mode_non_bypassing_apps_list";
+ private static final String KEY_ADD = "zen_mode_bypassing_apps_add";
+ @Nullable private final NotificationBackend mNotificationBackend;
+
+ @Nullable @VisibleForTesting ApplicationsState mApplicationsState;
+ @VisibleForTesting PreferenceScreen mPreferenceScreen;
+ @VisibleForTesting PreferenceCategory mPreferenceCategory;
+ @VisibleForTesting Context mPrefContext;
+
+ private Preference mAddPreference;
+ private ApplicationsState.Session mAppSession;
+ @Nullable private Fragment mHostFragment;
+
+ public ZenModeAddBypassingAppsPreferenceController(Context context, @Nullable Application app,
+ @Nullable Fragment host, @Nullable NotificationBackend notificationBackend) {
+ this(context, app == null ? null : ApplicationsState.getInstance(app), host,
+ notificationBackend);
+ }
+
+ private ZenModeAddBypassingAppsPreferenceController(Context context,
+ @Nullable ApplicationsState appState, @Nullable Fragment host,
+ @Nullable NotificationBackend notificationBackend) {
+ super(context);
+ mNotificationBackend = notificationBackend;
+ mApplicationsState = appState;
+ mHostFragment = host;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ mPreferenceScreen = screen;
+ mAddPreference = screen.findPreference(KEY_ADD);
+ mAddPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ mAddPreference.setVisible(false);
+ if (mApplicationsState != null && mHostFragment != null) {
+ mAppSession = mApplicationsState.newSession(mAppSessionCallbacks,
+ mHostFragment.getLifecycle());
+ }
+ return true;
+ }
+ });
+ mPrefContext = screen.getContext();
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ /**
+ * Call this method to trigger the app list to refresh.
+ */
+ public void updateAppList() {
+ if (mAppSession == null) {
+ return;
+ }
+
+ ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
+ ? ApplicationsState.FILTER_ENABLED_NOT_QUIET
+ : ApplicationsState.FILTER_ALL_ENABLED;
+ mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR);
+ }
+
+ // Set the icon for the given preference to the entry icon from cache if available, or look
+ // it up.
+ private void updateIcon(Preference pref, ApplicationsState.AppEntry entry) {
+ synchronized (entry) {
+ final Drawable cachedIcon = AppUtils.getIconFromCache(entry);
+ if (cachedIcon != null && entry.mounted) {
+ pref.setIcon(cachedIcon);
+ } else {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ final Drawable icon = AppUtils.getIcon(mPrefContext, entry);
+ if (icon != null) {
+ ThreadUtils.postOnMainThread(() -> pref.setIcon(icon));
+ }
+ });
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void updateAppList(List<ApplicationsState.AppEntry> apps) {
+ if (apps == null) {
+ return;
+ }
+
+ if (mPreferenceCategory == null) {
+ mPreferenceCategory = new PreferenceCategory(mPrefContext);
+ mPreferenceCategory.setTitle(R.string.zen_mode_bypassing_apps_add_header);
+ mPreferenceScreen.addPreference(mPreferenceCategory);
+ }
+
+ boolean doAnyAppsPassCriteria = false;
+ for (ApplicationsState.AppEntry app : apps) {
+ String pkg = app.info.packageName;
+ final String key = getKey(pkg, app.info.uid);
+ final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid);
+ final int appChannelsBypassingDnd = mNotificationBackend
+ .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
+ if (appChannelsBypassingDnd == 0 && appChannels > 0) {
+ doAnyAppsPassCriteria = true;
+ }
+
+ Preference pref = mPreferenceCategory.findPreference(key);
+
+ if (pref == null) {
+ if (appChannelsBypassingDnd == 0 && appChannels > 0) {
+ // does not exist but should
+ pref = new AppPreference(mPrefContext);
+ pref.setKey(key);
+ pref.setOnPreferenceClickListener(preference -> {
+ Bundle args = new Bundle();
+ args.putString(AppInfoBase.ARG_PACKAGE_NAME, app.info.packageName);
+ args.putInt(AppInfoBase.ARG_PACKAGE_UID, app.info.uid);
+ new SubSettingLauncher(mContext)
+ .setDestination(AppChannelsBypassingDndSettings.class.getName())
+ .setArguments(args)
+ .setResultListener(mHostFragment, 0)
+ .setUserHandle(new UserHandle(UserHandle.getUserId(app.info.uid)))
+ .setSourceMetricsCategory(
+ SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
+ .launch();
+ return true;
+ });
+ pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label));
+ updateIcon(pref, app);
+ mPreferenceCategory.addPreference(pref);
+ }
+ } else if (appChannelsBypassingDnd != 0 || appChannels == 0) {
+ // exists but shouldn't anymore
+ mPreferenceCategory.removePreference(pref);
+ }
+ }
+
+ Preference pref = mPreferenceCategory.findPreference(KEY_NO_APPS);
+ if (!doAnyAppsPassCriteria) {
+ if (pref == null) {
+ pref = new Preference(mPrefContext);
+ pref.setKey(KEY_NO_APPS);
+ pref.setTitle(R.string.zen_mode_bypassing_apps_none);
+ }
+ mPreferenceCategory.addPreference(pref);
+ } else if (pref != null) {
+ mPreferenceCategory.removePreference(pref);
+ }
+ }
+
+ static String getKey(String pkg, int uid) {
+ return "add|" + pkg + "|" + uid;
+ }
+
+ private final ApplicationsState.Callbacks mAppSessionCallbacks =
+ new ApplicationsState.Callbacks() {
+
+ @Override
+ public void onRunningStateChanged(boolean running) {
+
+ }
+
+ @Override
+ public void onPackageListChanged() {
+
+ }
+
+ @Override
+ public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
+ updateAppList(apps);
+ }
+
+ @Override
+ public void onPackageIconChanged() {
+ updateAppList();
+ }
+
+ @Override
+ public void onPackageSizeChanged(String packageName) {
+
+ }
+
+ @Override
+ public void onAllSizesComputed() { }
+
+ @Override
+ public void onLauncherInfoChanged() {
+
+ }
+
+ @Override
+ public void onLoadEntriesCompleted() {
+ updateAppList();
+ }
+ };
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceController.java
new file mode 100644
index 0000000..922ac5e
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceController.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import android.app.Application;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.text.BidiFormatter;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.notification.NotificationBackend;
+import com.android.settings.notification.app.AppChannelsBypassingDndSettings;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.utils.ThreadUtils;
+import com.android.settingslib.widget.AppPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adds a preference to the PreferenceScreen for each notification channel that can bypass DND.
+ */
+public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController
+ implements PreferenceControllerMixin {
+ public static final String KEY_NO_APPS = "all_none";
+ private static final String KEY = "zen_mode_bypassing_apps_list";
+
+ @Nullable private final NotificationBackend mNotificationBackend;
+
+ @Nullable @VisibleForTesting ApplicationsState mApplicationsState;
+ @VisibleForTesting PreferenceCategory mPreferenceCategory;
+ @VisibleForTesting Context mPrefContext;
+
+ private ApplicationsState.Session mAppSession;
+ @Nullable private Fragment mHostFragment;
+
+ public ZenModeAllBypassingAppsPreferenceController(Context context, @Nullable Application app,
+ @Nullable Fragment host, @Nullable NotificationBackend notificationBackend) {
+ this(context, app == null ? null : ApplicationsState.getInstance(app), host,
+ notificationBackend);
+ }
+
+ private ZenModeAllBypassingAppsPreferenceController(Context context,
+ @Nullable ApplicationsState appState, @Nullable Fragment host,
+ @Nullable NotificationBackend notificationBackend) {
+ super(context);
+ mNotificationBackend = notificationBackend;
+ mApplicationsState = appState;
+ mHostFragment = host;
+
+ if (mApplicationsState != null && host != null) {
+ mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, host.getLifecycle());
+ }
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ mPreferenceCategory = screen.findPreference(KEY);
+ mPrefContext = screen.getContext();
+ updateAppList();
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ /**
+ * Call this method to trigger the app list to refresh.
+ */
+ public void updateAppList() {
+ if (mAppSession == null) {
+ return;
+ }
+
+ ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
+ ? ApplicationsState.FILTER_ENABLED_NOT_QUIET
+ : ApplicationsState.FILTER_ALL_ENABLED;
+ mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR);
+ }
+
+ // Set the icon for the given preference to the entry icon from cache if available, or look
+ // it up.
+ private void updateIcon(Preference pref, ApplicationsState.AppEntry entry) {
+ synchronized (entry) {
+ final Drawable cachedIcon = AppUtils.getIconFromCache(entry);
+ if (cachedIcon != null && entry.mounted) {
+ pref.setIcon(cachedIcon);
+ } else {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ final Drawable icon = AppUtils.getIcon(mPrefContext, entry);
+ if (icon != null) {
+ ThreadUtils.postOnMainThread(() -> pref.setIcon(icon));
+ }
+ });
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void updateAppList(List<ApplicationsState.AppEntry> apps) {
+ if (mPreferenceCategory == null || apps == null) {
+ return;
+ }
+
+ boolean doAnyAppsPassCriteria = false;
+ for (ApplicationsState.AppEntry app : apps) {
+ String pkg = app.info.packageName;
+ final String key = getKey(pkg, app.info.uid);
+ final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid);
+ final int appChannelsBypassingDnd = mNotificationBackend
+ .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
+ if (appChannelsBypassingDnd > 0) {
+ doAnyAppsPassCriteria = true;
+ }
+
+ Preference pref = mPreferenceCategory.findPreference(key);
+ if (pref == null) {
+ if (appChannelsBypassingDnd > 0) {
+ // does not exist but should
+ pref = new AppPreference(mPrefContext);
+ pref.setKey(key);
+ pref.setOnPreferenceClickListener(preference -> {
+ Bundle args = new Bundle();
+ args.putString(AppInfoBase.ARG_PACKAGE_NAME, app.info.packageName);
+ args.putInt(AppInfoBase.ARG_PACKAGE_UID, app.info.uid);
+ new SubSettingLauncher(mContext)
+ .setDestination(AppChannelsBypassingDndSettings.class.getName())
+ .setArguments(args)
+ .setUserHandle(UserHandle.getUserHandleForUid(app.info.uid))
+ .setResultListener(mHostFragment, 0)
+ .setSourceMetricsCategory(
+ SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
+ .launch();
+ return true;
+ });
+ pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label));
+ updateIcon(pref, app);
+ if (appChannels > appChannelsBypassingDnd) {
+ pref.setSummary(R.string.zen_mode_bypassing_apps_summary_some);
+ } else {
+ pref.setSummary(R.string.zen_mode_bypassing_apps_summary_all);
+ }
+ mPreferenceCategory.addPreference(pref);
+ }
+ } else if (appChannelsBypassingDnd == 0) {
+ // exists but shouldn't anymore
+ mPreferenceCategory.removePreference(pref);
+ }
+ }
+
+ Preference pref = mPreferenceCategory.findPreference(KEY_NO_APPS);
+ if (!doAnyAppsPassCriteria) {
+ if (pref == null) {
+ pref = new Preference(mPrefContext);
+ pref.setKey(KEY_NO_APPS);
+ pref.setTitle(R.string.zen_mode_bypassing_apps_none);
+ }
+ mPreferenceCategory.addPreference(pref);
+ } else if (pref != null) {
+ mPreferenceCategory.removePreference(pref);
+ }
+ }
+
+ /**
+ * Create a unique key to idenfity an AppPreference
+ */
+ static String getKey(String pkg, int uid) {
+ return "all|" + pkg + "|" + uid;
+ }
+
+ private final ApplicationsState.Callbacks mAppSessionCallbacks =
+ new ApplicationsState.Callbacks() {
+
+ @Override
+ public void onRunningStateChanged(boolean running) {
+ }
+
+ @Override
+ public void onPackageListChanged() {
+ }
+
+ @Override
+ public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
+ updateAppList(apps);
+ }
+
+ @Override
+ public void onPackageIconChanged() {
+ }
+
+ @Override
+ public void onPackageSizeChanged(String packageName) {
+ }
+
+ @Override
+ public void onAllSizesComputed() { }
+
+ @Override
+ public void onLauncherInfoChanged() {
+ }
+
+ @Override
+ public void onLoadEntriesCompleted() {
+ updateAppList();
+ }
+ };
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsFragment.java b/src/com/android/settings/notification/modes/ZenModeAppsFragment.java
new file mode 100644
index 0000000..73329a2
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeAppsFragment.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mode > Apps
+ */
+public class ZenModeAppsFragment extends ZenModeFragmentBase {
+
+ @Override
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ List<AbstractPreferenceController> controllers = new ArrayList<>();
+ controllers.add(new ZenModeAppsPreferenceController(
+ context, ZenModeAppsPreferenceController.KEY_PRIORITY, mBackend));
+ controllers.add(new ZenModeAppsPreferenceController(
+ context, ZenModeAppsPreferenceController.KEY_NONE, mBackend));
+ // TODO: b/308819928 - The manual DND mode cannot have the ALL type;
+ // unify the controllers into one and only create a preference if isManualDnd is false.
+ controllers.add(new ZenModeAppsPreferenceController(
+ context, ZenModeAppsPreferenceController.KEY_ALL, mBackend));
+ return controllers;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.zen_mode_apps_settings;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: b/332937635 - make this the correct metrics category
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_PRIORITY;
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
new file mode 100644
index 0000000..42b58b1
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+
+import com.android.settings.core.SubSettingLauncher;
+
+/**
+ * Preference with a link and summary about what apps can break through the mode
+ */
+public class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController {
+
+ private final ZenModeSummaryHelper mSummaryHelper;
+
+ public ZenModeAppsLinkPreferenceController(Context context, String key,
+ ZenModesBackend backend) {
+ super(context, key, backend);
+ mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
+ }
+
+ @Override
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ Bundle bundle = new Bundle();
+ bundle.putString(MODE_ID, zenMode.getId());
+ // TODO(b/332937635): Update metrics category
+ preference.setIntent(new SubSettingLauncher(mContext)
+ .setDestination(ZenModeAppsFragment.class.getName())
+ .setSourceMetricsCategory(0)
+ .setArguments(bundle)
+ .toIntent());
+ preference.setSummary(mSummaryHelper.getAppsSummary(zenMode));
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
new file mode 100644
index 0000000..704bce0
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+import android.service.notification.ZenPolicy;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+public class ZenModeAppsPreferenceController extends
+ AbstractZenModePreferenceController implements Preference.OnPreferenceChangeListener {
+
+ static final String KEY_PRIORITY = "zen_mode_apps_priority";
+ static final String KEY_NONE = "zen_mode_apps_none";
+ static final String KEY_ALL = "zen_mode_apps_all";
+
+ String mModeId;
+
+
+ public ZenModeAppsPreferenceController(@NonNull Context context,
+ @NonNull String key, @Nullable ZenModesBackend backend) {
+ super(context, key, backend);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ SelectorWithWidgetPreference pref = screen.findPreference(getPreferenceKey());
+ if (pref != null) {
+ pref.setOnClickListener(mSelectorClickListener);
+
+ // Adds the widget to only the priority category.
+ if (getPreferenceKey().equals(KEY_PRIORITY)) {
+ pref.setExtraWidgetOnClickListener(p -> {
+ launchPrioritySettings();
+ });
+ }
+ }
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ mModeId = zenMode.getId();
+ TwoStatePreference pref = (TwoStatePreference) preference;
+ switch (getPreferenceKey()) {
+ case KEY_PRIORITY:
+ boolean policy_priority = zenMode.getPolicy().getAllowedChannels()
+ == ZenPolicy.CHANNEL_POLICY_PRIORITY;
+ pref.setChecked(policy_priority);
+ break;
+ case KEY_NONE:
+ boolean policy_none = zenMode.getPolicy().getAllowedChannels()
+ == ZenPolicy.CHANNEL_POLICY_NONE;
+ pref.setChecked(policy_none);
+ break;
+ case KEY_ALL:
+ // A UI-only setting; the underlying policy never actually has this value,
+ // but ZenMode acts as though it does for the sake of UI consistency.
+ boolean policy_all = zenMode.getPolicy().getAllowedChannels()
+ == ZenMode.CHANNEL_POLICY_ALL;
+ pref.setChecked(policy_all);
+ break;
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
+ switch (getPreferenceKey()) {
+ case KEY_PRIORITY:
+ return savePolicy(p -> p.allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY));
+ case KEY_NONE:
+ return savePolicy(p -> p.allowChannels(ZenPolicy.CHANNEL_POLICY_NONE));
+ case KEY_ALL:
+ return savePolicy(p -> p.allowChannels(ZenMode.CHANNEL_POLICY_ALL));
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ SelectorWithWidgetPreference.OnClickListener mSelectorClickListener =
+ new SelectorWithWidgetPreference.OnClickListener() {
+ @Override
+ public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
+ onPreferenceChange(preference, true);
+ }
+ };
+
+ private void launchPrioritySettings() {
+ Bundle bundle = new Bundle();
+ if (mModeId != null) {
+ bundle.putString(MODE_ID, mModeId);
+ }
+ // TODO(b/332937635): Update metrics category
+ new SubSettingLauncher(mContext)
+ .setDestination(ZenModeSelectBypassingAppsFragment.class.getName())
+ .setSourceMetricsCategory(SettingsEnums.SETTINGS_ZEN_NOTIFICATIONS)
+ .setArguments(bundle)
+ .launch();
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index b8666bd..1f6ae45 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -40,6 +40,8 @@
prefControllers.add(new ZenModeButtonPreferenceController(context, "activate", mBackend));
prefControllers.add(new ZenModePeopleLinkPreferenceController(
context, "zen_mode_people", mBackend));
+ prefControllers.add(new ZenModeAppsLinkPreferenceController(
+ context, "zen_mode_apps", mBackend));
prefControllers.add(new ZenModeOtherLinkPreferenceController(
context, "zen_other_settings", mBackend));
prefControllers.add(new ZenModeDisplayLinkPreferenceController(
diff --git a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
new file mode 100644
index 0000000..8b682b9
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.settings.R;
+import com.android.settings.notification.NotificationBackend;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SearchIndexable
+public class ZenModeSelectBypassingAppsFragment extends ZenModeFragmentBase implements
+ Indexable {
+ private static final String TAG = "ZenBypassingApps";
+
+ @Override
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ final Activity activity = getActivity();
+ final Application app;
+ if (activity != null) {
+ app = activity.getApplication();
+ } else {
+ app = null;
+ }
+ return buildPreferenceControllers(context, app, this, new NotificationBackend());
+ }
+
+ private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
+ @Nullable Application app, @Nullable Fragment host,
+ @Nullable NotificationBackend notificationBackend) {
+ final List<AbstractPreferenceController> controllers = new ArrayList<>();
+ controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host,
+ notificationBackend));
+ controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host,
+ notificationBackend));
+ return controllers;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.zen_mode_select_bypassing_apps;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO(b/332937635): Update metrics category
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APPS;
+ }
+
+ /**
+ * For Search.
+ */
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.zen_mode_select_bypassing_apps) {
+
+ @Override
+ public List<AbstractPreferenceController> createPreferenceControllers(
+ Context context) {
+ return buildPreferenceControllers(context, null, null, null);
+ }
+ };
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
index 41a3d20..b4075cd 100644
--- a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
+++ b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
@@ -395,4 +395,19 @@
return mContext.getResources().getString(R.string.zen_mode_people_some);
}
}
+
+ /**
+ * Generates a summary to display under the top level "Apps" preference for a mode.
+ */
+ public String getAppsSummary(ZenMode zenMode) {
+ // TODO: b/308819928 - Set summary using priority app list if Selected Apps Chosen.
+ if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_PRIORITY) {
+ return mContext.getResources().getString(R.string.zen_mode_apps_priority_apps);
+ } else if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
+ return mContext.getResources().getString(R.string.zen_mode_apps_none_apps);
+ } else if (zenMode.getPolicy().getAllowedChannels() == ZenMode.CHANNEL_POLICY_ALL) {
+ return mContext.getResources().getString(R.string.zen_mode_apps_all_apps);
+ }
+ return "";
+ }
}
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index dc9c2ec..069f910 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -313,7 +313,7 @@
mForceVerifyPath = userProperties.isCredentialShareableWithParent();
if (android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
&& isBiometricAllowed(effectiveUserId, mUserId)) {
- promptInfo.setUseParentProfileForDeviceCredential(true);
+ setBiometricPromptPropertiesForPrivateProfile(promptInfo);
showBiometricPrompt(promptInfo, effectiveUserId);
launchedBiometric = true;
} else {
@@ -344,6 +344,11 @@
}
}
+ private static void setBiometricPromptPropertiesForPrivateProfile(PromptInfo promptInfo) {
+ promptInfo.setUseParentProfileForDeviceCredential(true);
+ promptInfo.setConfirmationRequested(false);
+ }
+
private String getTitleFromCredentialType(@LockPatternUtils.CredentialType int credentialType,
boolean isEffectiveUserManagedProfile) {
switch (credentialType) {
diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
index debec45..bdbe993 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
@@ -19,6 +19,7 @@
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK;
+import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART;
import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
import static android.provider.Settings.Secure.SKIP_FIRST_USE_HINTS;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -70,6 +71,10 @@
@Settings.Secure.PrivateSpaceAutoLockOption
public static final int PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL =
PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
+ /** Value for private space auto lock settings after private space creation. */
+ @Settings.Secure.PrivateSpaceAutoLockOption
+ public static final int PRIVATE_SPACE_CREATE_AUTO_LOCK_VAL =
+ PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART;
/** Default value for the hide private space sensitive notifications on lockscreen. */
public static final int HIDE_PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DISABLED_VAL = 0;
@@ -327,7 +332,7 @@
@GuardedBy("this")
private void resetPrivateSpaceSettings() {
setHidePrivateSpaceEntryPointSetting(HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL);
- setPrivateSpaceAutoLockSetting(PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL);
+ setPrivateSpaceAutoLockSetting(PRIVATE_SPACE_CREATE_AUTO_LOCK_VAL);
setPrivateSpaceSensitiveNotificationsDefaultValue();
}
diff --git a/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java b/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java
index 9d04e79..4f2634e 100644
--- a/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java
+++ b/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java
@@ -19,6 +19,8 @@
import static android.content.Intent.ACTION_PROFILE_INACCESSIBLE;
import static android.content.Intent.ACTION_PROFILE_UNAVAILABLE;
+import static com.android.settings.privatespace.PrivateSpaceMaintainer.PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL;
+
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -101,8 +103,13 @@
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
if (savedInstanceState == null) {
- // TODO(b/307729746): Add a test to verify PS is locked after setup completion.
- PrivateSpaceMaintainer.getInstance(getActivity()).lockPrivateSpace();
+ // TODO(b/307729746): Add test to verify PS is locked and auto-lock value is set to
+ // auto-lock on device lock after setup completion.
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(getActivity());
+ privateSpaceMaintainer.setPrivateSpaceAutoLockSetting(
+ PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL);
+ privateSpaceMaintainer.lockPrivateSpace();
}
return rootView;
}
diff --git a/src/com/android/settings/search/BaseSearchIndexProvider.java b/src/com/android/settings/search/BaseSearchIndexProvider.java
index 7fa8355..cc05270 100644
--- a/src/com/android/settings/search/BaseSearchIndexProvider.java
+++ b/src/com/android/settings/search/BaseSearchIndexProvider.java
@@ -45,6 +45,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
/**
* A basic SearchIndexProvider that returns no data to index.
@@ -117,11 +118,18 @@
@Override
@CallSuper
public List<String> getNonIndexableKeys(Context context) {
+ final List<String> nonIndexableKeys = new ArrayList<>();
if (!isPageSearchEnabled(context)) {
// Entire page should be suppressed, mark all keys from this page as non-indexable.
- return getNonIndexableKeysFromXml(context, true /* suppressAllPage */);
+ nonIndexableKeys.addAll(
+ getNonIndexableKeysFromXml(context, true /* suppressAllPage */));
+ nonIndexableKeys.addAll(
+ getRawDataToIndex(context, true /* enabled */)
+ .stream()
+ .map(data -> data.key)
+ .collect(Collectors.toList()));
+ return nonIndexableKeys;
}
- final List<String> nonIndexableKeys = new ArrayList<>();
nonIndexableKeys.addAll(getNonIndexableKeysFromXml(context, false /* suppressAllPage */));
final List<AbstractPreferenceController> controllers = getPreferenceControllers(context);
if (controllers != null && !controllers.isEmpty()) {
diff --git a/src/com/android/settings/spa/network/SimOnboardingLabelSim.kt b/src/com/android/settings/spa/network/SimOnboardingLabelSim.kt
index 4dd0def..6c8ce6e 100644
--- a/src/com/android/settings/spa/network/SimOnboardingLabelSim.kt
+++ b/src/com/android/settings/spa/network/SimOnboardingLabelSim.kt
@@ -28,6 +28,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.network.SimOnboardingService
@@ -104,9 +105,9 @@
value = titleSimName,
label = stringResource(R.string.sim_onboarding_label_sim_dialog_label),
placeholder = {Text(text = originalSimCarrierName)},
- modifier = Modifier.fillMaxWidth()
+ modifier = Modifier.fillMaxWidth().testTag("contentInput")
) {
- titleSimName = if (it.isEmpty()) originalSimCarrierName else it
+ titleSimName = if (it.matches(Regex("^\\s*$"))) originalSimCarrierName else it
}
},
)
diff --git a/tests/robotests/src/com/android/settings/MainClearConfirmTest.java b/tests/robotests/src/com/android/settings/MainClearConfirmTest.java
index b866c96..6d85368 100644
--- a/tests/robotests/src/com/android/settings/MainClearConfirmTest.java
+++ b/tests/robotests/src/com/android/settings/MainClearConfirmTest.java
@@ -25,6 +25,10 @@
import android.app.admin.DevicePolicyManager;
import android.app.admin.FactoryResetProtectionPolicy;
import android.content.Context;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.security.Flags;
import android.service.persistentdata.PersistentDataBlockManager;
import android.view.LayoutInflater;
import android.widget.TextView;
@@ -32,6 +36,7 @@
import androidx.fragment.app.FragmentActivity;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -48,6 +53,9 @@
})
public class MainClearConfirmTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private FragmentActivity mActivity;
@Mock
@@ -66,6 +74,9 @@
MockitoAnnotations.initMocks(this);
mActivity = Robolectric.setupActivity(FragmentActivity.class);
mMainClearConfirm = spy(new MainClearConfirm());
+
+ when(mMockActivity.getSystemService(Context.DEVICE_POLICY_SERVICE))
+ .thenReturn(mDevicePolicyManager);
}
@Test
@@ -110,12 +121,29 @@
}
@Test
- public void shouldWipePersistentDataBlock_oemUnlockAllowed_shouldReturnFalse() {
+ @DisableFlags(Flags.FLAG_FRP_ENFORCEMENT)
+ public void shouldWipePersistentDataBlock_oemUnlockAllowedAndFlagDiscabled_shouldReturnFalse() {
+ when(mMainClearConfirm.getActivity()).thenReturn(mMockActivity);
+
+ when(mDevicePolicyManager.isFactoryResetProtectionPolicySupported()).thenReturn(true);
doReturn(false).when(mMainClearConfirm).isDeviceStillBeingProvisioned();
doReturn(true).when(mMainClearConfirm).isOemUnlockedAllowed();
- assertThat(mMainClearConfirm.shouldWipePersistentDataBlock(
- mPersistentDataBlockManager)).isFalse();
+ assertThat(mMainClearConfirm.shouldWipePersistentDataBlock(mPersistentDataBlockManager))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FRP_ENFORCEMENT)
+ public void shouldWipePersistentDataBlock_oemUnlockAllowedAndFlagEnabled_shouldReturnTrue() {
+ when(mMainClearConfirm.getActivity()).thenReturn(mMockActivity);
+
+ when(mDevicePolicyManager.isFactoryResetProtectionPolicySupported()).thenReturn(true);
+ doReturn(false).when(mMainClearConfirm).isDeviceStillBeingProvisioned();
+ doReturn(true).when(mMainClearConfirm).isOemUnlockedAllowed();
+
+ assertThat(mMainClearConfirm.shouldWipePersistentDataBlock(mPersistentDataBlockManager))
+ .isTrue();
}
@Test
@@ -124,8 +152,7 @@
doReturn(false).when(mMainClearConfirm).isDeviceStillBeingProvisioned();
doReturn(false).when(mMainClearConfirm).isOemUnlockedAllowed();
- when(mMockActivity.getSystemService(Context.DEVICE_POLICY_SERVICE))
- .thenReturn(mDevicePolicyManager);
+
when(mDevicePolicyManager.isFactoryResetProtectionPolicySupported()).thenReturn(false);
assertThat(mMainClearConfirm.shouldWipePersistentDataBlock(
@@ -144,8 +171,6 @@
.setFactoryResetProtectionAccounts(accounts)
.setFactoryResetProtectionEnabled(true)
.build();
- when(mMockActivity.getSystemService(Context.DEVICE_POLICY_SERVICE))
- .thenReturn(mDevicePolicyManager);
when(mDevicePolicyManager.isFactoryResetProtectionPolicySupported()).thenReturn(true);
when(mDevicePolicyManager.getFactoryResetProtectionPolicy(null)).thenReturn(frp);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
@@ -161,8 +186,6 @@
doReturn(false).when(mMainClearConfirm).isDeviceStillBeingProvisioned();
doReturn(false).when(mMainClearConfirm).isOemUnlockedAllowed();
- when(mMockActivity.getSystemService(Context.DEVICE_POLICY_SERVICE))
- .thenReturn(mDevicePolicyManager);
when(mDevicePolicyManager.isFactoryResetProtectionPolicySupported()).thenReturn(true);
when(mDevicePolicyManager.getFactoryResetProtectionPolicy(null)).thenReturn(null);
when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false);
diff --git a/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java
index 36c48e3..aedb922 100644
--- a/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java
@@ -26,15 +26,19 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.flags.Flags;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -42,6 +46,8 @@
@RunWith(RobolectricTestRunner.class)
public class TopLevelSettingsTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
private TopLevelSettings mSettings;
@@ -58,6 +64,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_HOMEPAGE_REVAMP)
public void onCreatePreferences_shouldTintPreferenceIcon() {
final Preference preference = new Preference(mContext);
preference.setTitle(R.string.network_dashboard_title);
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java
new file mode 100644
index 0000000..bca1ccf
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ParceledListSlice;
+
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.settings.notification.NotificationBackend;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeAddBypassingAppsPreferenceControllerTest {
+
+ @Mock
+ private NotificationBackend mBackend;
+ @Mock
+ private PreferenceCategory mPreferenceCategory;
+ @Mock
+ private ApplicationsState mApplicationState;
+ private ZenModeAddBypassingAppsPreferenceController mController;
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ mController = new ZenModeAddBypassingAppsPreferenceController(
+ mContext, null, mock(Fragment.class), mBackend);
+ mController.mPreferenceCategory = mPreferenceCategory;
+ mController.mApplicationsState = mApplicationState;
+ mController.mPrefContext = mContext;
+ }
+
+ @Test
+ public void testIsAvailable() {
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void testUpdateAppList() {
+ // GIVEN there's an app with bypassing channels, app without any channels, and then an app
+ // with notification channels but none that can bypass DND
+ ApplicationsState.AppEntry appWithBypassingChannels =
+ mock(ApplicationsState.AppEntry.class);
+ appWithBypassingChannels.info = new ApplicationInfo();
+ appWithBypassingChannels.info.packageName = "appWithBypassingChannels";
+ appWithBypassingChannels.info.uid = 0;
+ when(mBackend.getNotificationChannelsBypassingDnd(
+ appWithBypassingChannels.info.packageName,
+ appWithBypassingChannels.info.uid))
+ .thenReturn(new ParceledListSlice<>(
+ Arrays.asList(mock(NotificationChannel.class))));
+ when(mBackend.getChannelCount(
+ appWithBypassingChannels.info.packageName,
+ appWithBypassingChannels.info.uid))
+ .thenReturn(5);
+
+ ApplicationsState.AppEntry appWithoutChannels = mock(ApplicationsState.AppEntry.class);
+ appWithoutChannels.info = new ApplicationInfo();
+ appWithoutChannels.info.packageName = "appWithoutChannels";
+ appWithoutChannels.info.uid = 0;
+ when(mBackend.getChannelCount(
+ appWithoutChannels.info.packageName,
+ appWithoutChannels.info.uid))
+ .thenReturn(0);
+ when(mBackend.getNotificationChannelsBypassingDnd(
+ appWithoutChannels.info.packageName,
+ appWithoutChannels.info.uid))
+ .thenReturn(new ParceledListSlice<>(new ArrayList<>()));
+
+ ApplicationsState.AppEntry appWithChannelsNoneBypassing =
+ mock(ApplicationsState.AppEntry.class);
+ appWithChannelsNoneBypassing.info = new ApplicationInfo();
+ appWithChannelsNoneBypassing.info.packageName = "appWithChannelsNoneBypassing";
+ appWithChannelsNoneBypassing.info.uid = 0;
+ when(mBackend.getChannelCount(
+ appWithChannelsNoneBypassing.info.packageName,
+ appWithChannelsNoneBypassing.info.uid))
+ .thenReturn(5);
+ when(mBackend.getNotificationChannelsBypassingDnd(
+ appWithChannelsNoneBypassing.info.packageName,
+ appWithChannelsNoneBypassing.info.uid))
+ .thenReturn(new ParceledListSlice<>(new ArrayList<>()));
+
+ List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
+ appEntries.add(appWithBypassingChannels);
+ appEntries.add(appWithoutChannels);
+ appEntries.add(appWithChannelsNoneBypassing);
+
+ // WHEN the controller updates the app list with the app entries
+ mController.updateAppList(appEntries);
+
+ // THEN only the appWithChannelsNoneBypassing makes it to the app list
+ ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
+ verify(mPreferenceCategory).addPreference(prefCaptor.capture());
+
+ Preference pref = prefCaptor.getValue();
+ assertThat(pref.getKey()).isEqualTo(
+ ZenModeAddBypassingAppsPreferenceController.getKey(
+ appWithChannelsNoneBypassing.info.packageName,
+ appWithChannelsNoneBypassing.info.uid));
+ }
+
+ @Test
+ public void testUpdateAppList_nullApps() {
+ mController.updateAppList(null);
+ verify(mPreferenceCategory, never()).addPreference(any());
+ }
+
+ @Test
+ public void testUpdateAppList_emptyAppList() {
+ // WHEN there are no apps
+ mController.updateAppList(new ArrayList<>());
+
+ // THEN only the appWithChannelsNoneBypassing makes it to the app list
+ ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
+ verify(mPreferenceCategory).addPreference(prefCaptor.capture());
+
+ Preference pref = prefCaptor.getValue();
+ assertThat(pref.getKey()).isEqualTo(
+ ZenModeAddBypassingAppsPreferenceController.KEY_NO_APPS);
+ }
+
+ // TODO(b/331624810): Add tests to verify updateAppList() when the filter is
+ // ApplicationsState.FILTER_ENABLED_NOT_QUIET
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceControllerTest.java
new file mode 100644
index 0000000..3114a2d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAllBypassingAppsPreferenceControllerTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ParceledListSlice;
+
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.settings.notification.NotificationBackend;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeAllBypassingAppsPreferenceControllerTest {
+ private ZenModeAllBypassingAppsPreferenceController mController;
+
+ private Context mContext;
+ @Mock
+ private NotificationBackend mBackend;
+ @Mock
+ private PreferenceCategory mPreferenceCategory;
+ @Mock
+ private ApplicationsState mApplicationState;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ mController = new ZenModeAllBypassingAppsPreferenceController(
+ mContext, null, mock(Fragment.class), mBackend);
+ mController.mPreferenceCategory = mPreferenceCategory;
+ mController.mApplicationsState = mApplicationState;
+ mController.mPrefContext = mContext;
+ }
+
+ @Test
+ public void testIsAvailable() {
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void testUpdateAppList() {
+ // WHEN there's two apps with notification channels that bypass DND
+ ApplicationsState.AppEntry entry1 = mock(ApplicationsState.AppEntry.class);
+ entry1.info = new ApplicationInfo();
+ entry1.info.packageName = "test";
+ entry1.info.uid = 0;
+
+ ApplicationsState.AppEntry entry2 = mock(ApplicationsState.AppEntry.class);
+ entry2.info = new ApplicationInfo();
+ entry2.info.packageName = "test2";
+ entry2.info.uid = 0;
+
+ List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
+ appEntries.add(entry1);
+ appEntries.add(entry2);
+ List<NotificationChannel> channelsBypassing = new ArrayList<>();
+ channelsBypassing.add(mock(NotificationChannel.class));
+ channelsBypassing.add(mock(NotificationChannel.class));
+ when(mBackend.getNotificationChannelsBypassingDnd(anyString(),
+ anyInt())).thenReturn(new ParceledListSlice<>(channelsBypassing));
+
+ // THEN there's are two preferences
+ mController.updateAppList(appEntries);
+ verify(mPreferenceCategory, times(2)).addPreference(any());
+ }
+
+ @Test
+ public void testUpdateAppList_nullApps() {
+ mController.updateAppList(null);
+ verify(mPreferenceCategory, never()).addPreference(any());
+ }
+
+ @Test
+ public void testUpdateAppList_emptyAppList() {
+ // WHEN there are no apps
+ mController.updateAppList(new ArrayList<>());
+
+ // THEN only the appWithChannelsNoneBypassing makes it to the app list
+ ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
+ verify(mPreferenceCategory).addPreference(prefCaptor.capture());
+
+ Preference pref = prefCaptor.getValue();
+ assertThat(pref.getKey()).isEqualTo(
+ ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
new file mode 100644
index 0000000..67e1f9f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.AutomaticZenRule;
+import android.app.Flags;
+import android.content.Context;
+import android.net.Uri;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.ZenPolicy;
+
+import androidx.preference.Preference;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+@EnableFlags(Flags.FLAG_MODES_UI)
+public final class ZenModeAppsLinkPreferenceControllerTest {
+
+ private ZenModeAppsLinkPreferenceController mController;
+
+ private Context mContext;
+ @Mock
+ private ZenModesBackend mBackend;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mController = new ZenModeAppsLinkPreferenceController(
+ mContext, "controller_key", mBackend);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ public void testHasSummary() {
+ Preference pref = mock(Preference.class);
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
+ .build(), true);
+ mController.updateZenMode(pref, zenMode);
+ verify(pref).setSummary(any());
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java
new file mode 100644
index 0000000..750453d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+
+import static com.android.settings.notification.modes.ZenModeAppsPreferenceController.KEY_ALL;
+import static com.android.settings.notification.modes.ZenModeAppsPreferenceController.KEY_NONE;
+import static com.android.settings.notification.modes.ZenModeAppsPreferenceController.KEY_PRIORITY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.AutomaticZenRule;
+import android.app.Flags;
+import android.content.Context;
+import android.net.Uri;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.ZenPolicy;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+@EnableFlags(Flags.FLAG_MODES_UI)
+public final class ZenModeAppsPreferenceControllerTest {
+
+ private Context mContext;
+ @Mock
+ private ZenModesBackend mBackend;
+ private ZenModeAppsPreferenceController mPriorityController;
+ private ZenModeAppsPreferenceController mAllController;
+ private ZenModeAppsPreferenceController mNoneController;
+
+ private SelectorWithWidgetPreference mPriorityPref;
+ private SelectorWithWidgetPreference mAllPref;
+ private SelectorWithWidgetPreference mNonePref;
+ private PreferenceCategory mPrefCategory;
+ private PreferenceScreen mPreferenceScreen;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ mPriorityController = new ZenModeAppsPreferenceController(mContext, KEY_PRIORITY, mBackend);
+ mNoneController = new ZenModeAppsPreferenceController(mContext, KEY_NONE, mBackend);
+ mAllController = new ZenModeAppsPreferenceController(mContext, KEY_ALL, mBackend);
+
+ mPriorityPref = makePreference(KEY_PRIORITY, mPriorityController);
+ mAllPref = makePreference(KEY_ALL, mAllController);
+ mNonePref = makePreference(KEY_NONE, mNoneController);
+
+ mPrefCategory = new PreferenceCategory(mContext);
+ mPrefCategory.setKey("zen_mode_apps_category");
+
+ PreferenceManager preferenceManager = new PreferenceManager(mContext);
+ mPreferenceScreen = preferenceManager.createPreferenceScreen(mContext);
+
+ mPreferenceScreen.addPreference(mPrefCategory);
+ mPrefCategory.addPreference(mPriorityPref);
+ mPrefCategory.addPreference(mAllPref);
+ mPrefCategory.addPreference(mNonePref);
+
+ mAllController.displayPreference(mPreferenceScreen);
+ mPriorityController.displayPreference(mPreferenceScreen);
+ mNoneController.displayPreference(mPreferenceScreen);
+ }
+
+ private SelectorWithWidgetPreference makePreference(String key,
+ ZenModeAppsPreferenceController controller) {
+ final SelectorWithWidgetPreference pref = new SelectorWithWidgetPreference(mContext, false);
+ pref.setKey(key);
+ pref.setOnClickListener(controller.mSelectorClickListener);
+ return pref;
+ }
+
+ @Test
+ public void testUpdateState_All() {
+ TwoStatePreference preference = mock(TwoStatePreference.class);
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenMode.CHANNEL_POLICY_ALL)
+ .build())
+ .build(), true);
+ mAllController.updateZenMode(preference, zenMode);
+
+ verify(preference).setChecked(true);
+ }
+
+ @Test
+ public void testUpdateState_All_Unchecked() {
+ TwoStatePreference preference = mock(TwoStatePreference.class);
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
+ .build())
+ .build(), true);
+ mAllController.updateZenMode(preference, zenMode);
+
+ verify(preference).setChecked(false);
+ }
+
+ @Test
+ public void testUpdateState_None() {
+ TwoStatePreference preference = mock(TwoStatePreference.class);
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
+ .build())
+ .build(), true);
+ mNoneController.updateZenMode(preference, zenMode);
+
+ verify(preference).setChecked(true);
+ }
+
+ @Test
+ public void testUpdateState_None_Unchecked() {
+ TwoStatePreference preference = mock(TwoStatePreference.class);
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenMode.CHANNEL_POLICY_ALL)
+ .build())
+ .build(), true);
+ mNoneController.updateZenMode(preference, zenMode);
+
+ verify(preference).setChecked(false);
+ }
+
+ @Test
+ public void testUpdateState_Priority() {
+ TwoStatePreference preference = mock(TwoStatePreference.class);
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
+ .build())
+ .build(), true);
+ mPriorityController.updateZenMode(preference, zenMode);
+
+ verify(preference).setChecked(true);
+ }
+
+ @Test
+ public void testUpdateState_Priority_Unchecked() {
+ TwoStatePreference preference = mock(TwoStatePreference.class);
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
+ .build())
+ .build(), true);
+ mPriorityController.updateZenMode(preference, zenMode);
+
+ verify(preference).setChecked(false);
+ }
+
+ @Test
+ public void testOnPreferenceChange_All() {
+ TwoStatePreference preference = mock(TwoStatePreference.class);
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenMode.CHANNEL_POLICY_ALL)
+ .build())
+ .build(), true);
+
+ mAllController.updateZenMode(preference, zenMode);
+ mAllController.onPreferenceChange(preference, true);
+ ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
+ verify(mBackend).updateMode(captor.capture());
+
+ assertThat(captor.getValue().getPolicy().getAllowedChannels())
+ .isEqualTo(ZenMode.CHANNEL_POLICY_ALL);
+ }
+
+ @Test
+ public void testPreferenceClick_passesCorrectCheckedState_All() {
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
+ .build())
+ .build(), true);
+
+
+ mAllController.updateZenMode(mAllPref, zenMode);
+ mNoneController.updateZenMode(mNonePref, zenMode);
+ mPriorityController.updateZenMode(mPriorityPref, zenMode);
+
+ // MPME is checked; ALL and PRIORITY are unchecked.
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_ALL))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked());
+
+ mPrefCategory.findPreference(KEY_ALL).performClick();
+
+ ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
+ verify(mBackend).updateMode(captor.capture());
+ // Checks the policy value for ALL is set.
+ // The important part is that the interruption filter is propagated to the backend.
+ assertThat(captor.getValue().getRule().getInterruptionFilter())
+ .isEqualTo(INTERRUPTION_FILTER_ALL);
+ // ALL is now checked; others are unchecked.
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_ALL))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked());
+ }
+
+ @Test
+ public void testPreferenceClick_passesCorrectCheckedState_None() {
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
+ .build())
+ .build(), true);
+
+ mAllController.updateZenMode(mAllPref, zenMode);
+ mNoneController.updateZenMode(mNonePref, zenMode);
+ mPriorityController.updateZenMode(mPriorityPref, zenMode);
+
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_ALL))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked());
+
+ // Click on NONE
+ mPrefCategory.findPreference(KEY_NONE).performClick();
+
+ ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
+ verify(mBackend).updateMode(captor.capture());
+ // NONE is not actually propagated to the backend as an interruption filter;
+ // the filter is set to priority, and sounds and visual effects are disallowed.
+ // See AbstractZenModePreferenceController.
+ assertThat(captor.getValue().getRule().getInterruptionFilter())
+ .isEqualTo(INTERRUPTION_FILTER_PRIORITY);
+ // NONE is now checked; others are unchecked.
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_ALL))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked());
+ }
+
+ @Test
+ public void testPreferenceClick_passesCorrectCheckedState_Priority() {
+ ZenMode zenMode = new ZenMode("id",
+ new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
+ .setType(AutomaticZenRule.TYPE_DRIVING)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
+ .build())
+ .build(), true);
+
+ mAllController.updateZenMode(mAllPref, zenMode);
+ mNoneController.updateZenMode(mNonePref, zenMode);
+ mPriorityController.updateZenMode(mPriorityPref, zenMode);
+
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_ALL))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked());
+
+ // Click on PRIORITY
+ mPrefCategory.findPreference(KEY_PRIORITY).performClick();
+
+ ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
+ verify(mBackend).updateMode(captor.capture());
+ // Checks the policy value for PRIORITY is propagated to the backend.
+ assertThat(captor.getValue().getRule().getInterruptionFilter())
+ .isEqualTo(INTERRUPTION_FILTER_PRIORITY);
+ // PRIORITY is now checked; others are unchecked.
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_ALL))
+ .isChecked());
+ assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
+ .isChecked());
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
index 3e41778..d8c8bf0 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
@@ -22,6 +22,7 @@
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_AMBIENT;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS;
+
import static com.google.common.truth.Truth.assertThat;
import android.app.AutomaticZenRule;
@@ -29,6 +30,7 @@
import android.net.Uri;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -325,4 +327,46 @@
assertThat(mSummaryHelper.getDisplayEffectsSummary(zenMode)).isEqualTo(
"Notifications partially hidden, grayscale, and 2 more");
}
+
+ @Test
+ public void getAppsSummary_all() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
+ .setType(AutomaticZenRule.TYPE_BEDTIME)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenMode.CHANNEL_POLICY_ALL)
+ .build())
+ .build();
+ ZenMode zenMode = new ZenMode("id", rule, true);
+
+ assertThat(mSummaryHelper.getAppsSummary(zenMode)).isEqualTo("All");
+ }
+
+ @Test
+ public void getAppsSummary_none() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
+ .setType(AutomaticZenRule.TYPE_BEDTIME)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
+ .build())
+ .build();
+ ZenMode zenMode = new ZenMode("id", rule, true);
+
+ assertThat(mSummaryHelper.getAppsSummary(zenMode)).isEqualTo("None");
+ }
+
+ @Test
+ public void getAppsSummary_priorityApps() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
+ .setType(AutomaticZenRule.TYPE_BEDTIME)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
+ .build())
+ .build();
+ ZenMode zenMode = new ZenMode("id", rule, true);
+
+ assertThat(mSummaryHelper.getAppsSummary(zenMode)).isEqualTo("Selected apps");
+ }
}
diff --git a/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java b/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java
index 09b1ea9..18dc004 100644
--- a/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java
+++ b/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java
@@ -167,6 +167,16 @@
}
@Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ List<SearchIndexableRaw> rawData = super.getRawDataToIndex(context, enabled);
+ SearchIndexableRaw raw = new SearchIndexableRaw(context);
+ raw.key = TEST_PREF_KEY;
+ raw.title = "title";
+ rawData.add(raw);
+ return rawData;
+ }
+
+ @Override
protected boolean isPageSearchEnabled(Context context) {
return false;
}
@@ -176,6 +186,7 @@
provider.getNonIndexableKeys(RuntimeEnvironment.application);
assertThat(nonIndexableKeys).contains("status_header");
+ assertThat(nonIndexableKeys).contains(TEST_PREF_KEY);
}
@Test
diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/ImsRegistrationStateControllerTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/ImsRegistrationStateControllerTest.kt
deleted file mode 100644
index 5e486dd..0000000
--- a/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/ImsRegistrationStateControllerTest.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.deviceinfo.simstatus
-
-import android.content.Context
-import androidx.lifecycle.testing.TestLifecycleOwner
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settings.network.telephony.SimSlotRepository
-import com.android.settings.network.telephony.ims.ImsMmTelRepository
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.runBlocking
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-
-@RunWith(AndroidJUnit4::class)
-class ImsRegistrationStateControllerTest {
-
- private val context: Context = ApplicationProvider.getApplicationContext()
-
- private val mockSimSlotRepository = mock<SimSlotRepository> {
- on { subIdInSimSlotFlow(SIM_SLOT_INDEX) } doReturn flowOf(SUB_ID)
- }
-
- private val mockImsMmTelRepository = mock<ImsMmTelRepository> {
- on { imsRegisteredFlow() } doReturn flowOf(true)
- }
-
- private val controller = ImsRegistrationStateController(
- context = context,
- simSlotRepository = mockSimSlotRepository,
- imsMmTelRepositoryFactory = { subId ->
- assertThat(subId).isEqualTo(SUB_ID)
- mockImsMmTelRepository
- },
- )
-
- @Test
- fun collectImsRegistered() = runBlocking {
- var imsRegistered = false
-
- controller.collectImsRegistered(TestLifecycleOwner(), SIM_SLOT_INDEX) {
- imsRegistered = it
- }
- delay(100)
-
- assertThat(imsRegistered).isTrue()
- }
-
- private companion object {
- const val SIM_SLOT_INDEX = 0
- const val SUB_ID = 1
- }
-}
diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SignalStrengthRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SignalStrengthRepositoryTest.kt
new file mode 100644
index 0000000..d748660
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SignalStrengthRepositoryTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.simstatus
+
+import android.content.Context
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.CellSignalStrengthGsm
+import android.telephony.CellSignalStrengthLte
+import android.telephony.CellSignalStrengthNr
+import android.telephony.CellSignalStrengthTdscdma
+import android.telephony.CellSignalStrengthWcdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+
+@RunWith(AndroidJUnit4::class)
+class SignalStrengthRepositoryTest {
+
+ private var signalStrength = SignalStrength()
+
+ private val mockTelephonyManager = mock<TelephonyManager> {
+ on { createForSubscriptionId(SUB_ID) } doReturn mock
+ on { registerTelephonyCallback(any(), any()) } doAnswer {
+ val listener = it.getArgument<TelephonyCallback.SignalStrengthsListener>(1)
+ listener.onSignalStrengthsChanged(signalStrength)
+ }
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(TelephonyManager::class.java) } doAnswer { mockTelephonyManager }
+ }
+
+ private val serviceState = ServiceState()
+
+ private val repository = SignalStrengthRepository(context) { flowOf(serviceState) }
+
+ @Test
+ fun signalStrengthDisplayFlow_serviceStatePowerOff() = runBlocking {
+ serviceState.state = ServiceState.STATE_POWER_OFF
+
+ val signalStrength = repository.signalStrengthDisplayFlow(SUB_ID).firstWithTimeoutOrNull()
+
+ assertThat(signalStrength).isEqualTo("0")
+ }
+
+ @Test
+ fun signalStrengthDisplayFlow_lteWcdma() = runBlocking {
+ serviceState.state = ServiceState.STATE_IN_SERVICE
+ signalStrength = SignalStrength(
+ CellSignalStrengthCdma(),
+ CellSignalStrengthGsm(),
+ mock<CellSignalStrengthWcdma> {
+ on { isValid } doReturn true
+ on { dbm } doReturn 40
+ on { asuLevel } doReturn 41
+ },
+ CellSignalStrengthTdscdma(),
+ mock<CellSignalStrengthLte> {
+ on { isValid } doReturn true
+ on { dbm } doReturn 50
+ on { asuLevel } doReturn 51
+ },
+ CellSignalStrengthNr(),
+ )
+
+ val signalStrength = repository.signalStrengthDisplayFlow(SUB_ID).firstWithTimeoutOrNull()
+
+ assertThat(signalStrength).isEqualTo("50 dBm 51 asu")
+ }
+
+ @Test
+ fun signalStrengthDisplayFlow_lteCdma() = runBlocking {
+ serviceState.state = ServiceState.STATE_IN_SERVICE
+ signalStrength = SignalStrength(
+ mock<CellSignalStrengthCdma> {
+ on { isValid } doReturn true
+ on { dbm } doReturn 30
+ on { asuLevel } doReturn 31
+ },
+ CellSignalStrengthGsm(),
+ CellSignalStrengthWcdma(),
+ CellSignalStrengthTdscdma(),
+ mock<CellSignalStrengthLte> {
+ on { isValid } doReturn true
+ on { dbm } doReturn 50
+ on { asuLevel } doReturn 51
+ },
+ CellSignalStrengthNr(),
+ )
+
+ val signalStrength = repository.signalStrengthDisplayFlow(SUB_ID).firstWithTimeoutOrNull()
+
+ assertThat(signalStrength).isEqualTo("50 dBm 51 asu")
+ }
+
+ @Test
+ fun signalStrengthDisplayFlow_lteOnly() = runBlocking {
+ serviceState.state = ServiceState.STATE_IN_SERVICE
+ signalStrength = SignalStrength(
+ CellSignalStrengthCdma(),
+ CellSignalStrengthGsm(),
+ CellSignalStrengthWcdma(),
+ CellSignalStrengthTdscdma(),
+ mock<CellSignalStrengthLte> {
+ on { isValid } doReturn true
+ on { dbm } doReturn 50
+ on { asuLevel } doReturn 51
+ },
+ CellSignalStrengthNr(),
+ )
+
+ val signalStrength = repository.signalStrengthDisplayFlow(SUB_ID).firstWithTimeoutOrNull()
+
+ assertThat(signalStrength).isEqualTo("50 dBm 51 asu")
+ }
+
+ private companion object {
+ const val SUB_ID = 1
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepositoryTest.kt
new file mode 100644
index 0000000..01f32bf
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepositoryTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.simstatus
+
+import android.content.Context
+import android.os.PersistableBundle
+import android.telephony.CarrierConfigManager
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.deviceinfo.simstatus.SimStatusDialogRepository.SimStatusDialogInfo
+import com.android.settings.network.telephony.SimSlotRepository
+import com.android.settings.network.telephony.ims.ImsMmTelRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.anyVararg
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+
+@RunWith(AndroidJUnit4::class)
+class SimStatusDialogRepositoryTest {
+
+ private val carrierConfig = PersistableBundle().apply {
+ putBoolean(CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, true)
+ }
+
+ private val mockCarrierConfigManager = mock<CarrierConfigManager> {
+ on { getConfigForSubId(eq(SUB_ID), anyVararg()) } doReturn carrierConfig
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
+ }
+
+ private val mockSimSlotRepository = mock<SimSlotRepository> {
+ on { subIdInSimSlotFlow(SIM_SLOT_INDEX) } doReturn flowOf(SUB_ID)
+ }
+
+ private val mockSignalStrengthRepository = mock<SignalStrengthRepository> {
+ on { signalStrengthDisplayFlow(SUB_ID) } doReturn flowOf(SIGNAL_STRENGTH)
+ }
+
+ private val mockImsMmTelRepository = mock<ImsMmTelRepository> {
+ on { imsRegisteredFlow() } doReturn flowOf(true)
+ }
+
+ private val controller = SimStatusDialogRepository(
+ context = context,
+ simSlotRepository = mockSimSlotRepository,
+ signalStrengthRepository = mockSignalStrengthRepository,
+ imsMmTelRepositoryFactory = { subId ->
+ assertThat(subId).isEqualTo(SUB_ID)
+ mockImsMmTelRepository
+ },
+ )
+
+ @Test
+ fun collectSimStatusDialogInfo() = runBlocking {
+ var simStatusDialogInfo = SimStatusDialogInfo()
+
+ controller.collectSimStatusDialogInfo(TestLifecycleOwner(), SIM_SLOT_INDEX) {
+ simStatusDialogInfo = it
+ }
+ delay(100)
+
+ assertThat(simStatusDialogInfo).isEqualTo(
+ SimStatusDialogInfo(
+ signalStrength = SIGNAL_STRENGTH,
+ imsRegistered = true,
+ )
+ )
+ }
+
+ @Test
+ fun collectSimStatusDialogInfo_doNotShowSignalStrength() = runBlocking {
+ carrierConfig.putBoolean(
+ CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL,
+ false
+ )
+ var simStatusDialogInfo = SimStatusDialogInfo()
+
+ controller.collectSimStatusDialogInfo(TestLifecycleOwner(), SIM_SLOT_INDEX) {
+ simStatusDialogInfo = it
+ }
+ delay(100)
+
+ assertThat(simStatusDialogInfo.signalStrength).isNull()
+ }
+
+ @Test
+ fun collectSimStatusDialogInfo_doNotShowImsRegistration() = runBlocking {
+ carrierConfig.putBoolean(CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false)
+ var simStatusDialogInfo = SimStatusDialogInfo()
+
+ controller.collectSimStatusDialogInfo(TestLifecycleOwner(), SIM_SLOT_INDEX) {
+ simStatusDialogInfo = it
+ }
+ delay(100)
+
+ assertThat(simStatusDialogInfo.imsRegistered).isNull()
+ }
+
+ private companion object {
+ const val SIM_SLOT_INDEX = 0
+ const val SUB_ID = 1
+
+ const val SIGNAL_STRENGTH = "-82 dBm 58 asu"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt
index 43270c2..79f53ca 100644
--- a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt
@@ -19,20 +19,30 @@
import android.content.Context
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.KEYCODE_FORWARD_DEL
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.NativeKeyEvent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performKeyPress
+import androidx.compose.ui.test.performTextClearance
+import androidx.compose.ui.test.performTextInput
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.testutils.waitUntilExists
+import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -125,22 +135,7 @@
@Test
fun simOnboardingLabelSimImpl_showItem_show3Items() {
- mockSimOnboardingService.stub {
- on { targetSubId }.doReturn(SUB_ID_1)
- on { targetSubInfo }.doReturn(SUB_INFO_1)
- on { availableSubInfoList }.doReturn(listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_3))
- on { activeSubInfoList }.doReturn(listOf(SUB_INFO_2, SUB_INFO_3))
- on { getSelectableSubscriptionInfoList() }.doReturn(
- listOf(
- SUB_INFO_1,
- SUB_INFO_2,
- SUB_INFO_3
- )
- )
- on { getSubscriptionInfoDisplayName(SUB_INFO_1) }.doReturn(DISPLAY_NAME_1)
- on { getSubscriptionInfoDisplayName(SUB_INFO_2) }.doReturn(DISPLAY_NAME_2)
- on { getSubscriptionInfoDisplayName(SUB_INFO_3) }.doReturn(DISPLAY_NAME_3)
- }
+ preSetupContent()
composeTestRule.setContent {
CompositionLocalProvider(
@@ -161,6 +156,118 @@
@Test
fun simOnboardingLabelSimImpl_showDialog_checkTitle() {
+ preSetupContent()
+
+ composeTestRule.setContent {
+ SimOnboardingLabelSimImpl(nextAction, cancelAction, mockSimOnboardingService)
+ }
+ composeTestRule.onNodeWithText(DISPLAY_NAME_1).performClick()
+
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.sim_onboarding_label_sim_dialog_title)
+ ).assertIsDisplayed()
+ }
+
+ @Test
+ fun showDialog_noContentInput_showOriginalDisplayName() {
+ preSetupContent()
+
+ composeTestRule.setContent {
+ SimOnboardingLabelSimImpl(nextAction, cancelAction, mockSimOnboardingService)
+ }
+
+ composeTestRule.onNodeWithText(DISPLAY_NAME_1).performClick()
+
+ assertEquals(
+ composeTestRule.onNodeWithTag(TEXT_FIELD_INPUT)
+ .fetchSemanticsNode()
+ .config[SemanticsProperties.EditableText].text, DISPLAY_NAME_1
+ )
+ }
+
+ @Test
+ fun showDialog_clearContent_showOriginalDisplayName() {
+ preSetupContent()
+
+ composeTestRule.setContent {
+ SimOnboardingLabelSimImpl(nextAction, cancelAction, mockSimOnboardingService)
+ }
+
+ composeTestRule.onNodeWithText(DISPLAY_NAME_1).performClick()
+ composeTestRule.onNodeWithTag(TEXT_FIELD_INPUT).performTextClearance()
+
+ assertEquals(
+ composeTestRule.onNodeWithTag(TEXT_FIELD_INPUT)
+ .fetchSemanticsNode()
+ .config[SemanticsProperties.EditableText].text, DISPLAY_NAME_1
+ )
+ }
+
+ @Test
+ fun showDialog_modifyContent_showModifiedDisplayName() {
+ val inputData = "input_data"
+ preSetupContent()
+
+ composeTestRule.setContent {
+ SimOnboardingLabelSimImpl(nextAction, cancelAction, mockSimOnboardingService)
+ }
+
+ composeTestRule.onNodeWithText(DISPLAY_NAME_1).performClick()
+ composeTestRule.onNodeWithTag(TEXT_FIELD_INPUT).performTextInput(inputData)
+
+ // Due to this TextField with Text and EditText, it need fetch correct node to get correct
+ // content.
+ assertEquals(
+ composeTestRule.onNodeWithTag(TEXT_FIELD_INPUT)
+ .fetchSemanticsNode()
+ .config[SemanticsProperties.EditableText].text, inputData + DISPLAY_NAME_1
+ )
+
+ // Click save button
+ composeTestRule.onNodeWithText(context.getString(R.string.mobile_network_sim_name_rename))
+ .performClick()
+
+ // Check preference's name is still DISPLAY_NAME_1
+ composeTestRule.onNodeWithText(inputData + DISPLAY_NAME_1).assertExists()
+ }
+
+ @Test
+ fun showDialog_onlySpaceCharContent_showAndSaveOriginalDisplayName() {
+ val spaceChars = " ";
+ preSetupContent()
+
+ composeTestRule.setContent {
+ SimOnboardingLabelSimImpl(nextAction, cancelAction, mockSimOnboardingService)
+ }
+
+ // Simulate real operation,
+ // 1. Click preference of DISPLAY_NAME_1
+ composeTestRule.onNodeWithText(DISPLAY_NAME_1).performClick()
+ // 2. Input space chars to EditText view
+ composeTestRule.onNodeWithTag(TEXT_FIELD_INPUT).performTextInput(spaceChars)
+ // 3. Remove the string of DISPLAY_NAME_1 from EditText view
+ repeat(DISPLAY_NAME_1.length) {
+ composeTestRule.onNodeWithTag(TEXT_FIELD_INPUT)
+ .performKeyPress(KeyEvent(NativeKeyEvent(ACTION_DOWN, KEYCODE_FORWARD_DEL)))
+ }
+
+ // Due to this TextField with Text and EditText, it need fetch correct node to get correct
+ // content.
+ assertEquals(
+ DISPLAY_NAME_1, composeTestRule.onNodeWithTag(TEXT_FIELD_INPUT)
+ .fetchSemanticsNode()
+ .config[SemanticsProperties.EditableText].text
+ )
+
+ // Click save button
+ composeTestRule.onNodeWithText(context.getString(R.string.mobile_network_sim_name_rename))
+ .performClick()
+
+ // Check preference's name is still DISPLAY_NAME_1
+ composeTestRule.onNodeWithText(DISPLAY_NAME_1).assertExists()
+ }
+
+ fun preSetupContent() {
mockSimOnboardingService.stub {
on { targetSubId }.doReturn(SUB_ID_1)
on { targetSubInfo }.doReturn(SUB_INFO_1)
@@ -177,21 +284,10 @@
on { getSubscriptionInfoDisplayName(SUB_INFO_2) }.doReturn(DISPLAY_NAME_2)
on { getSubscriptionInfoDisplayName(SUB_INFO_3) }.doReturn(DISPLAY_NAME_3)
}
-
- composeTestRule.setContent {
- SimOnboardingLabelSimImpl(nextAction, cancelAction, mockSimOnboardingService)
- }
-
-
- composeTestRule.onNodeWithText(DISPLAY_NAME_1).performClick()
-
- composeTestRule.onNodeWithText(
- context.getString(R.string.sim_onboarding_label_sim_dialog_title)
- )
- .assertIsDisplayed()
}
private companion object {
+ const val TEXT_FIELD_INPUT = "contentInput"
const val SUB_ID_1 = 1
const val SUB_ID_2 = 2
const val SUB_ID_3 = 3
diff --git a/tests/spa_unit/src/com/android/settings/system/ClientInitiatedActionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/system/ClientInitiatedActionRepositoryTest.kt
index 2f52031..a962d49 100644
--- a/tests/spa_unit/src/com/android/settings/system/ClientInitiatedActionRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/system/ClientInitiatedActionRepositoryTest.kt
@@ -27,6 +27,7 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.stub
@@ -44,6 +45,28 @@
private val repository = ClientInitiatedActionRepository(context)
@Test
+ fun onSystemUpdate_runtimeException_doNothing() {
+ mockCarrierConfigManager.stub {
+ on { getConfigForSubId(any(), any()) } doThrow(RuntimeException())
+ }
+
+ repository.onSystemUpdate()
+
+ verify(context, never()).sendBroadcast(any())
+ }
+
+ @Test
+ fun onSystemUpdate_illegalStateException_doNothing() {
+ mockCarrierConfigManager.stub {
+ on { getConfigForSubId(any(), any()) } doThrow(IllegalStateException())
+ }
+
+ repository.onSystemUpdate()
+
+ verify(context, never()).sendBroadcast(any())
+ }
+
+ @Test
fun onSystemUpdate_notEnabled() {
mockCarrierConfigManager.stub {
on { getConfigForSubId(any(), any()) } doReturn persistableBundleOf()
diff --git a/tests/unit/src/com/android/settings/accessibility/PreferredShortcutsTest.java b/tests/unit/src/com/android/settings/accessibility/PreferredShortcutsTest.java
index 364b17c..d068194 100644
--- a/tests/unit/src/com/android/settings/accessibility/PreferredShortcutsTest.java
+++ b/tests/unit/src/com/android/settings/accessibility/PreferredShortcutsTest.java
@@ -25,9 +25,10 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.view.accessibility.Flags;
@@ -61,7 +62,7 @@
private static final ContentResolver sContentResolver =
ApplicationProvider.getApplicationContext().getContentResolver();
@Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
@Before
@@ -174,7 +175,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ @RequiresFlagsEnabled(Flags.FLAG_A11Y_QS_SHORTCUT)
public void updatePreferredShortcutFromSettings_colorInversionWithQsAndSoftwareShortcut_preferredShortcutsMatches() {
String target = COLOR_INVERSION_COMPONENT_NAME.flattenToString();
Settings.Secure.putString(sContentResolver,
@@ -192,7 +193,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ @RequiresFlagsDisabled(Flags.FLAG_A11Y_QS_SHORTCUT)
public void updatePreferredShortcutFromSettings_colorInversionWithQsAndHardwareShortcut_qsShortcutNotSaved() {
String target = COLOR_INVERSION_COMPONENT_NAME.flattenToString();
Settings.Secure.putString(sContentResolver,
diff --git a/tests/unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java
index f65480d..3fa3808 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java
@@ -28,26 +28,18 @@
import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController.OPERATOR_INFO_VALUE_ID;
import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController.ROAMING_INFO_VALUE_ID;
import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController.SERVICE_STATE_VALUE_ID;
-import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController.SIGNAL_STRENGTH_LABEL_ID;
-import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController.SIGNAL_STRENGTH_VALUE_ID;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
-import android.telephony.CellSignalStrength;
import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -71,7 +63,6 @@
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -87,14 +78,6 @@
@Mock
private ServiceState mServiceState;
@Mock
- private SignalStrength mSignalStrength;
- @Mock
- private CellSignalStrength mCellSignalStrengthCdma;
- @Mock
- private CellSignalStrength mCellSignalStrengthLte;
- @Mock
- private CellSignalStrength mCellSignalStrengthWcdma;
- @Mock
private CarrierConfigManager mCarrierConfigManager;
private PersistableBundle mPersistableBundle;
@Mock
@@ -149,15 +132,6 @@
mUpdatePhoneNumberCount.incrementAndGet();
}
};
- // CellSignalStrength setup
- doReturn(0).when(mCellSignalStrengthCdma).getDbm();
- doReturn(0).when(mCellSignalStrengthCdma).getAsuLevel();
- doReturn(0).when(mCellSignalStrengthLte).getDbm();
- doReturn(0).when(mCellSignalStrengthLte).getAsuLevel();
- doReturn(0).when(mCellSignalStrengthWcdma).getDbm();
- doReturn(0).when(mCellSignalStrengthWcdma).getAsuLevel();
-
- doReturn(null).when(mSignalStrength).getCellSignalStrengths();
doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
when(mTelephonyManager.getActiveModemCount()).thenReturn(MAX_PHONE_COUNT_SINGLE_SIM);
@@ -172,10 +146,7 @@
mPersistableBundle = new PersistableBundle();
when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mPersistableBundle);
- mPersistableBundle.putBoolean(
- CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL, true);
doReturn(mServiceState).when(mTelephonyManager).getServiceState();
- doReturn(mSignalStrength).when(mTelephonyManager).getSignalStrength();
}
@Test
@@ -218,7 +189,7 @@
@Test
@Ignore("b/337417520")
- public void initialize_updateServiceStateWithPowerOff_shouldUpdateTextAndResetSignalStrength() {
+ public void initialize_updateServiceStateWithPowerOff_shouldUpdateText() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
mController.initialize();
@@ -226,12 +197,11 @@
final String offServiceText = ResourcesUtils.getResourcesString(
mContext, "radioInfo_service_off");
verify(mDialog).setText(SERVICE_STATE_VALUE_ID, offServiceText);
- verify(mDialog).setText(SIGNAL_STRENGTH_VALUE_ID, "0");
}
@Test
@Ignore("b/337417520")
- public void initialize_updateVoiceDataOutOfService_shouldUpdateSettingAndResetSignalStrength() {
+ public void initialize_updateVoiceDataOutOfService_shouldUpdateSetting() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegistrationState()).thenReturn(
ServiceState.STATE_OUT_OF_SERVICE);
@@ -241,7 +211,6 @@
final String offServiceText = ResourcesUtils.getResourcesString(
mContext, "radioInfo_service_out");
verify(mDialog).setText(SERVICE_STATE_VALUE_ID, offServiceText);
- verify(mDialog).setText(SIGNAL_STRENGTH_VALUE_ID, "0");
}
@Test
@@ -257,52 +226,6 @@
}
@Test
- public void initialize_updateSignalStrengthWithLte50Wcdma40_shouldUpdateSignalStrengthTo50() {
- final int lteDbm = 50;
- final int lteAsu = 50;
- final int wcdmaDbm = 40;
- final int wcdmaAsu = 40;
- setupCellSignalStrength_lteWcdma(lteDbm, lteAsu, wcdmaDbm, wcdmaAsu);
-
- mController.initialize();
-
- final String signalStrengthString = ResourcesUtils.getResourcesString(
- mContext, "sim_signal_strength", lteDbm, lteAsu);
- verify(mDialog, times(2)).setText(SIGNAL_STRENGTH_VALUE_ID, signalStrengthString);
- }
-
- @Test
- public void initialize_updateSignalStrengthWithLte50Cdma30_shouldUpdateSignalStrengthTo50() {
- final int lteDbm = 50;
- final int lteAsu = 50;
- final int cdmaDbm = 30;
- final int cdmaAsu = 30;
- setupCellSignalStrength_lteCdma(lteDbm, lteAsu, cdmaDbm, cdmaAsu);
-
- mController.initialize();
-
- final String signalStrengthString = ResourcesUtils.getResourcesString(
- mContext, "sim_signal_strength", lteDbm, lteAsu);
- verify(mDialog, times(2)).setText(SIGNAL_STRENGTH_VALUE_ID, signalStrengthString);
- }
-
- @Test
- public void initialize_updateVoiceOutOfServiceDataInService_shouldUpdateSignalStrengthTo50() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
-
- final int lteDbm = 50;
- final int lteAsu = 50;
- setupCellSignalStrength_lteOnly(lteDbm, lteAsu);
-
- mController.initialize();
-
- final String signalStrengthString = ResourcesUtils.getResourcesString(
- mContext, "sim_signal_strength", lteDbm, lteAsu);
- verify(mDialog, times(2)).setText(SIGNAL_STRENGTH_VALUE_ID, signalStrengthString);
- }
-
- @Test
public void initialize_updateVoiceNetworkTypeWithEdge_shouldUpdateSettingToEdge() {
when(mTelephonyManager.getVoiceNetworkType()).thenReturn(
TelephonyManager.NETWORK_TYPE_EDGE);
@@ -358,24 +281,12 @@
}
@Test
- public void initialize_doNotShowSignalStrength_shouldRemoveSignalStrengthSetting() {
- mPersistableBundle.putBoolean(
- CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL, false);
-
- mController.initialize();
-
- verify(mDialog, times(2)).removeSettingFromScreen(SIGNAL_STRENGTH_LABEL_ID);
- verify(mDialog, times(2)).removeSettingFromScreen(SIGNAL_STRENGTH_VALUE_ID);
- }
-
- @Test
public void initialize_showSignalStrengthAndIccId_shouldShowSignalStrengthAndIccIdSetting() {
// getConfigForSubId is nullable, so make sure the default behavior is correct
when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(null);
mController.initialize();
- verify(mDialog, times(2)).setText(eq(SIGNAL_STRENGTH_VALUE_ID), any());
verify(mDialog).removeSettingFromScreen(ICCID_INFO_LABEL_ID);
verify(mDialog).removeSettingFromScreen(ICCID_INFO_VALUE_ID);
}
@@ -394,8 +305,6 @@
@Test
@Ignore
public void initialize_imsRegistered_shouldSetImsRegistrationStateSummaryToRegisterd() {
- mPersistableBundle.putBoolean(
- CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, true);
when(mTelephonyManager.isImsRegistered(anyInt())).thenReturn(true);
mController.initialize();
@@ -407,8 +316,6 @@
@Test
@Ignore
public void initialize_imsNotRegistered_shouldSetImsRegistrationStateSummaryToNotRegisterd() {
- mPersistableBundle.putBoolean(
- CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, true);
when(mTelephonyManager.isImsRegistered(anyInt())).thenReturn(false);
mController.initialize();
@@ -418,67 +325,19 @@
}
@Test
- public void initialize_showImsRegistration_shouldNotRemoveImsRegistrationStateSetting() {
- mPersistableBundle.putBoolean(
- CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, true);
-
+ @Ignore("b/337417520")
+ public void initialize_showImsRegistration_shouldShowImsRegistrationStateSetting() {
mController.initialize();
- verify(mDialog, never()).removeSettingFromScreen(IMS_REGISTRATION_STATE_VALUE_ID);
+ verify(mDialog).setSettingVisibility(IMS_REGISTRATION_STATE_VALUE_ID, true);
}
@Test
- public void initialize_doNotShowImsRegistration_shouldRemoveImsRegistrationStateSetting() {
- mPersistableBundle.putBoolean(
- CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
-
+ @Ignore("b/337417520")
+ public void initialize_doNotShowImsRegistration_shouldHideImsRegistrationStateSetting() {
mController.initialize();
- verify(mDialog).removeSettingFromScreen(IMS_REGISTRATION_STATE_LABEL_ID);
- verify(mDialog).removeSettingFromScreen(IMS_REGISTRATION_STATE_VALUE_ID);
- }
-
- @Test
- public void initialize_nullSignalStrength_noCrash() {
- doReturn(null).when(mTelephonyManager).getSignalStrength();
- // we should not crash when running the following line
- mController.initialize();
- }
-
- private void setupCellSignalStrength_lteWcdma(int lteDbm, int lteAsu, int wcdmaDbm,
- int wcdmaAsu) {
- doReturn(lteDbm).when(mCellSignalStrengthLte).getDbm();
- doReturn(lteAsu).when(mCellSignalStrengthLte).getAsuLevel();
- doReturn(wcdmaDbm).when(mCellSignalStrengthWcdma).getDbm();
- doReturn(wcdmaAsu).when(mCellSignalStrengthWcdma).getAsuLevel();
-
- List<CellSignalStrength> cellSignalStrengthList = new ArrayList<>(2);
- cellSignalStrengthList.add(mCellSignalStrengthLte);
- cellSignalStrengthList.add(mCellSignalStrengthWcdma);
-
- doReturn(cellSignalStrengthList).when(mSignalStrength).getCellSignalStrengths();
- }
-
- private void setupCellSignalStrength_lteCdma(int lteDbm, int lteAsu, int cdmaDbm, int cdmaAsu) {
- doReturn(lteDbm).when(mCellSignalStrengthLte).getDbm();
- doReturn(lteAsu).when(mCellSignalStrengthLte).getAsuLevel();
- doReturn(cdmaDbm).when(mCellSignalStrengthCdma).getDbm();
- doReturn(cdmaAsu).when(mCellSignalStrengthCdma).getAsuLevel();
-
- List<CellSignalStrength> cellSignalStrengthList = new ArrayList<>(2);
- cellSignalStrengthList.add(mCellSignalStrengthLte);
- cellSignalStrengthList.add(mCellSignalStrengthCdma);
-
- doReturn(cellSignalStrengthList).when(mSignalStrength).getCellSignalStrengths();
- }
-
- private void setupCellSignalStrength_lteOnly(int lteDbm, int lteAsu) {
- doReturn(lteDbm).when(mCellSignalStrengthLte).getDbm();
- doReturn(lteAsu).when(mCellSignalStrengthLte).getAsuLevel();
-
- List<CellSignalStrength> cellSignalStrengthList = new ArrayList<>(2);
- cellSignalStrengthList.add(mCellSignalStrengthLte);
-
- doReturn(cellSignalStrengthList).when(mSignalStrength).getCellSignalStrengths();
+ verify(mDialog).setSettingVisibility(IMS_REGISTRATION_STATE_LABEL_ID, false);
+ verify(mDialog).setSettingVisibility(IMS_REGISTRATION_STATE_VALUE_ID, false);
}
}
diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
index 522dcd5..36edfa0 100644
--- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
+++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
@@ -22,7 +22,7 @@
import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL;
import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL;
import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DISABLED_VAL;
-import static com.android.settings.privatespace.PrivateSpaceMaintainer.PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL;
+import static com.android.settings.privatespace.PrivateSpaceMaintainer.PRIVATE_SPACE_CREATE_AUTO_LOCK_VAL;
import static com.google.common.truth.Truth.assertThat;
@@ -336,9 +336,9 @@
privateSpaceMaintainer.deletePrivateSpace();
privateSpaceMaintainer.createPrivateSpace();
assertThat(privateSpaceMaintainer.getPrivateSpaceAutoLockSetting())
- .isEqualTo(PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL);
+ .isEqualTo(PRIVATE_SPACE_CREATE_AUTO_LOCK_VAL);
assertThat(Settings.Secure.getInt(mContentResolver, PRIVATE_SPACE_AUTO_LOCK, -1))
- .isEqualTo(PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL);
+ .isEqualTo(PRIVATE_SPACE_CREATE_AUTO_LOCK_VAL);
}
/**