Snap for 12002618 from b67c157e2f to 24Q4-release

Change-Id: I5e0a3b98210f88cafed1531de5c6cf9f12d4625a
This commit is contained in:
Android Build Coastguard Worker
2024-06-22 01:22:26 +00:00
90 changed files with 1077 additions and 1871 deletions

View File

@@ -125,6 +125,9 @@ android_library {
"telephony-common",
"ims-common",
],
flags_packages: [
"android.app.flags-aconfig",
],
}
platform_compat_config {
@@ -155,6 +158,9 @@ android_app {
optimize: {
proguard_flags_files: ["proguard.flags"],
},
flags_packages: [
"android.app.flags-aconfig",
],
}
android_library_import {

View File

@@ -1287,20 +1287,63 @@
</activity>
<activity
android:name="Settings$ZenModeSettingsActivity"
android:name="Settings$ModesSettingsActivity"
android:label="@string/zen_mode_settings_title"
android:icon="@drawable/ic_homepage_notification"
android:exported="true">
<intent-filter android:priority="1">
<intent-filter android:priority="1"
android:featureFlag="android.app.modes_ui">
<action android:name="android.settings.ZEN_MODE_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:priority="1">
<intent-filter android:priority="1"
android:featureFlag="android.app.modes_ui">
<action android:name="android.settings.ZEN_MODE_PRIORITY_SETTINGS" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:priority="41">
<intent-filter android:priority="41"
android:featureFlag="android.app.modes_ui">
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.settings.SHORTCUT" />
</intent-filter>
<intent-filter android:priority="10"
android:featureFlag="android.app.modes_ui">
<action android:name="android.settings.ZEN_MODE_AUTOMATION_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:priority="10"
android:featureFlag="android.app.modes_ui">
<action android:name="android.settings.ACTION_CONDITION_PROVIDER_SETTINGS" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.notification.modes.ZenModesListFragment"/>
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
android:value="@string/menu_key_notifications"/>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
<activity
android:name="Settings$ZenModeSettingsActivity"
android:label="@string/zen_mode_settings_title"
android:icon="@drawable/ic_homepage_notification"
android:exported="true">
<intent-filter android:priority="1"
android:featureFlag="!android.app.modes_ui">
<action android:name="android.settings.ZEN_MODE_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:priority="1"
android:featureFlag="!android.app.modes_ui">
<action android:name="android.settings.ZEN_MODE_PRIORITY_SETTINGS" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:priority="41"
android:featureFlag="!android.app.modes_ui">
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.settings.SHORTCUT" />
</intent-filter>
@@ -1312,6 +1355,20 @@
android:value="true" />
</activity>
<activity
android:name="Settings$ModeSettingsActivity"
android:exported="true">
<intent-filter android:priority="1"
android:featureFlag="android.app.modes_ui">
<action android:name="android.settings.AUTOMATIC_ZEN_RULE_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.notification.modes.ZenModeFragment"/>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
<activity
android:name=".notification.zen.ZenSuggestionActivity"
android:label="@string/zen_mode_settings_title"
@@ -1351,11 +1408,13 @@
android:label="@string/zen_mode_automation_settings_title"
android:icon="@drawable/ic_notifications"
android:exported="true">
<intent-filter android:priority="1">
<intent-filter android:priority="10"
android:featureFlag="!android.app.modes_ui">
<action android:name="android.settings.ZEN_MODE_AUTOMATION_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter android:priority="1">
<intent-filter android:priority="10"
android:featureFlag="!android.app.modes_ui">
<action android:name="android.settings.ACTION_CONDITION_PROVIDER_SETTINGS" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />

View File

@@ -1,28 +0,0 @@
<!--
Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c5.52,0 10,-4.48 10,-10C22,6.48 17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8c0,-4.41 3.59,-8 8,-8c4.41,0 8,3.59 8,8C20,16.41 16.41,20 12,20z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M7,11h10v2h-10z"/>
</vector>

View File

@@ -45,6 +45,6 @@
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/search_settings"/>
android:text="@string/homepage_search"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -69,8 +69,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="24dp"
android:paddingVertical="8dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">

View File

@@ -13604,4 +13604,7 @@
<!-- url for learning more about bluetooth audio sharing -->
<string name="help_url_audio_sharing" translatable="false"></string>
<!-- Text for Search bar of Settings home screen [CHAR LIMIT=34] -->
<string name="homepage_search">Search Settings</string>
</resources>

View File

@@ -85,13 +85,9 @@
android:summary="@string/auto_data_switch_summary"
settings:controller="com.android.settings.network.telephony.AutoDataSwitchPreferenceController"/>
<com.android.settingslib.RestrictedSwitchPreference
<com.android.settings.spa.preference.ComposePreference
android:key="button_roaming_key"
android:title="@string/roaming"
android:persistent="false"
android:summaryOn="@string/roaming_enable"
android:summaryOff="@string/roaming_disable"
settings:userRestriction="no_data_roaming"
settings:controller="com.android.settings.network.telephony.RoamingPreferenceController"/>
<Preference

View File

@@ -18,6 +18,8 @@ package com.android.settings;
import static android.provider.Settings.ACTION_PRIVACY_SETTINGS;
import android.annotation.FlaggedApi;
import android.app.Flags;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
@@ -317,11 +319,13 @@ public class Settings extends SettingsActivity {
public static class PrintSettingsActivity extends SettingsActivity { /* empty */ }
public static class PrintJobSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeBehaviorSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeBlockedEffectsSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeAutomationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeScheduleRuleSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeEventRuleSettingsActivity extends SettingsActivity { /* empty */ }
@FlaggedApi(Flags.FLAG_MODES_UI)
public static class ModeSettingsActivity extends SettingsActivity { /* empty */ }
@FlaggedApi(Flags.FLAG_MODES_UI)
public static class ModesSettingsActivity extends SettingsActivity { /* empty */ }
public static class SoundSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConfigureNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConversationListSettingsActivity extends SettingsActivity { /* empty */ }

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
@@ -42,6 +43,7 @@ public abstract class AccessibilityQuickSettingsPrimarySwitchPreferenceControlle
private boolean mNeedsQSTooltipReshow = false;
/** Returns the accessibility tile component name. */
@Nullable
abstract ComponentName getTileComponentName();
/** Returns the accessibility tile tooltip content. */

View File

@@ -29,6 +29,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -125,9 +126,14 @@ public class ReduceBrightColorsPreferenceController
mContext.getContentResolver().unregisterContentObserver(mSettingsContentObserver);
}
@Nullable
@Override
protected ComponentName getTileComponentName() {
return REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME;
// TODO: When clean up the feature flag, change the parent class from
// AccessibilityQuickSettingsPrimarySwitchPreferenceController to
// TogglePreferenceController
return android.view.accessibility.Flags.a11yQsShortcut()
? null : REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME;
}
@Override

View File

@@ -162,6 +162,8 @@ import com.android.settings.notification.app.AppNotificationSettings;
import com.android.settings.notification.app.ChannelNotificationSettings;
import com.android.settings.notification.app.ConversationListSettings;
import com.android.settings.notification.history.NotificationStation;
import com.android.settings.notification.modes.ZenModeFragment;
import com.android.settings.notification.modes.ZenModesListFragment;
import com.android.settings.notification.zen.ZenAccessSettings;
import com.android.settings.notification.zen.ZenModeAutomationSettings;
import com.android.settings.notification.zen.ZenModeBlockedEffectsSettings;
@@ -396,6 +398,8 @@ public class SettingsGateway {
CellularSecuritySettingsFragment.class.getName(),
AccessibilityHearingAidsFragment.class.getName(),
HearingDevicePairingFragment.class.getName(),
ZenModesListFragment.class.getName(),
ZenModeFragment.class.getName()
};
public static final String[] SETTINGS_FOR_RESTRICTED = {

View File

@@ -23,10 +23,10 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settings.network.telephony.CarrierConfigRepository
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
@@ -39,7 +39,9 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
class SimStatusDialogRepository @JvmOverloads constructor(
class SimStatusDialogRepository
@JvmOverloads
constructor(
private val context: Context,
private val simSlotRepository: SimSlotRepository = SimSlotRepository(context),
private val signalStrengthRepository: SignalStrengthRepository =
@@ -48,7 +50,7 @@ class SimStatusDialogRepository @JvmOverloads constructor(
ImsMmTelRepositoryImpl(context, subId)
},
) {
private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
private val carrierConfigRepository = CarrierConfigRepository(context)
data class SimStatusDialogInfo(
val signalStrength: String? = null,
@@ -73,7 +75,8 @@ class SimStatusDialogRepository @JvmOverloads constructor(
}
private fun simStatusDialogInfoBySlotFlow(simSlotIndex: Int): Flow<SimStatusDialogInfo> =
simSlotRepository.subIdInSimSlotFlow(simSlotIndex)
simSlotRepository
.subIdInSimSlotFlow(simSlotIndex)
.flatMapLatest { subId ->
if (SubscriptionManager.isValidSubscriptionId(subId)) {
simStatusDialogInfoFlow(subId)
@@ -99,22 +102,16 @@ class SimStatusDialogRepository @JvmOverloads constructor(
}
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
),
)
val visibility =
carrierConfigRepository.transformConfig(subId) {
SimStatusDialogVisibility(
signalStrengthShowUp =
getBoolean(
CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL),
imsRegisteredShowUp =
getBoolean(CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL),
)
}
emit(visibility)
}
}

View File

@@ -33,10 +33,10 @@ import androidx.room.RoomDatabase;
BatteryUsageSlotEntity.class,
BatteryReattributeEntity.class
},
version = 2)
version = 3)
public abstract class BatteryStateDatabase extends RoomDatabase {
private static final String TAG = "BatteryStateDatabase";
private static final String DB_FILE_NAME = "battery-usage-db-v10";
private static final String DB_FILE_NAME = "battery-usage-db-v11";
private static BatteryStateDatabase sBatteryStateDatabase;

View File

@@ -105,7 +105,8 @@ public class DndConditionCardController implements ConditionalCardController {
+ mAppContext.getText(R.string.condition_zen_title))
.setTitleText(mAppContext.getText(R.string.condition_zen_title).toString())
.setSummaryText(getSummary())
.setIconDrawable(mAppContext.getDrawable(R.drawable.ic_do_not_disturb_on_24dp))
.setIconDrawable(mAppContext.getDrawable(
com.android.settingslib.R.drawable.ic_do_not_disturb_on_24dp))
.setViewType(ConditionContextualCardRenderer.VIEW_TYPE_HALF_WIDTH)
.build();
}

View File

@@ -49,7 +49,6 @@ import com.android.settingslib.mobile.dataservice.MobileNetworkInfoDao;
import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoDao;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import com.android.settingslib.mobile.dataservice.UiccInfoDao;
import com.android.settingslib.mobile.dataservice.UiccInfoEntity;
import java.util.ArrayList;
@@ -81,12 +80,9 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
private SubscriptionManager mSubscriptionManager;
private MobileNetworkDatabase mMobileNetworkDatabase;
private SubscriptionInfoDao mSubscriptionInfoDao;
private UiccInfoDao mUiccInfoDao;
private MobileNetworkInfoDao mMobileNetworkInfoDao;
private List<SubscriptionInfoEntity> mAvailableSubInfoEntityList = new ArrayList<>();
private List<SubscriptionInfoEntity> mActiveSubInfoEntityList = new ArrayList<>();
private List<UiccInfoEntity> mUiccInfoEntityList = new ArrayList<>();
private List<MobileNetworkInfoEntity> mMobileNetworkInfoEntityList = new ArrayList<>();
private Context mContext;
private AirplaneModeObserver mAirplaneModeObserver;
private DataRoamingObserver mDataRoamingObserver;
@@ -124,7 +120,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_MOBILE_NETWORK_DB_CREATED);
mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
mSubscriptionInfoDao = mMobileNetworkDatabase.mSubscriptionInfoDao();
mUiccInfoDao = mMobileNetworkDatabase.mUiccInfoDao();
mMobileNetworkInfoDao = mMobileNetworkDatabase.mMobileNetworkInfoDao();
mAirplaneModeObserver = new AirplaneModeObserver(new Handler(Looper.getMainLooper()));
mDataRoamingObserver = new DataRoamingObserver(new Handler(Looper.getMainLooper()));
@@ -338,22 +333,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
lifecycleOwner, this::onAllMobileNetworkInfoChanged);
}
public List<SubscriptionInfoEntity> getAvailableSubInfoEntityList() {
return mAvailableSubInfoEntityList;
}
public List<SubscriptionInfoEntity> getActiveSubscriptionInfoList() {
return mActiveSubInfoEntityList;
}
public List<UiccInfoEntity> getUiccInfoEntityList() {
return mUiccInfoEntityList;
}
public List<MobileNetworkInfoEntity> getMobileNetworkInfoEntityList() {
return mMobileNetworkInfoEntityList;
}
public SubscriptionInfoEntity getSubInfoById(String subId) {
return mSubscriptionInfoDao.querySubInfoById(subId);
}
@@ -464,7 +443,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
}
private void onAllUiccInfoChanged(List<UiccInfoEntity> uiccInfoEntityList) {
mUiccInfoEntityList = new ArrayList<>(uiccInfoEntityList);
for (MobileNetworkCallback callback : sCallbacks) {
callback.onAllUiccInfoChanged(uiccInfoEntityList);
}
@@ -474,7 +452,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
private void onAllMobileNetworkInfoChanged(
List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
mMobileNetworkInfoEntityList = new ArrayList<>(mobileNetworkInfoEntityList);
for (MobileNetworkCallback callback : sCallbacks) {
callback.onAllMobileNetworkInfoChanged(mobileNetworkInfoEntityList);
}
@@ -515,8 +492,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
mMobileNetworkDatabase.deleteSubInfoBySubId(subId);
mMobileNetworkDatabase.deleteUiccInfoBySubId(subId);
mMobileNetworkDatabase.deleteMobileNetworkInfoBySubId(subId);
mUiccInfoEntityList.removeIf(info -> info.subId.equals(subId));
mMobileNetworkInfoEntityList.removeIf(info -> info.subId.equals(subId));
int id = Integer.parseInt(subId);
removerRegisterBySubId(id);
mSubscriptionInfoMap.remove(id);
@@ -741,7 +716,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
}
private class PhoneCallStateTelephonyCallback extends TelephonyCallback implements
TelephonyCallback.CallStateListener,
TelephonyCallback.UserMobileDataStateListener {
private int mSubId;
@@ -750,13 +724,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
mSubId = subId;
}
@Override
public void onCallStateChanged(int state) {
for (MobileNetworkCallback callback : sCallbacks) {
callback.onCallStateChanged(state);
}
}
@Override
public void onUserMobileDataStateChanged(boolean enabled) {
Log.d(TAG, "onUserMobileDataStateChanged enabled " + enabled + " on SUB " + mSubId);
@@ -793,9 +760,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
*/
default void onDataRoamingChanged(int subId, boolean enabled) {
}
default void onCallStateChanged(int state) {
}
}
public void dump(IndentingPrintWriter printwriter) {
@@ -803,8 +767,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
printwriter.increaseIndent();
printwriter.println(" availableSubInfoEntityList= " + mAvailableSubInfoEntityList);
printwriter.println(" activeSubInfoEntityList=" + mActiveSubInfoEntityList);
printwriter.println(" mobileNetworkInfoEntityList= " + mMobileNetworkInfoEntityList);
printwriter.println(" uiccInfoEntityList= " + mUiccInfoEntityList);
printwriter.println(" CacheSubscriptionInfoEntityMap= " + sCacheSubscriptionInfoEntityMap);
printwriter.println(" SubscriptionInfoMap= " + mSubscriptionInfoMap);
printwriter.flush();

View File

@@ -24,6 +24,7 @@ import androidx.core.os.persistableBundleOf
/**
* Gets the configuration values of the specified config keys applied.
*/
@Deprecated("Use CarrierConfigRepository instead")
fun CarrierConfigManager.safeGetConfig(
keys: List<String>,
subId: Int = SubscriptionManager.getDefaultSubscriptionId(),

View File

@@ -0,0 +1,217 @@
/*
* 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.network.telephony
import android.content.Context
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.util.Log
import androidx.annotation.VisibleForTesting
import java.util.concurrent.ConcurrentHashMap
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
class CarrierConfigRepository(private val context: Context) {
private val carrierConfigManager: CarrierConfigManager? =
context.getSystemService(CarrierConfigManager::class.java)
private enum class KeyType {
BOOLEAN,
INT,
STRING
}
interface CarrierConfigAccessor {
fun getBoolean(key: String): Boolean
fun getInt(key: String): Int
fun getString(key: String): String?
}
private class Accessor(private val cache: ConfigCache) : CarrierConfigAccessor {
private val keysToRetrieve = mutableMapOf<String, KeyType>()
override fun getBoolean(key: String): Boolean {
check(key.endsWith("_bool")) { "Boolean key should ends with _bool" }
val value = cache[key]
return if (value == null) {
keysToRetrieve += key to KeyType.BOOLEAN
DefaultConfig.getBoolean(key)
} else {
check(value is BooleanConfigValue) { "Boolean value type wrong" }
value.value
}
}
override fun getInt(key: String): Int {
check(key.endsWith("_int")) { "Int key should ends with _int" }
val value = cache[key]
return if (value == null) {
keysToRetrieve += key to KeyType.INT
DefaultConfig.getInt(key)
} else {
check(value is IntConfigValue) { "Int value type wrong" }
value.value
}
}
override fun getString(key: String): String? {
check(key.endsWith("_string")) { "String key should ends with _string" }
val value = cache[key]
return if (value == null) {
keysToRetrieve += key to KeyType.STRING
DefaultConfig.getString(key)
} else {
check(value is StringConfigValue) { "String value type wrong" }
value.value
}
}
fun getKeysToRetrieve(): Map<String, KeyType> = keysToRetrieve
}
/**
* Gets the configuration values for the given [subId].
*
* Configuration values could be accessed in [block]. Note: [block] could be called multiple
* times, so it should be pure function without side effort.
*/
fun <T> transformConfig(subId: Int, block: CarrierConfigAccessor.() -> T): T {
val perSubCache = getPerSubCache(subId)
val accessor = Accessor(perSubCache)
val result = accessor.block()
val keysToRetrieve = accessor.getKeysToRetrieve()
// If all keys found in the first pass, no need to collect again
if (keysToRetrieve.isEmpty()) return result
perSubCache.update(subId, keysToRetrieve)
return accessor.block()
}
/** Gets the configuration boolean for the given [subId] and [key]. */
fun getBoolean(subId: Int, key: String): Boolean = transformConfig(subId) { getBoolean(key) }
/** Gets the configuration int for the given [subId] and [key]. */
fun getInt(subId: Int, key: String): Int = transformConfig(subId) { getInt(key) }
/** Gets the configuration string for the given [subId] and [key]. */
fun getString(subId: Int, key: String): String? = transformConfig(subId) { getString(key) }
private fun ConfigCache.update(subId: Int, keysToRetrieve: Map<String, KeyType>) {
val config = safeGetConfig(subId, keysToRetrieve.keys) ?: return
for ((key, type) in keysToRetrieve) {
when (type) {
KeyType.BOOLEAN -> this[key] = BooleanConfigValue(config.getBoolean(key))
KeyType.INT -> this[key] = IntConfigValue(config.getInt(key))
KeyType.STRING -> this[key] = StringConfigValue(config.getString(key))
}
}
}
/** Gets the configuration values of the specified config keys applied. */
private fun safeGetConfig(subId: Int, keys: Collection<String>): PersistableBundle? {
if (carrierConfigManager == null || !SubscriptionManager.isValidSubscriptionId(subId)) {
return null
}
tryRegisterListener(context)
return try {
carrierConfigManager.getConfigForSubId(subId, *keys.toTypedArray())
} catch (e: Exception) {
Log.e(TAG, "safeGetConfig: exception", e)
// The CarrierConfigLoader (the service implemented the CarrierConfigManager) hasn't
// been initialized yet. This may occurs during very early phase of phone booting up
// or when Phone process has been restarted.
// Settings should not assume Carrier config loader (and any other system services
// as well) are always available. If not available, use default value instead.
null
}
}
companion object {
private const val TAG = "CarrierConfigRepository"
private val DefaultConfig = CarrierConfigManager.getDefaultConfig()
/** Cache of config values for each subscription. */
private val Cache = ConcurrentHashMap<Int, ConfigCache>()
private fun getPerSubCache(subId: Int) =
Cache.computeIfAbsent(subId) { ConcurrentHashMap() }
/** To make sure the registerCarrierConfigChangeListener is only called once. */
private val ListenerRegistered = atomic(false)
private fun tryRegisterListener(context: Context) {
if (ListenerRegistered.compareAndSet(expect = false, update = true)) {
val carrierConfigManager =
context.applicationContext.getSystemService(CarrierConfigManager::class.java)
if (carrierConfigManager != null) {
carrierConfigManager.registerCarrierConfigChangeListener()
} else {
ListenerRegistered.getAndSet(false)
}
}
}
private fun CarrierConfigManager.registerCarrierConfigChangeListener() {
val executor = Dispatchers.Default.asExecutor()
registerCarrierConfigChangeListener(executor) { _, subId, _, _ ->
Log.d(TAG, "[$subId] onCarrierConfigChanged")
Cache.remove(subId)
}
}
@VisibleForTesting
fun resetForTest() {
Cache.clear()
ListenerRegistered.getAndSet(false)
}
@VisibleForTesting
fun setBooleanForTest(subId: Int, key: String, value: Boolean) {
check(key.endsWith("_bool")) { "Boolean key should ends with _bool" }
getPerSubCache(subId)[key] = BooleanConfigValue(value)
}
@VisibleForTesting
fun setIntForTest(subId: Int, key: String, value: Int) {
check(key.endsWith("_int")) { "Int key should ends with _int" }
getPerSubCache(subId)[key] = IntConfigValue(value)
}
@VisibleForTesting
fun setStringForTest(subId: Int, key: String, value: String) {
check(key.endsWith("_string")) { "String key should ends with _string" }
getPerSubCache(subId)[key] = StringConfigValue(value)
}
}
}
private sealed interface ConfigValue
private data class BooleanConfigValue(val value: Boolean) : ConfigValue
private data class IntConfigValue(val value: Int) : ConfigValue
private data class StringConfigValue(val value: String?) : ConfigValue
private typealias ConfigCache = ConcurrentHashMap<String, ConfigValue>

View File

@@ -118,6 +118,18 @@ class MobileDataRepository(
}
}
/** Creates an instance of a cold Flow for whether data roaming is enabled of given [subId]. */
fun isDataRoamingEnabledFlow(subId: Int): Flow<Boolean> {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
val telephonyManager = context.telephonyManager(subId)
return mobileSettingsGlobalChangedFlow(Settings.Global.DATA_ROAMING, subId)
.map { telephonyManager.isDataRoamingEnabled }
.distinctUntilChanged()
.conflate()
.onEach { Log.d(TAG, "[$subId] isDataRoamingEnabledFlow: $it") }
.flowOn(Dispatchers.Default)
}
private companion object {
private const val TAG = "MobileDataRepository"
}

View File

@@ -79,7 +79,6 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
@VisibleForTesting
static final String KEY_CLICKED_PREF = "key_clicked_pref";
private static final String KEY_ROAMING_PREF = "button_roaming_key";
private static final String KEY_CALLS_PREF = "calls_preference";
private static final String KEY_SMS_PREF = "sms_preference";
private static final String KEY_MOBILE_DATA_PREF = "mobile_data_enable";
@@ -178,8 +177,6 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
return Arrays.asList(
new DataUsageSummaryPreferenceController(context, mSubId),
new RoamingPreferenceController(context, KEY_ROAMING_PREF, getSettingsLifecycle(),
this, mSubId),
new CallsDefaultSubscriptionController(context, KEY_CALLS_PREF,
getSettingsLifecycle(), this),
new SmsDefaultSubscriptionController(context, KEY_SMS_PREF, getSettingsLifecycle(),
@@ -263,8 +260,7 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
final RoamingPreferenceController roamingPreferenceController =
use(RoamingPreferenceController.class);
if (roamingPreferenceController != null) {
roamingPreferenceController.init(getFragmentManager(), mSubId,
mMobileNetworkInfoEntity);
roamingPreferenceController.init(getParentFragmentManager(), mSubId);
}
final SatelliteSettingPreferenceController satelliteSettingPreferenceController = use(
SatelliteSettingPreferenceController.class);

View File

@@ -1,215 +0,0 @@
/*
* Copyright (C) 2018 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.network.telephony;
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.content.Context;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.network.MobileNetworkRepository;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
import java.util.ArrayList;
import java.util.List;
/**
* Preference controller for "Roaming"
*/
public class RoamingPreferenceController extends TelephonyTogglePreferenceController implements
LifecycleObserver, MobileNetworkRepository.MobileNetworkCallback {
private static final String TAG = "RoamingController";
private static final String DIALOG_TAG = "MobileDataDialog";
private RestrictedSwitchPreference mSwitchPreference;
private TelephonyManager mTelephonyManager;
private CarrierConfigManager mCarrierConfigManager;
protected MobileNetworkRepository mMobileNetworkRepository;
protected LifecycleOwner mLifecycleOwner;
private List<MobileNetworkInfoEntity> mMobileNetworkInfoEntityList = new ArrayList<>();
@VisibleForTesting
FragmentManager mFragmentManager;
MobileNetworkInfoEntity mMobileNetworkInfoEntity;
public RoamingPreferenceController(Context context, String key, Lifecycle lifecycle,
LifecycleOwner lifecycleOwner, int subId) {
this(context, key);
mSubId = subId;
mLifecycleOwner = lifecycleOwner;
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
public RoamingPreferenceController(Context context, String key) {
super(context, key);
mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
}
@Override
public int getAvailabilityStatus() {
final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
if (carrierConfig != null && carrierConfig.getBoolean(
CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL)) {
return CONDITIONALLY_UNAVAILABLE;
}
return AVAILABLE;
}
@OnLifecycleEvent(ON_START)
public void onStart() {
mMobileNetworkRepository.addRegister(mLifecycleOwner, this, mSubId);
mMobileNetworkRepository.updateEntity();
}
@OnLifecycleEvent(ON_STOP)
public void onStop() {
mMobileNetworkRepository.removeRegister(this);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mSwitchPreference = screen.findPreference(getPreferenceKey());
}
@Override
public int getAvailabilityStatus(int subId) {
return mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
? AVAILABLE
: AVAILABLE_UNSEARCHABLE;
}
@Override
public boolean setChecked(boolean isChecked) {
if (isDialogNeeded()) {
showDialog();
} else {
// Update data directly if we don't need dialog
mTelephonyManager.setDataRoamingEnabled(isChecked);
return true;
}
return false;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
mSwitchPreference = (RestrictedSwitchPreference) preference;
update();
}
private void update() {
if (mSwitchPreference == null) {
return;
}
if (!mSwitchPreference.isDisabledByAdmin()) {
mSwitchPreference.setEnabled(mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mSwitchPreference.setChecked(isChecked());
}
}
@VisibleForTesting
boolean isDialogNeeded() {
final boolean isRoamingEnabled = mMobileNetworkInfoEntity == null ? false
: mMobileNetworkInfoEntity.isDataRoamingEnabled;
final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(
mSubId);
// Need dialog if we need to turn on roaming and the roaming charge indication is allowed
if (!isRoamingEnabled && (carrierConfig == null || !carrierConfig.getBoolean(
CarrierConfigManager.KEY_DISABLE_CHARGE_INDICATION_BOOL))) {
return true;
}
return false;
}
@Override
public boolean isChecked() {
return mMobileNetworkInfoEntity == null ? false
: mMobileNetworkInfoEntity.isDataRoamingEnabled;
}
public void init(FragmentManager fragmentManager, int subId, MobileNetworkInfoEntity entity) {
mFragmentManager = fragmentManager;
mSubId = subId;
mMobileNetworkInfoEntity = entity;
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return;
}
final TelephonyManager telephonyManager = mTelephonyManager
.createForSubscriptionId(mSubId);
if (telephonyManager == null) {
Log.w(TAG, "fail to init in sub" + mSubId);
mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
return;
}
mTelephonyManager = telephonyManager;
}
private void showDialog() {
final RoamingDialogFragment dialogFragment = RoamingDialogFragment.newInstance(mSubId);
dialogFragment.show(mFragmentManager, DIALOG_TAG);
}
@VisibleForTesting
public void setMobileNetworkInfoEntity(MobileNetworkInfoEntity mobileNetworkInfoEntity) {
mMobileNetworkInfoEntity = mobileNetworkInfoEntity;
}
@Override
public void onAllMobileNetworkInfoChanged(
List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
mMobileNetworkInfoEntityList = mobileNetworkInfoEntityList;
mMobileNetworkInfoEntityList.forEach(entity -> {
if (Integer.parseInt(entity.subId) == mSubId) {
mMobileNetworkInfoEntity = entity;
update();
refreshSummary(mSwitchPreference);
return;
}
});
}
@Override
public void onDataRoamingChanged(int subId, boolean enabled) {
if (subId != mSubId) {
Log.d(TAG, "onDataRoamingChanged - wrong subId : " + subId + " / " + enabled);
return;
}
update();
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.network.telephony
import android.content.Context
import android.os.UserManager
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
/** Preference controller for "Roaming" */
class RoamingPreferenceController
@JvmOverloads
constructor(
context: Context,
key: String,
private val mobileDataRepository: MobileDataRepository = MobileDataRepository(context),
) : ComposePreferenceController(context, key) {
@VisibleForTesting var fragmentManager: FragmentManager? = null
private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID
private var telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
private val carrierConfigRepository = CarrierConfigRepository(context)
fun init(fragmentManager: FragmentManager, subId: Int) {
this.fragmentManager = fragmentManager
this.subId = subId
telephonyManager = telephonyManager.createForSubscriptionId(subId)
}
override fun getAvailabilityStatus(): Int {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return CONDITIONALLY_UNAVAILABLE
val isForceHomeNetwork =
carrierConfigRepository.getBoolean(
subId, CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL)
return if (isForceHomeNetwork) CONDITIONALLY_UNAVAILABLE else AVAILABLE
}
@Composable
override fun Content() {
val summary = stringResource(R.string.roaming_enable)
val isDataRoamingEnabled by
remember { mobileDataRepository.isDataRoamingEnabledFlow(subId) }
.collectAsStateWithLifecycle(null)
RestrictedSwitchPreference(
model =
object : SwitchPreferenceModel {
override val title = stringResource(R.string.roaming)
override val summary = { summary }
override val checked = { isDataRoamingEnabled }
override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
if (newChecked && isDialogNeeded()) {
showDialog()
} else {
// Update data directly if we don't need dialog
telephonyManager.isDataRoamingEnabled = newChecked
}
}
},
restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_DATA_ROAMING)),
)
}
@VisibleForTesting
fun isDialogNeeded(): Boolean {
// Need dialog if we need to turn on roaming and the roaming charge indication is allowed
return !carrierConfigRepository.getBoolean(
subId, CarrierConfigManager.KEY_DISABLE_CHARGE_INDICATION_BOOL)
}
private fun showDialog() {
fragmentManager?.let { RoamingDialogFragment.newInstance(subId).show(it, DIALOG_TAG) }
}
companion object {
private const val DIALOG_TAG = "MobileDataDialog"
}
}

View File

@@ -26,6 +26,8 @@ import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.google.common.base.Preconditions;

View File

@@ -1,161 +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.notification.modes;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static java.util.Objects.requireNonNull;
import android.annotation.Nullable;
import android.app.AutomaticZenRule;
import android.content.Context;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.service.notification.SystemZenRules;
import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.content.res.AppCompatResources;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class IconLoader {
private static final String TAG = "ZenIconLoader";
private static final Drawable MISSING = new ColorDrawable();
@Nullable // Until first usage
private static IconLoader sInstance;
private final LruCache<String, Drawable> mCache;
private final ListeningExecutorService mBackgroundExecutor;
static IconLoader getInstance() {
if (sInstance == null) {
sInstance = new IconLoader();
}
return sInstance;
}
private IconLoader() {
this(Executors.newFixedThreadPool(4));
}
@VisibleForTesting
IconLoader(ExecutorService backgroundExecutor) {
mCache = new LruCache<>(50);
mBackgroundExecutor =
MoreExecutors.listeningDecorator(backgroundExecutor);
}
@NonNull
ListenableFuture<Drawable> getIcon(Context context, @NonNull AutomaticZenRule rule) {
if (rule.getIconResId() == 0) {
return Futures.immediateFuture(getFallbackIcon(context, rule.getType()));
}
return FluentFuture.from(loadIcon(context, rule.getPackageName(), rule.getIconResId()))
.transform(icon ->
icon != null ? icon : getFallbackIcon(context, rule.getType()),
MoreExecutors.directExecutor());
}
@NonNull
private ListenableFuture</* @Nullable */ Drawable> loadIcon(Context context, String pkg,
int iconResId) {
String cacheKey = pkg + ":" + iconResId;
synchronized (mCache) {
Drawable cachedValue = mCache.get(cacheKey);
if (cachedValue != null) {
return immediateFuture(cachedValue != MISSING ? cachedValue : null);
}
}
return FluentFuture.from(mBackgroundExecutor.submit(() -> {
if (TextUtils.isEmpty(pkg) || SystemZenRules.PACKAGE_ANDROID.equals(pkg)) {
return context.getDrawable(iconResId);
} else {
Context appContext = context.createPackageContext(pkg, 0);
Drawable appDrawable = AppCompatResources.getDrawable(appContext, iconResId);
return getMonochromeIconIfPresent(appDrawable);
}
})).catching(Exception.class, ex -> {
// If we cannot resolve the icon, then store MISSING in the cache below, so
// we don't try again.
Log.e(TAG, "Error while loading icon " + cacheKey, ex);
return null;
}, MoreExecutors.directExecutor()).transform(drawable -> {
synchronized (mCache) {
mCache.put(cacheKey, drawable != null ? drawable : MISSING);
}
return drawable;
}, MoreExecutors.directExecutor());
}
private static Drawable getFallbackIcon(Context context, int ruleType) {
int iconResIdFromType = switch (ruleType) {
case AutomaticZenRule.TYPE_UNKNOWN ->
com.android.internal.R.drawable.ic_zen_mode_type_unknown;
case AutomaticZenRule.TYPE_OTHER ->
com.android.internal.R.drawable.ic_zen_mode_type_other;
case AutomaticZenRule.TYPE_SCHEDULE_TIME ->
com.android.internal.R.drawable.ic_zen_mode_type_schedule_time;
case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR ->
com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar;
case AutomaticZenRule.TYPE_BEDTIME ->
com.android.internal.R.drawable.ic_zen_mode_type_bedtime;
case AutomaticZenRule.TYPE_DRIVING ->
com.android.internal.R.drawable.ic_zen_mode_type_driving;
case AutomaticZenRule.TYPE_IMMERSIVE ->
com.android.internal.R.drawable.ic_zen_mode_type_immersive;
case AutomaticZenRule.TYPE_THEATER ->
com.android.internal.R.drawable.ic_zen_mode_type_theater;
case AutomaticZenRule.TYPE_MANAGED ->
com.android.internal.R.drawable.ic_zen_mode_type_managed;
default ->
com.android.internal.R.drawable.ic_zen_mode_type_unknown;
};
return requireNonNull(context.getDrawable(iconResIdFromType));
}
private static Drawable getMonochromeIconIfPresent(Drawable icon) {
// For created rules, the app should've provided a monochrome Drawable. However, implicit
// rules have the app's icon, which is not -- but might have a monochrome layer. Thus
// we choose it, if present.
if (icon instanceof AdaptiveIconDrawable adaptiveIcon) {
if (adaptiveIcon.getMonochrome() != null) {
// Wrap with negative inset => scale icon (inspired from BaseIconFactory)
return new InsetDrawable(adaptiveIcon.getMonochrome(),
-2.0f * AdaptiveIconDrawable.getExtraInsetFraction());
}
}
return icon;
}
}

View File

@@ -26,6 +26,8 @@ import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class InterruptionFilterPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {

View File

@@ -1,257 +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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import android.annotation.SuppressLint;
import android.app.AutomaticZenRule;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.service.notification.SystemZenRules;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Objects;
/**
* Represents either an {@link AutomaticZenRule} or the manual DND rule in a unified way.
*
* <p>It also adapts other rule features that we don't want to expose in the UI, such as
* interruption filters other than {@code PRIORITY}, rules without specific icons, etc.
*/
class ZenMode {
private static final String TAG = "ZenMode";
static final String MANUAL_DND_MODE_ID = "manual_dnd";
// Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
private static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALARMS =
new ZenPolicy.Builder()
.disallowAllSounds()
.allowAlarms(true)
.allowMedia(true)
.allowPriorityChannels(false)
.build();
// Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
private static final ZenPolicy POLICY_INTERRUPTION_FILTER_NONE =
new ZenPolicy.Builder()
.disallowAllSounds()
.hideAllVisualEffects()
.allowPriorityChannels(false)
.build();
private final String mId;
private AutomaticZenRule mRule;
private final boolean mIsActive;
private final boolean mIsManualDnd;
ZenMode(String id, AutomaticZenRule rule, boolean isActive) {
this(id, rule, isActive, false);
}
private ZenMode(String id, AutomaticZenRule rule, boolean isActive, boolean isManualDnd) {
mId = id;
mRule = rule;
mIsActive = isActive;
mIsManualDnd = isManualDnd;
}
static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
return new ZenMode(MANUAL_DND_MODE_ID, manualRule, isActive, true);
}
@NonNull
public String getId() {
return mId;
}
@NonNull
public AutomaticZenRule getRule() {
return mRule;
}
@NonNull
public ListenableFuture<Drawable> getIcon(@NonNull Context context,
@NonNull IconLoader iconLoader) {
if (mIsManualDnd) {
return Futures.immediateFuture(requireNonNull(
context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
}
return iconLoader.getIcon(context, mRule);
}
@NonNull
public ZenPolicy getPolicy() {
switch (mRule.getInterruptionFilter()) {
case INTERRUPTION_FILTER_PRIORITY:
case NotificationManager.INTERRUPTION_FILTER_ALL:
return requireNonNull(mRule.getZenPolicy());
case NotificationManager.INTERRUPTION_FILTER_ALARMS:
return POLICY_INTERRUPTION_FILTER_ALARMS;
case NotificationManager.INTERRUPTION_FILTER_NONE:
return POLICY_INTERRUPTION_FILTER_NONE;
case NotificationManager.INTERRUPTION_FILTER_UNKNOWN:
default:
Log.wtf(TAG, "Rule " + mId + " with unexpected interruptionFilter "
+ mRule.getInterruptionFilter());
return requireNonNull(mRule.getZenPolicy());
}
}
/**
* Updates the {@link ZenPolicy} of the associated {@link AutomaticZenRule} based on the
* supplied policy. In some cases this involves conversions, so that the following call
* to {@link #getPolicy} might return a different policy from the one supplied here.
*/
@SuppressLint("WrongConstant")
public void setPolicy(@NonNull ZenPolicy policy) {
ZenPolicy currentPolicy = getPolicy();
if (currentPolicy.equals(policy)) {
return;
}
if (mRule.getInterruptionFilter() == INTERRUPTION_FILTER_ALL) {
Log.wtf(TAG, "Able to change policy without filtering being enabled");
}
// If policy is customized from any of the "special" ones, make the rule PRIORITY.
if (mRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) {
mRule.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
}
mRule.setZenPolicy(policy);
}
@NonNull
public ZenDeviceEffects getDeviceEffects() {
return mRule.getDeviceEffects() != null
? mRule.getDeviceEffects()
: new ZenDeviceEffects.Builder().build();
}
public void setCustomModeConditionId(Context context, Uri conditionId) {
checkState(SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName()),
"Trying to change condition of non-system-owned rule %s (to %s)",
mRule, conditionId);
Uri oldCondition = mRule.getConditionId();
mRule.setConditionId(conditionId);
ZenModeConfig.ScheduleInfo scheduleInfo = tryParseScheduleConditionId(conditionId);
if (scheduleInfo != null) {
mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_TIME);
mRule.setOwner(ZenModeConfig.getScheduleConditionProvider());
mRule.setTriggerDescription(
getTriggerDescriptionForScheduleTime(context, scheduleInfo));
return;
}
ZenModeConfig.EventInfo eventInfo = tryParseEventConditionId(conditionId);
if (eventInfo != null) {
mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR);
mRule.setOwner(ZenModeConfig.getEventConditionProvider());
mRule.setTriggerDescription(getTriggerDescriptionForScheduleEvent(context, eventInfo));
return;
}
if (ZenModeConfig.isValidCustomManualConditionId(conditionId)) {
mRule.setType(AutomaticZenRule.TYPE_OTHER);
mRule.setOwner(ZenModeConfig.getCustomManualConditionProvider());
mRule.setTriggerDescription("");
return;
}
Log.wtf(TAG, String.format(
"Changed condition of rule %s (%s -> %s) but cannot recognize which kind of "
+ "condition it was!",
mRule, oldCondition, conditionId));
}
public boolean canEditName() {
return !isManualDnd();
}
public boolean canEditIcon() {
return !isManualDnd();
}
public boolean canBeDeleted() {
return !mIsManualDnd;
}
public boolean isManualDnd() {
return mIsManualDnd;
}
public boolean isActive() {
return mIsActive;
}
public boolean isSystemOwned() {
return SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName());
}
@AutomaticZenRule.Type
public int getType() {
return mRule.getType();
}
@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof ZenMode other
&& mId.equals(other.mId)
&& mRule.equals(other.mRule)
&& mIsActive == other.mIsActive;
}
@Override
public int hashCode() {
return Objects.hash(mId, mRule, mIsActive);
}
@Override
public String toString() {
return mId + "(" + (mIsActive ? "active" : "inactive") + ") -> " + mRule;
}
}

