From 9f196efcad1d013507d316795feb4d02d0682d93 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Fri, 10 Apr 2020 10:35:21 +0800 Subject: [PATCH 01/11] Fix when search "storage", there are multiple results root cause: if device doesn't have external storage, StorageSettings will launch PrivateVolumeSettings and finish itself. But we still index the preferences in StorageSettings. Solution: Check if device doesn't support external storage, add all of StorageSettings preference into nonIndexableKey list. Bug: 152366015 Test: robotest & manual Change-Id: Ib559db3455d46a343ad7eb73e54eb72d0e833308 --- .../settings/deviceinfo/StorageSettings.java | 92 +++++++++++++++++-- 1 file changed, 83 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/deviceinfo/StorageSettings.java b/src/com/android/settings/deviceinfo/StorageSettings.java index 7f2c645a3b5..de6394e61ea 100644 --- a/src/com/android/settings/deviceinfo/StorageSettings.java +++ b/src/com/android/settings/deviceinfo/StorageSettings.java @@ -70,6 +70,16 @@ import java.util.List; public class StorageSettings extends SettingsPreferenceFragment implements Indexable { static final String TAG = "StorageSettings"; + private static final String KEY_STORAGE_SETTINGS = "storage_settings"; + private static final String KEY_INTERNAL_STORAGE = "storage_settings_internal_storage"; + private static final String KEY_STORAGE_SETTINGS_VOLUME = "storage_settings_volume_"; + private static final String KEY_STORAGE_SETTINGS_MEMORY_SIZE = "storage_settings_memory_size"; + private static final String KEY_STORAGE_SETTINGS_MEMORY = "storage_settings_memory_available"; + private static final String KEY_STORAGE_SETTINGS_DCIM = "storage_settings_dcim_space"; + private static final String KEY_STORAGE_SETTINGS_MUSIC = "storage_settings_music_space"; + private static final String KEY_STORAGE_SETTINGS_MISC = "storage_settings_misc_space"; + private static final String KEY_STORAGE_SETTINGS_FREE_SPACE = "storage_settings_free_space"; + private static final String TAG_VOLUME_UNMOUNTED = "volume_unmounted"; private static final String TAG_DISK_INIT = "disk_init"; private static final int METRICS_CATEGORY = SettingsEnums.DEVICEINFO_STORAGE; @@ -551,14 +561,14 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index SearchIndexableRaw data = new SearchIndexableRaw(context); data.title = context.getString(R.string.storage_settings); - data.key = "storage_settings"; + data.key = KEY_STORAGE_SETTINGS; data.screenTitle = context.getString(R.string.storage_settings); data.keywords = context.getString(R.string.keywords_storage_settings); result.add(data); data = new SearchIndexableRaw(context); data.title = context.getString(R.string.internal_storage); - data.key = "storage_settings_internal_storage"; + data.key = KEY_INTERNAL_STORAGE; data.screenTitle = context.getString(R.string.storage_settings); result.add(data); @@ -568,7 +578,7 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index for (VolumeInfo vol : vols) { if (isInteresting(vol)) { data.title = storage.getBestVolumeDescription(vol); - data.key = "storage_settings_volume_" + vol.id; + data.key = KEY_STORAGE_SETTINGS_VOLUME + vol.id; data.screenTitle = context.getString(R.string.storage_settings); result.add(data); } @@ -576,37 +586,37 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index data = new SearchIndexableRaw(context); data.title = context.getString(R.string.memory_size); - data.key = "storage_settings_memory_size"; + data.key = KEY_STORAGE_SETTINGS_MEMORY_SIZE; data.screenTitle = context.getString(R.string.storage_settings); result.add(data); data = new SearchIndexableRaw(context); data.title = context.getString(R.string.memory_available); - data.key = "storage_settings_memory_available"; + data.key = KEY_STORAGE_SETTINGS_MEMORY; data.screenTitle = context.getString(R.string.storage_settings); result.add(data); data = new SearchIndexableRaw(context); data.title = context.getString(R.string.memory_dcim_usage); - data.key = "storage_settings_dcim_space"; + data.key = KEY_STORAGE_SETTINGS_DCIM; data.screenTitle = context.getString(R.string.storage_settings); result.add(data); data = new SearchIndexableRaw(context); data.title = context.getString(R.string.memory_music_usage); - data.key = "storage_settings_music_space"; + data.key = KEY_STORAGE_SETTINGS_MUSIC; data.screenTitle = context.getString(R.string.storage_settings); result.add(data); data = new SearchIndexableRaw(context); data.title = context.getString(R.string.memory_media_misc_usage); - data.key = "storage_settings_misc_space"; + data.key = KEY_STORAGE_SETTINGS_MISC; data.screenTitle = context.getString(R.string.storage_settings); result.add(data); data = new SearchIndexableRaw(context); data.title = context.getString(R.string.storage_menu_free); - data.key = "storage_settings_free_space"; + data.key = KEY_STORAGE_SETTINGS_FREE_SPACE; data.screenTitle = context.getString(R.string.storage_menu_free); // We need to define all three in order for this to trigger properly. data.intentAction = StorageManager.ACTION_MANAGE_STORAGE; @@ -618,5 +628,69 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index return result; } + + @Override + public List getNonIndexableKeys(Context context) { + final List niks = super.getNonIndexableKeys(context); + if (isExternalExist(context)) { + niks.add(KEY_STORAGE_SETTINGS); + niks.add(KEY_INTERNAL_STORAGE); + niks.add(KEY_STORAGE_SETTINGS_MEMORY_SIZE); + niks.add(KEY_STORAGE_SETTINGS_MEMORY); + niks.add(KEY_STORAGE_SETTINGS_DCIM); + niks.add(KEY_STORAGE_SETTINGS_MUSIC); + niks.add(KEY_STORAGE_SETTINGS_MISC); + niks.add(KEY_STORAGE_SETTINGS_FREE_SPACE); + + final StorageManager storage = context.getSystemService( + StorageManager.class); + final List vols = storage.getVolumes(); + for (VolumeInfo vol : vols) { + if (isInteresting(vol)) { + niks.add(KEY_STORAGE_SETTINGS_VOLUME + vol.id); + } + } + } + return niks; + } + + @Override + protected boolean isPageSearchEnabled(Context context) { + return !isExternalExist(context); + } + + private boolean isExternalExist(Context context) { + int internalCount = 0; + StorageManager storageManager = context.getSystemService(StorageManager.class); + final List volumes = storageManager.getVolumes(); + for (VolumeInfo vol : volumes) { + //External storage + if (vol.getType() == VolumeInfo.TYPE_PUBLIC + || vol.getType() == VolumeInfo.TYPE_STUB) { + return true; + } else if (vol.getType() == VolumeInfo.TYPE_PRIVATE) { + internalCount++; + } + } + + // Unsupported disks + final List disks = storageManager.getDisks(); + for (DiskInfo disk : disks) { + if (disk.volumeCount == 0 && disk.size > 0) { + return true; + } + } + + // Missing private volumes + final List recs = storageManager.getVolumeRecords(); + for (VolumeRecord rec : recs) { + if (rec.getType() == VolumeInfo.TYPE_PRIVATE + && storageManager.findVolumeByUuid(rec.getFsUuid()) == null) { + internalCount++; + } + } + + return (internalCount != 1); + } }; } From 69653f0585f0c933abbebe94d2ca0d0335ad3fb6 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Mon, 27 Apr 2020 19:37:30 +0800 Subject: [PATCH 02/11] [Wi-Fi] Show Wi-Fi editor for authentication failures When the Wi-Fi network is disabled because of authentication failures, Wi-Fi picker should show editor before connecting or the network will always fail to connect. Bug: 112137755 Test: atest com.android.wifitrackerlib.StandardWifiEntryTest Change-Id: I0ae42e7974e6bd2548e7f2595c6ae71a73398030 --- src/com/android/settings/wifi/WifiSettings2.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/wifi/WifiSettings2.java b/src/com/android/settings/wifi/WifiSettings2.java index 9bf5c72037f..3b18f5a4fc7 100644 --- a/src/com/android/settings/wifi/WifiSettings2.java +++ b/src/com/android/settings/wifi/WifiSettings2.java @@ -544,14 +544,9 @@ public class WifiSettings2 extends RestrictedSettingsFragment final WifiEntry selectedEntry = ((LongPressWifiEntryPreference) preference).getWifiEntry(); - // If the clicked WiFi entry is never connected, launch Wi-Fi edit UI to edit password. - if (selectedEntry.getSecurity() != WifiEntry.SECURITY_NONE - && selectedEntry.getSecurity() != WifiEntry.SECURITY_OWE) { - final WifiConfiguration config = selectedEntry.getWifiConfiguration(); - if (config != null && !config.getNetworkSelectionStatus().hasEverConnected()) { - launchConfigNewNetworkFragment(selectedEntry); - return true; - } + if (selectedEntry.shouldEditBeforeConnect()) { + launchConfigNewNetworkFragment(selectedEntry); + return true; } connect(selectedEntry, true /* editIfNoConfig */, true /* fullScreenEdit */); From 260ad5c5233d206e71c6bcc6e66f9117fb104aa5 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Mon, 27 Apr 2020 19:35:43 +0800 Subject: [PATCH 03/11] [Settings] Align data usage calculation Align the calculation of data usage between DataUsageSummaryPreferenceController and DataUsagePrefereneController. Bug: 143996139 Test: make RunSettingsRoboTests -j ROBOTEST_FILTER=DataUsageSummaryPreferenceControllerTest Change-Id: I73de79b5cc298bf949827eda0a8bb0274089c016 --- .../DataUsageSummaryPreferenceController.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java index 239ad2178e8..2a91c482057 100644 --- a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java +++ b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java @@ -210,6 +210,11 @@ public class DataUsageSummaryPreferenceController extends TelephonyBasePreferenc final DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo(mDefaultTemplate); + long usageLevel = info.usageLevel; + if (usageLevel <= 0L) { + usageLevel = mDataUsageController.getHistoricalUsageLevel(mDefaultTemplate); + } + if (subInfo != null) { mDataInfoController.updateDataLimit(info, mPolicyEditor.getPolicy(mDefaultTemplate)); summaryPreference.setWifiMode(/* isWifiMode */ false, @@ -218,7 +223,7 @@ public class DataUsageSummaryPreferenceController extends TelephonyBasePreferenc summaryPreference.setWifiMode(/* isWifiMode */ true, /* usagePeriod */ info.period, /* isSingleWifi */ false); summaryPreference.setLimitInfo(null); - summaryPreference.setUsageNumbers(info.usageLevel, + summaryPreference.setUsageNumbers(usageLevel, /* dataPlanSize */ -1L, /* hasMobileData */ true); summaryPreference.setChartEnabled(false); @@ -231,6 +236,11 @@ public class DataUsageSummaryPreferenceController extends TelephonyBasePreferenc } refreshDataplanInfo(info, subInfo); + if ((mDataplanUse <= 0L) && (mSnapshotTime < 0)) { + Log.d(TAG, "Display data usage from history"); + mDataplanUse = usageLevel; + mSnapshotTime = -1L; + } if (info.warningLevel > 0 && info.limitLevel > 0) { summaryPreference.setLimitInfo(TextUtils.expandTemplate( From 8fe4b28312376f292b54d31fe290a110efba30ed Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Sun, 26 Apr 2020 19:14:31 -0400 Subject: [PATCH 04/11] Updates for msg apps that don't use shortcuts - Notice on app specific page - No individual conversation bubble controls Test: manual Bug: 154814754 Change-Id: I74c291890ba6203e6a3a162fc0c0335ed5eba3d4 --- res/values/strings.xml | 2 ++ .../settings/notification/NotificationBackend.java | 9 +++++++++ .../app/AppConversationListPreferenceController.java | 12 +++++++++++- .../settings/notification/app/BubblePreference.java | 10 +++++++++- .../notification/app/BubblePreferenceController.java | 6 ++++++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e1eae680a2c..27188f441bd 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8464,6 +8464,8 @@ Shows at top of conversation section and appears as a bubble. + %1$s does not support conversation-specific settings. + diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index b172879b64e..6172268bd0b 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -268,6 +268,15 @@ public class NotificationBackend { } } + public boolean hasSentMessage(String pkg, int uid) { + try { + return sINM.hasSentMessage(pkg, uid); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + /** * Returns all notification channels associated with the package and uid that will bypass DND */ diff --git a/src/com/android/settings/notification/app/AppConversationListPreferenceController.java b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java index 32278db907a..bea015709c2 100644 --- a/src/com/android/settings/notification/app/AppConversationListPreferenceController.java +++ b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java @@ -44,6 +44,7 @@ public class AppConversationListPreferenceController extends NotificationPrefere private List mConversations; private PreferenceCategory mPreference; + private boolean mHasSentMsg; public AppConversationListPreferenceController(Context context, NotificationBackend backend) { super(context, backend); @@ -78,6 +79,7 @@ public class AppConversationListPreferenceController extends NotificationPrefere new AsyncTask() { @Override protected Void doInBackground(Void... unused) { + mHasSentMsg = mBackend.hasSentMessage(mAppRow.pkg, mAppRow.uid); mConversations = mBackend.getConversations(mAppRow.pkg, mAppRow.uid).getList(); Collections.sort(mConversations, mConversationComparator); return null; @@ -99,7 +101,15 @@ public class AppConversationListPreferenceController extends NotificationPrefere mPreference.setTitle(R.string.conversations_category_title); if (mConversations.isEmpty()) { - mPreference.setVisible(false); + if (mHasSentMsg) { + mPreference.setVisible(true); + Preference notSupportedPref = new Preference(mContext); + notSupportedPref.setSummary(mContext.getString( + R.string.convo_not_supported_summary, mAppRow.label)); + mPreference.addPreference(notSupportedPref); + } else { + mPreference.setVisible(false); + } } else { mPreference.setVisible(true); populateConversations(); diff --git a/src/com/android/settings/notification/app/BubblePreference.java b/src/com/android/settings/notification/app/BubblePreference.java index 679b663c8dd..7e071ff938a 100644 --- a/src/com/android/settings/notification/app/BubblePreference.java +++ b/src/com/android/settings/notification/app/BubblePreference.java @@ -52,6 +52,8 @@ public class BubblePreference extends Preference implements View.OnClickListener private ButtonViewHolder mBubbleSelectedButton; private ButtonViewHolder mBubbleNoneButton; + private boolean mSelectedVisible; + public BubblePreference(Context context) { this(context, null); } @@ -89,6 +91,11 @@ public class BubblePreference extends Preference implements View.OnClickListener } } + public void setSelectedVisibility(boolean visible) { + mSelectedVisible = visible; + notifyChanged(); + } + @Override public void onBindViewHolder(final PreferenceViewHolder holder) { super.onBindViewHolder(holder); @@ -122,7 +129,8 @@ public class BubblePreference extends Preference implements View.OnClickListener mSelectedPreference == BUBBLE_PREFERENCE_SELECTED); bubbleSelected.setTag(BUBBLE_PREFERENCE_SELECTED); bubbleSelected.setOnClickListener(this); - bubbleSelected.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE); + bubbleSelected.setVisibility((!mSelectedVisible || disabledByAdmin) + ? View.GONE : View.VISIBLE); View bubbleNone = holder.findViewById(R.id.bubble_none); ImageView bubbleNoneImage = (ImageView) holder.findViewById(R.id.bubble_none_icon); diff --git a/src/com/android/settings/notification/app/BubblePreferenceController.java b/src/com/android/settings/notification/app/BubblePreferenceController.java index 3255192a912..02d6d99e049 100644 --- a/src/com/android/settings/notification/app/BubblePreferenceController.java +++ b/src/com/android/settings/notification/app/BubblePreferenceController.java @@ -47,6 +47,8 @@ public class BubblePreferenceController extends NotificationPreferenceController private FragmentManager mFragmentManager; private boolean mIsAppPage; + private boolean mHasSentInvalidMsg; + private int mNumConversations; public BubblePreferenceController(Context context, @Nullable FragmentManager fragmentManager, NotificationBackend backend, boolean isAppPage) { @@ -81,10 +83,14 @@ public class BubblePreferenceController extends NotificationPreferenceController @Override public void updateState(Preference preference) { if (mIsAppPage && mAppRow != null) { + mHasSentInvalidMsg = mBackend.hasSentMessage(mAppRow.pkg, mAppRow.uid); + mNumConversations = mBackend.getConversations( + mAppRow.pkg, mAppRow.uid).getList().size(); // We're on the app specific bubble page which displays a tri-state int backEndPref = mAppRow.bubblePreference; BubblePreference pref = (BubblePreference) preference; pref.setDisabledByAdmin(mAdmin); + pref.setSelectedVisibility(!mHasSentInvalidMsg || mNumConversations > 0); if (!isGloballyEnabled()) { pref.setSelectedPreference(BUBBLE_PREFERENCE_NONE); } else { From 43daeb90da13fcde61fe4f9cf20b43eaa65deb18 Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Mon, 27 Apr 2020 18:06:44 +0800 Subject: [PATCH 05/11] Change the name of accessibility service fragment type to more descriptive names Bug: 155052371 Test: manual test Change-Id: I097138c84083528acd4d16a145921227ffe3f736 --- .../accessibility/AccessibilitySettings.java | 17 +++++++----- .../AccessibilitySettingsForSetupWizard.java | 12 ++++----- .../accessibility/AccessibilityUtil.java | 27 ++++++++++--------- ...cessibilityServicePreferenceFragment.java} | 2 +- ...aderPreferenceFragmentForSetupWizard.java} | 4 +-- ...peakPreferenceFragmentForSetupWizard.java} | 4 +-- .../AccessibilitySettingsTest.java | 6 ++--- .../accessibility/AccessibilityUtilTest.java | 12 ++++----- 8 files changed, 44 insertions(+), 40 deletions(-) rename src/com/android/settings/accessibility/{LegacyAccessibilityServicePreferenceFragment.java => VolumeShortcutToggleAccessibilityServicePreferenceFragment.java} (97%) rename src/com/android/settings/accessibility/{LegacyToggleScreenReaderPreferenceFragmentForSetupWizard.java => VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.java} (91%) rename src/com/android/settings/accessibility/{LegacyToggleSelectToSpeakPreferenceFragmentForSetupWizard.java => VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.java} (91%) diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index becd4e78b02..f918046bc37 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -269,7 +269,7 @@ public class AccessibilitySettings extends DashboardFragment { final CharSequence serviceState; final int fragmentType = AccessibilityUtil.getAccessibilityServiceFragmentType(info); - if (fragmentType == AccessibilityServiceFragmentType.INVISIBLE) { + if (fragmentType == AccessibilityServiceFragmentType.INVISIBLE_TOGGLE) { final ComponentName componentName = new ComponentName( info.getResolveInfo().serviceInfo.packageName, info.getResolveInfo().serviceInfo.name); @@ -652,13 +652,16 @@ public class AccessibilitySettings extends DashboardFragment { } private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) { - switch (AccessibilityUtil.getAccessibilityServiceFragmentType( - info)) { - case AccessibilityServiceFragmentType.LEGACY: - return LegacyAccessibilityServicePreferenceFragment.class.getName(); - case AccessibilityServiceFragmentType.INVISIBLE: + // Shorten the name to avoid exceeding 100 characters in one line. + final String volumeShortcutToggleAccessibilityServicePreferenceFragment = + VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName(); + + switch (AccessibilityUtil.getAccessibilityServiceFragmentType(info)) { + case AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE: + return volumeShortcutToggleAccessibilityServicePreferenceFragment; + case AccessibilityServiceFragmentType.INVISIBLE_TOGGLE: return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName(); - case AccessibilityServiceFragmentType.INTUITIVE: + case AccessibilityServiceFragmentType.TOGGLE: return ToggleAccessibilityServicePreferenceFragment.class.getName(); default: // impossible status diff --git a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java index 2bcb5b3b3cf..090e5d48493 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java +++ b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java @@ -16,7 +16,7 @@ package com.android.settings.accessibility; -import static com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType.LEGACY; +import static com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.settings.SettingsEnums; @@ -102,10 +102,10 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm super.onResume(); updateAccessibilityServicePreference(mScreenReaderPreference, SCREEN_READER_PACKAGE_NAME, SCREEN_READER_SERVICE_NAME, - LegacyToggleScreenReaderPreferenceFragmentForSetupWizard.class.getName()); + VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.class.getName()); updateAccessibilityServicePreference(mSelectToSpeakPreference, SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME, - LegacyToggleSelectToSpeakPreferenceFragmentForSetupWizard.class.getName()); + VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.class.getName()); configureMagnificationPreferenceIfNeeded(mDisplayMagnificationPreference); } @@ -147,7 +147,7 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm } private void updateAccessibilityServicePreference(Preference preference, - String packageName, String serviceName, String targetLegacyFragment) { + String packageName, String serviceName, String targetFragment) { final AccessibilityServiceInfo info = findService(packageName, serviceName); if (info == null) { getPreferenceScreen().removePreference(preference); @@ -159,8 +159,8 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm preference.setTitle(title); ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name); preference.setKey(componentName.flattenToString()); - if (AccessibilityUtil.getAccessibilityServiceFragmentType(info) == LEGACY) { - preference.setFragment(targetLegacyFragment); + if (AccessibilityUtil.getAccessibilityServiceFragmentType(info) == VOLUME_SHORTCUT_TOGGLE) { + preference.setFragment(targetFragment); } // Update the extras. diff --git a/src/com/android/settings/accessibility/AccessibilityUtil.java b/src/com/android/settings/accessibility/AccessibilityUtil.java index a0e3d81403e..eb202b5b044 100644 --- a/src/com/android/settings/accessibility/AccessibilityUtil.java +++ b/src/com/android/settings/accessibility/AccessibilityUtil.java @@ -46,22 +46,23 @@ final class AccessibilityUtil { /** * Annotation for different accessibilityService fragment UI type. * - * {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service - * page, but only hardware shortcut allowed. - * {@code INVISIBLE} for displaying appearance without switch bar. - * {@code INTUITIVE} for displaying appearance with new design. + * {@code VOLUME_SHORTCUT_TOGGLE} for displaying basic accessibility service fragment but + * only hardware shortcut allowed. + * {@code INVISIBLE_TOGGLE} for displaying basic accessibility service fragment without + * switch bar. + * {@code TOGGLE} for displaying basic accessibility service fragment. */ @Retention(RetentionPolicy.SOURCE) @IntDef({ - AccessibilityServiceFragmentType.LEGACY, - AccessibilityServiceFragmentType.INVISIBLE, - AccessibilityServiceFragmentType.INTUITIVE, + AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE, + AccessibilityServiceFragmentType.INVISIBLE_TOGGLE, + AccessibilityServiceFragmentType.TOGGLE, }) public @interface AccessibilityServiceFragmentType { - int LEGACY = 0; - int INVISIBLE = 1; - int INTUITIVE = 2; + int VOLUME_SHORTCUT_TOGGLE = 0; + int INVISIBLE_TOGGLE = 1; + int TOGGLE = 2; } // TODO(b/147021230): Will move common functions and variables to @@ -162,11 +163,11 @@ final class AccessibilityUtil { & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; if (targetSdk <= Build.VERSION_CODES.Q) { - return AccessibilityServiceFragmentType.LEGACY; + return AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE; } return requestA11yButton - ? AccessibilityServiceFragmentType.INVISIBLE - : AccessibilityServiceFragmentType.INTUITIVE; + ? AccessibilityServiceFragmentType.INVISIBLE_TOGGLE + : AccessibilityServiceFragmentType.TOGGLE; } /** diff --git a/src/com/android/settings/accessibility/LegacyAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/VolumeShortcutToggleAccessibilityServicePreferenceFragment.java similarity index 97% rename from src/com/android/settings/accessibility/LegacyAccessibilityServicePreferenceFragment.java rename to src/com/android/settings/accessibility/VolumeShortcutToggleAccessibilityServicePreferenceFragment.java index c5cede3453e..31f97a8f600 100644 --- a/src/com/android/settings/accessibility/LegacyAccessibilityServicePreferenceFragment.java +++ b/src/com/android/settings/accessibility/VolumeShortcutToggleAccessibilityServicePreferenceFragment.java @@ -34,7 +34,7 @@ import com.google.common.collect.ImmutableSet; * *

For accessibility services that target SDK <= Q. */ -public class LegacyAccessibilityServicePreferenceFragment extends +public class VolumeShortcutToggleAccessibilityServicePreferenceFragment extends ToggleAccessibilityServicePreferenceFragment { @Override diff --git a/src/com/android/settings/accessibility/LegacyToggleScreenReaderPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.java similarity index 91% rename from src/com/android/settings/accessibility/LegacyToggleScreenReaderPreferenceFragmentForSetupWizard.java rename to src/com/android/settings/accessibility/VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.java index 1b37a42afb0..6e4a233c608 100644 --- a/src/com/android/settings/accessibility/LegacyToggleScreenReaderPreferenceFragmentForSetupWizard.java +++ b/src/com/android/settings/accessibility/VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.java @@ -21,8 +21,8 @@ import android.os.Bundle; import android.view.View; /** For accessibility services that target SDK <= Q in setup wizard. */ -public class LegacyToggleScreenReaderPreferenceFragmentForSetupWizard - extends LegacyAccessibilityServicePreferenceFragment { +public class VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard + extends VolumeShortcutToggleAccessibilityServicePreferenceFragment { private boolean mToggleSwitchWasInitiallyChecked; diff --git a/src/com/android/settings/accessibility/LegacyToggleSelectToSpeakPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.java similarity index 91% rename from src/com/android/settings/accessibility/LegacyToggleSelectToSpeakPreferenceFragmentForSetupWizard.java rename to src/com/android/settings/accessibility/VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.java index ffb363cd154..3dd648cd592 100644 --- a/src/com/android/settings/accessibility/LegacyToggleSelectToSpeakPreferenceFragmentForSetupWizard.java +++ b/src/com/android/settings/accessibility/VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.java @@ -21,8 +21,8 @@ import android.os.Bundle; import android.view.View; /** For accessibility services that target SDK <= Q in setup wizard. */ -public class LegacyToggleSelectToSpeakPreferenceFragmentForSetupWizard - extends LegacyAccessibilityServicePreferenceFragment { +public class VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard + extends VolumeShortcutToggleAccessibilityServicePreferenceFragment { private boolean mToggleSwitchWasInitiallyChecked; diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index c6a7684dd8e..bf631306b10 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -130,8 +130,8 @@ public class AccessibilitySettingsTest { } @Test - public void getServiceSummary_invisibleType_shortcutDisabled_showsOffSummary() { - setInvisibleFragmentType(mServiceInfo); + public void getServiceSummary_invisibleToggle_shortcutDisabled_showsOffSummary() { + setInvisibleToggleFragmentType(mServiceInfo); doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any()); final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, @@ -277,7 +277,7 @@ public class AccessibilitySettingsTest { when(mockInfo.getComponentName()).thenReturn(DUMMY_COMPONENT_NAME); } - private void setInvisibleFragmentType(AccessibilityServiceInfo info) { + private void setInvisibleToggleFragmentType(AccessibilityServiceInfo info) { info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityUtilTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityUtilTest.java index b8936c4601e..ac3198ff546 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityUtilTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityUtilTest.java @@ -102,36 +102,36 @@ public final class AccessibilityUtilTest { } @Test - public void getAccessibilityServiceFragmentType_targetSdkQ_legacyType() { + public void getAccessibilityServiceFragmentType_targetSdkQ_volumeShortcutType() { final AccessibilityServiceInfo info = getMockAccessibilityServiceInfo(); info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q; info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; assertThat(AccessibilityUtil.getAccessibilityServiceFragmentType(info)).isEqualTo( - AccessibilityUtil.AccessibilityServiceFragmentType.LEGACY); + AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE); } @Test - public void getAccessibilityServiceFragmentType_targetSdkR_HaveA11yButton_headlessType() { + public void getAccessibilityServiceFragmentType_targetSdkR_HaveA11yButton_invisibleType() { final AccessibilityServiceInfo info = getMockAccessibilityServiceInfo(); info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; assertThat(AccessibilityUtil.getAccessibilityServiceFragmentType(info)).isEqualTo( - AccessibilityUtil.AccessibilityServiceFragmentType.INVISIBLE); + AccessibilityUtil.AccessibilityServiceFragmentType.INVISIBLE_TOGGLE); } @Test - public void getAccessibilityServiceFragmentType_targetSdkR_NoA11yButton_intuitiveType() { + public void getAccessibilityServiceFragmentType_targetSdkR_NoA11yButton_toggleType() { final AccessibilityServiceInfo info = getMockAccessibilityServiceInfo(); info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; info.flags |= ~AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; assertThat(AccessibilityUtil.getAccessibilityServiceFragmentType(info)).isEqualTo( - AccessibilityUtil.AccessibilityServiceFragmentType.INTUITIVE); + AccessibilityUtil.AccessibilityServiceFragmentType.TOGGLE); } @Test From 96127fe6212da82ffb1c75b852cbea60bc7bd12d Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Fri, 20 Mar 2020 12:04:09 +0800 Subject: [PATCH 06/11] Remove sub-text from slice builder We would like to remove all sub-text from Settings Search. But slice view does not support API to configure the sub-text visibility. Therefore, the only way is to remove the sub-text from slices directly. Since Settings slices are also invoked by other apps, we can not directly remove the sub-text. Finally, we decide to check the caller's uid. If it comes from Settings Search, we will return the slice without the sub-text. Bug: 143118037 Test: visual, robotests Change-Id: Iac72f1683a2c930592634e0599058890d86f669d --- src/com/android/settings/Utils.java | 12 ++++ .../network/telephony/MobileDataSlice.java | 13 ++-- .../notification/zen/ZenModeSliceBuilder.java | 13 ++-- .../settings/slices/SliceBuilderUtils.java | 64 +++++++++++-------- .../wifi/calling/WifiCallingSliceHelper.java | 24 ++++--- .../src/com/android/settings/UtilsTest.java | 20 +++++- .../slices/SliceBuilderUtilsTest.java | 32 ---------- 7 files changed, 101 insertions(+), 77 deletions(-) diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index e8eb8baadb0..2391fe8cc40 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -57,6 +57,7 @@ import android.net.LinkProperties; import android.net.Network; import android.net.wifi.WifiManager; import android.os.BatteryManager; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; @@ -1089,4 +1090,15 @@ public final class Utils extends com.android.settingslib.Utils { } return f; } + + /** + * Returns true if current binder uid is Settings Intelligence. + */ + public static boolean isSettingsIntelligence(Context context) { + final int callingUid = Binder.getCallingUid(); + final String callingPackage = context.getPackageManager().getPackagesForUid(callingUid)[0]; + final boolean isSettingsIntelligence = TextUtils.equals(callingPackage, + context.getString(R.string.config_settingsintelligence_package_name)); + return isSettingsIntelligence; + } } diff --git a/src/com/android/settings/network/telephony/MobileDataSlice.java b/src/com/android/settings/network/telephony/MobileDataSlice.java index b76eb3814cf..c3b08d38bfa 100644 --- a/src/com/android/settings/network/telephony/MobileDataSlice.java +++ b/src/com/android/settings/network/telephony/MobileDataSlice.java @@ -96,15 +96,18 @@ public class MobileDataSlice implements CustomSliceable { ListBuilder.ICON_IMAGE, title); final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction, null /* actionTitle */, isMobileDataEnabled()); + final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder() + .setTitle(title) + .addEndItem(toggleSliceAction) + .setPrimaryAction(primarySliceAction); + if (!Utils.isSettingsIntelligence(mContext)) { + rowBuilder.setSubtitle(summary); + } final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY) .setAccentColor(color) - .addRow(new ListBuilder.RowBuilder() - .setTitle(title) - .setSubtitle(summary) - .addEndItem(toggleSliceAction) - .setPrimaryAction(primarySliceAction)); + .addRow(rowBuilder); return listBuilder.build(); } diff --git a/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java b/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java index fe1e20461e9..c9a203e2f4c 100644 --- a/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java +++ b/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java @@ -82,15 +82,18 @@ public class ZenModeSliceBuilder { final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction, null /* actionTitle */, isZenModeEnabled); + final RowBuilder rowBuilder = new RowBuilder() + .setTitle(title) + .addEndItem(toggleSliceAction) + .setPrimaryAction(primarySliceAction); + if (!Utils.isSettingsIntelligence(context)) { + rowBuilder.setSubtitle(subtitle); + } return new ListBuilder(context, CustomSliceRegistry.ZEN_MODE_SLICE_URI, ListBuilder.INFINITY) .setAccentColor(color) - .addRow(new RowBuilder() - .setTitle(title) - .setSubtitle(subtitle) - .addEndItem(toggleSliceAction) - .setPrimaryAction(primarySliceAction)) + .addRow(rowBuilder) .build(); } diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java index 34c0f1b5ff2..2d5b4aa39a2 100644 --- a/src/com/android/settings/slices/SliceBuilderUtils.java +++ b/src/com/android/settings/slices/SliceBuilderUtils.java @@ -241,16 +241,19 @@ public class SliceBuilderUtils { final SliceAction sliceAction = getToggleAction(context, sliceData, toggleController.isChecked()); final Set keywords = buildSliceKeywords(sliceData); + final RowBuilder rowBuilder = new RowBuilder() + .setTitle(sliceData.getTitle()) + .setPrimaryAction( + SliceAction.createDeeplink(contentIntent, icon, + ListBuilder.ICON_IMAGE, sliceData.getTitle())) + .addEndItem(sliceAction); + if (!Utils.isSettingsIntelligence(context)) { + rowBuilder.setSubtitle(subtitleText); + } return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY) .setAccentColor(color) - .addRow(new RowBuilder() - .setTitle(sliceData.getTitle()) - .setSubtitle(subtitleText) - .setPrimaryAction( - SliceAction.createDeeplink(contentIntent, icon, - ListBuilder.ICON_IMAGE, sliceData.getTitle())) - .addEndItem(sliceAction)) + .addRow(rowBuilder) .setKeywords(keywords) .build(); } @@ -262,16 +265,19 @@ public class SliceBuilderUtils { final CharSequence subtitleText = getSubtitleText(context, controller, sliceData); @ColorInt final int color = Utils.getColorAccentDefaultColor(context); final Set keywords = buildSliceKeywords(sliceData); + final RowBuilder rowBuilder = new RowBuilder() + .setTitle(sliceData.getTitle()) + .setPrimaryAction( + SliceAction.createDeeplink(contentIntent, icon, + ListBuilder.ICON_IMAGE, + sliceData.getTitle())); + if (!Utils.isSettingsIntelligence(context)) { + rowBuilder.setSubtitle(subtitleText); + } return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY) .setAccentColor(color) - .addRow(new RowBuilder() - .setTitle(sliceData.getTitle()) - .setSubtitle(subtitleText) - .setPrimaryAction( - SliceAction.createDeeplink(contentIntent, icon, - ListBuilder.ICON_IMAGE, - sliceData.getTitle()))) + .addRow(rowBuilder) .setKeywords(keywords) .build(); } @@ -301,7 +307,6 @@ public class SliceBuilderUtils { } final InputRangeBuilder inputRangeBuilder = new InputRangeBuilder() .setTitle(sliceData.getTitle()) - .setSubtitle(subtitleText) .setPrimaryAction(primaryAction) .setMax(sliderController.getMax()) .setMin(sliderController.getMin()) @@ -311,6 +316,9 @@ public class SliceBuilderUtils { inputRangeBuilder.setTitleItem(icon, ListBuilder.ICON_IMAGE); color = CustomSliceable.COLOR_NOT_TINTED; } + if (!Utils.isSettingsIntelligence(context)) { + inputRangeBuilder.setSubtitle(subtitleText); + } return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY) .setAccentColor(color) @@ -330,14 +338,17 @@ public class SliceBuilderUtils { final CharSequence subtitleText = getSubtitleText(context, controller, sliceData); @ColorInt final int color = Utils.getColorAccentDefaultColor(context); final Set keywords = buildSliceKeywords(sliceData); + final RowBuilder rowBuilder = new RowBuilder() + .setTitle(sliceData.getTitle()) + .setPrimaryAction(primaryAction) + .addEndItem(copyableAction); + if (!Utils.isSettingsIntelligence(context)) { + rowBuilder.setSubtitle(subtitleText); + } return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY) .setAccentColor(color) - .addRow(new RowBuilder() - .setTitle(sliceData.getTitle()) - .setSubtitle(subtitleText) - .setPrimaryAction(primaryAction) - .addEndItem(copyableAction)) + .addRow(rowBuilder) .setKeywords(keywords) .build(); } @@ -418,14 +429,17 @@ public class SliceBuilderUtils { final SliceAction primaryAction = SliceAction.createDeeplink( getContentPendingIntent(context, data), icon, ListBuilder.ICON_IMAGE, title); + final RowBuilder rowBuilder = new RowBuilder() + .setTitle(title) + .setTitleItem(icon, ListBuilder.ICON_IMAGE) + .setPrimaryAction(primaryAction); + if (!Utils.isSettingsIntelligence(context)) { + rowBuilder.setSubtitle(subtitle); + } return new ListBuilder(context, data.getUri(), ListBuilder.INFINITY) .setAccentColor(color) - .addRow(new RowBuilder() - .setTitle(title) - .setTitleItem(icon, ListBuilder.ICON_IMAGE) - .setSubtitle(subtitle) - .setPrimaryAction(primaryAction)) + .addRow(rowBuilder) .setKeywords(keywords) .build(); } diff --git a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java index 69adf55be9c..958db7ebd8d 100644 --- a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java +++ b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java @@ -269,14 +269,17 @@ public class WifiCallingSliceHelper { // Top row shows information on current preference state final ListBuilder listBuilder = new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY) .setAccentColor(Utils.getColorAccentDefaultColor(mContext)); - listBuilder.setHeader(new ListBuilder.HeaderBuilder() + final ListBuilder.HeaderBuilder headerBuilder = new ListBuilder.HeaderBuilder() .setTitle(res.getText(R.string.wifi_calling_mode_title)) - .setSubtitle(getWifiCallingPreferenceSummary(currentWfcPref, subId)) .setPrimaryAction(SliceAction.createDeeplink( getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY), icon, ListBuilder.ICON_IMAGE, - res.getText(R.string.wifi_calling_mode_title)))); + res.getText(R.string.wifi_calling_mode_title))); + if (!Utils.isSettingsIntelligence(mContext)) { + headerBuilder.setSubtitle(getWifiCallingPreferenceSummary(currentWfcPref, subId)); + } + listBuilder.setHeader(headerBuilder); if (isWifiOnlySupported) { listBuilder.addRow(wifiPreferenceRowBuilder(listBuilder, @@ -458,14 +461,17 @@ public class WifiCallingSliceHelper { private Slice getNonActionableWifiCallingSlice(CharSequence title, CharSequence subtitle, Uri sliceUri, PendingIntent primaryActionIntent) { final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal); + final RowBuilder rowBuilder = new RowBuilder() + .setTitle(title) + .setPrimaryAction(SliceAction.createDeeplink( + primaryActionIntent, icon, ListBuilder.SMALL_IMAGE, + title)); + if (!Utils.isSettingsIntelligence(mContext)) { + rowBuilder.setSubtitle(subtitle); + } return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY) .setAccentColor(Utils.getColorAccentDefaultColor(mContext)) - .addRow(new RowBuilder() - .setTitle(title) - .setSubtitle(subtitle) - .setPrimaryAction(SliceAction.createDeeplink( - primaryActionIntent, icon, ListBuilder.SMALL_IMAGE, - title))) + .addRow(rowBuilder) .build(); } diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java index 3d3aff46e69..2aa8418227f 100644 --- a/tests/robotests/src/com/android/settings/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/UtilsTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActionBar; -import android.app.Activity; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; @@ -67,6 +66,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowBinder; import java.net.InetAddress; import java.util.ArrayList; @@ -281,4 +281,22 @@ public class UtilsTest { assertThat(actionBar.getElevation()).isEqualTo(0.f); } + + @Test + public void isSettingsIntelligence_IsSI_returnTrue() { + final String siPackageName = mContext.getString( + R.string.config_settingsintelligence_package_name); + ShadowBinder.setCallingUid(USER_ID); + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{siPackageName}); + + assertThat(Utils.isSettingsIntelligence(mContext)).isTrue(); + } + + @Test + public void isSettingsIntelligence_IsNotSI_returnFalse() { + ShadowBinder.setCallingUid(USER_ID); + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{PACKAGE_NAME}); + + assertThat(Utils.isSettingsIntelligence(mContext)).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java index fc82abb21f2..33bdc61cd85 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.settings.SettingsEnums; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -455,37 +454,6 @@ public class SliceBuilderUtilsTest { assertThat(actualIconResource).isEqualTo(settingsIcon); } - @Test - public void buildUnavailableSlice_customizeSubtitle_returnsSliceWithCustomizedSubtitle() { - final String subtitleOfUnavailableSlice = "subtitleOfUnavailableSlice"; - final SliceData data = getDummyData(FakeUnavailablePreferenceController.class, - SUMMARY, SliceData.SliceType.SWITCH, SCREEN_TITLE, 0 /* icon */, - subtitleOfUnavailableSlice); - Settings.Global.putInt(mContext.getContentResolver(), - FakeUnavailablePreferenceController.AVAILABILITY_KEY, - BasePreferenceController.DISABLED_DEPENDENT_SETTING); - - final Slice slice = SliceBuilderUtils.buildSlice(mContext, data); - - final SliceMetadata metadata = SliceMetadata.from(mContext, slice); - assertThat(metadata.getSubtitle()).isEqualTo(subtitleOfUnavailableSlice); - } - - @Test - public void buildUnavailableSlice_notCustomizeSubtitle_returnsSliceWithDefaultSubtitle() { - final SliceData data = getDummyData(FakeUnavailablePreferenceController.class, - SliceData.SliceType.SWITCH); - Settings.Global.putInt(mContext.getContentResolver(), - FakeUnavailablePreferenceController.AVAILABILITY_KEY, - BasePreferenceController.DISABLED_DEPENDENT_SETTING); - - final Slice slice = SliceBuilderUtils.buildSlice(mContext, data); - - final SliceMetadata metadata = SliceMetadata.from(mContext, slice); - assertThat(metadata.getSubtitle()).isEqualTo( - mContext.getString(R.string.disabled_dependent_setting_summary)); - } - private SliceData getDummyData() { return getDummyData(TOGGLE_CONTROLLER, SUMMARY, SliceData.SliceType.SWITCH, SCREEN_TITLE, ICON, null /* unavailableSliceSubtitle */); From 225db346b6345c8c7fdcdd42e2413e62b8096981 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Tue, 28 Apr 2020 14:08:11 +0800 Subject: [PATCH 07/11] [Settings] Avoid mobile data usage init in WiFi usage Avoid from initial in WiFi usage and update into mobile data usage after refresh. Bug: 151751844 Test: make RunSettingsRoboTests -j ROBOTEST_FILTER=DataUsageSummaryPreferenceControllerTest Change-Id: Ic6176952953ded9d47f1fc52c3f311e6568a5b90 --- .../datausage/DataUsageSummaryPreferenceController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java index 239ad2178e8..3680016af6f 100644 --- a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java +++ b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java @@ -114,6 +114,7 @@ public class DataUsageSummaryPreferenceController extends TelephonyBasePreferenc */ public void init(int subscriptionId) { mSubId = subscriptionId; + mHasMobileData = DataUsageUtils.hasMobileData(mContext); mDataUsageController = null; } @@ -123,8 +124,6 @@ public class DataUsageSummaryPreferenceController extends TelephonyBasePreferenc context.getSystemService(NetworkPolicyManager.class); mPolicyEditor = new NetworkPolicyEditor(policyManager); - mHasMobileData = DataUsageUtils.hasMobileData(context); - mDataUsageController = new DataUsageController(context); mDataUsageController.setSubscriptionId(subscriptionId); mDataInfoController = new DataUsageInfoController(); From c91a8bce5b2d548c38a1df1c63669f4f66608490 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Sat, 25 Apr 2020 02:25:48 +0800 Subject: [PATCH 08/11] Add icon to current screen saver preference The screen saver setting is coming from different apps to modify their options. This will confuse people that they were modifying something in Settings rather than in other apps. To improve that, we added an icon of active dream and put it on the left side of current screen saver setting, which is to let people clearer to know that they were modifying the options in other apps. Bug: 129994275 Test: robotests Change-Id: I014c9de745044c2bd39d22782b6c36f8605d8f56 --- .../dream/CurrentDreamPreferenceController.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/com/android/settings/dream/CurrentDreamPreferenceController.java b/src/com/android/settings/dream/CurrentDreamPreferenceController.java index fee4826c5a9..a73898fdc0a 100644 --- a/src/com/android/settings/dream/CurrentDreamPreferenceController.java +++ b/src/com/android/settings/dream/CurrentDreamPreferenceController.java @@ -20,8 +20,10 @@ import android.content.Context; import androidx.preference.Preference; +import com.android.settings.Utils; import com.android.settings.core.BasePreferenceController; import com.android.settings.widget.GearPreference; +import com.android.settingslib.RestrictedPreference; import com.android.settingslib.dream.DreamBackend; import com.android.settingslib.dream.DreamBackend.DreamInfo; @@ -45,6 +47,7 @@ public class CurrentDreamPreferenceController extends BasePreferenceController { public void updateState(Preference preference) { super.updateState(preference); setGearClickListenerForPreference(preference); + setActiveDreamIcon(preference); } @Override @@ -78,4 +81,13 @@ public class CurrentDreamPreferenceController extends BasePreferenceController { .filter((info) -> info.isActive) .findFirst(); } + + private void setActiveDreamIcon(Preference preference) { + if (!(preference instanceof GearPreference)) { + return; + } + final GearPreference gearPref = (GearPreference) preference; + gearPref.setIconSize(RestrictedPreference.ICON_SIZE_SMALL); + Utils.setSafeIcon(gearPref, mBackend.getActiveIcon()); + } } From dcc79e99425d4cc4542a010f48dc72fb69d27ba6 Mon Sep 17 00:00:00 2001 From: Beverly Date: Tue, 28 Apr 2020 10:21:15 -0400 Subject: [PATCH 09/11] Update strings to be more translation friendly Use different strings for "None" based on the context of the string. Some languages may require different gendered grammar depending on its context. Also update contacts summary string to include plurals (1 vs many). Test: make RunSettingsRoboTests7 Bug: 154898476 Fixes: 154944295 Change-Id: If9949d75de573042389a19df3d4c5d15c6cbeb25 --- res/values/arrays.xml | 4 ++-- res/values/strings.xml | 17 +++++++++++------ .../notification/zen/ZenModeBackend.java | 19 ++++++++++--------- ...dePrioritySendersPreferenceController.java | 4 +++- ...nModeSendersImagePreferenceController.java | 5 ++++- .../notification/zen/ZenModeSettings.java | 4 ++-- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 1649cb2ae0a..6e88b769bda 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1148,14 +1148,14 @@ @string/zen_mode_from_anyone @string/zen_mode_from_contacts @string/zen_mode_from_starred - @string/zen_mode_from_none_messages + @string/zen_mode_none_messages @string/zen_mode_from_anyone @string/zen_mode_from_contacts @string/zen_mode_from_starred - @string/zen_mode_from_none_calls + @string/zen_mode_none_calls diff --git a/res/values/strings.xml b/res/values/strings.xml index eff1ede4010..d4f3f7344ab 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8978,6 +8978,8 @@ 1 other %d others + + None Messages @@ -8997,8 +8999,13 @@ All calls can reach you - - %d contacts + + None + + + 1 contact + %d contacts + Anyone @@ -9006,8 +9013,6 @@ Contacts Starred contacts - - None From starred contacts and repeat callers @@ -9017,9 +9022,9 @@ From repeat callers only - Don\u2019t allow any calls + None - Don\u2019t allow any messages + None Alarms diff --git a/src/com/android/settings/notification/zen/ZenModeBackend.java b/src/com/android/settings/notification/zen/ZenModeBackend.java index b9b27c400f7..2f6a2b1b52e 100644 --- a/src/com/android/settings/notification/zen/ZenModeBackend.java +++ b/src/com/android/settings/notification/zen/ZenModeBackend.java @@ -287,13 +287,13 @@ public class ZenModeBackend { protected int getAlarmsTotalSilencePeopleSummary(int category) { if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) { - return R.string.zen_mode_from_none; + return R.string.zen_mode_none_messages; } else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS){ - return R.string.zen_mode_from_none; + return R.string.zen_mode_none_calls; } else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS) { return R.string.zen_mode_from_no_conversations; } - return R.string.zen_mode_from_none; + return R.string.zen_mode_from_no_conversations; } protected int getConversationSummary() { @@ -322,7 +322,7 @@ public class ZenModeBackend { return R.string.zen_mode_from_starred; case ZenPolicy.PEOPLE_TYPE_NONE: default: - return R.string.zen_mode_from_none; + return R.string.zen_mode_none_calls; } } @@ -337,7 +337,7 @@ public class ZenModeBackend { return R.string.zen_mode_from_starred; case ZenPolicy.PEOPLE_TYPE_NONE: default: - return R.string.zen_mode_from_none; + return R.string.zen_mode_none_messages; } } @@ -472,7 +472,7 @@ public class ZenModeBackend { List displayContacts = new ArrayList<>(); if (numStarredContacts == 0) { - displayContacts.add(context.getString(R.string.zen_mode_from_none)); + displayContacts.add(context.getString(R.string.zen_mode_starred_contacts_summary_none)); } else { for (int i = 0; i < 2 && i < numStarredContacts; i++) { displayContacts.add(starredContacts.get(i)); @@ -494,10 +494,11 @@ public class ZenModeBackend { String getContactsNumberSummary(Context context) { final int numContacts = queryAllContactsData().getCount(); if (numContacts == 0) { - return context.getResources().getString(R.string.zen_mode_from_none); + return context.getResources().getString( + R.string.zen_mode_contacts_count_none); } - return context.getResources().getString(R.string.zen_mode_contacts_senders_summary, - numContacts); + return context.getResources().getQuantityString(R.plurals.zen_mode_contacts_count, + numContacts, numContacts); } private Cursor queryStarredContactsData() { diff --git a/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceController.java index 8fd59c39a6a..abf2cedd3dc 100644 --- a/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceController.java @@ -91,7 +91,9 @@ public class ZenModePrioritySendersPreferenceController makeRadioPreference(KEY_ANY, com.android.settings.R.string.zen_mode_from_anyone); makeRadioPreference(KEY_NONE, - com.android.settings.R.string.zen_mode_from_none); + mIsMessages + ? com.android.settings.R.string.zen_mode_none_messages + : com.android.settings.R.string.zen_mode_none_calls); updateSummaries(); } diff --git a/src/com/android/settings/notification/zen/ZenModeSendersImagePreferenceController.java b/src/com/android/settings/notification/zen/ZenModeSendersImagePreferenceController.java index e8cd40db63a..6a0cf7acc71 100644 --- a/src/com/android/settings/notification/zen/ZenModeSendersImagePreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenModeSendersImagePreferenceController.java @@ -91,7 +91,10 @@ public class ZenModeSendersImagePreferenceController newImageRes = mIsMessages ? R.drawable.zen_messages_none : R.drawable.zen_calls_none; - newContentDescription = mContext.getString(R.string.zen_mode_from_none); + newContentDescription = + mContext.getString(mIsMessages + ? R.string.zen_mode_none_messages + : R.string.zen_mode_none_calls); } mImageView.setImageResource(newImageRes); diff --git a/src/com/android/settings/notification/zen/ZenModeSettings.java b/src/com/android/settings/notification/zen/ZenModeSettings.java index 3e59203a8b9..436a3990801 100644 --- a/src/com/android/settings/notification/zen/ZenModeSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeSettings.java @@ -156,7 +156,7 @@ public class ZenModeSettings extends ZenModeSettingsBase { || PRIORITY_CATEGORY_REPEAT_CALLERS == category, true); int numCategories = enabledCategories.size(); if (numCategories == 0) { - return mContext.getString(R.string.zen_mode_from_none); + return mContext.getString(R.string.zen_mode_none_calls); } else if (numCategories == 1) { return mContext.getString(R.string.zen_mode_calls_summary_one, enabledCategories.get(0)); @@ -172,7 +172,7 @@ public class ZenModeSettings extends ZenModeSettingsBase { category -> PRIORITY_CATEGORY_MESSAGES == category, false); int numCategories = enabledCategories.size(); if (numCategories == 0) { - return mContext.getString(R.string.zen_mode_from_none); + return mContext.getString(R.string.zen_mode_none_messages); } else { return enabledCategories.get(0); } From 1a2e7ad52fd1f23696e28df6ed923459cb9036be Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 28 Apr 2020 15:04:39 -0400 Subject: [PATCH 10/11] Fix some icons on conversation screen Test: manual Fixes: 154930822 Change-Id: Ic480203f494eaccd743a7c77895596174f52539b --- res/drawable/ic_lock_closed.xml | 28 +++++++++++++++++++ .../conversation_notification_settings.xml | 4 +-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 res/drawable/ic_lock_closed.xml diff --git a/res/drawable/ic_lock_closed.xml b/res/drawable/ic_lock_closed.xml new file mode 100644 index 00000000000..cb0ea241246 --- /dev/null +++ b/res/drawable/ic_lock_closed.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/res/xml/conversation_notification_settings.xml b/res/xml/conversation_notification_settings.xml index c034eb5d6d8..65fdee5e9da 100644 --- a/res/xml/conversation_notification_settings.xml +++ b/res/xml/conversation_notification_settings.xml @@ -78,14 +78,14 @@ android:key="ringtone" android:title="@string/notification_channel_sound_title" android:dialogTitle="@string/notification_channel_sound_title" - android:icon="@drawable/ic_media_stream" + android:icon="@drawable/ic_notifications" android:showSilent="true" android:showDefault="true"/> From a27918494f7b897dc5a2db3616373a7ea4e206a2 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Fri, 17 Apr 2020 11:24:50 -0700 Subject: [PATCH 11/11] Add list of selected/excluded convos to bubble settings * Some changes to extend AppConversationListPrefController * When a user removes something from the selected or excluded list that resets the channel setting Bug: 148619540 Test: make -j40 RunSettingsRoboTests ROBOTEST_FILTER="Bubble" Change-Id: I7a9ed7b70208dbdefca6c3c09d34d55c65f06408 --- res/drawable/ic_delete_x.xml | 25 +++ .../bubble_conversation_remove_button.xml | 24 +++ res/xml/app_bubble_notification_settings.xml | 8 + .../AppBubbleListPreferenceController.java | 154 ++++++++++++++++ .../app/AppBubbleNotificationSettings.java | 11 +- ...pConversationListPreferenceController.java | 43 ++++- .../app/BubblePreferenceController.java | 8 +- .../app/ConversationNotificationSettings.java | 2 +- ...AppBubbleListPreferenceControllerTest.java | 173 ++++++++++++++++++ .../app/BubblePreferenceControllerTest.java | 59 +++--- 10 files changed, 472 insertions(+), 35 deletions(-) create mode 100644 res/drawable/ic_delete_x.xml create mode 100644 res/layout/bubble_conversation_remove_button.xml create mode 100644 src/com/android/settings/notification/AppBubbleListPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/app/AppBubbleListPreferenceControllerTest.java diff --git a/res/drawable/ic_delete_x.xml b/res/drawable/ic_delete_x.xml new file mode 100644 index 00000000000..345daf03fe6 --- /dev/null +++ b/res/drawable/ic_delete_x.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/res/layout/bubble_conversation_remove_button.xml b/res/layout/bubble_conversation_remove_button.xml new file mode 100644 index 00000000000..6ec48dcbea8 --- /dev/null +++ b/res/layout/bubble_conversation_remove_button.xml @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/res/xml/app_bubble_notification_settings.xml b/res/xml/app_bubble_notification_settings.xml index 3f52ad38153..976d9f0c48f 100644 --- a/res/xml/app_bubble_notification_settings.xml +++ b/res/xml/app_bubble_notification_settings.xml @@ -27,4 +27,12 @@ android:title="@string/notification_bubbles_title" settings:allowDividerBelow="false"/> + + + diff --git a/src/com/android/settings/notification/AppBubbleListPreferenceController.java b/src/com/android/settings/notification/AppBubbleListPreferenceController.java new file mode 100644 index 00000000000..457a61ce45b --- /dev/null +++ b/src/com/android/settings/notification/AppBubbleListPreferenceController.java @@ -0,0 +1,154 @@ +/* + * 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.notification; + +import static android.app.NotificationChannel.DEFAULT_ALLOW_BUBBLE; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; + +import android.annotation.Nullable; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.graphics.drawable.Drawable; +import android.service.notification.ConversationChannelWrapper; +import android.view.View; +import android.widget.ImageView; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; +import com.android.settings.notification.app.AppConversationListPreferenceController; +import com.android.settingslib.RestrictedLockUtils; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Displays a list of conversations that have either been selected or excluded from bubbling. + */ +public class AppBubbleListPreferenceController extends AppConversationListPreferenceController { + + private static final String KEY = "bubble_conversations"; + + public AppBubbleListPreferenceController(Context context, NotificationBackend backend) { + super(context, backend); + } + + @Override + public void onResume(NotificationBackend.AppRow appRow, + @Nullable NotificationChannel channel, @Nullable NotificationChannelGroup group, + Drawable conversationDrawable, + ShortcutInfo conversationInfo, + RestrictedLockUtils.EnforcedAdmin admin) { + super.onResume(appRow, channel, group, conversationDrawable, conversationInfo, admin); + // In case something changed in the foreground (e.g. via bubble button on notification) + loadConversationsAndPopulate(); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mAppRow.bubblePreference == BUBBLE_PREFERENCE_NONE) { + return false; + } + return true; + } + + @VisibleForTesting + @Override + public List filterAndSortConversations( + List conversations) { + return conversations.stream() + .sorted(mConversationComparator) + .filter((c) -> { + if (mAppRow.bubblePreference == BUBBLE_PREFERENCE_SELECTED) { + return c.getNotificationChannel().canBubble(); + } else if (mAppRow.bubblePreference == BUBBLE_PREFERENCE_ALL) { + return c.getNotificationChannel().getAllowBubbles() == 0; + } + return false; + }) + .collect(Collectors.toList()); + } + + @Override + protected int getTitleResId() { + // TODO: possible to left align like mocks? + return mAppRow.bubblePreference == BUBBLE_PREFERENCE_SELECTED + ? R.string.bubble_app_setting_selected_conversation_title + : R.string.bubble_app_setting_excluded_conversation_title; + } + + @VisibleForTesting + @Override + public Preference createConversationPref(final ConversationChannelWrapper conversation) { + final ConversationPreference pref = new ConversationPreference(mContext); + populateConversationPreference(conversation, pref); + pref.setOnClickListener((v) -> { + conversation.getNotificationChannel().setAllowBubbles(DEFAULT_ALLOW_BUBBLE); + mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, conversation.getNotificationChannel()); + mPreference.removePreference(pref); + if (mPreference.getPreferenceCount() == 0) { + mPreference.setVisible(false); + } + }); + return pref; + } + + /** Simple preference with a 'x' button at the end. */ + @VisibleForTesting + public static class ConversationPreference extends Preference implements View.OnClickListener { + + View.OnClickListener mOnClickListener; + + ConversationPreference(Context context) { + super(context); + setWidgetLayoutResource(R.layout.bubble_conversation_remove_button); + } + + @Override + public void onBindViewHolder(final PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + ImageView view = holder.itemView.findViewById(R.id.button); + view.setOnClickListener(mOnClickListener); + } + + public void setOnClickListener(View.OnClickListener listener) { + mOnClickListener = listener; + } + + @Override + public void onClick(View v) { + if (mOnClickListener != null) { + mOnClickListener.onClick(v); + } + } + } +} diff --git a/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java b/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java index 5026a26a52a..b1b126e56c0 100644 --- a/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java +++ b/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java @@ -22,6 +22,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.settings.R; +import com.android.settings.notification.AppBubbleListPreferenceController; import com.android.settings.notification.NotificationBackend; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; @@ -56,18 +57,20 @@ public class AppBubbleNotificationSettings extends NotificationSettings implemen @Override protected List createPreferenceControllers(Context context) { - mControllers = getPreferenceControllers(context, this); + mControllers = getPreferenceControllers(context, this, mDependentFieldListener); return new ArrayList<>(mControllers); } protected static List getPreferenceControllers( - Context context, AppBubbleNotificationSettings fragment) { + Context context, AppBubbleNotificationSettings fragment, + DependentFieldListener listener) { List controllers = new ArrayList<>(); controllers.add(new HeaderPreferenceController(context, fragment)); controllers.add(new BubblePreferenceController(context, fragment != null ? fragment.getChildFragmentManager() : null, - new NotificationBackend(), true /* isAppPage */)); + new NotificationBackend(), true /* isAppPage */, listener)); + controllers.add(new AppBubbleListPreferenceController(context, new NotificationBackend())); return controllers; } @@ -114,7 +117,7 @@ public class AppBubbleNotificationSettings extends NotificationSettings implemen public List createPreferenceControllers(Context context) { return new ArrayList<>(AppBubbleNotificationSettings.getPreferenceControllers( - context, null)); + context, null, null)); } }; } diff --git a/src/com/android/settings/notification/app/AppConversationListPreferenceController.java b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java index bea015709c2..2fcd2b2059a 100644 --- a/src/com/android/settings/notification/app/AppConversationListPreferenceController.java +++ b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java @@ -19,6 +19,7 @@ package com.android.settings.notification.app; import android.app.NotificationChannel; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.os.AsyncTask; import android.os.Bundle; @@ -33,6 +34,7 @@ import com.android.settings.applications.AppInfoBase; import com.android.settings.core.SubSettingLauncher; import com.android.settings.notification.NotificationBackend; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -42,8 +44,8 @@ public class AppConversationListPreferenceController extends NotificationPrefere private static final String KEY = "conversations"; public static final String ARG_FROM_SETTINGS = "fromSettings"; - private List mConversations; - private PreferenceCategory mPreference; + protected List mConversations = new ArrayList<>(); + protected PreferenceCategory mPreference; private boolean mHasSentMsg; public AppConversationListPreferenceController(Context context, NotificationBackend backend) { @@ -75,13 +77,23 @@ public class AppConversationListPreferenceController extends NotificationPrefere @Override public void updateState(Preference preference) { mPreference = (PreferenceCategory) preference; + loadConversationsAndPopulate(); + } + + protected void loadConversationsAndPopulate() { + if (mAppRow == null) { + return; + } // Load channel settings new AsyncTask() { @Override protected Void doInBackground(Void... unused) { mHasSentMsg = mBackend.hasSentMessage(mAppRow.pkg, mAppRow.uid); - mConversations = mBackend.getConversations(mAppRow.pkg, mAppRow.uid).getList(); - Collections.sort(mConversations, mConversationComparator); + ParceledListSlice list = + mBackend.getConversations(mAppRow.pkg, mAppRow.uid); + if (list != null) { + mConversations = filterAndSortConversations(list.getList()); + } return null; } @@ -95,11 +107,22 @@ public class AppConversationListPreferenceController extends NotificationPrefere }.execute(); } + protected List filterAndSortConversations( + List conversations) { + Collections.sort(conversations, mConversationComparator); + return conversations; + } + + protected int getTitleResId() { + return R.string.conversations_category_title; + } + private void populateList() { + if (mPreference == null) { + return; + } // TODO: if preference has children, compare with newly loaded list mPreference.removeAll(); - mPreference.setTitle(R.string.conversations_category_title); - if (mConversations.isEmpty()) { if (mHasSentMsg) { mPreference.setVisible(true); @@ -112,6 +135,7 @@ public class AppConversationListPreferenceController extends NotificationPrefere } } else { mPreference.setVisible(true); + mPreference.setTitle(getTitleResId()); populateConversations(); } } @@ -127,6 +151,12 @@ public class AppConversationListPreferenceController extends NotificationPrefere protected Preference createConversationPref(final ConversationChannelWrapper conversation) { Preference pref = new Preference(mContext); + populateConversationPreference(conversation, pref); + return pref; + } + + protected void populateConversationPreference(final ConversationChannelWrapper conversation, + final Preference pref) { ShortcutInfo si = conversation.getShortcutInfo(); pref.setTitle(si != null @@ -157,7 +187,6 @@ public class AppConversationListPreferenceController extends NotificationPrefere .setTitleText(pref.getTitle()) .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_APP_NOTIFICATION) .toIntent()); - return pref; } protected Comparator mConversationComparator = diff --git a/src/com/android/settings/notification/app/BubblePreferenceController.java b/src/com/android/settings/notification/app/BubblePreferenceController.java index 02d6d99e049..0ca2095d6ec 100644 --- a/src/com/android/settings/notification/app/BubblePreferenceController.java +++ b/src/com/android/settings/notification/app/BubblePreferenceController.java @@ -49,12 +49,15 @@ public class BubblePreferenceController extends NotificationPreferenceController private boolean mIsAppPage; private boolean mHasSentInvalidMsg; private int mNumConversations; + private NotificationSettings.DependentFieldListener mListener; public BubblePreferenceController(Context context, @Nullable FragmentManager fragmentManager, - NotificationBackend backend, boolean isAppPage) { + NotificationBackend backend, boolean isAppPage, + @Nullable NotificationSettings.DependentFieldListener listener) { super(context, backend); mFragmentManager = fragmentManager; mIsAppPage = isAppPage; + mListener = listener; } @Override @@ -128,6 +131,9 @@ public class BubblePreferenceController extends NotificationPreferenceController mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value); } } + if (mListener != null) { + mListener.onFieldValueChanged(); + } } return true; } diff --git a/src/com/android/settings/notification/app/ConversationNotificationSettings.java b/src/com/android/settings/notification/app/ConversationNotificationSettings.java index 9ee4a2cf2e1..902c81c8996 100644 --- a/src/com/android/settings/notification/app/ConversationNotificationSettings.java +++ b/src/com/android/settings/notification/app/ConversationNotificationSettings.java @@ -92,7 +92,7 @@ public class ConversationNotificationSettings extends NotificationSettings { mControllers.add(new BadgePreferenceController(context, mBackend)); mControllers.add(new NotificationsOffPreferenceController(context)); mControllers.add(new BubblePreferenceController(context, getChildFragmentManager(), - mBackend, false /* isAppPage */)); + mBackend, false /* isAppPage */, null /* dependentFieldListener */)); mControllers.add(new ConversationDemotePreferenceController(context, this, mBackend)); mControllers.add(new BubbleCategoryPreferenceController(context)); mControllers.add(new BubbleLinkPreferenceController(context)); diff --git a/tests/robotests/src/com/android/settings/notification/app/AppBubbleListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/AppBubbleListPreferenceControllerTest.java new file mode 100644 index 00000000000..d176827c08b --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/app/AppBubbleListPreferenceControllerTest.java @@ -0,0 +1,173 @@ +/* + * 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.notification.app; + +import static android.app.NotificationChannel.DEFAULT_ALLOW_BUBBLE; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; +import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; +import android.os.UserManager; +import android.service.notification.ConversationChannelWrapper; + +import androidx.preference.PreferenceCategory; + +import com.android.settings.notification.AppBubbleListPreferenceController; +import com.android.settings.notification.NotificationBackend; + +import org.junit.Before; +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.util.ArrayList; +import java.util.List; + + +@RunWith(RobolectricTestRunner.class) +public class AppBubbleListPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + @Mock + private NotificationManager mNm; + @Mock + private UserManager mUm; + + private AppBubbleListPreferenceController mController; + private ParceledListSlice mConvoList; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + shadowApplication.setSystemService(Context.USER_SERVICE, mUm); + mContext = RuntimeEnvironment.application; + + List convoList = new ArrayList<>(); + convoList.add(getConvo(-1, "default")); + convoList.add(getConvo(1, "selected")); + convoList.add(getConvo(0, "excluded")); + mConvoList = new ParceledListSlice<>(convoList); + when(mBackend.getConversations(anyString(), anyInt())).thenReturn(mConvoList); + mController = new AppBubbleListPreferenceController(mContext, mBackend); + } + + ConversationChannelWrapper getConvo(int bubbleChannelPref, String channelId) { + ConversationChannelWrapper ccw = new ConversationChannelWrapper(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getId()).thenReturn(channelId); + when(channel.getAllowBubbles()).thenReturn(bubbleChannelPref); + when(channel.canBubble()).thenReturn(bubbleChannelPref == 1); + ccw.setNotificationChannel(channel); + ccw.setPkg("pkg"); + ccw.setUid(1); + ccw.setShortcutInfo(mock(ShortcutInfo.class)); + return ccw; + } + + @Test + public void isAvailable_BUBBLE_PREFERENCE_NONE_false() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.bubblePreference = BUBBLE_PREFERENCE_NONE; + mController.onResume(appRow, null, null, null, null, null); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_BUBBLE_PREFERENCE_SELECTED_true() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED; + mController.onResume(appRow, null, null, null, null, null); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_BUBBLE_PREFERENCE_ALL_true() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.bubblePreference = BUBBLE_PREFERENCE_ALL; + mController.onResume(appRow, null, null, null, null, null); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void filterAndSortConversations_BUBBLE_PREFERENCE_SELECTED_filtersAllowedBubbles() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED; + mController.onResume(appRow, null, null, null, null, null); + + List result = + mController.filterAndSortConversations(mConvoList.getList()); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getNotificationChannel().getId()) + .isEqualTo("selected"); + } + + @Test + public void filterAndSortConversations_BUBBLE_PREFERENCE_ALL_filtersExcludedBubbles() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.bubblePreference = BUBBLE_PREFERENCE_ALL; + mController.onResume(appRow, null, null, null, null, null); + + List result = + mController.filterAndSortConversations(mConvoList.getList()); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getNotificationChannel().getId()) + .isEqualTo("excluded"); + } + + @Test + public void clickConversationPref_updatesChannel() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.bubblePreference = BUBBLE_PREFERENCE_ALL; + appRow.pkg = "PKG"; + mController.onResume(appRow, null, null, null, null, null); + mController.mPreference = new PreferenceCategory(mContext); + + ConversationChannelWrapper ccw = mConvoList.getList().get(0); + AppBubbleListPreferenceController.ConversationPreference pref = + (AppBubbleListPreferenceController.ConversationPreference) + mController.createConversationPref(ccw); + pref.onClick(null); + + verify(ccw.getNotificationChannel()).setAllowBubbles(DEFAULT_ALLOW_BUBBLE); + verify(mBackend).updateChannel(anyString(), anyInt(), any(NotificationChannel.class)); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java index 0cf6dc67829..685bca9b119 100644 --- a/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java @@ -80,6 +80,8 @@ public class BubblePreferenceControllerTest { private PreferenceScreen mScreen; @Mock private FragmentManager mFragmentManager; + @Mock + private NotificationSettings.DependentFieldListener mListener; private BubblePreferenceController mController; private BubblePreferenceController mAppPageController; @@ -93,9 +95,9 @@ public class BubblePreferenceControllerTest { mContext = RuntimeEnvironment.application; when(mFragmentManager.beginTransaction()).thenReturn(mock(FragmentTransaction.class)); mController = spy(new BubblePreferenceController(mContext, mFragmentManager, mBackend, - false /* isAppPage */)); + false /* isAppPage */, mListener)); mAppPageController = spy(new BubblePreferenceController(mContext, mFragmentManager, - mBackend, true /* isAppPage */)); + mBackend, true /* isAppPage */, mListener)); } @Test @@ -106,7 +108,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testIsAvailable_notIfAppBlocked() { + public void isAvailable_notIfAppBlocked() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.banned = true; @@ -115,7 +117,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testIsAvailable_notIfChannelBlocked() { + public void isAvailable_notIfChannelBlocked() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); NotificationChannel channel = mock(NotificationChannel.class); @@ -125,7 +127,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testIsAvailable_channel_yesIfAppOff() { + public void isAvailable_channel_yesIfAppOff() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.bubblePreference = BUBBLE_PREFERENCE_NONE; @@ -137,7 +139,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testIsNotAvailable_ifOffGlobally_app() { + public void isNotAvailable_ifOffGlobally_app() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); mController.onResume(appRow, null, null, null, null, null); Settings.Global.putInt(mContext.getContentResolver(), @@ -147,7 +149,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testIsAvailable_notIfOffGlobally_channel() { + public void isAvailable_notIfOffGlobally_channel() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); NotificationChannel channel = mock(NotificationChannel.class); when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); @@ -159,7 +161,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testIsAvailable_app_evenIfOffGlobally() { + public void isAvailable_app_evenIfOffGlobally() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); mAppPageController.onResume(appRow, null, null, null, null, null); Settings.Global.putInt(mContext.getContentResolver(), @@ -169,7 +171,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testIsAvailable_app() { + public void isAvailable_app() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); mController.onResume(appRow, null, null, null, null, null); Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); @@ -178,7 +180,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testIsAvailable_defaultChannel() { + public void isAvailable_defaultChannel() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.bubblePreference = BUBBLE_PREFERENCE_ALL; NotificationChannel channel = mock(NotificationChannel.class); @@ -191,7 +193,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testIsAvailable_channel() { + public void isAvailable_channel() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.bubblePreference = BUBBLE_PREFERENCE_ALL; NotificationChannel channel = mock(NotificationChannel.class); @@ -203,7 +205,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testUpdateState_disabledByAdmin() { + public void updateState_disabledByAdmin() { NotificationChannel channel = mock(NotificationChannel.class); when(channel.getId()).thenReturn("something"); mController.onResume(new NotificationBackend.AppRow(), channel, null, @@ -216,7 +218,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testUpdateState_app_disabledByAdmin() { + public void updateState_app_disabledByAdmin() { NotificationChannel channel = mock(NotificationChannel.class); when(channel.getId()).thenReturn("something"); mAppPageController.onResume(new NotificationBackend.AppRow(), channel, null, @@ -229,7 +231,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testUpdateState_channel_channelNotBlockable() { + public void updateState_channel_channelNotBlockable() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); NotificationChannel channel = mock(NotificationChannel.class); @@ -243,7 +245,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testUpdateState_channel() { + public void updateState_channel() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); NotificationChannel channel = mock(NotificationChannel.class); @@ -263,7 +265,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testUpdateState_app() { + public void updateState_app() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.label = "App!"; @@ -288,7 +290,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testUpdateState_app_offGlobally() { + public void updateState_app_offGlobally() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); @@ -302,7 +304,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testOnPreferenceChange_on_channel() { + public void onPreferenceChange_on_channel() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED; @@ -321,7 +323,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testOnPreferenceChange_off_channel() { + public void onPreferenceChange_off_channel() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED; @@ -341,7 +343,7 @@ public class BubblePreferenceControllerTest { @Test - public void testOnPreferenceChange_app_all() { + public void onPreferenceChange_app_all() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.bubblePreference = BUBBLE_PREFERENCE_NONE; @@ -379,7 +381,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testOnPreferenceChange_app_selected() { + public void onPreferenceChange_app_selected() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.bubblePreference = BUBBLE_PREFERENCE_ALL; @@ -397,7 +399,7 @@ public class BubblePreferenceControllerTest { } @Test - public void testOnPreferenceChange_app_none() { + public void onPreferenceChange_app_none() { Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); appRow.bubblePreference = BUBBLE_PREFERENCE_ALL; @@ -413,4 +415,17 @@ public class BubblePreferenceControllerTest { assertEquals(BUBBLE_PREFERENCE_NONE, appRow.bubblePreference); verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(BUBBLE_PREFERENCE_NONE)); } + + @Test + public void onPreferenceChange_dependentFieldListenerCalled() { + Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON); + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.bubblePreference = BUBBLE_PREFERENCE_ALL; + mAppPageController.onResume(appRow, null, null, null, null, null); + + BubblePreference pref = new BubblePreference(mContext); + mAppPageController.onPreferenceChange(pref, BUBBLE_PREFERENCE_NONE); + + verify(mListener, times(1)).onFieldValueChanged(); + } }