From 4f3b5563880821ace4801da835ed3f680411cb5c Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 30 Jan 2018 11:28:38 -0500 Subject: [PATCH 1/9] Notification settings updates - Footer preferences were too dim - Fix text and target on recently sent apps - Make 'behavior' a dialog - reorder settings on apps page Change-Id: Idf8056bc77ead89fe2025bbde3346861e23a3c8d Fixes: 72652526 Fixes: 72651810 Fixes: 72652024 Bug: 72651953 Test: make RunSettingsRoboTests --- AndroidManifest.xml | 13 -- res/values/strings.xml | 3 + res/xml/app_notification_settings.xml | 19 +- res/xml/channel_notification_settings.xml | 2 +- res/xml/configure_notification_settings.xml | 2 +- src/com/android/settings/Settings.java | 1 - .../applications/NotificationApps.java | 83 --------- .../ManageApplications.java | 9 +- .../core/gateway/SettingsGateway.java | 2 - .../AllowSoundPreferenceController.java | 1 + .../notification/AppNotificationSettings.java | 3 +- .../ChannelImportanceSettings.java | 166 ------------------ .../ChannelNotificationSettings.java | 3 +- .../ConfigureNotificationSettings.java | 11 -- .../DeletedChannelsPreferenceController.java | 1 - .../ImportancePreferenceController.java | 99 +++++++---- .../NotificationsOffPreferenceController.java | 1 - ...centNotifyingAppsPreferenceController.java | 3 +- ...randfather_not_implementing_index_provider | 1 - .../applications/NotificationAppsTest.java | 114 ------------ ...letedChannelsPreferenceControllerTest.java | 1 - .../ImportancePreferenceControllerTest.java | 67 ++++++- ...ificationsOffPreferenceControllerTest.java | 3 - 23 files changed, 147 insertions(+), 461 deletions(-) delete mode 100644 src/com/android/settings/applications/NotificationApps.java delete mode 100644 src/com/android/settings/notification/ChannelImportanceSettings.java delete mode 100644 tests/robotests/src/com/android/settings/applications/NotificationAppsTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f9e123a1e42..1766ffbcd3d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2645,19 +2645,6 @@ android:value="com.android.settings.notification.SoundSettings" /> - - - - - - - - diff --git a/res/values/strings.xml b/res/values/strings.xml index fe32e9d2f02..f86cc56cb28 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6983,6 +6983,9 @@ Recently sent + + See all apps + Advanced diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml index b21d168d4b8..2d5dc57ca4f 100644 --- a/res/xml/app_notification_settings.xml +++ b/res/xml/app_notification_settings.xml @@ -15,7 +15,8 @@ --> + xmlns:settings="http://schemas.android.com/apk/res-auto" + settings:initialExpandedChildrenCount="500"> + + + + - - - - + android:order="502" /> - diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index e080be4dc6e..9d724416785 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -79,7 +79,7 @@ + android:targetClass="com.android.settings.Settings$ManageApplicationsActivity"> diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index b4908ddc706..7bd85cdcc2a 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -108,7 +108,6 @@ public class Settings extends SettingsActivity { public static class ZenModeExternalRuleSettingsActivity extends SettingsActivity { /* empty */ } public static class SoundSettingsActivity extends SettingsActivity { /* empty */ } public static class ConfigureNotificationSettingsActivity extends SettingsActivity { /* empty */ } - public static class NotificationAppListActivity extends SettingsActivity { /* empty */ } public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ } public static class ChannelNotificationSettingsActivity extends SettingsActivity { /* empty */ } public static class ChannelGroupNotificationSettingsActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/applications/NotificationApps.java b/src/com/android/settings/applications/NotificationApps.java deleted file mode 100644 index f921092654e..00000000000 --- a/src/com/android/settings/applications/NotificationApps.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.settings.applications; - -import android.app.Activity; -import android.content.Context; -import android.content.pm.ApplicationInfo; - -import com.android.settings.R; -import com.android.settings.applications.manageapplications.ManageApplications; -import com.android.settings.dashboard.SummaryLoader; -import com.android.settings.notification.NotificationBackend; -import com.android.settingslib.wrapper.PackageManagerWrapper; - -/** - * Extension of ManageApplications with no changes other than having its own - * SummaryProvider. - */ -public class NotificationApps extends ManageApplications { - - public static class SummaryProvider implements SummaryLoader.SummaryProvider { - - private final Context mContext; - private final SummaryLoader mLoader; - private final NotificationBackend mNotificationBackend; - private final PackageManagerWrapper mPackageManager; - - public SummaryProvider(Context context, SummaryLoader loader) { - mContext = context; - mLoader = loader; - mNotificationBackend = new NotificationBackend(); - mPackageManager = new PackageManagerWrapper(mContext.getPackageManager()); - } - - @Override - public void setListening(boolean listening) { - if (listening) { - new AppCounter(mContext, mPackageManager) { - @Override - protected void onCountComplete(int num) { - updateSummary(num); - } - - @Override - protected boolean includeInCount(ApplicationInfo info) { - return mNotificationBackend.getNotificationsBanned(info.packageName, - info.uid); - } - }.execute(); - } - } - - private void updateSummary(int count) { - if (count == 0) { - mLoader.setSummary(this, mContext.getString(R.string.notification_summary_none)); - } else { - mLoader.setSummary(this, mContext.getResources().getQuantityString( - R.plurals.notification_summary, count, count)); - } - } - } - - public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY - = new SummaryLoader.SummaryProviderFactory() { - @Override - public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, - SummaryLoader summaryLoader) { - return new SummaryProvider(activity, summaryLoader); - } - }; -} diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index 06ba86e4a82..b0949ea4a62 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -70,7 +70,6 @@ import com.android.settings.Settings.GamesStorageActivity; import com.android.settings.Settings.HighPowerApplicationsActivity; import com.android.settings.Settings.ManageExternalSourcesActivity; import com.android.settings.Settings.MoviesStorageActivity; -import com.android.settings.Settings.NotificationAppListActivity; import com.android.settings.Settings.OverlaySettingsActivity; import com.android.settings.Settings.StorageUseActivity; import com.android.settings.Settings.UsageAccessSettingsActivity; @@ -90,7 +89,6 @@ import com.android.settings.applications.AppStateWriteSettingsBridge; import com.android.settings.applications.AppStorageSettings; import com.android.settings.applications.DefaultAppSettings; import com.android.settings.applications.InstalledAppCounter; -import com.android.settings.applications.NotificationApps; import com.android.settings.applications.DirectoryAccessDetails; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.appinfo.AppInfoDashboardFragment; @@ -238,12 +236,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment if (className == null) { className = intent.getComponent().getClassName(); } - if (className.equals(NotificationAppListActivity.class.getName()) - || this instanceof NotificationApps) { - mListType = LIST_TYPE_NOTIFICATION; - mNotifBackend = new NotificationBackend(); - screenTitle = R.string.app_notifications_title; - } else if (className.equals(StorageUseActivity.class.getName())) { + if (className.equals(StorageUseActivity.class.getName())) { if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) { mVolumeUuid = args.getString(EXTRA_VOLUME_UUID); mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT); diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 91fc108c541..af85ac90fdf 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -40,7 +40,6 @@ import com.android.settings.accounts.AccountDashboardFragment; import com.android.settings.applications.AppAndNotificationDashboardFragment; import com.android.settings.applications.DefaultAppSettings; import com.android.settings.applications.ManageDomainUrls; -import com.android.settings.applications.NotificationApps; import com.android.settings.applications.ProcessStatsSummary; import com.android.settings.applications.ProcessStatsUi; import com.android.settings.applications.DirectoryAccessDetails; @@ -157,7 +156,6 @@ public class SettingsGateway { DisplaySettings.class.getName(), DeviceInfoSettings.class.getName(), ManageApplications.class.getName(), - NotificationApps.class.getName(), ManageAssist.class.getName(), ProcessStatsUi.class.getName(), NotificationStation.class.getName(), diff --git a/src/com/android/settings/notification/AllowSoundPreferenceController.java b/src/com/android/settings/notification/AllowSoundPreferenceController.java index dcd5e45de93..7186be1f5b3 100644 --- a/src/com/android/settings/notification/AllowSoundPreferenceController.java +++ b/src/com/android/settings/notification/AllowSoundPreferenceController.java @@ -56,6 +56,7 @@ public class AllowSoundPreferenceController extends NotificationPreferenceContro } + @Override public void updateState(Preference preference) { if (mChannel != null) { RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 333e0600118..14ccf2350c4 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -120,7 +120,8 @@ public class AppNotificationSettings extends NotificationSettingsBase { mControllers.add(new BadgePreferenceController(context, mBackend)); mControllers.add(new AllowSoundPreferenceController( context, mImportanceListener, mBackend)); - mControllers.add(new ImportancePreferenceController(context)); + mControllers.add(new ImportancePreferenceController( + context, mImportanceListener, mBackend)); mControllers.add(new SoundPreferenceController(context, this, mImportanceListener, mBackend)); mControllers.add(new LightsPreferenceController(context, mBackend)); diff --git a/src/com/android/settings/notification/ChannelImportanceSettings.java b/src/com/android/settings/notification/ChannelImportanceSettings.java deleted file mode 100644 index 27b23b8c2cc..00000000000 --- a/src/com/android/settings/notification/ChannelImportanceSettings.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2017 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.USER_LOCKED_IMPORTANCE; -import static android.app.NotificationChannel.USER_LOCKED_SOUND; -import static android.app.NotificationManager.IMPORTANCE_DEFAULT; -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_LOW; -import static android.app.NotificationManager.IMPORTANCE_MAX; -import static android.app.NotificationManager.IMPORTANCE_MIN; - -import android.content.Context; -import android.media.RingtoneManager; -import android.provider.SearchIndexableResource; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; -import android.text.TextUtils; -import android.util.Log; - -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.R; -import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.search.Indexable; -import com.android.settings.widget.RadioButtonPreference; -import com.android.settingslib.core.AbstractPreferenceController; - -import java.util.ArrayList; -import java.util.List; - -public class ChannelImportanceSettings extends NotificationSettingsBase - implements RadioButtonPreference.OnClickListener, Indexable { - private static final String TAG = "NotiImportance"; - - private static final String KEY_IMPORTANCE_HIGH = "importance_high"; - private static final String KEY_IMPORTANCE_DEFAULT = "importance_default"; - private static final String KEY_IMPORTANCE_LOW = "importance_low"; - private static final String KEY_IMPORTANCE_MIN = "importance_min"; - - List mImportances = new ArrayList<>(); - - @Override - public int getMetricsCategory() { - return MetricsEvent.NOTIFICATION_CHANNEL_IMPORTANCE; - } - - @Override - public void onResume() { - super.onResume(); - if (mAppRow == null || mChannel == null) { - Log.w(TAG, "Missing package or channel"); - finish(); - return; - } - createPreferenceHierarchy(); - } - - @Override - protected String getLogTag() { - return TAG; - } - - @Override - protected int getPreferenceScreenResId() { - return R.xml.notification_importance; - } - - @Override - protected List getPreferenceControllers(Context context) { - return null; - } - - @Override - public void onPause() { - super.onPause(); - } - - private PreferenceScreen createPreferenceHierarchy() { - PreferenceScreen root = getPreferenceScreen(); - - for (int i = 0; i < root.getPreferenceCount(); i++) { - Preference pref = root.getPreference(i); - if (pref instanceof RadioButtonPreference) { - RadioButtonPreference radioPref = (RadioButtonPreference) pref; - radioPref.setOnClickListener(this); - mImportances.add(radioPref); - } - } - - switch (mChannel.getImportance()) { - case IMPORTANCE_MIN: - updateRadioButtons(KEY_IMPORTANCE_MIN); - break; - case IMPORTANCE_LOW: - updateRadioButtons(KEY_IMPORTANCE_LOW); - break; - case IMPORTANCE_DEFAULT: - updateRadioButtons(KEY_IMPORTANCE_DEFAULT); - break; - case IMPORTANCE_HIGH: - case IMPORTANCE_MAX: - updateRadioButtons(KEY_IMPORTANCE_HIGH); - break; - } - - return root; - } - - private void updateRadioButtons(String selectionKey) { - for (RadioButtonPreference pref : mImportances) { - if (selectionKey.equals(pref.getKey())) { - pref.setChecked(true); - } else { - pref.setChecked(false); - } - } - } - - @Override - public void onRadioButtonClicked(RadioButtonPreference clicked) { - int oldImportance = mChannel.getImportance(); - switch (clicked.getKey()) { - case KEY_IMPORTANCE_HIGH: - mChannel.setImportance(IMPORTANCE_HIGH); - break; - case KEY_IMPORTANCE_DEFAULT: - mChannel.setImportance(IMPORTANCE_DEFAULT); - break; - case KEY_IMPORTANCE_LOW: - mChannel.setImportance(IMPORTANCE_LOW); - break; - case KEY_IMPORTANCE_MIN: - mChannel.setImportance(IMPORTANCE_MIN); - break; - } - updateRadioButtons(clicked.getKey()); - - // If you are moving from an importance level without sound to one with sound, - // but the sound you had selected was "Silence", - // then set sound for this channel to your default sound, - // because you probably intended to cause this channel to actually start making sound. - if (oldImportance < IMPORTANCE_DEFAULT - && !SoundPreferenceController.hasValidSound(mChannel) - && mChannel.getImportance() >= IMPORTANCE_DEFAULT) { - mChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), - mChannel.getAudioAttributes()); - mChannel.lockFields(USER_LOCKED_SOUND); - } - mChannel.lockFields(USER_LOCKED_IMPORTANCE); - mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, mChannel); - } -} diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java index ea17a05c6d3..23451ec0a2b 100644 --- a/src/com/android/settings/notification/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/ChannelNotificationSettings.java @@ -79,7 +79,8 @@ public class ChannelNotificationSettings extends NotificationSettingsBase { mControllers = new ArrayList<>(); mControllers.add(new HeaderPreferenceController(context, this)); mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend)); - mControllers.add(new ImportancePreferenceController(context)); + mControllers.add(new ImportancePreferenceController( + context, mImportanceListener, mBackend)); mControllers.add(new AllowSoundPreferenceController( context, mImportanceListener, mBackend)); mControllers.add(new SoundPreferenceController(context, this, diff --git a/src/com/android/settings/notification/ConfigureNotificationSettings.java b/src/com/android/settings/notification/ConfigureNotificationSettings.java index 7cfa1248245..a3af471c917 100644 --- a/src/com/android/settings/notification/ConfigureNotificationSettings.java +++ b/src/com/android/settings/notification/ConfigureNotificationSettings.java @@ -30,9 +30,7 @@ import android.support.v7.preference.Preference; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.RingtonePreference; -import com.android.settings.applications.NotificationApps; import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.dashboard.SummaryLoader; import com.android.settings.gestures.SwipeToNotificationPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; @@ -153,15 +151,6 @@ public class ConfigureNotificationSettings extends DashboardFragment { } } - public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY - = new SummaryLoader.SummaryProviderFactory() { - @Override - public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, - SummaryLoader summaryLoader) { - return new NotificationApps.SummaryProvider(activity, summaryLoader); - } - }; - /** * For Search. */ diff --git a/src/com/android/settings/notification/DeletedChannelsPreferenceController.java b/src/com/android/settings/notification/DeletedChannelsPreferenceController.java index 16eb9edb742..45fb8ec69f1 100644 --- a/src/com/android/settings/notification/DeletedChannelsPreferenceController.java +++ b/src/com/android/settings/notification/DeletedChannelsPreferenceController.java @@ -55,7 +55,6 @@ public class DeletedChannelsPreferenceController extends NotificationPreferenceC preference.setTitle(mContext.getResources().getQuantityString( R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount)); } - preference.setEnabled(false); preference.setSelectable(false); } } diff --git a/src/com/android/settings/notification/ImportancePreferenceController.java b/src/com/android/settings/notification/ImportancePreferenceController.java index 977cd9a1fb2..f95c34ae6ae 100644 --- a/src/com/android/settings/notification/ImportancePreferenceController.java +++ b/src/com/android/settings/notification/ImportancePreferenceController.java @@ -16,41 +16,39 @@ package com.android.settings.notification; +import static android.app.NotificationChannel.USER_LOCKED_SOUND; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.provider.Settings; +import android.media.RingtoneManager; import android.support.v7.preference.Preference; -import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; -import com.android.settings.Utils; -import com.android.settings.applications.AppInfoBase; +import com.android.settings.RestrictedListPreference; import com.android.settings.core.PreferenceControllerMixin; public class ImportancePreferenceController extends NotificationPreferenceController - implements PreferenceControllerMixin { + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { private static final String KEY_IMPORTANCE = "importance"; + private NotificationSettingsBase.ImportanceListener mImportanceListener; - // Ironically doesn't take an importance listener because the importance is not changed - // by this controller's preference but by the screen it links to. - public ImportancePreferenceController(Context context) { - super(context, null); + public ImportancePreferenceController(Context context, + NotificationSettingsBase.ImportanceListener importanceListener, + NotificationBackend backend) { + super(context, backend); + mImportanceListener = importanceListener; } @Override public String getPreferenceKey() { return KEY_IMPORTANCE; } - - private int getMetricsCategory() { - return MetricsProto.MetricsEvent.NOTIFICATION_TOPIC_NOTIFICATION; - } @Override public boolean isAvailable() { @@ -63,51 +61,82 @@ public class ImportancePreferenceController extends NotificationPreferenceContro return !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId()); } + @Override public void updateState(Preference preference) { if (mAppRow!= null && mChannel != null) { preference.setEnabled(mAdmin == null && isChannelConfigurable()); - Bundle channelArgs = new Bundle(); - channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid); - channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg); - channelArgs.putString(Settings.EXTRA_CHANNEL_ID, mChannel.getId()); - if (preference.isEnabled()) { - Intent channelIntent = Utils.onBuildStartFragmentIntent(mContext, - ChannelImportanceSettings.class.getName(), - channelArgs, null, - R.string.notification_importance_title, null, - false, getMetricsCategory()); - preference.setIntent(channelIntent); - preference.setSummary(getImportanceSummary(mContext, mChannel)); + preference.setSummary(getImportanceSummary(mChannel)); + + int importances = IMPORTANCE_HIGH - IMPORTANCE_MIN + 1; + CharSequence[] entries = new CharSequence[importances]; + CharSequence[] values = new CharSequence[importances]; + + int index = 0; + for (int i = IMPORTANCE_HIGH; i >= IMPORTANCE_MIN; i--) { + NotificationChannel channel = new NotificationChannel("", "", i); + entries[index] = getImportanceSummary(channel); + values[index] = String.valueOf(i); + index++; } + + RestrictedListPreference pref = (RestrictedListPreference) preference; + pref.setEntries(entries); + pref.setEntryValues(values); + pref.setValue(String.valueOf(mChannel.getImportance())); } } - protected static String getImportanceSummary(Context context, NotificationChannel channel) { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mChannel != null) { + final int importance = Integer.parseInt((String) newValue); + + // If you are moving from an importance level without sound to one with sound, + // but the sound you had selected was "Silence", + // then set sound for this channel to your default sound, + // because you probably intended to cause this channel to actually start making sound. + if (mChannel.getImportance() < IMPORTANCE_DEFAULT + && !SoundPreferenceController.hasValidSound(mChannel) + && importance >= IMPORTANCE_DEFAULT) { + mChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), + mChannel.getAudioAttributes()); + mChannel.lockFields(USER_LOCKED_SOUND); + } + + mChannel.setImportance(importance); + mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + saveChannel(); + mImportanceListener.onImportanceChanged(); + } + return true; + } + + protected String getImportanceSummary(NotificationChannel channel) { String summary = ""; int importance = channel.getImportance(); switch (importance) { case IMPORTANCE_UNSPECIFIED: - summary = context.getString(R.string.notification_importance_unspecified); + summary = mContext.getString(R.string.notification_importance_unspecified); break; case NotificationManager.IMPORTANCE_MIN: - summary = context.getString(R.string.notification_importance_min); + summary = mContext.getString(R.string.notification_importance_min); break; case NotificationManager.IMPORTANCE_LOW: - summary = context.getString(R.string.notification_importance_low); + summary = mContext.getString(R.string.notification_importance_low); break; case NotificationManager.IMPORTANCE_DEFAULT: if (SoundPreferenceController.hasValidSound(channel)) { - summary = context.getString(R.string.notification_importance_default); + summary = mContext.getString(R.string.notification_importance_default); } else { - summary = context.getString(R.string.notification_importance_low); + summary = mContext.getString(R.string.notification_importance_low); } break; case NotificationManager.IMPORTANCE_HIGH: case NotificationManager.IMPORTANCE_MAX: if (SoundPreferenceController.hasValidSound(channel)) { - summary = context.getString(R.string.notification_importance_high); + summary = mContext.getString(R.string.notification_importance_high); } else { - summary = context.getString(R.string.notification_importance_high_silent); + summary = mContext.getString(R.string.notification_importance_high_silent); } break; default: diff --git a/src/com/android/settings/notification/NotificationsOffPreferenceController.java b/src/com/android/settings/notification/NotificationsOffPreferenceController.java index 74591cfd4ce..ba304de3665 100644 --- a/src/com/android/settings/notification/NotificationsOffPreferenceController.java +++ b/src/com/android/settings/notification/NotificationsOffPreferenceController.java @@ -57,7 +57,6 @@ public class NotificationsOffPreferenceController extends NotificationPreference preference.setTitle(R.string.app_notifications_off_desc); } } - preference.setEnabled(false); preference.setSelectable(false); } } diff --git a/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java b/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java index dbffc550d5e..3240ae030af 100644 --- a/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java +++ b/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java @@ -147,7 +147,8 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC @Override protected void onCountComplete(int num) { if (mHasRecentApps) { - mSeeAllPref.setTitle(mContext.getString(R.string.see_all_apps_title, num)); + mSeeAllPref.setTitle( + mContext.getString(R.string.recent_notifications_see_all_title)); } else { mSeeAllPref.setSummary(mContext.getString(R.string.apps_summary, num)); } diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index be910e105f3..43697bdd529 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -10,7 +10,6 @@ com.android.settings.development.featureflags.FeatureFlagsDashboard com.android.settings.development.qstile.DevelopmentTileConfigFragment com.android.settings.deviceinfo.StorageProfileFragment com.android.settings.notification.ChannelNotificationSettings -com.android.settings.notification.ChannelImportanceSettings com.android.settings.notification.ChannelGroupNotificationSettings com.android.settings.notification.AppNotificationSettings com.android.settings.wifi.details.WifiNetworkDetailsFragment diff --git a/tests/robotests/src/com/android/settings/applications/NotificationAppsTest.java b/tests/robotests/src/com/android/settings/applications/NotificationAppsTest.java deleted file mode 100644 index 11d757fc504..00000000000 --- a/tests/robotests/src/com/android/settings/applications/NotificationAppsTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2017 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.applications; - -import android.content.Context; - -import android.content.pm.ApplicationInfo; -import android.content.pm.UserInfo; -import android.os.UserManager; - -import com.android.settings.R; -import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settings.TestConfig; -import com.android.settings.dashboard.SummaryLoader; -import com.android.settings.notification.NotificationBackend; -import com.android.settingslib.wrapper.PackageManagerWrapper; - -import java.util.List; -import java.util.ArrayList; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; -import org.robolectric.util.ReflectionHelpers; - -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(SettingsRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) -public class NotificationAppsTest { - - @Mock - private PackageManagerWrapper mPackageManager; - @Mock - private UserManager mUserManager; - @Mock - private SummaryLoader mSummaryLoader; - @Mock - private NotificationBackend mBackend; - - private Context mContext; - private NotificationApps.SummaryProvider mSummaryProvider; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - ShadowApplication shadowApplication = ShadowApplication.getInstance(); - shadowApplication.setSystemService(Context.USER_SERVICE, mUserManager); - mContext = shadowApplication.getApplicationContext(); - mSummaryProvider = spy(new NotificationApps.SummaryProvider(mContext, mSummaryLoader)); - ReflectionHelpers.setField(mSummaryProvider, "mNotificationBackend", mBackend); - ReflectionHelpers.setField(mSummaryProvider, "mPackageManager", mPackageManager); - } - - @Test - public void setListening_shouldSetSummary() { - List userInfos = new ArrayList<>(); - userInfos.add(new UserInfo(1, "user1", 0)); - when(mUserManager.getProfiles(anyInt())).thenReturn(userInfos); - List appInfos = new ArrayList<>(); - ApplicationInfo info1 = new ApplicationInfo(); - info1.packageName = "package1"; - appInfos.add(info1); - ApplicationInfo info2 = new ApplicationInfo(); - info2.packageName = "package2"; - appInfos.add(info2); - when(mPackageManager.getInstalledApplicationsAsUser(anyInt(), anyInt())) - .thenReturn(appInfos); - - // no notification off - when(mBackend.getNotificationsBanned(anyString(), anyInt())).thenReturn(false); - mSummaryProvider.setListening(true); - ShadowApplication.runBackgroundTasks(); - verify(mSummaryLoader).setSummary(mSummaryProvider, - mContext.getString(R.string.notification_summary_none)); - - // some notification off - when(mBackend.getNotificationsBanned(eq("package1"), anyInt())).thenReturn(true); - mSummaryProvider.setListening(true); - ShadowApplication.runBackgroundTasks(); - verify(mSummaryLoader).setSummary(mSummaryProvider, - mContext.getResources().getQuantityString(R.plurals.notification_summary, 1, 1)); - - when(mBackend.getNotificationsBanned(eq("package2"), anyInt())).thenReturn(true); - mSummaryProvider.setListening(true); - ShadowApplication.runBackgroundTasks(); - verify(mSummaryLoader).setSummary(mSummaryProvider, - mContext.getResources().getQuantityString(R.plurals.notification_summary, 2, 2)); - } - -} diff --git a/tests/robotests/src/com/android/settings/notification/DeletedChannelsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/DeletedChannelsPreferenceControllerTest.java index fd903f9322d..0d8b43043d3 100644 --- a/tests/robotests/src/com/android/settings/notification/DeletedChannelsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/DeletedChannelsPreferenceControllerTest.java @@ -119,7 +119,6 @@ public class DeletedChannelsPreferenceControllerTest { Preference pref = mock(Preference.class); mController.updateState(pref); - verify(pref, times(1)).setEnabled(false); verify(pref, times(1)).setSelectable(false); verify(mBackend, times(1)).getDeletedChannelCount(any(), anyInt()); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(CharSequence.class); diff --git a/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java index 0065e3042a6..229a212785d 100644 --- a/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/ImportancePreferenceControllerTest.java @@ -21,6 +21,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_NONE; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; @@ -30,13 +31,16 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.UserManager; import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; +import com.android.settings.RestrictedListPreference; import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settingslib.RestrictedLockUtils; @@ -44,6 +48,7 @@ import com.android.settingslib.RestrictedLockUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; @@ -58,7 +63,13 @@ public class ImportancePreferenceControllerTest { @Mock private NotificationManager mNm; @Mock + private NotificationBackend mBackend; + @Mock + NotificationSettingsBase.ImportanceListener mImportanceListener; + @Mock private UserManager mUm; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; private ImportancePreferenceController mController; @@ -69,7 +80,8 @@ public class ImportancePreferenceControllerTest { shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); shadowApplication.setSystemService(Context.USER_SERVICE, mUm); mContext = shadowApplication.getApplicationContext(); - mController = spy(new ImportancePreferenceController(mContext)); + mController = spy(new ImportancePreferenceController( + mContext, mImportanceListener, mBackend)); } @Test @@ -123,14 +135,15 @@ public class ImportancePreferenceControllerTest { @Test public void testUpdateState_disabledByAdmin() throws Exception { NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); mController.onResume(new NotificationBackend.AppRow(), channel, null, mock( RestrictedLockUtils.EnforcedAdmin.class)); - Preference pref = new Preference(RuntimeEnvironment.application); + Preference pref = new RestrictedListPreference(RuntimeEnvironment.application, null); mController.updateState(pref); assertFalse(pref.isEnabled()); - assertNull(pref.getIntent()); + assertFalse(TextUtils.isEmpty(pref.getSummary())); } @Test @@ -140,13 +153,14 @@ public class ImportancePreferenceControllerTest { appRow.lockedChannelId = lockedId; NotificationChannel channel = mock(NotificationChannel.class); when(channel.getId()).thenReturn(lockedId); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); mController.onResume(appRow, channel, null, null); - Preference pref = new Preference(RuntimeEnvironment.application); + Preference pref = new RestrictedListPreference(RuntimeEnvironment.application, null); mController.updateState(pref); assertFalse(pref.isEnabled()); - assertNull(pref.getIntent()); + assertFalse(TextUtils.isEmpty(pref.getSummary())); } @Test @@ -155,11 +169,50 @@ public class ImportancePreferenceControllerTest { NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); mController.onResume(appRow, channel, null, null); - Preference pref = new Preference(RuntimeEnvironment.application); + Preference pref = new RestrictedListPreference(RuntimeEnvironment.application, null); mController.updateState(pref); assertTrue(pref.isEnabled()); - assertNotNull(pref.getIntent()); assertFalse(TextUtils.isEmpty(pref.getSummary())); } + + @Test + public void testImportanceLowToHigh() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_LOW); + channel.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedListPreference pref = + new RestrictedListPreference(RuntimeEnvironment.application, null); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + pref.setValue(String.valueOf(IMPORTANCE_HIGH)); + mController.onPreferenceChange(pref, pref.getValue()); + + assertEquals(IMPORTANCE_HIGH, channel.getImportance()); + assertNotNull(channel.getSound()); + } + + @Test + public void testImportanceHightToLow() { + NotificationChannel channel = + new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH); + channel.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + mController.onResume(new NotificationBackend.AppRow(), channel, null, null); + + RestrictedListPreference pref = + new RestrictedListPreference(RuntimeEnvironment.application, null); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref); + mController.displayPreference(mScreen); + mController.updateState(pref); + + pref.setValue(String.valueOf(IMPORTANCE_LOW)); + mController.onPreferenceChange(pref, pref.getValue()); + + assertEquals(IMPORTANCE_LOW, channel.getImportance()); + assertNull(channel.getSound()); + } } diff --git a/tests/robotests/src/com/android/settings/notification/NotificationsOffPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/NotificationsOffPreferenceControllerTest.java index 654d90c36e7..82ef6fd12e1 100644 --- a/tests/robotests/src/com/android/settings/notification/NotificationsOffPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/NotificationsOffPreferenceControllerTest.java @@ -110,7 +110,6 @@ public class NotificationsOffPreferenceControllerTest { mController.updateState(pref); assertTrue(pref.getTitle().toString().contains("category")); - assertFalse(pref.isEnabled()); assertFalse(pref.isSelectable()); } @@ -125,7 +124,6 @@ public class NotificationsOffPreferenceControllerTest { mController.updateState(pref); assertTrue(pref.getTitle().toString().contains("group")); - assertFalse(pref.isEnabled()); assertFalse(pref.isSelectable()); } @@ -139,7 +137,6 @@ public class NotificationsOffPreferenceControllerTest { mController.updateState(pref); assertTrue(pref.getTitle().toString().contains("app")); - assertFalse(pref.isEnabled()); assertFalse(pref.isSelectable()); } } From 4b34dad4cd6b3c5319e027fa04271895dd7a7173 Mon Sep 17 00:00:00 2001 From: Carlos Valdivia Date: Mon, 29 Jan 2018 18:30:33 -0800 Subject: [PATCH 2/9] AR/FR: Keyboard challenge takes precedence. Tested manually by: (1) Setting up a gmail account. (2) Setting up a screen pattern. (3) Going to Settings > System > Reset Options > Factory Wipe (4) Hit the Erase button. (5) Enter pattern into the Screenlock prompt. (6) Notice the Google credential confirmation screen. Prior to this change we would skip (5) if there was a Google account on the device. Test: make RunSettingsRoboTests ROBOTEST_FILTER=andorid.settings.MasterClear Bug: 72694056 Change-Id: Ie07c678ecbc8361e8e1792f82efdfb1261db49fb --- src/com/android/settings/MasterClear.java | 62 ++++++--- .../com/android/settings/MasterClearTest.java | 129 +++++++++++++++--- 2 files changed, 155 insertions(+), 36 deletions(-) diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java index 687e6458361..47aa8a6b9a9 100644 --- a/src/com/android/settings/MasterClear.java +++ b/src/com/android/settings/MasterClear.java @@ -123,24 +123,34 @@ public class MasterClear extends InstrumentedPreferenceFragment { return !((requestCode != KEYGUARD_REQUEST) && (requestCode != CREDENTIAL_CONFIRM_REQUEST)); } - @VisibleForTesting - boolean isShowFinalConfirmation(int requestCode, int resultCode) { - return (resultCode == Activity.RESULT_OK) || (requestCode == CREDENTIAL_CONFIRM_REQUEST); - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); + onActivityResultInternal(requestCode, resultCode, data); + } + + /* + * Internal method that allows easy testing without dealing with super references. + */ + @VisibleForTesting + void onActivityResultInternal(int requestCode, int resultCode, Intent data) { if (!isValidRequestCode(requestCode)) { return; } - // If the user entered a valid keyguard trace, present the final - // confirmation prompt; otherwise, go back to the initial state. - if (isShowFinalConfirmation(requestCode, resultCode)) { - showFinalConfirmation(); - } else { + if (resultCode != Activity.RESULT_OK) { establishInitialState(); + return; + } + + Intent intent = null; + // If returning from a Keyguard request, try to show an account confirmation request if + // applciable. + if (CREDENTIAL_CONFIRM_REQUEST != requestCode + && (intent = getAccountConfirmationIntent()) != null) { + showAccountCredentialConfirmation(intent); + } else { + showFinalConfirmation(); } } @@ -155,7 +165,12 @@ public class MasterClear extends InstrumentedPreferenceFragment { } @VisibleForTesting - boolean tryShowAccountConfirmation() { + void showAccountCredentialConfirmation(Intent intent) { + startActivityForResult(intent, CREDENTIAL_CONFIRM_REQUEST); + } + + @VisibleForTesting + Intent getAccountConfirmationIntent() { final Context context = getActivity(); final String accountType = context.getString(R.string.account_type); final String packageName = context.getString(R.string.account_confirmation_package); @@ -163,7 +178,8 @@ public class MasterClear extends InstrumentedPreferenceFragment { if (TextUtils.isEmpty(accountType) || TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) { - return false; + Log.i(TAG, "Resources not set for account confirmation."); + return null; } final AccountManager am = AccountManager.get(context); Account[] accounts = am.getAccountsByType(accountType); @@ -179,12 +195,14 @@ public class MasterClear extends InstrumentedPreferenceFragment { && packageName.equals(resolution.activityInfo.packageName)) { // Note that we need to check the packagename to make sure that an Activity resolver // wasn't returned. - startActivityForResult( - requestAccountConfirmation, CREDENTIAL_CONFIRM_REQUEST); - return true; + return requestAccountConfirmation; + } else { + Log.i(TAG, "Unable to resolve Activity: " + packageName + "/" + className); } + } else { + Log.d(TAG, "No " + accountType + " accounts installed!"); } - return false; + return null; } /** @@ -210,7 +228,14 @@ public class MasterClear extends InstrumentedPreferenceFragment { return; } - if (!tryShowAccountConfirmation() && !runKeyguardConfirmation(KEYGUARD_REQUEST)) { + if (runKeyguardConfirmation(KEYGUARD_REQUEST)) { + return; + } + + Intent intent = getAccountConfirmationIntent(); + if (intent != null) { + showAccountCredentialConfirmation(intent); + } else { showFinalConfirmation(); } } @@ -228,7 +253,8 @@ public class MasterClear extends InstrumentedPreferenceFragment { * time, then simply reuse the inflated views directly whenever we need * to change contents. */ - private void establishInitialState() { + @VisibleForTesting + void establishInitialState() { mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_master_clear); mInitiateButton.setOnClickListener(mInitiateListener); mExternalStorageContainer = mContentView.findViewById(R.id.erase_external_container); diff --git a/tests/robotests/src/com/android/settings/MasterClearTest.java b/tests/robotests/src/com/android/settings/MasterClearTest.java index 776025f1fad..3ba3edb8d7e 100644 --- a/tests/robotests/src/com/android/settings/MasterClearTest.java +++ b/tests/robotests/src/com/android/settings/MasterClearTest.java @@ -18,11 +18,14 @@ package com.android.settings; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @@ -86,6 +89,9 @@ public class MasterClearTest { @Mock private Activity mMockActivity; + @Mock + private Intent mMockIntent; + private ShadowActivity mShadowActivity; private ShadowAccountManager mShadowAccountManager; private Activity mActivity; @@ -110,7 +116,7 @@ public class MasterClearTest { MockitoAnnotations.initMocks(this); mMasterClear = spy(new MasterClear()); mActivity = Robolectric.setupActivity(Activity.class); - mShadowActivity = shadowOf(mActivity); + mShadowActivity = shadowOf(mActivity);https://stackoverflow.com/questions/14889951/how-to-verify-a-method-is-called-two-times-with-mockito-verify // mShadowAccountManager = shadowOf(AccountManager.get(mActivity)); mContentView = LayoutInflater.from(mActivity).inflate(R.layout.master_clear, null); @@ -213,38 +219,115 @@ public class MasterClearTest { } @Test - public void testTryShowAccountConfirmation_unsupported() { - when(mMasterClear.getActivity()).thenReturn(mActivity); - /* Using the default resources, account confirmation shouldn't trigger */ - assertThat(mMasterClear.tryShowAccountConfirmation()).isFalse(); + public void testOnActivityResultInternal_invalideRequest() { + int invalidRequestCode = -1; + doReturn(false).when(mMasterClear).isValidRequestCode(eq(invalidRequestCode)); + + mMasterClear.onActivityResultInternal(invalidRequestCode, Activity.RESULT_OK, null); + + verify(mMasterClear, times(1)).isValidRequestCode(eq(invalidRequestCode)); + verify(mMasterClear, times(0)).establishInitialState(); + verify(mMasterClear, times(0)).getAccountConfirmationIntent(); + verify(mMasterClear, times(0)).showFinalConfirmation(); } @Test - public void testTryShowAccountConfirmation_no_relevant_accounts() { + public void testOnActivityResultInternal_resultCanceled() { + doReturn(true).when(mMasterClear).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST)); + doNothing().when(mMasterClear).establishInitialState(); + + mMasterClear.onActivityResultInternal( + MasterClear.KEYGUARD_REQUEST, Activity.RESULT_CANCELED, null); + + verify(mMasterClear, times(1)).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST)); + verify(mMasterClear, times(1)).establishInitialState(); + verify(mMasterClear, times(0)).getAccountConfirmationIntent(); + verify(mMasterClear, times(0)).showFinalConfirmation(); + } + + @Test + public void testOnActivityResultInternal_keyguardRequestTriggeringConfirmAccount() { + doReturn(true).when(mMasterClear).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST)); + doReturn(mMockIntent).when(mMasterClear).getAccountConfirmationIntent(); + doNothing().when(mMasterClear).showAccountCredentialConfirmation(eq(mMockIntent)); + + mMasterClear.onActivityResultInternal( + MasterClear.KEYGUARD_REQUEST, Activity.RESULT_OK, null); + + verify(mMasterClear, times(1)).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST)); + verify(mMasterClear, times(0)).establishInitialState(); + verify(mMasterClear, times(1)).getAccountConfirmationIntent(); + verify(mMasterClear, times(1)).showAccountCredentialConfirmation(eq(mMockIntent)); + } + + @Test + public void testOnActivityResultInternal_keyguardRequestTriggeringShowFinal() { + doReturn(true).when(mMasterClear).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST)); + doReturn(null).when(mMasterClear).getAccountConfirmationIntent(); + doNothing().when(mMasterClear).showFinalConfirmation(); + + mMasterClear.onActivityResultInternal( + MasterClear.KEYGUARD_REQUEST, Activity.RESULT_OK, null); + + verify(mMasterClear, times(1)).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST)); + verify(mMasterClear, times(0)).establishInitialState(); + verify(mMasterClear, times(1)).getAccountConfirmationIntent(); + verify(mMasterClear, times(1)).showFinalConfirmation(); + } + + @Test + public void testOnActivityResultInternal_confirmRequestTriggeringShowFinal() { + doReturn(true).when(mMasterClear) + .isValidRequestCode(eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST)); + doNothing().when(mMasterClear).showFinalConfirmation(); + + mMasterClear.onActivityResultInternal( + MasterClear.CREDENTIAL_CONFIRM_REQUEST, Activity.RESULT_OK, null); + + verify(mMasterClear, times(1)) + .isValidRequestCode(eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST)); + verify(mMasterClear, times(0)).establishInitialState(); + verify(mMasterClear, times(0)).getAccountConfirmationIntent(); + verify(mMasterClear, times(1)).showFinalConfirmation(); + } + + @Test + public void testGetAccountConfirmationIntent_unsupported() { + when(mMasterClear.getActivity()).thenReturn(mActivity); + /* Using the default resources, account confirmation shouldn't trigger */ + assertThat(mMasterClear.getAccountConfirmationIntent()).isNull(); + } + + @Test + public void testGetAccountConfirmationIntent_no_relevant_accounts() { when(mMasterClear.getActivity()).thenReturn(mMockActivity); when(mMockActivity.getString(R.string.account_type)).thenReturn(TEST_ACCOUNT_TYPE); - when(mMockActivity.getString(R.string.account_confirmation_package)).thenReturn(TEST_CONFIRMATION_PACKAGE); - when(mMockActivity.getString(R.string.account_confirmation_class)).thenReturn(TEST_CONFIRMATION_CLASS); + when(mMockActivity.getString(R.string.account_confirmation_package)) + .thenReturn(TEST_CONFIRMATION_PACKAGE); + when(mMockActivity.getString(R.string.account_confirmation_class)) + .thenReturn(TEST_CONFIRMATION_CLASS); Account[] accounts = new Account[0]; when(mMockActivity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager); when(mAccountManager.getAccountsByType(TEST_ACCOUNT_TYPE)).thenReturn(accounts); - assertThat(mMasterClear.tryShowAccountConfirmation()).isFalse(); + assertThat(mMasterClear.getAccountConfirmationIntent()).isNull(); } @Test - public void testTryShowAccountConfirmation_unresolved() { + public void testGetAccountConfirmationIntent_unresolved() { when(mMasterClear.getActivity()).thenReturn(mMockActivity); when(mMockActivity.getString(R.string.account_type)).thenReturn(TEST_ACCOUNT_TYPE); - when(mMockActivity.getString(R.string.account_confirmation_package)).thenReturn(TEST_CONFIRMATION_PACKAGE); - when(mMockActivity.getString(R.string.account_confirmation_class)).thenReturn(TEST_CONFIRMATION_CLASS); + when(mMockActivity.getString(R.string.account_confirmation_package)) + .thenReturn(TEST_CONFIRMATION_PACKAGE); + when(mMockActivity.getString(R.string.account_confirmation_class)) + .thenReturn(TEST_CONFIRMATION_CLASS); Account[] accounts = new Account[] { new Account(TEST_ACCOUNT_NAME, TEST_ACCOUNT_TYPE) }; when(mMockActivity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager); when(mAccountManager.getAccountsByType(TEST_ACCOUNT_TYPE)).thenReturn(accounts); // The package manager should not resolve the confirmation intent targeting the non-existent // confirmation package. when(mMockActivity.getPackageManager()).thenReturn(mPackageManager); - assertThat(mMasterClear.tryShowAccountConfirmation()).isFalse(); + assertThat(mMasterClear.getAccountConfirmationIntent()).isNull(); } @Test @@ -252,8 +335,10 @@ public class MasterClearTest { when(mMasterClear.getActivity()).thenReturn(mMockActivity); // Only try to show account confirmation if the appropriate resource overlays are available. when(mMockActivity.getString(R.string.account_type)).thenReturn(TEST_ACCOUNT_TYPE); - when(mMockActivity.getString(R.string.account_confirmation_package)).thenReturn(TEST_CONFIRMATION_PACKAGE); - when(mMockActivity.getString(R.string.account_confirmation_class)).thenReturn(TEST_CONFIRMATION_CLASS); + when(mMockActivity.getString(R.string.account_confirmation_package)) + .thenReturn(TEST_CONFIRMATION_PACKAGE); + when(mMockActivity.getString(R.string.account_confirmation_class)) + .thenReturn(TEST_CONFIRMATION_CLASS); // Add accounts to trigger the search for a resolving intent. Account[] accounts = new Account[] { new Account(TEST_ACCOUNT_NAME, TEST_ACCOUNT_TYPE) }; when(mMockActivity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager); @@ -268,10 +353,18 @@ public class MasterClearTest { resolveInfo.activityInfo = activityInfo; when(mPackageManager.resolveActivity(any(), eq(0))).thenReturn(resolveInfo); - // Finally mock out the startActivityForResultCall - doNothing().when(mMasterClear).startActivityForResult(any(), eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST)); + Intent actualIntent = mMasterClear.getAccountConfirmationIntent(); + assertEquals(TEST_CONFIRMATION_PACKAGE, actualIntent.getComponent().getPackageName()); + assertEquals(TEST_CONFIRMATION_CLASS, actualIntent.getComponent().getClassName()); + } - assertThat(mMasterClear.tryShowAccountConfirmation()).isTrue(); + public void testShowAccountCredentialConfirmation() { + // Finally mock out the startActivityForResultCall + doNothing().when(mMasterClear) + .startActivityForResult(eq(mMockIntent), eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST)); + mMasterClear.showAccountCredentialConfirmation(mMockIntent); + verify(mMasterClear, times(1)) + .startActivityForResult(eq(mMockIntent), eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST)); } @Test From dc2a3528d51d7292a8b4c39e176b8b3b3f0eff86 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 30 Jan 2018 17:04:46 -0800 Subject: [PATCH 3/9] Misc fixes for account/users fragments - Hide add user button when it's not functional - Display "No accounts added" under accounts setting when there is no account. Change-Id: Iefede9939d206eb3064fa22bdcfbcb1e826f29ab Fixes: 72643060 Test: robotest Test: 72713118 --- res/values/strings.xml | 2 + .../accounts/AccountDashboardFragment.java | 23 ++++++----- .../android/settings/users/UserSettings.java | 3 ++ .../AccountDashboardFragmentTest.java | 39 ++++++++++++++++++- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index f86cc56cb28..3e211b44d49 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6652,6 +6652,8 @@ Permissions, default apps Accounts + + No accounts added Default apps diff --git a/src/com/android/settings/accounts/AccountDashboardFragment.java b/src/com/android/settings/accounts/AccountDashboardFragment.java index 65a5ff09e6f..0a4c343b2bc 100644 --- a/src/com/android/settings/accounts/AccountDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountDashboardFragment.java @@ -91,17 +91,20 @@ public class AccountDashboardFragment extends DashboardFragment { final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); CharSequence summary = null; + if (types == null || types.length == 0) { + summary = mContext.getString(R.string.account_dashboard_default_summary); + } else { + // Show up to 3 account types + final int size = Math.min(3, types.length); - // Show up to 3 account types - final int size = Math.min(3, types.length); - - for (int i = 0; i < size; i++) { - final CharSequence label = authHelper.getLabelForType(mContext, types[i]); - if (summary == null) { - summary = bidiFormatter.unicodeWrap(label); - } else { - summary = mContext.getString(R.string.join_many_items_middle, summary, - bidiFormatter.unicodeWrap(label)); + for (int i = 0; i < size; i++) { + final CharSequence label = authHelper.getLabelForType(mContext, types[i]); + if (summary == null) { + summary = bidiFormatter.unicodeWrap(label); + } else { + summary = mContext.getString(R.string.join_many_items_middle, summary, + bidiFormatter.unicodeWrap(label)); + } } } mSummaryLoader.setSummary(this, summary); diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index a8fab139485..fcb8aef0b47 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -249,11 +249,14 @@ public class UserSettings extends SettingsPreferenceFragment mAddUser.useAdminDisabledSummary(false); // Determine if add user/profile button should be visible if (mUserCaps.mCanAddUser && Utils.isDeviceProvisioned(getActivity())) { + mAddUser.setVisible(true); mAddUser.setOnPreferenceClickListener(this); // change label to only mention user, if restricted profiles are not supported if (!mUserCaps.mCanAddRestrictedProfile) { mAddUser.setTitle(R.string.user_add_user_menu); } + } else { + mAddUser.setVisible(false); } final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_INFO_CHANGED); diff --git a/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java index aeffd20d005..9371019fd60 100644 --- a/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java @@ -27,11 +27,13 @@ import android.os.UserHandle; import android.provider.SearchIndexableResource; import android.text.TextUtils; +import com.android.settings.R; import com.android.settings.TestConfig; import com.android.settings.dashboard.SummaryLoader; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.drawer.CategoryKey; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,6 +43,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import org.robolectric.shadows.ShadowApplication; import java.util.List; @@ -57,6 +60,11 @@ public class AccountDashboardFragmentTest { mFragment = new AccountDashboardFragment(); } + @After + public void tearDown() { + ShadowAuthenticationHelper.reset(); + } + @Test public void testCategory_isAccount() { assertThat(mFragment.getCategoryKey()).isEqualTo(CategoryKey.CATEGORY_ACCOUNT); @@ -66,7 +74,8 @@ public class AccountDashboardFragmentTest { @Config(shadows = { ShadowAuthenticationHelper.class }) - public void updateSummary_shouldDisplayUpTo3AccountTypes() { + public void updateSummary_hasAccount_shouldDisplayUpTo3AccountTypes() { + ShadowAuthenticationHelper.setHasAccount(true); final SummaryLoader loader = mock(SummaryLoader.class); final Activity activity = Robolectric.buildActivity(Activity.class).setup().get(); @@ -77,6 +86,23 @@ public class AccountDashboardFragmentTest { verify(loader).setSummary(provider, LABELS[0] + ", " + LABELS[1] + ", " + LABELS[2]); } + @Test + @Config(shadows = { + ShadowAuthenticationHelper.class + }) + public void updateSummary_noAccount_shouldDisplayDefaultSummary() { + ShadowAuthenticationHelper.setHasAccount(false); + final SummaryLoader loader = mock(SummaryLoader.class); + final Activity activity = Robolectric.buildActivity(Activity.class).setup().get(); + + final SummaryLoader.SummaryProvider provider = mFragment.SUMMARY_PROVIDER_FACTORY + .createSummaryProvider(activity, loader); + provider.setListening(true); + + verify(loader).setSummary(provider, + activity.getString(R.string.account_dashboard_default_summary)); + } + @Test public void testSearchIndexProvider_shouldIndexResource() { final List indexRes = @@ -94,15 +120,24 @@ public class AccountDashboardFragmentTest { static final String[] TYPES = new String[] {"type1", "type2", "type3", "type4"}; static final String[] LABELS = new String[] {"LABEL1", "LABEL2", "LABEL3", "LABEL4"}; + private static boolean sHasAccount = true; public void __constructor__(Context context, UserHandle userHandle, AuthenticatorHelper.OnAccountsUpdateListener listener) { + } + public static void setHasAccount(boolean hasAccount) { + sHasAccount = hasAccount; + } + + @Resetter + public static void reset() { + sHasAccount = true; } @Implementation public String[] getEnabledAccountTypes() { - return TYPES; + return sHasAccount ? TYPES : null; } @Implementation From a5d0c3e36e1430d28fcb934395252ccb30aacd1b Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 30 Jan 2018 12:49:35 -0800 Subject: [PATCH 4/9] Remove suggestion UI v1 codes. - remove the check for feature flag for suggestion UI v2 and switch to use the v2 codes. - remove all code related to v1 of suggestion UI Fixes: 70573674 Test: make RunSettingsRoboTests Change-Id: I99ab318c1c0192508a9c5e9e708e86319120d55b --- ...dition_footer.xml => condition_footer.xml} | 0 ...dition_header.xml => condition_header.xml} | 0 res/layout/suggestion_condition_container.xml | 43 -- res/layout/suggestion_tile.xml | 86 ++-- res/layout/suggestion_tile_v2.xml | 82 ---- res/layout/suggestion_tile_with_button.xml | 78 +-- res/layout/suggestion_tile_with_button_v2.xml | 91 ---- .../android/settings/core/FeatureFlags.java | 1 - .../settings/dashboard/DashboardAdapter.java | 332 +++++-------- .../dashboard/DashboardAdapterV2.java | 429 ----------------- .../settings/dashboard/DashboardData.java | 167 +++---- .../settings/dashboard/DashboardDataV2.java | 446 ------------------ .../dashboard/DashboardFeatureProvider.java | 5 - .../DashboardFeatureProviderImpl.java | 5 - .../settings/dashboard/DashboardSummary.java | 88 +--- .../conditional/ConditionAdapter.java | 13 +- .../conditional/ConditionAdapterV2.java | 186 -------- .../suggestions/SuggestionAdapter.java | 172 ++++++- .../suggestions/SuggestionAdapterV2.java | 282 ----------- .../SuggestionDismissController.java | 90 ---- .../dashboard/DashboardAdapterTest.java | 186 +++++--- .../dashboard/DashboardAdapterV2Test.java | 286 ----------- .../settings/dashboard/DashboardDataTest.java | 47 +- .../dashboard/DashboardSummaryTest.java | 6 - .../conditional/ConditionAdapterTest.java | 51 +- .../conditional/ConditionAdapterV2Test.java | 135 ------ .../suggestions/SuggestionAdapterTest.java | 139 +++++- .../suggestions/SuggestionAdapterV2Test.java | 297 ------------ .../SuggestionDismissControllerTest.java | 117 ----- 29 files changed, 746 insertions(+), 3114 deletions(-) rename res/layout/{suggestion_condition_footer.xml => condition_footer.xml} (100%) rename res/layout/{suggestion_condition_header.xml => condition_header.xml} (100%) delete mode 100644 res/layout/suggestion_condition_container.xml delete mode 100644 res/layout/suggestion_tile_v2.xml delete mode 100644 res/layout/suggestion_tile_with_button_v2.xml delete mode 100644 src/com/android/settings/dashboard/DashboardAdapterV2.java delete mode 100644 src/com/android/settings/dashboard/DashboardDataV2.java delete mode 100644 src/com/android/settings/dashboard/conditional/ConditionAdapterV2.java delete mode 100644 src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java delete mode 100644 src/com/android/settings/dashboard/suggestions/SuggestionDismissController.java delete mode 100644 tests/robotests/src/com/android/settings/dashboard/DashboardAdapterV2Test.java delete mode 100644 tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterV2Test.java delete mode 100644 tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2Test.java delete mode 100644 tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionDismissControllerTest.java diff --git a/res/layout/suggestion_condition_footer.xml b/res/layout/condition_footer.xml similarity index 100% rename from res/layout/suggestion_condition_footer.xml rename to res/layout/condition_footer.xml diff --git a/res/layout/suggestion_condition_header.xml b/res/layout/condition_header.xml similarity index 100% rename from res/layout/suggestion_condition_header.xml rename to res/layout/condition_header.xml diff --git a/res/layout/suggestion_condition_container.xml b/res/layout/suggestion_condition_container.xml deleted file mode 100644 index a4c60ad26a8..00000000000 --- a/res/layout/suggestion_condition_container.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - diff --git a/res/layout/suggestion_tile.xml b/res/layout/suggestion_tile.xml index b9474528c37..79f6dcae359 100644 --- a/res/layout/suggestion_tile.xml +++ b/res/layout/suggestion_tile.xml @@ -14,51 +14,69 @@ limitations under the License. --> - + app:cardPreventCornerOverlap="false" + app:cardUseCompatPadding="true" + app:cardElevation="2dp" + app:cardCornerRadius="@dimen/suggestion_card_corner_radius"> + android:minHeight="112dp" + android:orientation="vertical"> - - - + android:orientation="horizontal"> - + - + - + + + + + - - - \ No newline at end of file + diff --git a/res/layout/suggestion_tile_v2.xml b/res/layout/suggestion_tile_v2.xml deleted file mode 100644 index a7717b74cb3..00000000000 --- a/res/layout/suggestion_tile_v2.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/suggestion_tile_with_button.xml b/res/layout/suggestion_tile_with_button.xml index 081df5b061f..a674bcb08b1 100644 --- a/res/layout/suggestion_tile_with_button.xml +++ b/res/layout/suggestion_tile_with_button.xml @@ -14,58 +14,78 @@ limitations under the License. --> - - - + app:cardPreventCornerOverlap="false" + app:cardUseCompatPadding="true" + app:cardElevation="2dp" + app:cardCornerRadius="@dimen/suggestion_card_corner_radius"> + + + + + + + + + style="@style/SuggestionCardText" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" + android:singleLine="true" + android:textAppearance="@style/TextAppearance.SuggestionTitleV2" + android:ellipsize="end" + android:fadingEdge="horizontal" />