View File

@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -27,6 +27,8 @@ import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.ActionButtonsPreference;
class ZenModeActionsPreferenceController extends AbstractZenModePreferenceController {
@@ -50,7 +52,7 @@ class ZenModeActionsPreferenceController extends AbstractZenModePreferenceContro
buttonsPreference.setButton2Enabled(zenMode.canEditIcon());
buttonsPreference.setButton2OnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, zenMode.getId());
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
new SubSettingLauncher(mContext)
.setDestination(ZenModeIconPickerFragment.class.getName())
// TODO: b/332937635 - Update metrics category

View File

@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -32,6 +31,8 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import java.util.ArrayList;
import java.util.HashMap;
@@ -72,7 +73,7 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, zenMode.getId());
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeAppsFragment.class.getName())
@@ -129,11 +130,13 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
return appsBypassingDnd;
}
@VisibleForTesting final ApplicationsState.Callbacks mAppSessionCallbacks =
@VisibleForTesting
final ApplicationsState.Callbacks mAppSessionCallbacks =
new ApplicationsState.Callbacks() {
@Override
public void onRunningStateChanged(boolean running) { }
public void onRunningStateChanged(boolean running) {
}
@Override
public void onPackageListChanged() {
@@ -146,16 +149,20 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
}
@Override
public void onPackageIconChanged() { }
public void onPackageIconChanged() {
}
@Override
public void onPackageSizeChanged(String packageName) { }
public void onPackageSizeChanged(String packageName) {
}
@Override
public void onAllSizesComputed() { }
public void onAllSizesComputed() {
}
@Override
public void onLauncherInfoChanged() { }
public void onLauncherInfoChanged() {
}
@Override
public void onLoadEntriesCompleted() {

View File

@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -31,6 +31,8 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
public class ZenModeAppsPreferenceController extends
@@ -103,7 +105,7 @@ public class ZenModeAppsPreferenceController extends
private void launchPrioritySettings() {
Bundle bundle = new Bundle();
if (mModeId != null) {
bundle.putString(MODE_ID, mModeId);
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, mModeId);
}
// TODO(b/332937635): Update metrics category
new SubSettingLauncher(mContext)

View File

@@ -23,6 +23,8 @@ import android.widget.Button;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
public class ZenModeButtonPreferenceController extends AbstractZenModePreferenceController {

View File

@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -25,6 +25,8 @@ import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
@@ -39,7 +41,7 @@ class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceCont
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, zenMode.getId());
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeCallsFragment.class.getName())

View File

@@ -23,6 +23,9 @@ import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
public class ZenModeDisplayEffectPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {

View File

@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -25,6 +25,8 @@ import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeDisplayLinkPreferenceController extends AbstractZenModePreferenceController {
@@ -39,7 +41,7 @@ class ZenModeDisplayLinkPreferenceController extends AbstractZenModePreferenceCo
@Override
void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, zenMode.getId());
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeDisplayFragment.class.getName())

View File

@@ -23,6 +23,9 @@ import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference controller controlling whether a time schedule-based mode ends at the next alarm.
*/

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
import java.util.ArrayList;
import java.util.List;

View File

@@ -16,6 +16,8 @@
package com.android.settings.notification.modes;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.app.AutomaticZenRule;
import android.content.Context;
import android.os.Bundle;
@@ -29,6 +31,7 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
import java.util.List;
@@ -37,7 +40,6 @@ import java.util.List;
*/
abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
static final String TAG = "ZenModeSettings";
static final String MODE_ID = "MODE_ID";
@Nullable // only until reloadMode() is called
private ZenMode mZenMode;
@@ -46,17 +48,21 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
public void onAttach(@NonNull Context context) {
super.onAttach(context);
// TODO: b/322373473 - Update if modes page ends up using a different method of passing id
String id = null;
if (getActivity() != null && getActivity().getIntent() != null) {
id = getActivity().getIntent().getStringExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID);
}
Bundle bundle = getArguments();
if (bundle != null && bundle.containsKey(MODE_ID)) {
String id = bundle.getString(MODE_ID);
if (!reloadMode(id)) {
Log.e(TAG, "Mode id " + id + " not found");
toastAndFinish();
return;
}
} else {
Log.e(TAG, "Mode id required to set mode config settings");
if (id == null && bundle != null && bundle.containsKey(EXTRA_AUTOMATIC_ZEN_RULE_ID)) {
id = bundle.getString(EXTRA_AUTOMATIC_ZEN_RULE_ID);
}
if (id == null) {
Log.d(TAG, "No id provided");
toastAndFinish();
return;
}
if (!reloadMode(id)) {
Log.d(TAG, "Mode id " + id + " not found");
toastAndFinish();
return;
}

View File

@@ -25,6 +25,9 @@ import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
class ZenModeHeaderController extends AbstractZenModePreferenceController {
@@ -62,7 +65,7 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
}
FutureUtil.whenDone(
zenMode.getIcon(mContext, IconLoader.getInstance()),
zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
icon -> mHeaderController.setIcon(IconUtil.applyTint(mContext, icon))
.done(/* rebindActions= */ false),
mContext.getMainExecutor());

View File

@@ -25,6 +25,9 @@ import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenceController {
@@ -51,7 +54,7 @@ class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenc
}
FutureUtil.whenDone(
zenMode.getIcon(mContext, IconLoader.getInstance()),
zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
icon -> mHeaderController.setIcon(IconUtil.applyTint(mContext, icon))
.done(/* rebindActions= */ false),
mContext.getMainExecutor());

View File

@@ -33,6 +33,8 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList;

View File

@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -25,6 +25,8 @@ import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
@@ -38,7 +40,7 @@ class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceC
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, zenMode.getId());
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeMessagesFragment.class.getName())

