From 8607f3a34e388e316efed65e7d914bf5df0a815e Mon Sep 17 00:00:00 2001 From: Chris Antol Date: Tue, 4 Jun 2024 17:00:46 +0000 Subject: [PATCH 01/16] Ignore fragment attr from ext authenticator resource Bug: 341886134 Test: Unit Test Test: Manual - see ticket for steps Flag: EXEMPT (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2cb9b10ed97b1b9b29661115789605a762f3c2ef) Merged-In: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b Change-Id: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b --- .../accounts/AccountTypePreferenceLoader.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java index f1b5be109d2..16519af6730 100644 --- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java @@ -33,6 +33,10 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceFragmentCompat; @@ -46,6 +50,8 @@ import com.android.settings.utils.LocalClassLoaderContextThemeWrapper; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.instrumentation.Instrumentable; +import java.util.Set; + /** * Class to load the preference screen to be added to the settings page for the specific account * type as specified in the account-authenticator. @@ -83,6 +89,7 @@ public class AccountTypePreferenceLoader { try { desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { + Set fragmentAllowList = generateFragmentAllowlist(parent); // Load the context of the target package, then apply the // base Settings theme (no references to local resources) // and create a context theme wrapper so that we get the @@ -98,6 +105,12 @@ public class AccountTypePreferenceLoader { themedCtx.getTheme().setTo(baseTheme); prefs = mFragment.getPreferenceManager().inflateFromResource(themedCtx, desc.accountPreferencesId, parent); + // Ignore Fragments provided dynamically, as these are coming from external + // applications which must not have access to internal Settings' fragments. + // These preferences are rendered into Settings, so they also won't have access + // to their own Fragments, meaning there is no acceptable usage of + // android:fragment here. + filterBlockedFragments(prefs, fragmentAllowList); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); @@ -185,6 +198,48 @@ public class AccountTypePreferenceLoader { } } + // Build allowlist from existing Fragments in PreferenceGroup + @VisibleForTesting + Set generateFragmentAllowlist(@Nullable PreferenceGroup prefs) { + Set fragmentAllowList = new ArraySet<>(); + if (prefs == null) { + return fragmentAllowList; + } + + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + fragmentAllowList.addAll(generateFragmentAllowlist((PreferenceGroup) pref)); + } + + String fragmentName = pref.getFragment(); + if (!TextUtils.isEmpty(fragmentName)) { + fragmentAllowList.add(fragmentName); + } + } + return fragmentAllowList; + } + + // Block clicks on any Preference with android:fragment that is not contained in the allowlist + @VisibleForTesting + void filterBlockedFragments(@Nullable PreferenceGroup prefs, + @NonNull Set allowedFragments) { + if (prefs == null) { + return; + } + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + filterBlockedFragments((PreferenceGroup) pref, allowedFragments); + } + + String fragmentName = pref.getFragment(); + if (fragmentName != null && !allowedFragments.contains(fragmentName)) { + pref.setOnPreferenceClickListener(preference -> true); + } + } + } + /** * Determines if the supplied Intent is safe. A safe intent is one that is * will launch a exported=true activity or owned by the same uid as the From d43f82be7a1d2fc5659afeb9d7eca45bf4ba3a8e Mon Sep 17 00:00:00 2001 From: Chris Antol Date: Tue, 4 Jun 2024 17:00:46 +0000 Subject: [PATCH 02/16] Ignore fragment attr from ext authenticator resource Bug: 341886134 Test: Unit Test Test: Manual - see ticket for steps Flag: EXEMPT Change-Id: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b --- .../accounts/AccountTypePreferenceLoader.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java index f1b5be109d2..16519af6730 100644 --- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java @@ -33,6 +33,10 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceFragmentCompat; @@ -46,6 +50,8 @@ import com.android.settings.utils.LocalClassLoaderContextThemeWrapper; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.instrumentation.Instrumentable; +import java.util.Set; + /** * Class to load the preference screen to be added to the settings page for the specific account * type as specified in the account-authenticator. @@ -83,6 +89,7 @@ public class AccountTypePreferenceLoader { try { desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { + Set fragmentAllowList = generateFragmentAllowlist(parent); // Load the context of the target package, then apply the // base Settings theme (no references to local resources) // and create a context theme wrapper so that we get the @@ -98,6 +105,12 @@ public class AccountTypePreferenceLoader { themedCtx.getTheme().setTo(baseTheme); prefs = mFragment.getPreferenceManager().inflateFromResource(themedCtx, desc.accountPreferencesId, parent); + // Ignore Fragments provided dynamically, as these are coming from external + // applications which must not have access to internal Settings' fragments. + // These preferences are rendered into Settings, so they also won't have access + // to their own Fragments, meaning there is no acceptable usage of + // android:fragment here. + filterBlockedFragments(prefs, fragmentAllowList); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); @@ -185,6 +198,48 @@ public class AccountTypePreferenceLoader { } } + // Build allowlist from existing Fragments in PreferenceGroup + @VisibleForTesting + Set generateFragmentAllowlist(@Nullable PreferenceGroup prefs) { + Set fragmentAllowList = new ArraySet<>(); + if (prefs == null) { + return fragmentAllowList; + } + + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + fragmentAllowList.addAll(generateFragmentAllowlist((PreferenceGroup) pref)); + } + + String fragmentName = pref.getFragment(); + if (!TextUtils.isEmpty(fragmentName)) { + fragmentAllowList.add(fragmentName); + } + } + return fragmentAllowList; + } + + // Block clicks on any Preference with android:fragment that is not contained in the allowlist + @VisibleForTesting + void filterBlockedFragments(@Nullable PreferenceGroup prefs, + @NonNull Set allowedFragments) { + if (prefs == null) { + return; + } + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + filterBlockedFragments((PreferenceGroup) pref, allowedFragments); + } + + String fragmentName = pref.getFragment(); + if (fragmentName != null && !allowedFragments.contains(fragmentName)) { + pref.setOnPreferenceClickListener(preference -> true); + } + } + } + /** * Determines if the supplied Intent is safe. A safe intent is one that is * will launch a exported=true activity or owned by the same uid as the From abf11e7350519af705237e17aad5004466a084e4 Mon Sep 17 00:00:00 2001 From: Chris Antol Date: Tue, 4 Jun 2024 17:00:46 +0000 Subject: [PATCH 03/16] Ignore fragment attr from ext authenticator resource Bug: 341886134 Test: Unit Test Test: Manual - see ticket for steps Flag: EXEMPT (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2cb9b10ed97b1b9b29661115789605a762f3c2ef) Merged-In: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b Change-Id: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b --- .../accounts/AccountTypePreferenceLoader.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java index 42bb34a0ee4..73583ea8592 100644 --- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java @@ -32,6 +32,10 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceFragmentCompat; @@ -45,6 +49,8 @@ import com.android.settings.utils.LocalClassLoaderContextThemeWrapper; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.instrumentation.Instrumentable; +import java.util.Set; + /** * Class to load the preference screen to be added to the settings page for the specific account * type as specified in the account-authenticator. @@ -82,6 +88,7 @@ public class AccountTypePreferenceLoader { try { desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { + Set fragmentAllowList = generateFragmentAllowlist(parent); // Load the context of the target package, then apply the // base Settings theme (no references to local resources) // and create a context theme wrapper so that we get the @@ -97,6 +104,12 @@ public class AccountTypePreferenceLoader { themedCtx.getTheme().setTo(baseTheme); prefs = mFragment.getPreferenceManager().inflateFromResource(themedCtx, desc.accountPreferencesId, parent); + // Ignore Fragments provided dynamically, as these are coming from external + // applications which must not have access to internal Settings' fragments. + // These preferences are rendered into Settings, so they also won't have access + // to their own Fragments, meaning there is no acceptable usage of + // android:fragment here. + filterBlockedFragments(prefs, fragmentAllowList); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); @@ -181,6 +194,48 @@ public class AccountTypePreferenceLoader { } } + // Build allowlist from existing Fragments in PreferenceGroup + @VisibleForTesting + Set generateFragmentAllowlist(@Nullable PreferenceGroup prefs) { + Set fragmentAllowList = new ArraySet<>(); + if (prefs == null) { + return fragmentAllowList; + } + + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + fragmentAllowList.addAll(generateFragmentAllowlist((PreferenceGroup) pref)); + } + + String fragmentName = pref.getFragment(); + if (!TextUtils.isEmpty(fragmentName)) { + fragmentAllowList.add(fragmentName); + } + } + return fragmentAllowList; + } + + // Block clicks on any Preference with android:fragment that is not contained in the allowlist + @VisibleForTesting + void filterBlockedFragments(@Nullable PreferenceGroup prefs, + @NonNull Set allowedFragments) { + if (prefs == null) { + return; + } + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + filterBlockedFragments((PreferenceGroup) pref, allowedFragments); + } + + String fragmentName = pref.getFragment(); + if (fragmentName != null && !allowedFragments.contains(fragmentName)) { + pref.setOnPreferenceClickListener(preference -> true); + } + } + } + /** * Determines if the supplied Intent is safe. A safe intent is one that is * will launch a exported=true activity or owned by the same uid as the From e2dc98c21db7ac35137417bf72f33177a1b70b48 Mon Sep 17 00:00:00 2001 From: Chris Antol Date: Tue, 4 Jun 2024 17:00:46 +0000 Subject: [PATCH 04/16] Ignore fragment attr from ext authenticator resource Bug: 341886134 Test: Unit Test Test: Manual - see ticket for steps Flag: EXEMPT (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2cb9b10ed97b1b9b29661115789605a762f3c2ef) Merged-In: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b Change-Id: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b --- .../accounts/AccountTypePreferenceLoader.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java index 42bb34a0ee4..73583ea8592 100644 --- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java @@ -32,6 +32,10 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceFragmentCompat; @@ -45,6 +49,8 @@ import com.android.settings.utils.LocalClassLoaderContextThemeWrapper; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.instrumentation.Instrumentable; +import java.util.Set; + /** * Class to load the preference screen to be added to the settings page for the specific account * type as specified in the account-authenticator. @@ -82,6 +88,7 @@ public class AccountTypePreferenceLoader { try { desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { + Set fragmentAllowList = generateFragmentAllowlist(parent); // Load the context of the target package, then apply the // base Settings theme (no references to local resources) // and create a context theme wrapper so that we get the @@ -97,6 +104,12 @@ public class AccountTypePreferenceLoader { themedCtx.getTheme().setTo(baseTheme); prefs = mFragment.getPreferenceManager().inflateFromResource(themedCtx, desc.accountPreferencesId, parent); + // Ignore Fragments provided dynamically, as these are coming from external + // applications which must not have access to internal Settings' fragments. + // These preferences are rendered into Settings, so they also won't have access + // to their own Fragments, meaning there is no acceptable usage of + // android:fragment here. + filterBlockedFragments(prefs, fragmentAllowList); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); @@ -181,6 +194,48 @@ public class AccountTypePreferenceLoader { } } + // Build allowlist from existing Fragments in PreferenceGroup + @VisibleForTesting + Set generateFragmentAllowlist(@Nullable PreferenceGroup prefs) { + Set fragmentAllowList = new ArraySet<>(); + if (prefs == null) { + return fragmentAllowList; + } + + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + fragmentAllowList.addAll(generateFragmentAllowlist((PreferenceGroup) pref)); + } + + String fragmentName = pref.getFragment(); + if (!TextUtils.isEmpty(fragmentName)) { + fragmentAllowList.add(fragmentName); + } + } + return fragmentAllowList; + } + + // Block clicks on any Preference with android:fragment that is not contained in the allowlist + @VisibleForTesting + void filterBlockedFragments(@Nullable PreferenceGroup prefs, + @NonNull Set allowedFragments) { + if (prefs == null) { + return; + } + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + filterBlockedFragments((PreferenceGroup) pref, allowedFragments); + } + + String fragmentName = pref.getFragment(); + if (fragmentName != null && !allowedFragments.contains(fragmentName)) { + pref.setOnPreferenceClickListener(preference -> true); + } + } + } + /** * Determines if the supplied Intent is safe. A safe intent is one that is * will launch a exported=true activity or owned by the same uid as the From 24e2f2d2f65d233527e1f50e3e215c266f040792 Mon Sep 17 00:00:00 2001 From: Chris Antol Date: Tue, 4 Jun 2024 17:00:46 +0000 Subject: [PATCH 05/16] Ignore fragment attr from ext authenticator resource Bug: 341886134 Test: Unit Test Test: Manual - see ticket for steps Flag: EXEMPT (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2cb9b10ed97b1b9b29661115789605a762f3c2ef) Merged-In: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b Change-Id: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b --- .../accounts/AccountTypePreferenceLoader.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java index 42bb34a0ee4..73583ea8592 100644 --- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java @@ -32,6 +32,10 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceFragmentCompat; @@ -45,6 +49,8 @@ import com.android.settings.utils.LocalClassLoaderContextThemeWrapper; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.instrumentation.Instrumentable; +import java.util.Set; + /** * Class to load the preference screen to be added to the settings page for the specific account * type as specified in the account-authenticator. @@ -82,6 +88,7 @@ public class AccountTypePreferenceLoader { try { desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { + Set fragmentAllowList = generateFragmentAllowlist(parent); // Load the context of the target package, then apply the // base Settings theme (no references to local resources) // and create a context theme wrapper so that we get the @@ -97,6 +104,12 @@ public class AccountTypePreferenceLoader { themedCtx.getTheme().setTo(baseTheme); prefs = mFragment.getPreferenceManager().inflateFromResource(themedCtx, desc.accountPreferencesId, parent); + // Ignore Fragments provided dynamically, as these are coming from external + // applications which must not have access to internal Settings' fragments. + // These preferences are rendered into Settings, so they also won't have access + // to their own Fragments, meaning there is no acceptable usage of + // android:fragment here. + filterBlockedFragments(prefs, fragmentAllowList); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); @@ -181,6 +194,48 @@ public class AccountTypePreferenceLoader { } } + // Build allowlist from existing Fragments in PreferenceGroup + @VisibleForTesting + Set generateFragmentAllowlist(@Nullable PreferenceGroup prefs) { + Set fragmentAllowList = new ArraySet<>(); + if (prefs == null) { + return fragmentAllowList; + } + + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + fragmentAllowList.addAll(generateFragmentAllowlist((PreferenceGroup) pref)); + } + + String fragmentName = pref.getFragment(); + if (!TextUtils.isEmpty(fragmentName)) { + fragmentAllowList.add(fragmentName); + } + } + return fragmentAllowList; + } + + // Block clicks on any Preference with android:fragment that is not contained in the allowlist + @VisibleForTesting + void filterBlockedFragments(@Nullable PreferenceGroup prefs, + @NonNull Set allowedFragments) { + if (prefs == null) { + return; + } + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + filterBlockedFragments((PreferenceGroup) pref, allowedFragments); + } + + String fragmentName = pref.getFragment(); + if (fragmentName != null && !allowedFragments.contains(fragmentName)) { + pref.setOnPreferenceClickListener(preference -> true); + } + } + } + /** * Determines if the supplied Intent is safe. A safe intent is one that is * will launch a exported=true activity or owned by the same uid as the From f5e9b0167aa9161540416c2c03c3f43c7f5586a5 Mon Sep 17 00:00:00 2001 From: Chris Antol Date: Tue, 4 Jun 2024 17:00:46 +0000 Subject: [PATCH 06/16] Ignore fragment attr from ext authenticator resource Bug: 341886134 Test: Unit Test Test: Manual - see ticket for steps Flag: EXEMPT (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2cb9b10ed97b1b9b29661115789605a762f3c2ef) Merged-In: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b Change-Id: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b --- .../accounts/AccountTypePreferenceLoader.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java index f1b5be109d2..16519af6730 100644 --- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java @@ -33,6 +33,10 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceFragmentCompat; @@ -46,6 +50,8 @@ import com.android.settings.utils.LocalClassLoaderContextThemeWrapper; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.instrumentation.Instrumentable; +import java.util.Set; + /** * Class to load the preference screen to be added to the settings page for the specific account * type as specified in the account-authenticator. @@ -83,6 +89,7 @@ public class AccountTypePreferenceLoader { try { desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { + Set fragmentAllowList = generateFragmentAllowlist(parent); // Load the context of the target package, then apply the // base Settings theme (no references to local resources) // and create a context theme wrapper so that we get the @@ -98,6 +105,12 @@ public class AccountTypePreferenceLoader { themedCtx.getTheme().setTo(baseTheme); prefs = mFragment.getPreferenceManager().inflateFromResource(themedCtx, desc.accountPreferencesId, parent); + // Ignore Fragments provided dynamically, as these are coming from external + // applications which must not have access to internal Settings' fragments. + // These preferences are rendered into Settings, so they also won't have access + // to their own Fragments, meaning there is no acceptable usage of + // android:fragment here. + filterBlockedFragments(prefs, fragmentAllowList); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); @@ -185,6 +198,48 @@ public class AccountTypePreferenceLoader { } } + // Build allowlist from existing Fragments in PreferenceGroup + @VisibleForTesting + Set generateFragmentAllowlist(@Nullable PreferenceGroup prefs) { + Set fragmentAllowList = new ArraySet<>(); + if (prefs == null) { + return fragmentAllowList; + } + + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + fragmentAllowList.addAll(generateFragmentAllowlist((PreferenceGroup) pref)); + } + + String fragmentName = pref.getFragment(); + if (!TextUtils.isEmpty(fragmentName)) { + fragmentAllowList.add(fragmentName); + } + } + return fragmentAllowList; + } + + // Block clicks on any Preference with android:fragment that is not contained in the allowlist + @VisibleForTesting + void filterBlockedFragments(@Nullable PreferenceGroup prefs, + @NonNull Set allowedFragments) { + if (prefs == null) { + return; + } + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + filterBlockedFragments((PreferenceGroup) pref, allowedFragments); + } + + String fragmentName = pref.getFragment(); + if (fragmentName != null && !allowedFragments.contains(fragmentName)) { + pref.setOnPreferenceClickListener(preference -> true); + } + } + } + /** * Determines if the supplied Intent is safe. A safe intent is one that is * will launch a exported=true activity or owned by the same uid as the From 7e0b376b11318e1e79b31bac6aafc0c923868bc4 Mon Sep 17 00:00:00 2001 From: Chris Antol Date: Tue, 4 Jun 2024 17:00:46 +0000 Subject: [PATCH 07/16] Ignore fragment attr from ext authenticator resource Bug: 341886134 Test: Unit Test Test: Manual - see ticket for steps Flag: EXEMPT (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2cb9b10ed97b1b9b29661115789605a762f3c2ef) Merged-In: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b Change-Id: Id91c2b3b6d16ba3702ee2cd6723365a4db52863b --- .../accounts/AccountTypePreferenceLoader.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java index 42bb34a0ee4..73583ea8592 100644 --- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java +++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java @@ -32,6 +32,10 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.collection.ArraySet; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.PreferenceFragmentCompat; @@ -45,6 +49,8 @@ import com.android.settings.utils.LocalClassLoaderContextThemeWrapper; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.instrumentation.Instrumentable; +import java.util.Set; + /** * Class to load the preference screen to be added to the settings page for the specific account * type as specified in the account-authenticator. @@ -82,6 +88,7 @@ public class AccountTypePreferenceLoader { try { desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { + Set fragmentAllowList = generateFragmentAllowlist(parent); // Load the context of the target package, then apply the // base Settings theme (no references to local resources) // and create a context theme wrapper so that we get the @@ -97,6 +104,12 @@ public class AccountTypePreferenceLoader { themedCtx.getTheme().setTo(baseTheme); prefs = mFragment.getPreferenceManager().inflateFromResource(themedCtx, desc.accountPreferencesId, parent); + // Ignore Fragments provided dynamically, as these are coming from external + // applications which must not have access to internal Settings' fragments. + // These preferences are rendered into Settings, so they also won't have access + // to their own Fragments, meaning there is no acceptable usage of + // android:fragment here. + filterBlockedFragments(prefs, fragmentAllowList); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); @@ -181,6 +194,48 @@ public class AccountTypePreferenceLoader { } } + // Build allowlist from existing Fragments in PreferenceGroup + @VisibleForTesting + Set generateFragmentAllowlist(@Nullable PreferenceGroup prefs) { + Set fragmentAllowList = new ArraySet<>(); + if (prefs == null) { + return fragmentAllowList; + } + + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + fragmentAllowList.addAll(generateFragmentAllowlist((PreferenceGroup) pref)); + } + + String fragmentName = pref.getFragment(); + if (!TextUtils.isEmpty(fragmentName)) { + fragmentAllowList.add(fragmentName); + } + } + return fragmentAllowList; + } + + // Block clicks on any Preference with android:fragment that is not contained in the allowlist + @VisibleForTesting + void filterBlockedFragments(@Nullable PreferenceGroup prefs, + @NonNull Set allowedFragments) { + if (prefs == null) { + return; + } + for (int i = 0; i < prefs.getPreferenceCount(); i++) { + Preference pref = prefs.getPreference(i); + if (pref instanceof PreferenceGroup) { + filterBlockedFragments((PreferenceGroup) pref, allowedFragments); + } + + String fragmentName = pref.getFragment(); + if (fragmentName != null && !allowedFragments.contains(fragmentName)) { + pref.setOnPreferenceClickListener(preference -> true); + } + } + } + /** * Determines if the supplied Intent is safe. A safe intent is one that is * will launch a exported=true activity or owned by the same uid as the From e36a2d863e1e7595e87e75e536c9bf409af5d040 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 19 Jun 2024 15:36:16 +0800 Subject: [PATCH 08/16] Clean up unused fields from MobileNetworkRepository Bug: 348118234 Flag: EXEMPT refactor Test: manual on Mobile Settings Change-Id: I45520aac3bd3f2e31c396056f4f38b52e4e6fa55 --- .../network/MobileNetworkRepository.java | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/src/com/android/settings/network/MobileNetworkRepository.java b/src/com/android/settings/network/MobileNetworkRepository.java index 8ee5389bfca..ce6f8842f9c 100644 --- a/src/com/android/settings/network/MobileNetworkRepository.java +++ b/src/com/android/settings/network/MobileNetworkRepository.java @@ -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 mAvailableSubInfoEntityList = new ArrayList<>(); private List mActiveSubInfoEntityList = new ArrayList<>(); - private List mUiccInfoEntityList = new ArrayList<>(); - private List 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 getAvailableSubInfoEntityList() { - return mAvailableSubInfoEntityList; - } - - public List getActiveSubscriptionInfoList() { - return mActiveSubInfoEntityList; - } - - public List getUiccInfoEntityList() { - return mUiccInfoEntityList; - } - - public List 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 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 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(); From dea5102c44c24db792e70a4b1d8270b1277ad69c Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Wed, 12 Jun 2024 20:48:32 -0400 Subject: [PATCH 09/16] Migrate Settings intents to our new modes ui Fixes: 333909883 Test: manual - created test app that launches each intent, launched each with flag on and flag off Test: atest com.android.settings.notification.modes Flag: android.app.modes_ui Change-Id: I8259b554fe34b453880890c667165547033ccd06 --- Android.bp | 6 ++ AndroidManifest.xml | 71 +++++++++++++++++-- src/com/android/settings/Settings.java | 8 ++- .../core/gateway/SettingsGateway.java | 4 ++ .../ZenModeActionsPreferenceController.java | 4 +- .../ZenModeAppsLinkPreferenceController.java | 5 +- .../ZenModeAppsPreferenceController.java | 4 +- .../ZenModeCallsLinkPreferenceController.java | 4 +- ...enModeDisplayLinkPreferenceController.java | 4 +- .../modes/ZenModeFragmentBase.java | 27 ++++--- ...nModeMessagesLinkPreferenceController.java | 4 +- ...nModeNotifVisLinkPreferenceController.java | 5 +- .../ZenModeOtherLinkPreferenceController.java | 5 +- ...ZenModePeopleLinkPreferenceController.java | 5 +- ...odeSetTriggerLinkPreferenceController.java | 1 - .../modes/ZenSubSettingLauncher.java | 4 +- ...nModeAppsLinkPreferenceControllerTest.java | 5 +- 17 files changed, 120 insertions(+), 46 deletions(-) diff --git a/Android.bp b/Android.bp index cb898bef834..0c6d8d1952b 100644 --- a/Android.bp +++ b/Android.bp @@ -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 { diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ca963287c18..b589fd9fdf1 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1287,20 +1287,63 @@ - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1312,6 +1355,20 @@ android:value="true" /> + + + + + + + + + - + - + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 3367bf18f9f..e3bb1a15425 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -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 */ } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 1c14712df30..11c05f318d4 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -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 = { diff --git a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java index 8585234506f..52ca7e60d15 100644 --- a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java @@ -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; @@ -50,7 +50,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 diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java index 6835e6cd853..a30b0aceaba 100644 --- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java @@ -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; @@ -72,7 +71,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()) diff --git a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java index d5ef0440b5e..beb8327aaab 100644 --- a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java @@ -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; @@ -103,7 +103,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) diff --git a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java index 8d27d4cbf63..b7306fc8ee1 100644 --- a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java @@ -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; @@ -39,7 +39,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()) diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java index 712c78a2e50..351a7a7f558 100644 --- a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java @@ -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; @@ -39,7 +39,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()) diff --git a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java index 67cc13beb4a..9127182aa8f 100644 --- a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java +++ b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java @@ -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; @@ -37,7 +39,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 +47,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; } diff --git a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java index 6e563c42a64..c95c3560850 100644 --- a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java @@ -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; @@ -38,7 +38,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()) diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java index 1b1fec4e3f6..4cb68d1ca3e 100644 --- a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java @@ -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; @@ -46,7 +45,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()) diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java index f6df9e3e3d7..f90f1e64b32 100644 --- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java @@ -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; @@ -49,7 +48,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) diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java index db8e135e5a2..98d5126e98f 100644 --- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java @@ -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; @@ -49,7 +48,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()) diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java index fd27958db95..73cb058e2c6 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java @@ -63,7 +63,6 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc } switchPref.setChecked(zenMode.getRule().isEnabled()); switchPref.setOnPreferenceChangeListener(mSwitchChangeListener); - switchPref.setSummary(zenMode.getRule().getTriggerDescription()); switchPref.setIcon(null); switchPref.setOnPreferenceClickListener(null); diff --git a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java index aa66e6c5186..529f7fa1cdd 100644 --- a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java +++ b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java @@ -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 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()) diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java index b199a2bf922..8278aa3b17e 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java @@ -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; @@ -138,7 +137,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 From 57fa134ade9b5e37b3f945db6629dae34ab0cccb Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Thu, 20 Jun 2024 17:18:29 +0000 Subject: [PATCH 10/16] [Homepage revamp] Adjust the padding of Search bar in the homepage container. Bug: 340773713 Test: visual Flag: com.android.settings.flags.homepage_revamp Change-Id: I3fc8a8f56ba8e8c401e907fbb45fc980eb335538 --- res/layout/settings_homepage_container_v2.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/layout/settings_homepage_container_v2.xml b/res/layout/settings_homepage_container_v2.xml index 5ae5fbdb67b..b2445790347 100644 --- a/res/layout/settings_homepage_container_v2.xml +++ b/res/layout/settings_homepage_container_v2.xml @@ -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"> From 764f8d73a479cc203a7e2ec0d0ca46312787187a Mon Sep 17 00:00:00 2001 From: Chun-Ku Lin Date: Thu, 20 Jun 2024 22:30:34 +0000 Subject: [PATCH 11/16] Don't show the QuickSetting tooltip when we stopped auto add qs tile for the user Bug: 348110750 Test: atest ReduceBrightColorsPreferenceControllerTest Test: manual - Go to Settings > Accessibility - Turn on Extra dim for the first time - Verify the QS tooltip prompt doesn't show up Flag: android.view.accessibility.a11y_qs_shortcut Change-Id: I6fecb818df3bb865ba81cbcda94149a98d8255ab --- ...ingsPrimarySwitchPreferenceController.java | 2 ++ ...educeBrightColorsPreferenceController.java | 8 ++++++- ...eBrightColorsPreferenceControllerTest.java | 24 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/accessibility/AccessibilityQuickSettingsPrimarySwitchPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityQuickSettingsPrimarySwitchPreferenceController.java index e82cd96e851..8a3f22d807a 100644 --- a/src/com/android/settings/accessibility/AccessibilityQuickSettingsPrimarySwitchPreferenceController.java +++ b/src/com/android/settings/accessibility/AccessibilityQuickSettingsPrimarySwitchPreferenceController.java @@ -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. */ diff --git a/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceController.java b/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceController.java index 3ca089ca08c..e7f59f49974 100644 --- a/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceController.java +++ b/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceController.java @@ -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 diff --git a/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceControllerTest.java index e1c0277c7e9..7229996b2e3 100644 --- a/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceControllerTest.java @@ -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()); } From ef12f1ddb56dd2e7c7793676d049e09e76942ed3 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 21 Jun 2024 11:30:36 +0800 Subject: [PATCH 12/16] New CarrierConfigRepository Benefices, - Gets the configuration values of the specified keys, for better performance - Check key suffix for correctness - Support cache - If CarrierConfigManager throw exception, use default value Bug: 337417520 Flag: EXEMPT refactor Test: manual on Sim Status Test: unit Change-Id: I68f41ef66d495080f628794ade63cf807efba619 --- .../simstatus/SimStatusDialogRepository.kt | 37 ++- .../telephony/CarrierConfigManagerExt.kt | 1 + .../telephony/CarrierConfigRepository.kt | 217 ++++++++++++++++++ .../SimStatusDialogRepositoryTest.kt | 87 +++---- .../telephony/CarrierConfigRepositoryTest.kt | 138 +++++++++++ 5 files changed, 419 insertions(+), 61 deletions(-) create mode 100644 src/com/android/settings/network/telephony/CarrierConfigRepository.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepository.kt b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepository.kt index 5ed6993cc98..760f8b698f5 100644 --- a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepository.kt +++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepository.kt @@ -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 = - 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) } } diff --git a/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt b/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt index 05b4c07c53e..5408ab04e77 100644 --- a/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt +++ b/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt @@ -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, subId: Int = SubscriptionManager.getDefaultSubscriptionId(), diff --git a/src/com/android/settings/network/telephony/CarrierConfigRepository.kt b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt new file mode 100644 index 00000000000..3ec529dbe82 --- /dev/null +++ b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt @@ -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() + + 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 = 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 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) { + 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): 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() + + 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 diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepositoryTest.kt index 01f32bfccf0..1c1d9df79ca 100644 --- a/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogRepositoryTest.kt @@ -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 { - on { getConfigForSubId(eq(SUB_ID), anyVararg()) } doReturn carrierConfig - } + private val mockSimSlotRepository = + mock { + 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 { + on { signalStrengthDisplayFlow(SUB_ID) } doReturn flowOf(SIGNAL_STRENGTH) + } - private val mockSimSlotRepository = mock { - on { subIdInSimSlotFlow(SIM_SLOT_INDEX) } doReturn flowOf(SUB_ID) - } + private val mockImsMmTelRepository = + mock { on { imsRegisteredFlow() } doReturn flowOf(true) } - private val mockSignalStrengthRepository = mock { - 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 { - 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) { diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt new file mode 100644 index 00000000000..8c54751da35 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt @@ -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() + + private val context = + mock { + 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" + } +} From cfd401b04e64e3f665aa4b695b132006f18f900b Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 19 Jun 2024 11:59:27 +0800 Subject: [PATCH 13/16] Fix crash in RoamingPreferenceController With new MobileDataRepository.isDataRoamingEnabledFlow() to provide the data instead of MobileNetworkRepository. Fix: 347224962 Flag: EXEMPT bug fix Test: manual on Mobile Settings Test: unit test Change-Id: I2a994cb11c93296fb46558f566d6d4467ba4c846 --- res/xml/mobile_network_settings.xml | 6 +- .../network/telephony/MobileDataRepository.kt | 12 + .../telephony/MobileNetworkSettings.java | 6 +- .../RoamingPreferenceController.java | 215 ---------------- .../telephony/RoamingPreferenceController.kt | 105 ++++++++ .../telephony/MobileDataRepositoryTest.kt | 20 ++ .../RoamingPreferenceControllerTest.kt | 194 +++++++++++++++ .../RoamingPreferenceControllerTest.java | 234 ------------------ 8 files changed, 333 insertions(+), 459 deletions(-) delete mode 100644 src/com/android/settings/network/telephony/RoamingPreferenceController.java create mode 100644 src/com/android/settings/network/telephony/RoamingPreferenceController.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/RoamingPreferenceControllerTest.kt delete mode 100644 tests/unit/src/com/android/settings/network/telephony/RoamingPreferenceControllerTest.java diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml index eb9f4427533..51cbbe6b86f 100644 --- a/res/xml/mobile_network_settings.xml +++ b/res/xml/mobile_network_settings.xml @@ -85,13 +85,9 @@ android:summary="@string/auto_data_switch_summary" settings:controller="com.android.settings.network.telephony.AutoDataSwitchPreferenceController"/> - { + 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" } diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index 8b927a9f1ca..34d2fbd5436 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -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); diff --git a/src/com/android/settings/network/telephony/RoamingPreferenceController.java b/src/com/android/settings/network/telephony/RoamingPreferenceController.java deleted file mode 100644 index bf02308be39..00000000000 --- a/src/com/android/settings/network/telephony/RoamingPreferenceController.java +++ /dev/null @@ -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 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 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(); - } -} diff --git a/src/com/android/settings/network/telephony/RoamingPreferenceController.kt b/src/com/android/settings/network/telephony/RoamingPreferenceController.kt new file mode 100644 index 00000000000..2529d41324e --- /dev/null +++ b/src/com/android/settings/network/telephony/RoamingPreferenceController.kt @@ -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" + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileDataRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileDataRepositoryTest.kt index 268be570048..fc762fa894a 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/MobileDataRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileDataRepositoryTest.kt @@ -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 } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/RoamingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/RoamingPreferenceControllerTest.kt new file mode 100644 index 00000000000..ee4cff684ca --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/RoamingPreferenceControllerTest.kt @@ -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 { + 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(), SUB_ID) + + val availabilityStatus = controller.getAvailabilityStatus() + + assertThat(availabilityStatus).isEqualTo(AVAILABLE) + } + + @Test + fun getAvailabilityStatus_invalidSubId_returnConditionallyUnavailable() { + controller.init(mock(), 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(), 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(), SUB_ID) + + val availabilityStatus = controller.getAvailabilityStatus() + + assertThat(availabilityStatus).isEqualTo(AVAILABLE) + } + + @Test + fun title_displayed() { + controller.init(mock(), 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(), 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(), 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(), 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(), 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(), 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 + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/RoamingPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/RoamingPreferenceControllerTest.java deleted file mode 100644 index d221280ae48..00000000000 --- a/tests/unit/src/com/android/settings/network/telephony/RoamingPreferenceControllerTest.java +++ /dev/null @@ -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); - } -} From a1acf685d0b89bd302017283ae573b8d31e9498b Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Fri, 21 Jun 2024 04:50:08 +0000 Subject: [PATCH 14/16] Change the text of the search bar in Homepage screen Bug: 348069291 Change-Id: I35771e9a347715819e65e0d86f1cd38eaa5618ae Test: visual Flag: com.android.settings.flags.homepage_revamp --- res/layout/search_bar_unified_version.xml | 2 +- res/values/strings.xml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/res/layout/search_bar_unified_version.xml b/res/layout/search_bar_unified_version.xml index a8ad6fc4033..dbcf26611a7 100644 --- a/res/layout/search_bar_unified_version.xml +++ b/res/layout/search_bar_unified_version.xml @@ -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"/> diff --git a/res/values/strings.xml b/res/values/strings.xml index 6560e5446f1..075056db64e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13604,4 +13604,7 @@ + + + Search Settings From 871b2b7a85f9166af99287d971c3f466ec8bdb82 Mon Sep 17 00:00:00 2001 From: YK Hung Date: Fri, 21 Jun 2024 07:12:46 +0000 Subject: [PATCH 15/16] Update the database filename and the version number as the same time Fix: 348156270 Test: atest SettingsRoboTests:com.android.settings.fuelgauge.batteryusage Flag: EXEMPT bug fix Change-Id: I26d7e1bf0d1766374359b6c6f12e96d78726ed85 --- .../fuelgauge/batteryusage/db/BatteryStateDatabase.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java index 8e3d6e37e82..c3aea0884be 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java @@ -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; From b1dad7d5b44d4c728c7be4927537aea786b1761f Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Sun, 16 Jun 2024 18:56:48 +0200 Subject: [PATCH 16/16] Move ZenModesBackend to SettingsLib. This will allow us to access it from SystemUI. Bug: 346519570 Test: builds Flag: EXEMPT trivial refactor Change-Id: I5bc480bd4eb0cbf8a26989dd11c064e66e5ee70e --- res/drawable/ic_do_not_disturb_on_24dp.xml | 28 -- .../DndConditionCardController.java | 3 +- .../AbstractZenModePreferenceController.java | 2 + .../notification/modes/IconLoader.java | 161 -------- ...nterruptionFilterPreferenceController.java | 2 + .../settings/notification/modes/ZenMode.java | 257 ------------- .../ZenModeActionsPreferenceController.java | 2 + .../ZenModeAppsLinkPreferenceController.java | 20 +- .../ZenModeAppsPreferenceController.java | 2 + .../ZenModeButtonPreferenceController.java | 2 + .../ZenModeCallsLinkPreferenceController.java | 2 + ...ModeDisplayEffectPreferenceController.java | 3 + ...enModeDisplayLinkPreferenceController.java | 2 + ...enModeExitAtAlarmPreferenceController.java | 3 + .../notification/modes/ZenModeFragment.java | 1 + .../modes/ZenModeFragmentBase.java | 1 + .../modes/ZenModeHeaderController.java | 5 +- ...odeIconPickerIconPreferenceController.java | 5 +- ...odeIconPickerListPreferenceController.java | 2 + ...nModeMessagesLinkPreferenceController.java | 2 + ...nModeNotifVisLinkPreferenceController.java | 2 + .../ZenModeNotifVisPreferenceController.java | 10 +- .../ZenModeOtherLinkPreferenceController.java | 2 + .../ZenModeOtherPreferenceController.java | 3 + ...ZenModePeopleLinkPreferenceController.java | 2 + ...dePrioritySendersPreferenceController.java | 2 + ...ModeRepeatCallersPreferenceController.java | 2 + ...enModeSetCalendarPreferenceController.java | 2 + ...enModeSetSchedulePreferenceController.java | 2 + ...odeSetTriggerLinkPreferenceController.java | 2 + .../modes/ZenModeSummaryHelper.java | 3 +- .../notification/modes/ZenModesBackend.java | 207 ---------- .../modes/ZenModesFragmentBase.java | 1 + ...nModesListAddModePreferenceController.java | 2 + .../modes/ZenModesListFragment.java | 1 + .../modes/ZenModesListItemPreference.java | 4 +- .../ZenModesListPreferenceController.java | 2 + .../notification/zen/ZenModeBackend.java | 22 +- .../notification/zen/ZenModeSliceBuilder.java | 4 +- .../notification/modes/IconLoaderTest.java | 91 ----- ...ruptionFilterPreferenceControllerTest.java | 7 +- ...nModeAppsLinkPreferenceControllerTest.java | 2 + .../ZenModeAppsPreferenceControllerTest.java | 4 +- ...ZenModeButtonPreferenceControllerTest.java | 2 + ...ModeCallsLinkPreferenceControllerTest.java | 3 + ...DisplayEffectPreferenceControllerTest.java | 9 +- ...deDisplayLinkPreferenceControllerTest.java | 3 + ...deExitAtAlarmPreferenceControllerTest.java | 3 + ...conPickerListPreferenceControllerTest.java | 2 + ...eMessagesLinkPreferenceControllerTest.java | 3 + ...eNotifVisLinkPreferenceControllerTest.java | 3 + ...nModeNotifVisPreferenceControllerTest.java | 3 + ...ModeOtherLinkPreferenceControllerTest.java | 3 + .../ZenModeOtherPreferenceControllerTest.java | 3 + ...odePeopleLinkPreferenceControllerTest.java | 3 + ...ioritySendersPreferenceControllerTest.java | 2 + ...RepeatCallersPreferenceControllerTest.java | 8 +- ...deSetCalendarPreferenceControllerTest.java | 3 + ...deSetSchedulePreferenceControllerTest.java | 2 + ...etTriggerLinkPreferenceControllerTest.java | 2 + .../notification/modes/ZenModeTest.java | 117 ------ .../modes/ZenModesBackendTest.java | 363 ------------------ .../ZenModesListPreferenceControllerTest.java | 2 + .../modes/ZenModesSummaryHelperTest.java | 3 +- 64 files changed, 165 insertions(+), 1261 deletions(-) delete mode 100644 res/drawable/ic_do_not_disturb_on_24dp.xml delete mode 100644 src/com/android/settings/notification/modes/IconLoader.java delete mode 100644 src/com/android/settings/notification/modes/ZenMode.java delete mode 100644 src/com/android/settings/notification/modes/ZenModesBackend.java delete mode 100644 tests/robotests/src/com/android/settings/notification/modes/IconLoaderTest.java delete mode 100644 tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java delete mode 100644 tests/robotests/src/com/android/settings/notification/modes/ZenModesBackendTest.java diff --git a/res/drawable/ic_do_not_disturb_on_24dp.xml b/res/drawable/ic_do_not_disturb_on_24dp.xml deleted file mode 100644 index cace8d4433f..00000000000 --- a/res/drawable/ic_do_not_disturb_on_24dp.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java b/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java index e69a336edd5..63620688419 100644 --- a/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java +++ b/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java @@ -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(); } diff --git a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java index aebc4eb5ec6..9f819d1b6e4 100644 --- a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java +++ b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java @@ -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; diff --git a/src/com/android/settings/notification/modes/IconLoader.java b/src/com/android/settings/notification/modes/IconLoader.java deleted file mode 100644 index c5902856423..00000000000 --- a/src/com/android/settings/notification/modes/IconLoader.java +++ /dev/null @@ -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 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 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 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; - } -} diff --git a/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java b/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java index ee4c702d998..8bdeea40762 100644 --- a/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java +++ b/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java @@ -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 { diff --git a/src/com/android/settings/notification/modes/ZenMode.java b/src/com/android/settings/notification/modes/ZenMode.java deleted file mode 100644 index 4ece2e3046d..00000000000 --- a/src/com/android/settings/notification/modes/ZenMode.java +++ /dev/null @@ -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. - * - *

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 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; - } -} diff --git a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java index 8585234506f..6dddbcb0297 100644 --- a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java @@ -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 { diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java index 6835e6cd853..405d29900db 100644 --- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java @@ -32,6 +32,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; @@ -129,11 +131,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 +150,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() { diff --git a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java index d5ef0440b5e..50d1785d869 100644 --- a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java @@ -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 diff --git a/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java index 1846dfc2efe..79da24da57a 100644 --- a/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java @@ -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 { diff --git a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java index 8d27d4cbf63..dd111cefb69 100644 --- a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java @@ -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 { diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java index bca7b559a92..b42e5b8eed3 100644 --- a/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java @@ -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 { diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java index 712c78a2e50..c9ff871e17d 100644 --- a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java @@ -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 { diff --git a/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java index 8517af16975..326bc97715b 100644 --- a/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java @@ -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. */ diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java index f2f47b99ceb..9637764cee0 100644 --- a/src/com/android/settings/notification/modes/ZenModeFragment.java +++ b/src/com/android/settings/notification/modes/ZenModeFragment.java @@ -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; diff --git a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java index 67cc13beb4a..975a48bd4df 100644 --- a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java +++ b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java @@ -29,6 +29,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; diff --git a/src/com/android/settings/notification/modes/ZenModeHeaderController.java b/src/com/android/settings/notification/modes/ZenModeHeaderController.java index d8f0a6730fa..857d07949aa 100644 --- a/src/com/android/settings/notification/modes/ZenModeHeaderController.java +++ b/src/com/android/settings/notification/modes/ZenModeHeaderController.java @@ -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()); diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java index 9eaaa973305..d891e66c2f7 100644 --- a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java @@ -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()); diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java index fc991dcfb19..85ceafe0870 100644 --- a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java @@ -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; diff --git a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java index 6e563c42a64..427ccb766a0 100644 --- a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java @@ -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; diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java index 1b1fec4e3f6..d251c06712a 100644 --- a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java @@ -27,6 +27,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 { diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java index f918b256caf..3d9f713b809 100644 --- a/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java @@ -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, diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java index f6df9e3e3d7..f8736503a55 100644 --- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java @@ -27,6 +27,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 diff --git a/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java index a770164eb63..ad5fa6ad428 100644 --- a/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java @@ -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 { diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java index db8e135e5a2..6e04232df39 100644 --- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java @@ -27,6 +27,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 diff --git a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java index 31a8a0d0234..0f9323d81fc 100644 --- a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java @@ -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; diff --git a/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java index 75690519478..ae62e358b54 100644 --- a/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java @@ -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 { diff --git a/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java index e87907647db..4f45c5c8bb2 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java @@ -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; diff --git a/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java index 3432ed5154f..878a508949d 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java @@ -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; diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java index fd27958db95..a74cac5c21f 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java @@ -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. diff --git a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java index bd0b798a309..48a4c366142 100644 --- a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java +++ b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java @@ -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) { diff --git a/src/com/android/settings/notification/modes/ZenModesBackend.java b/src/com/android/settings/notification/modes/ZenModesBackend.java deleted file mode 100644 index 4f86778cf63..00000000000 --- a/src/com/android/settings/notification/modes/ZenModesBackend.java +++ /dev/null @@ -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. - * - *

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 getModes() { - ArrayList modes = new ArrayList<>(); - ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig(); - modes.add(getManualDndMode(currentConfig)); - - Map zenRules = mNotificationManager.getAutomaticZenRules(); - for (Map.Entry 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); - } -} diff --git a/src/com/android/settings/notification/modes/ZenModesFragmentBase.java b/src/com/android/settings/notification/modes/ZenModesFragmentBase.java index d99593a3b89..e1156fef159 100644 --- a/src/com/android/settings/notification/modes/ZenModesFragmentBase.java +++ b/src/com/android/settings/notification/modes/ZenModesFragmentBase.java @@ -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. diff --git a/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java index c229fb19b22..ba74b93aad9 100644 --- a/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java @@ -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; diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java index 80678f6bb69..77107f89d02 100644 --- a/src/com/android/settings/notification/modes/ZenModesListFragment.java +++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java @@ -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; diff --git a/src/com/android/settings/notification/modes/ZenModesListItemPreference.java b/src/com/android/settings/notification/modes/ZenModesListItemPreference.java index 7ecfb3acb87..0b04ac6c087 100644 --- a/src/com/android/settings/notification/modes/ZenModesListItemPreference.java +++ b/src/com/android/settings/notification/modes/ZenModesListItemPreference.java @@ -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()); } diff --git a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java index 5dcd9eb4675..fb07078cd39 100644 --- a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java @@ -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; diff --git a/src/com/android/settings/notification/zen/ZenModeBackend.java b/src/com/android/settings/notification/zen/ZenModeBackend.java index 426f52d519d..de641c5f38b 100644 --- a/src/com/android/settings/notification/zen/ZenModeBackend.java +++ b/src/com/android/settings/notification/zen/ZenModeBackend.java @@ -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()); } diff --git a/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java b/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java index 4f6f0587b46..d16b1e4ba45 100644 --- a/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java +++ b/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java @@ -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); } diff --git a/tests/robotests/src/com/android/settings/notification/modes/IconLoaderTest.java b/tests/robotests/src/com/android/settings/notification/modes/IconLoaderTest.java deleted file mode 100644 index 7d4a3679198..00000000000 --- a/tests/robotests/src/com/android/settings/notification/modes/IconLoaderTest.java +++ /dev/null @@ -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 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 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 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()); - } -} diff --git a/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java index aeb1b8ed8c4..0aef136d5f6 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java index b199a2bf922..42df8aaf4f5 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java @@ -46,6 +46,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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java index b67d3328fec..d89e187c7da 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeButtonPreferenceControllerTest.java index bda38437a37..98099aa1a20 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeButtonPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java index 94c2d8ad651..c8e654d5af8 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceControllerTest.java index 1a62b75468a..1052c0d831e 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java index 62aa046a9f3..c78883b0b68 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java index c1c4d61727f..f95fc85ec5f 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java index ba9a6b81862..31324604bd0 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java index 9400f83ebb5..6d70ec542b7 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java index 00a9fbe341b..bf5171a8726 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java index 54edaf440e3..137806771da 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java index 699762ee573..f272b80d427 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherPreferenceControllerTest.java index 4a4a6e43aa5..66ac7fa1a41 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java index a331318bb50..ceb8dbc7e88 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java index 709af434e8d..8317a143ead 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceControllerTest.java index 7bbb042c471..c86b57bbeba 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java index 0ede058aed7..9e1d94af09e 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java index 5f492b971a8..519bb433ffe 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java index ff4d4a3c94c..7782a9b2cf2 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java deleted file mode 100644 index 37b71a5ba34..00000000000 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java +++ /dev/null @@ -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); - } -} diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesBackendTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesBackendTest.java deleted file mode 100644 index 9483683669c..00000000000 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesBackendTest.java +++ /dev/null @@ -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 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))); - } -} diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java index 9a4de60613e..04d5f0ae22c 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java @@ -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; diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java index 51368c557ef..2822e003ee7 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java @@ -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;