View File

@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -27,6 +26,8 @@ import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceController {
@@ -46,7 +47,7 @@ class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceC
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, zenMode.getId());
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeNotifVisFragment.class.getName())

View File

@@ -21,19 +21,21 @@ import android.service.notification.ZenPolicy;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
import com.android.settings.widget.DisabledCheckBoxPreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
public class ZenModeNotifVisPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
@VisibleForTesting protected @ZenPolicy.VisualEffect int mEffect;
@VisibleForTesting
protected @ZenPolicy.VisualEffect int mEffect;
// if any of these effects are suppressed, this effect must be too
@VisibleForTesting protected @ZenPolicy.VisualEffect int[] mParentSuppressedEffects;
@VisibleForTesting
protected @ZenPolicy.VisualEffect int[] mParentSuppressedEffects;
public ZenModeNotifVisPreferenceController(Context context, String key,
@ZenPolicy.VisualEffect int visualEffect,

View File

@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -27,6 +26,8 @@ import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference with a link and summary about what other sounds can break through the mode
@@ -49,7 +50,7 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, zenMode.getId());
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeOtherFragment.class.getName())
.setSourceMetricsCategory(0)

View File

@@ -28,6 +28,9 @@ import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeOtherPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {

View File

@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -27,6 +26,8 @@ import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference with a link and summary about what calls and messages can break through the mode
@@ -49,7 +50,7 @@ class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceCon
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, zenMode.getId());
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModePeopleFragment.class.getName())

View File

@@ -46,6 +46,8 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.app.ConversationListSettings;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import java.util.ArrayList;

View File

@@ -26,6 +26,8 @@ import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeRepeatCallersPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {

View File

@@ -31,6 +31,8 @@ import androidx.preference.PreferenceCategory;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import java.util.ArrayList;
import java.util.Arrays;

View File

@@ -31,6 +31,8 @@ import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import java.text.SimpleDateFormat;

View File

@@ -29,6 +29,8 @@ import androidx.preference.PreferenceCategory;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference controller for the link to an individual mode's configuration page.
@@ -63,7 +65,6 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
}
switchPref.setChecked(zenMode.getRule().isEnabled());
switchPref.setOnPreferenceChangeListener(mSwitchChangeListener);
switchPref.setSummary(zenMode.getRule().getTriggerDescription());
switchPref.setIcon(null);
switchPref.setOnPreferenceClickListener(null);

View File

@@ -48,6 +48,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import java.util.ArrayList;
import java.util.Arrays;
@@ -193,7 +194,7 @@ class ZenModeSummaryHelper {
enabledEffects.add(getBlockedEffectsSummary(zenMode));
isFirst = false;
}
ZenDeviceEffects currEffects = zenMode.getRule().getDeviceEffects();
ZenDeviceEffects currEffects = zenMode.getRule().getDeviceEffects();
if (currEffects != null) {
if (currEffects.shouldDisplayGrayscale()) {
if (isFirst) {

View File

@@ -1,207 +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.notification.modes;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AutomaticZenRule;
import android.app.NotificationManager;
import android.content.Context;
import android.net.Uri;
import android.provider.Settings;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.util.Log;
import com.android.settings.R;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Class used for Settings-NMS interactions related to Mode management.
*
* <p>This class converts {@link AutomaticZenRule} instances, as well as the manual zen mode,
* into the unified {@link ZenMode} format.
*/
class ZenModesBackend {
private static final String TAG = "ZenModeBackend";
@Nullable // Until first usage
private static ZenModesBackend sInstance;
private final NotificationManager mNotificationManager;
private final Context mContext;
static ZenModesBackend getInstance(Context context) {
if (sInstance == null) {
sInstance = new ZenModesBackend(context.getApplicationContext());
}
return sInstance;
}
ZenModesBackend(Context context) {
mContext = context;
mNotificationManager = context.getSystemService(NotificationManager.class);
}
List<ZenMode> getModes() {
ArrayList<ZenMode> modes = new ArrayList<>();
ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig();
modes.add(getManualDndMode(currentConfig));
Map<String, AutomaticZenRule> zenRules = mNotificationManager.getAutomaticZenRules();
for (Map.Entry<String, AutomaticZenRule> zenRuleEntry : zenRules.entrySet()) {
String ruleId = zenRuleEntry.getKey();
modes.add(new ZenMode(ruleId, zenRuleEntry.getValue(),
isRuleActive(ruleId, currentConfig)));
}
modes.sort((l, r) -> {
if (l.isManualDnd()) {
return -1;
} else if (r.isManualDnd()) {
return 1;
}
return l.getRule().getName().compareTo(r.getRule().getName());
});
return modes;
}
@Nullable
ZenMode getMode(String id) {
ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig();
if (ZenMode.MANUAL_DND_MODE_ID.equals(id)) {
return getManualDndMode(currentConfig);
} else {
AutomaticZenRule rule = mNotificationManager.getAutomaticZenRule(id);
if (rule == null) {
return null;
}
return new ZenMode(id, rule, isRuleActive(id, currentConfig));
}
}
private ZenMode getManualDndMode(ZenModeConfig config) {
ZenModeConfig.ZenRule manualRule = config.manualRule;
// TODO: b/333682392 - Replace with final strings for name & trigger description
AutomaticZenRule manualDndRule = new AutomaticZenRule.Builder(
mContext.getString(R.string.zen_mode_settings_title), manualRule.conditionId)
.setType(manualRule.type)
.setZenPolicy(manualRule.zenPolicy)
.setDeviceEffects(manualRule.zenDeviceEffects)
.setManualInvocationAllowed(manualRule.allowManualInvocation)
.setConfigurationActivity(null) // No further settings
.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
.build();
return ZenMode.manualDndMode(manualDndRule, config != null && config.isManualActive());
}
private static boolean isRuleActive(String id, ZenModeConfig config) {
if (config == null) {
// shouldn't happen if the config is coming from NM, but be safe
return false;
}
ZenModeConfig.ZenRule configRule = config.automaticRules.get(id);
return configRule != null && configRule.isAutomaticActive();
}
void updateMode(ZenMode mode) {
if (mode.isManualDnd()) {
try {
NotificationManager.Policy dndPolicy =
new ZenModeConfig().toNotificationPolicy(mode.getPolicy());
mNotificationManager.setNotificationPolicy(dndPolicy, /* fromUser= */ true);
mNotificationManager.setManualZenRuleDeviceEffects(
mode.getRule().getDeviceEffects());
} catch (Exception e) {
Log.w(TAG, "Error updating manual mode", e);
}
} else {
mNotificationManager.updateAutomaticZenRule(mode.getId(), mode.getRule(),
/* fromUser= */ true);
}
}
void activateMode(ZenMode mode, @Nullable Duration forDuration) {
if (mode.isManualDnd()) {
Uri durationConditionId = null;
if (forDuration != null) {
durationConditionId = ZenModeConfig.toTimeCondition(mContext,
(int) forDuration.toMinutes(), ActivityManager.getCurrentUser(), true).id;
}
mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
durationConditionId, TAG, /* fromUser= */ true);
} else {
if (forDuration != null) {
throw new IllegalArgumentException(
"Only the manual DND mode can be activated for a specific duration");
}
mNotificationManager.setAutomaticZenRuleState(mode.getId(),
new Condition(mode.getRule().getConditionId(), "", Condition.STATE_TRUE,
Condition.SOURCE_USER_ACTION));
}
}
void deactivateMode(ZenMode mode) {
if (mode.isManualDnd()) {
// When calling with fromUser=true this will not snooze other modes.
mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG,
/* fromUser= */ true);
} else {
// TODO: b/333527800 - This should (potentially) snooze the rule if it was active.
mNotificationManager.setAutomaticZenRuleState(mode.getId(),
new Condition(mode.getRule().getConditionId(), "", Condition.STATE_FALSE,
Condition.SOURCE_USER_ACTION));
}
}
void removeMode(ZenMode mode) {
if (!mode.canBeDeleted()) {
throw new IllegalArgumentException("Mode " + mode + " cannot be deleted!");
}
mNotificationManager.removeAutomaticZenRule(mode.getId(), /* fromUser= */ true);
}
/**
* Creates a new custom mode with the provided {@code name}. The mode will be "manual" (i.e.
* not have a schedule), this can be later updated by the user in the mode settings page.
*
* @return the created mode. Only {@code null} if creation failed due to an internal error
*/
@Nullable
ZenMode addCustomMode(String name) {
AutomaticZenRule rule = new AutomaticZenRule.Builder(name,
ZenModeConfig.toCustomManualConditionId())
.setPackage(ZenModeConfig.getCustomManualConditionProvider().getPackageName())
.setType(AutomaticZenRule.TYPE_OTHER)
.setOwner(ZenModeConfig.getCustomManualConditionProvider())
.setManualInvocationAllowed(true)
.build();
String ruleId = mNotificationManager.addAutomaticZenRule(rule);
return getMode(ruleId);
}
}

View File

@@ -27,6 +27,7 @@ import android.provider.Settings.Global;
import android.util.Log;
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Base class for all Settings pages controlling Modes behavior.

View File

@@ -22,6 +22,8 @@ import androidx.preference.Preference;
import com.android.settings.utils.ZenServiceListing;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import java.util.Random;

View File

@@ -29,6 +29,7 @@ import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.utils.ManagedServiceSettings;
import com.android.settings.utils.ZenServiceListing;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.search.SearchIndexable;
import com.google.common.collect.ImmutableList;

View File

@@ -18,6 +18,8 @@ package com.android.settings.notification.modes;
import android.content.Context;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
/**
* Preference representing a single mode item on the modes aggregator page. Clicking on this
@@ -46,7 +48,7 @@ class ZenModesListItemPreference extends RestrictedPreference {
setIconSize(ICON_SIZE_SMALL);
FutureUtil.whenDone(
mZenMode.getIcon(mContext, IconLoader.getInstance()),
mZenMode.getIcon(mContext, ZenIconLoader.getInstance()),
icon -> setIcon(IconUtil.applyTint(mContext, icon)),
mContext.getMainExecutor());
}

View File

@@ -27,6 +27,8 @@ import androidx.preference.PreferenceCategory;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.HashMap;

View File

@@ -16,6 +16,8 @@
package com.android.settings.notification.modes;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
@@ -33,7 +35,7 @@ class ZenSubSettingLauncher {
Class<? extends ZenModeFragmentBase> fragmentClass, String modeId,
int sourceMetricsCategory) {
Bundle bundle = new Bundle();
bundle.putString(ZenModeFragmentBase.MODE_ID, modeId);
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, modeId);
return new SubSettingLauncher(context)
.setDestination(fragmentClass.getName())

View File

@@ -116,7 +116,7 @@ public class ZenModeBackend {
ActivityManager.getCurrentUser(), true).id;
if (android.app.Flags.modesApi()) {
mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
conditionId, TAG, /* fromUser= */ true);
conditionId, TAG, /* fromUser= */ true);
} else {
mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
conditionId, TAG);
@@ -241,12 +241,14 @@ public class ZenModeBackend {
}
savePolicy(getNewDefaultPriorityCategories(allowSenders, category),
priorityCallSenders, priorityMessagesSenders, mPolicy.suppressedVisualEffects,
priorityCallSenders, priorityMessagesSenders, mPolicy.suppressedVisualEffects,
mPolicy.priorityConversationSenders);
if (ZenModeSettingsBase.DEBUG) Log.d(TAG, "onPrefChange allow" +
stringCategory + "=" + allowSenders + " allow" + stringCategory + "From="
+ ZenModeConfig.sourceToString(allowSendersFrom));
if (ZenModeSettingsBase.DEBUG) {
Log.d(TAG, "onPrefChange allow"
+ stringCategory + "=" + allowSenders + " allow" + stringCategory + "From="
+ ZenModeConfig.sourceToString(allowSendersFrom));
}
}
protected void saveConversationSenders(int val) {
@@ -280,7 +282,7 @@ public class ZenModeBackend {
switch (contactType) {
case ZenPolicy.PEOPLE_TYPE_ANYONE:
return ZEN_MODE_FROM_ANYONE;
case ZenPolicy.PEOPLE_TYPE_CONTACTS:
case ZenPolicy.PEOPLE_TYPE_CONTACTS:
return ZEN_MODE_FROM_CONTACTS;
case ZenPolicy.PEOPLE_TYPE_STARRED:
return ZEN_MODE_FROM_STARRED;
@@ -308,7 +310,7 @@ public class ZenModeBackend {
switch (setting) {
case ZenPolicy.PEOPLE_TYPE_ANYONE:
return NotificationManager.Policy.PRIORITY_SENDERS_ANY;
case ZenPolicy.PEOPLE_TYPE_CONTACTS:
case ZenPolicy.PEOPLE_TYPE_CONTACTS:
return NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS;
case ZenPolicy.PEOPLE_TYPE_STARRED:
return NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
@@ -321,7 +323,7 @@ public class ZenModeBackend {
protected int getAlarmsTotalSilencePeopleSummary(int category) {
if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) {
return R.string.zen_mode_none_messages;
} else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS){
} else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) {
return R.string.zen_mode_none_calls;
} else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS) {
return R.string.zen_mode_from_no_conversations;
@@ -470,8 +472,8 @@ public class ZenModeBackend {
if (cursor != null && cursor.moveToFirst()) {
do {
String contact = cursor.getString(0);
starredContacts.add(contact != null ? contact :
mContext.getString(R.string.zen_mode_starred_contacts_empty_name));
int emptyNameId = R.string.zen_mode_starred_contacts_empty_name;
starredContacts.add(contact != null ? contact : mContext.getString(emptyNameId));
} while (cursor.moveToNext());
}

View File

@@ -132,8 +132,8 @@ public class ZenModeSliceBuilder {
final Uri contentUri = new Uri.Builder().appendPath(ZEN_MODE_SLICE_KEY).build();
final String screenTitle = context.getText(R.string.zen_mode_settings_title).toString();
return SliceBuilderUtils.buildSearchResultPageIntent(context,
ZenModeSettings.class.getName(), ZEN_MODE_SLICE_KEY, screenTitle,
SettingsEnums.NOTIFICATION_ZEN_MODE, R.string.menu_key_notifications)
ZenModeSettings.class.getName(), ZEN_MODE_SLICE_KEY, screenTitle,
SettingsEnums.NOTIFICATION_ZEN_MODE, R.string.menu_key_notifications)
.setClassName(context.getPackageName(), SubSettings.class.getName())
.setData(contentUri);
}

View File

@@ -1,91 +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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static com.google.common.truth.Truth.assertThat;
import android.app.AutomaticZenRule;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.service.notification.ZenPolicy;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class IconLoaderTest {
private Context mContext;
private IconLoader mLoader;
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
mLoader = new IconLoader(MoreExecutors.newDirectExecutorService());
}
@Test
public void getIcon_systemOwnedRuleWithIcon_loads() throws Exception {
AutomaticZenRule systemRule = newRuleBuilder()
.setPackage("android")
.setIconResId(android.R.drawable.ic_media_play)
.build();
ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, systemRule);
assertThat(loadFuture.isDone()).isTrue();
assertThat(loadFuture.get()).isNotNull();
}
@Test
public void getIcon_ruleWithoutSpecificIcon_loadsFallback() throws Exception {
AutomaticZenRule rule = newRuleBuilder()
.setType(AutomaticZenRule.TYPE_DRIVING)
.setPackage("com.blah")
.build();
ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, rule);
assertThat(loadFuture.isDone()).isTrue();
assertThat(loadFuture.get()).isNotNull();
}
@Test
public void getIcon_ruleWithAppIconWithLoadFailure_loadsFallback() throws Exception {
AutomaticZenRule rule = newRuleBuilder()
.setType(AutomaticZenRule.TYPE_DRIVING)
.setPackage("com.blah")
.setIconResId(-123456)
.build();
ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, rule);
assertThat(loadFuture.get()).isNotNull();
}
private static AutomaticZenRule.Builder newRuleBuilder() {
return new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().build());
}
}

View File

@@ -18,13 +18,10 @@ package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
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.verify;
@@ -36,9 +33,11 @@ import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import static com.google.common.truth.Truth.assertThat;
@@ -46,6 +45,8 @@ import androidx.preference.Preference;
import com.android.settings.SettingsActivity;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import org.junit.Before;
@@ -138,7 +139,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
Bundle bundle = launcherIntent.getBundleExtra(
SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
assertThat(bundle).isNotNull();
assertThat(bundle.getString(MODE_ID)).isEqualTo("id");
assertThat(bundle.getString(EXTRA_AUTOMATIC_ZEN_RULE_ID)).isEqualTo("id");
}
@Test

View File

@@ -16,8 +16,6 @@
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_NONE;
@@ -41,6 +39,8 @@ import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import org.junit.Before;

View File

@@ -34,6 +34,8 @@ import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import android.widget.Button;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import org.junit.Before;

View File

@@ -32,6 +32,9 @@ import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -17,9 +17,9 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -31,7 +31,12 @@ import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -32,6 +32,9 @@ import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -28,6 +28,9 @@ import android.service.notification.ZenModeConfig;
import androidx.preference.TwoStatePreference;
import androidx.test.core.app.ApplicationProvider;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@@ -33,6 +33,8 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList;

View File

@@ -32,6 +32,9 @@ import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -32,6 +32,9 @@ import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -43,6 +43,9 @@ import android.service.notification.ZenPolicy;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -32,6 +32,9 @@ import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -35,6 +35,9 @@ import android.service.notification.ZenPolicy;
import androidx.preference.TwoStatePreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -32,6 +32,9 @@ import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -54,6 +54,8 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import org.junit.Before;

View File

@@ -19,10 +19,11 @@ package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -33,7 +34,12 @@ 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.TwoStatePreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -41,6 +41,9 @@ import androidx.preference.DropDownPreference;
import androidx.preference.PreferenceCategory;
import androidx.test.core.app.ApplicationProvider;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

View File

@@ -38,6 +38,8 @@ import androidx.fragment.app.Fragment;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;

View File

@@ -46,6 +46,8 @@ import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;

View File

@@ -1,117 +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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
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.google.common.truth.Truth.assertThat;
import android.app.AutomaticZenRule;
import android.net.Uri;
import android.service.notification.ZenPolicy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class ZenModeTest {
private static final ZenPolicy ZEN_POLICY = new ZenPolicy.Builder().allowAllSounds().build();
private static final AutomaticZenRule ZEN_RULE =
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(ZEN_POLICY)
.build();
@Test
public void testBasicMethods() {
ZenMode zenMode = new ZenMode("id", ZEN_RULE, true);
assertThat(zenMode.getId()).isEqualTo("id");
assertThat(zenMode.getRule()).isEqualTo(ZEN_RULE);
assertThat(zenMode.isManualDnd()).isFalse();
assertThat(zenMode.canBeDeleted()).isTrue();
assertThat(zenMode.isActive()).isTrue();
ZenMode manualMode = ZenMode.manualDndMode(ZEN_RULE, false);
assertThat(manualMode.getId()).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
assertThat(manualMode.isManualDnd()).isTrue();
assertThat(manualMode.canBeDeleted()).isFalse();
assertThat(manualMode.isActive()).isFalse();
}
@Test
public void getPolicy_interruptionFilterPriority_returnsZenPolicy() {
ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(ZEN_POLICY)
.build(), false);
assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY);
}
@Test
public void getPolicy_interruptionFilterAlarms_returnsPolicyAllowingAlarms() {
ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(ZEN_POLICY) // should be ignored
.build(), false);
assertThat(zenMode.getPolicy()).isEqualTo(
new ZenPolicy.Builder()
.disallowAllSounds()
.allowAlarms(true)
.allowMedia(true)
.allowPriorityChannels(false)
.build());
}
@Test
public void getPolicy_interruptionFilterNone_returnsPolicyAllowingNothing() {
ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setInterruptionFilter(INTERRUPTION_FILTER_NONE)
.setZenPolicy(ZEN_POLICY) // should be ignored
.build(), false);
assertThat(zenMode.getPolicy()).isEqualTo(
new ZenPolicy.Builder()
.disallowAllSounds()
.hideAllVisualEffects()
.allowPriorityChannels(false)
.build());
}
@Test
public void setPolicy_setsInterruptionFilterPriority() {
ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build(), false);
zenMode.setPolicy(ZEN_POLICY);
assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo(
INTERRUPTION_FILTER_PRIORITY);
assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY);
assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(ZEN_POLICY);
}
}

View File

@@ -1,363 +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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.Condition.SOURCE_UNKNOWN;
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.notification.Condition;
import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import com.android.settings.R;
import com.google.common.collect.ImmutableMap;
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;
import org.robolectric.shadows.ShadowApplication;
import java.time.Duration;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@EnableFlags(Flags.FLAG_MODES_UI)
public class ZenModesBackendTest {
private static final String ZEN_RULE_ID = "rule";
private static final AutomaticZenRule ZEN_RULE =
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build();
private static final AutomaticZenRule MANUAL_DND_RULE =
new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build();
@Mock
private NotificationManager mNm;
private Context mContext;
private ZenModesBackend mBackend;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
// Helper methods to add active/inactive rule state to a config. Returns a copy.
private ZenModeConfig configWithManualRule(ZenModeConfig base, boolean active) {
ZenModeConfig out = base.copy();
if (active) {
out.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
out.manualRule.condition =
new Condition(out.manualRule.conditionId, "", STATE_TRUE, SOURCE_UNKNOWN);
} else {
out.manualRule.zenMode = ZEN_MODE_OFF;
out.manualRule.condition =
new Condition(out.manualRule.conditionId, "", STATE_FALSE, SOURCE_UNKNOWN);
}
return out;
}
private ZenModeConfig configWithRule(ZenModeConfig base, String ruleId, AutomaticZenRule rule,
boolean active) {
ZenModeConfig out = base.copy();
// Note that there are many other fields of zenRule, but here we only set the ones
// relevant to determining whether or not it is active.
ZenModeConfig.ZenRule zenRule = new ZenModeConfig.ZenRule();
zenRule.pkg = "package";
zenRule.enabled = active;
zenRule.snoozing = false;
zenRule.condition = new Condition(rule.getConditionId(), "",
active ? Condition.STATE_TRUE : Condition.STATE_FALSE,
Condition.SOURCE_USER_ACTION);
out.automaticRules.put(ruleId, zenRule);
return out;
}
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
ShadowApplication shadowApplication = ShadowApplication.getInstance();
shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm);
mContext = RuntimeEnvironment.application;
mBackend = new ZenModesBackend(mContext);
// Default catch-all case with no data. This isn't realistic, but tests below that rely
// on the config to get data on rules active will create those individually.
when(mNm.getZenModeConfig()).thenReturn(new ZenModeConfig());
}
@Test
public void getModes_containsManualDndAndZenRules() {
AutomaticZenRule rule2 = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
.build();
Policy dndPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS,
Policy.PRIORITY_SENDERS_CONTACTS, Policy.PRIORITY_SENDERS_CONTACTS);
when(mNm.getAutomaticZenRules()).thenReturn(
ImmutableMap.of("rule1", ZEN_RULE, "rule2", rule2));
ZenModeConfig config = new ZenModeConfig();
config.applyNotificationPolicy(dndPolicy);
assertThat(config.manualRule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_ALLOW);
when(mNm.getZenModeConfig()).thenReturn(config);
List<ZenMode> modes = mBackend.getModes();
// all modes exist, but none of them are currently active
assertThat(modes).containsExactly(
ZenMode.manualDndMode(
new AutomaticZenRule.Builder(
mContext.getString(R.string.zen_mode_settings_title), Uri.EMPTY)
.setType(AutomaticZenRule.TYPE_OTHER)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(ZenAdapters.notificationPolicyToZenPolicy(dndPolicy))
.setManualInvocationAllowed(true)
.build(),
false),
new ZenMode("rule2", rule2, false),
new ZenMode("rule1", ZEN_RULE, false))
.inOrder();
}
@Test
public void getMode_manualDnd_returnsMode() {
Policy dndPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS,
Policy.PRIORITY_SENDERS_CONTACTS, Policy.PRIORITY_SENDERS_CONTACTS);
ZenModeConfig config = new ZenModeConfig();
config.applyNotificationPolicy(dndPolicy);
when(mNm.getZenModeConfig()).thenReturn(config);
ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
assertThat(mode).isEqualTo(
ZenMode.manualDndMode(
new AutomaticZenRule.Builder(
mContext.getString(R.string.zen_mode_settings_title), Uri.EMPTY)
.setType(AutomaticZenRule.TYPE_OTHER)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(ZenAdapters.notificationPolicyToZenPolicy(dndPolicy))
.setManualInvocationAllowed(true)
.build(), false));
}
@Test
public void getMode_zenRule_returnsMode() {
when(mNm.getAutomaticZenRule(eq(ZEN_RULE_ID))).thenReturn(ZEN_RULE);
ZenMode mode = mBackend.getMode(ZEN_RULE_ID);
assertThat(mode).isEqualTo(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false));
}
@Test
public void getMode_missingRule_returnsNull() {
when(mNm.getAutomaticZenRule(any())).thenReturn(null);
ZenMode mode = mBackend.getMode(ZEN_RULE_ID);
assertThat(mode).isNull();
verify(mNm).getAutomaticZenRule(eq(ZEN_RULE_ID));
}
@Test
public void getMode_manualDnd_returnsCorrectActiveState() {
// Set up a base config with an active rule to make sure we're looking at the correct info
ZenModeConfig configWithActiveRule = configWithRule(new ZenModeConfig(), ZEN_RULE_ID,
ZEN_RULE, true);
// Equivalent to disallowAllSounds()
Policy dndPolicy = new Policy(0, 0, 0);
configWithActiveRule.applyNotificationPolicy(dndPolicy);
when(mNm.getZenModeConfig()).thenReturn(configWithActiveRule);
ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
// By default, manual rule is inactive
assertThat(mode.isActive()).isFalse();
// Now the returned config will represent the manual rule being active
when(mNm.getZenModeConfig()).thenReturn(configWithManualRule(configWithActiveRule, true));
ZenMode activeMode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
assertThat(activeMode.isActive()).isTrue();
}
@Test
public void getMode_zenRule_returnsCorrectActiveState() {
// Set up a base config that has an active manual rule and "rule2", to make sure we're
// looking at the correct rule's info.
ZenModeConfig configWithActiveRules = configWithRule(
configWithManualRule(new ZenModeConfig(), true), // active manual rule
"rule2", ZEN_RULE, true); // active rule 2
when(mNm.getAutomaticZenRule(eq(ZEN_RULE_ID))).thenReturn(ZEN_RULE);
when(mNm.getZenModeConfig()).thenReturn(
configWithRule(configWithActiveRules, ZEN_RULE_ID, ZEN_RULE, false));
// Round 1: the current config should indicate that the rule is not active
ZenMode mode = mBackend.getMode(ZEN_RULE_ID);
assertThat(mode.isActive()).isFalse();
when(mNm.getZenModeConfig()).thenReturn(
configWithRule(configWithActiveRules, ZEN_RULE_ID, ZEN_RULE, true));
ZenMode activeMode = mBackend.getMode(ZEN_RULE_ID);
assertThat(activeMode.isActive()).isTrue();
}
@Test
public void updateMode_manualDnd_setsDeviceEffects() throws Exception {
ZenMode manualDnd = ZenMode.manualDndMode(
new AutomaticZenRule.Builder("DND", Uri.EMPTY)
.setZenPolicy(new ZenPolicy())
.setDeviceEffects(new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build())
.build(), false);
mBackend.updateMode(manualDnd);
verify(mNm).setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build());
}
@Test
public void updateMode_manualDnd_setsNotificationPolicy() {
ZenMode manualDnd = ZenMode.manualDndMode(
new AutomaticZenRule.Builder("DND", Uri.EMPTY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build(), false);
mBackend.updateMode(manualDnd);
verify(mNm).setNotificationPolicy(eq(new ZenModeConfig().toNotificationPolicy(
new ZenPolicy.Builder().allowAllSounds().build())), eq(true));
}
@Test
public void updateMode_zenRule_updatesRule() {
ZenMode ruleMode = new ZenMode("rule", ZEN_RULE, false);
mBackend.updateMode(ruleMode);
verify(mNm).updateAutomaticZenRule(eq("rule"), eq(ZEN_RULE), eq(true));
}
@Test
public void activateMode_manualDnd_setsZenModeImportant() {
mBackend.activateMode(ZenMode.manualDndMode(MANUAL_DND_RULE, false), null);
verify(mNm).setZenMode(eq(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
any(), eq(true));
}
@Test
public void activateMode_manualDndWithDuration_setsZenModeImportantWithCondition() {
mBackend.activateMode(ZenMode.manualDndMode(MANUAL_DND_RULE, false),
Duration.ofMinutes(30));
verify(mNm).setZenMode(eq(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS),
eq(ZenModeConfig.toTimeCondition(mContext, 30, 0, true).id),
any(),
eq(true));
}
@Test
public void activateMode_zenRule_setsRuleStateActive() {
mBackend.activateMode(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false), null);
verify(mNm).setAutomaticZenRuleState(eq(ZEN_RULE_ID),
eq(new Condition(ZEN_RULE.getConditionId(), "", Condition.STATE_TRUE,
Condition.SOURCE_USER_ACTION)));
}
@Test
public void activateMode_zenRuleWithDuration_fails() {
assertThrows(IllegalArgumentException.class,
() -> mBackend.activateMode(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false),
Duration.ofMinutes(30)));
}
@Test
public void deactivateMode_manualDnd_setsZenModeOff() {
mBackend.deactivateMode(ZenMode.manualDndMode(MANUAL_DND_RULE, true));
verify(mNm).setZenMode(eq(ZEN_MODE_OFF), eq(null), any(), eq(true));
}
@Test
public void deactivateMode_zenRule_setsRuleStateInactive() {
mBackend.deactivateMode(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false));
verify(mNm).setAutomaticZenRuleState(eq(ZEN_RULE_ID),
eq(new Condition(ZEN_RULE.getConditionId(), "", Condition.STATE_FALSE,
Condition.SOURCE_USER_ACTION)));
}
@Test
public void removeMode_zenRule_deletesRule() {
mBackend.removeMode(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false));
verify(mNm).removeAutomaticZenRule(ZEN_RULE_ID, true);
}
@Test
public void removeMode_manualDnd_fails() {
assertThrows(IllegalArgumentException.class,
() -> mBackend.removeMode(ZenMode.manualDndMode(MANUAL_DND_RULE, false)));
}
}

View File

@@ -37,6 +37,8 @@ import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.search.SearchIndexableRaw;
import com.google.common.collect.ImmutableList;

View File

@@ -31,6 +31,8 @@ import android.net.Uri;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
import com.android.settingslib.notification.modes.ZenMode;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,7 +42,6 @@ import org.robolectric.RuntimeEnvironment;
import java.util.LinkedHashSet;
import java.util.Set;
@RunWith(RobolectricTestRunner.class)
public class ZenModesSummaryHelperTest {
private Context mContext;

View File

@@ -17,65 +17,65 @@
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.CarrierConfigRepository
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.Before
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 context: Context = ApplicationProvider.getApplicationContext()
private val mockCarrierConfigManager = mock<CarrierConfigManager> {
on { getConfigForSubId(eq(SUB_ID), anyVararg()) } doReturn carrierConfig
}
private val mockSimSlotRepository =
mock<SimSlotRepository> {
on { subIdInSimSlotFlow(SIM_SLOT_INDEX) } doReturn flowOf(SUB_ID)
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
}
private val mockSignalStrengthRepository =
mock<SignalStrengthRepository> {
on { signalStrengthDisplayFlow(SUB_ID) } doReturn flowOf(SIGNAL_STRENGTH)
}
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 mockSignalStrengthRepository = mock<SignalStrengthRepository> {
on { signalStrengthDisplayFlow(SUB_ID) } doReturn flowOf(SIGNAL_STRENGTH)
}
private val controller =
SimStatusDialogRepository(
context = context,
simSlotRepository = mockSimSlotRepository,
signalStrengthRepository = mockSignalStrengthRepository,
imsMmTelRepositoryFactory = { subId ->
assertThat(subId).isEqualTo(SUB_ID)
mockImsMmTelRepository
},
)
private val mockImsMmTelRepository = mock<ImsMmTelRepository> {
on { imsRegisteredFlow() } doReturn flowOf(true)
@Before
fun setUp() {
CarrierConfigRepository.resetForTest()
}
private val controller = SimStatusDialogRepository(
context = context,
simSlotRepository = mockSimSlotRepository,
signalStrengthRepository = mockSignalStrengthRepository,
imsMmTelRepositoryFactory = { subId ->
assertThat(subId).isEqualTo(SUB_ID)
mockImsMmTelRepository
},
)
@Test
fun collectSimStatusDialogInfo() = runBlocking {
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL,
value = true,
)
var simStatusDialogInfo = SimStatusDialogInfo()
controller.collectSimStatusDialogInfo(TestLifecycleOwner(), SIM_SLOT_INDEX) {
@@ -83,19 +83,20 @@ class SimStatusDialogRepositoryTest {
}
delay(100)
assertThat(simStatusDialogInfo).isEqualTo(
SimStatusDialogInfo(
signalStrength = SIGNAL_STRENGTH,
imsRegistered = true,
)
)
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
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL,
value = false,
)
var simStatusDialogInfo = SimStatusDialogInfo()
@@ -109,7 +110,11 @@ class SimStatusDialogRepositoryTest {
@Test
fun collectSimStatusDialogInfo_doNotShowImsRegistration() = runBlocking {
carrierConfig.putBoolean(CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false)
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL,
value = false,
)
var simStatusDialogInfo = SimStatusDialogInfo()
controller.collectSimStatusDialogInfo(TestLifecycleOwner(), SIM_SLOT_INDEX) {

View File

@@ -0,0 +1,138 @@
/*
* 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.network.telephony
import android.content.Context
import android.telephony.CarrierConfigManager
import androidx.core.os.persistableBundleOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.anyVararg
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class CarrierConfigRepositoryTest {
private val mockCarrierConfigManager = mock<CarrierConfigManager>()
private val context =
mock<Context> {
on { applicationContext } doReturn mock
on { getSystemService(CarrierConfigManager::class.java) } doReturn
mockCarrierConfigManager
}
private val repository = CarrierConfigRepository(context)
@Before
fun setUp() {
CarrierConfigRepository.resetForTest()
}
@Test
fun getBoolean_returnValue() {
val key = CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL
mockCarrierConfigManager.stub {
on { getConfigForSubId(any(), eq(key)) } doReturn persistableBundleOf(key to true)
}
val value = repository.getBoolean(SUB_ID, key)
assertThat(value).isTrue()
}
@Test
fun getInt_returnValue() {
val key = CarrierConfigManager.KEY_GBA_MODE_INT
mockCarrierConfigManager.stub {
on { getConfigForSubId(any(), eq(key)) } doReturn persistableBundleOf(key to 99)
}
val value = repository.getInt(SUB_ID, key)
assertThat(value).isEqualTo(99)
}
@Test
fun getString_returnValue() {
val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING
mockCarrierConfigManager.stub {
on { getConfigForSubId(any(), eq(key)) } doReturn
persistableBundleOf(key to STRING_VALUE)
}
val value = repository.getString(SUB_ID, key)
assertThat(value).isEqualTo(STRING_VALUE)
}
@Test
fun transformConfig_managerThrowIllegalStateException_returnDefaultValue() {
mockCarrierConfigManager.stub {
on { getConfigForSubId(any(), anyVararg()) } doThrow IllegalStateException()
}
val carrierName =
repository.transformConfig(SUB_ID) {
getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)
}
assertThat(carrierName)
.isEqualTo(
CarrierConfigManager.getDefaultConfig()
.getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT))
}
@Test
fun transformConfig_getValueTwice_cached() {
val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING
mockCarrierConfigManager.stub {
on { getConfigForSubId(any(), eq(key)) } doReturn
persistableBundleOf(key to STRING_VALUE)
}
repository.transformConfig(SUB_ID) { getString(key) }
repository.transformConfig(SUB_ID) { getString(key) }
verify(mockCarrierConfigManager, times(1)).getConfigForSubId(any(), anyVararg())
}
@Test
fun transformConfig_registerCarrierConfigChangeListener() {
val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING
repository.transformConfig(SUB_ID) { getString(key) }
repository.transformConfig(SUB_ID) { getString(key) }
verify(mockCarrierConfigManager, times(1)).registerCarrierConfigChangeListener(any(), any())
}
private companion object {
const val SUB_ID = 123
const val STRING_VALUE = "value"
}
}

View File

@@ -162,6 +162,26 @@ class MobileDataRepositoryTest {
assertThat(state.firstWithTimeoutOrNull()).isTrue()
}
@Test
fun isDataRoamingEnabledFlow_invalidSub_returnFalse() = runBlocking {
val isDataRoamingEnabled =
repository
.isDataRoamingEnabledFlow(subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.firstWithTimeoutOrNull()
assertThat(isDataRoamingEnabled).isFalse()
}
@Test
fun isDataRoamingEnabledFlow_validSub_returnCurrentValue() = runBlocking {
mockTelephonyManager.stub { on { isDataRoamingEnabled } doReturn true }
val isDataRoamingEnabled =
repository.isDataRoamingEnabledFlow(subId = SUB_ID).firstWithTimeoutOrNull()
assertThat(isDataRoamingEnabled).isTrue()
}
private companion object {
const val SUB_ID = 123
}

View File

@@ -0,0 +1,194 @@
/*
* 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.network.telephony
import android.content.Context
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.fragment.app.FragmentManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settings.core.BasePreferenceController.AVAILABLE
import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class RoamingPreferenceControllerTest {
@get:Rule val composeTestRule = createComposeRule()
private val context: Context = ApplicationProvider.getApplicationContext()
private val mockMobileDataRepository =
mock<MobileDataRepository> {
on { isDataRoamingEnabledFlow(SUB_ID) } doReturn flowOf(false)
}
private val controller =
RoamingPreferenceController(context, TEST_KEY, mockMobileDataRepository)
@Before
fun setUp() {
CarrierConfigRepository.resetForTest()
}
@Test
fun getAvailabilityStatus_validSubId_returnAvailable() {
controller.init(mock<FragmentManager>(), SUB_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(AVAILABLE)
}
@Test
fun getAvailabilityStatus_invalidSubId_returnConditionallyUnavailable() {
controller.init(mock<FragmentManager>(), SubscriptionManager.INVALID_SUBSCRIPTION_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
}
@Test
fun getAvailabilityStatus_forceHomeNetworkIsTrue_returnConditionallyUnavailable() {
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL,
value = true,
)
controller.init(mock<FragmentManager>(), SUB_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
}
@Test
fun getAvailabilityStatus_forceHomeNetworkIsFalse_returnAvailable() {
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL,
value = false,
)
controller.init(mock<FragmentManager>(), SUB_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(AVAILABLE)
}
@Test
fun title_displayed() {
controller.init(mock<FragmentManager>(), SUB_ID)
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) { controller.Content() }
}
composeTestRule.onNodeWithText(context.getString(R.string.roaming)).assertIsDisplayed()
}
@Test
fun summary_displayed() {
controller.init(mock<FragmentManager>(), SUB_ID)
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) { controller.Content() }
}
composeTestRule
.onNodeWithText(context.getString(R.string.roaming_enable))
.assertIsDisplayed()
}
@Test
fun isDialogNeeded_enableChargeIndication_returnTrue() {
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_DISABLE_CHARGE_INDICATION_BOOL,
value = false,
)
controller.init(mock<FragmentManager>(), SUB_ID)
val isDialogNeeded = controller.isDialogNeeded()
assertThat(isDialogNeeded).isTrue()
}
@Test
fun isDialogNeeded_disableChargeIndication_returnFalse() {
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_DISABLE_CHARGE_INDICATION_BOOL,
value = true,
)
controller.init(mock<FragmentManager>(), SUB_ID)
val isDialogNeeded = controller.isDialogNeeded()
assertThat(isDialogNeeded).isFalse()
}
@Test
fun checked_roamingEnabled_isOn() {
mockMobileDataRepository.stub {
on { isDataRoamingEnabledFlow(SUB_ID) } doReturn flowOf(true)
}
controller.init(mock<FragmentManager>(), SUB_ID)
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) { controller.Content() }
}
composeTestRule.onNodeWithText(context.getString(R.string.roaming)).assertIsOn()
}
@Test
fun checked_roamingDisabled_isOff() {
mockMobileDataRepository.stub {
on { isDataRoamingEnabledFlow(SUB_ID) } doReturn flowOf(false)
}
controller.init(mock<FragmentManager>(), SUB_ID)
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) { controller.Content() }
}
composeTestRule.onNodeWithText(context.getString(R.string.roaming)).assertIsOff()
}
private companion object {
const val TEST_KEY = "test_key"
const val SUB_ID = 2
}
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
@@ -24,7 +26,12 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
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;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -33,6 +40,7 @@ import com.android.internal.R;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,6 +48,8 @@ import org.junit.runner.RunWith;
public class ReduceBrightColorsPreferenceControllerTest {
private static final String PREF_KEY = "rbc_preference";
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private Context mContext;
private Resources mResources;;
private ReduceBrightColorsPreferenceController mController;
@@ -88,6 +98,20 @@ public class ReduceBrightColorsPreferenceControllerTest {
assertThat(mController.isAvailable()).isFalse();
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_A11Y_QS_SHORTCUT)
public void getTileComponentName_a11yQsFlagOff_returnComponentName() {
assertThat(mController.getTileComponentName())
.isEqualTo(REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_A11Y_QS_SHORTCUT)
public void getTileComponentName_a11yQsFlagOff_returnNull() {
assertThat(mController.getTileComponentName()).isNull();
}
private int resourceId(String type, String name) {
return mContext.getResources().getIdentifier(name, type, mContext.getPackageName());
}

View File

@@ -1,234 +0,0 @@
/*
* Copyright (C) 2020 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.network.telephony;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.Looper;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class RoamingPreferenceControllerTest {
private static final int SUB_ID = 2;
@Mock
private FragmentManager mFragmentManager;
@Mock
private TelephonyManager mTelephonyManager;
@Mock
private TelephonyManager mInvalidTelephonyManager;
@Mock
private SubscriptionManager mSubscriptionManager;
@Mock
private FragmentTransaction mFragmentTransaction;
@Mock
private CarrierConfigManager mCarrierConfigManager;
@Mock
private Lifecycle mLifecycle;
@Mock
private LifecycleOwner mLifecycleOwner;
private LifecycleRegistry mLifecycleRegistry;
private RoamingPreferenceController mController;
private RestrictedSwitchPreference mPreference;
private Context mContext;
private MobileNetworkInfoEntity mMobileNetworkInfoEntity;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
if (Looper.myLooper() == null) {
Looper.prepare();
}
mContext = spy(ApplicationProvider.getApplicationContext());
doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
doReturn(mSubscriptionManager).when(mContext).getSystemService(
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
doReturn(mCarrierConfigManager).when(mContext).getSystemService(
Context.CARRIER_CONFIG_SERVICE);
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID);
doReturn(mInvalidTelephonyManager).when(mTelephonyManager).createForSubscriptionId(
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
doReturn(mFragmentTransaction).when(mFragmentManager).beginTransaction();
mPreference = spy(new RestrictedSwitchPreference(mContext));
mController = spy(
new RoamingPreferenceController(mContext, "roaming", mLifecycle, mLifecycleOwner,
SUB_ID));
mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner);
when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
mController.init(mFragmentManager, SUB_ID, mMobileNetworkInfoEntity);
mPreference.setKey(mController.getPreferenceKey());
}
private MobileNetworkInfoEntity setupMobileNetworkInfoEntity(String subId,
boolean isDataRoaming) {
return new MobileNetworkInfoEntity(subId, false, false, true, false, false, false, false,
false, false, false, isDataRoaming);
}
@Test
public void getAvailabilityStatus_validSubId_returnAvailable() {
assertThat(mController.getAvailabilityStatus()).isEqualTo(
AVAILABLE);
}
@Test
public void getAvailabilityStatus_invalidSubId_returnUnsearchable() {
mController.init(mFragmentManager, SubscriptionManager.INVALID_SUBSCRIPTION_ID,
mMobileNetworkInfoEntity);
assertThat(mController.getAvailabilityStatus(
SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(
BasePreferenceController.AVAILABLE_UNSEARCHABLE);
}
@Test
public void isDialogNeeded_roamingDisabledWithoutFlag_returnTrue() {
final PersistableBundle bundle = new PersistableBundle();
bundle.putBoolean(CarrierConfigManager.KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(SUB_ID);
mMobileNetworkInfoEntity = setupMobileNetworkInfoEntity(String.valueOf(SUB_ID), false);
mController.setMobileNetworkInfoEntity(mMobileNetworkInfoEntity);
assertThat(mController.isDialogNeeded()).isTrue();
}
@Test
public void isDialogNeeded_roamingEnabled_returnFalse() {
mMobileNetworkInfoEntity = setupMobileNetworkInfoEntity(String.valueOf(SUB_ID), true);
mController.setMobileNetworkInfoEntity(mMobileNetworkInfoEntity);
assertThat(mController.isDialogNeeded()).isFalse();
}
@Test
@UiThreadTest
public void setChecked_needDialog_showDialog() {
mMobileNetworkInfoEntity = setupMobileNetworkInfoEntity(String.valueOf(SUB_ID), false);
mController.setMobileNetworkInfoEntity(mMobileNetworkInfoEntity);
doReturn(null).when(mCarrierConfigManager).getConfigForSubId(SUB_ID);
mController.setChecked(true);
verify(mFragmentManager).beginTransaction();
}
@Test
public void updateState_invalidSubId_disabled() {
mMobileNetworkInfoEntity = setupMobileNetworkInfoEntity(
String.valueOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID), false);
mController.setMobileNetworkInfoEntity(mMobileNetworkInfoEntity);
mController.init(mFragmentManager, SubscriptionManager.INVALID_SUBSCRIPTION_ID,
mMobileNetworkInfoEntity);
mController.updateState(mPreference);
assertThat(mPreference.isEnabled()).isFalse();
}
@Test
public void updateState_validSubId_enabled() {
mMobileNetworkInfoEntity = setupMobileNetworkInfoEntity(String.valueOf(SUB_ID), true);
mController.setMobileNetworkInfoEntity(mMobileNetworkInfoEntity);
mController.updateState(mPreference);
assertThat(mPreference.isEnabled()).isTrue();
assertThat(mPreference.isChecked()).isTrue();
}
@Test
public void updateState_isNotDisabledByAdmin_shouldInvokeSetEnabled() {
when(mPreference.isDisabledByAdmin()).thenReturn(false);
mController.updateState(mPreference);
verify(mPreference).setEnabled(anyBoolean());
}
@Test
public void updateState_isDisabledByAdmin_shouldNotInvokeSetEnabled() {
when(mPreference.isDisabledByAdmin()).thenReturn(true);
mController.updateState(mPreference);
verify(mPreference, never()).setEnabled(anyBoolean());
}
@Test
public void getAvailabilityStatus_carrierConfigIsNull_shouldReturnAvailable() {
doReturn(null).when(mCarrierConfigManager).getConfigForSubId(SUB_ID);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void getAvailabilityStatus_forceHomeNetworkIsFalse_shouldReturnAvailable() {
final PersistableBundle bundle = new PersistableBundle();
bundle.putBoolean(CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL, false);
doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(SUB_ID);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void getAvailabilityStatus_forceHomeNetworkIsTrue_shouldReturnConditionallyAvailable() {
final PersistableBundle bundle = new PersistableBundle();
bundle.putBoolean(CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL, true);
doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(SUB_ID);
assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
}
}