From 4aa42fed677824a3d48597f2f8c2fe0c20a44a88 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Mon, 23 Apr 2018 12:24:21 -0400 Subject: [PATCH 1/8] Redesign zen visual effects screens Test: robotests Bug: 78448988 Change-Id: I3040c1103d76c75601e82e3660a2ace202837ec2 --- AndroidManifest.xml | 17 -- res/layout/preference_two_target_radio.xml | 90 ++++++++++ .../preference_widget_zen_master_icon.xml | 26 +++ res/values/strings.xml | 33 +++- ...n_mode_restrict_notifications_settings.xml | 43 +++++ res/xml/zen_mode_settings.xml | 4 +- .../AbstractZenModePreferenceController.java | 28 +-- .../ZenCustomRadioButtonPreference.java | 140 +++++++++++++++ .../ZenFooterPreferenceController.java | 56 ++++++ .../ZenModeRestrictNotificationsSettings.java | 151 ++++++++++++++++ .../notification/ZenModeSettings.java | 23 +-- ...ModeVisEffectsAllPreferenceController.java | 76 ++++++++ ...eVisEffectsCustomPreferenceController.java | 92 ++++++++++ ...odeVisEffectsNonePreferenceController.java | 75 ++++++++ .../ZenCustomRadioButtonPreferenceTest.java | 113 ++++++++++++ .../ZenFooterPreferenceControllerTest.java | 138 +++++++++++++++ .../notification/ZenModeSettingsTest.java | 22 +-- ...VisEffectsAllPreferenceControllerTest.java | 141 +++++++++++++++ ...EffectsCustomPreferenceControllerTest.java | 167 ++++++++++++++++++ ...isEffectsNonePreferenceControllerTest.java | 132 ++++++++++++++ 20 files changed, 1507 insertions(+), 60 deletions(-) create mode 100644 res/layout/preference_two_target_radio.xml create mode 100644 res/layout/preference_widget_zen_master_icon.xml create mode 100644 res/xml/zen_mode_restrict_notifications_settings.xml create mode 100644 src/com/android/settings/notification/ZenCustomRadioButtonPreference.java create mode 100644 src/com/android/settings/notification/ZenFooterPreferenceController.java create mode 100644 src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java create mode 100644 src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceController.java create mode 100644 src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java create mode 100644 src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenCustomRadioButtonPreferenceTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenFooterPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 69416a7223b..40d7e94eeb4 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -738,23 +738,6 @@ android:value="true" /> - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/preference_widget_zen_master_icon.xml b/res/layout/preference_widget_zen_master_icon.xml new file mode 100644 index 00000000000..64c0db8d614 --- /dev/null +++ b/res/layout/preference_widget_zen_master_icon.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index c25e41a60de..5b40fe6dcb3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7215,8 +7215,39 @@ Allow visual signals + + Restrict notifications + + Notifications + + When Do Not Disturb is turned on + + Mute notifications + + Show notifications but mute sounds & vibrations + + When new notifications arrive your phone won\u2019t make a sound or vibration + + Hide & mute notifications + + Notifications will not appear at all + + You won\u2019t see new or existing notifications when Do Not Disturb is on. However, notifications needed for basic phone activity and status will still appear. + + Custom + + Enable custom setting + + Remove custom setting + + Will appear + + Partially hidden + + Hidden + - Block visual disturbances + Custom restrictions When the screen is on diff --git a/res/xml/zen_mode_restrict_notifications_settings.xml b/res/xml/zen_mode_restrict_notifications_settings.xml new file mode 100644 index 00000000000..d5243f62943 --- /dev/null +++ b/res/xml/zen_mode_restrict_notifications_settings.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + diff --git a/res/xml/zen_mode_settings.xml b/res/xml/zen_mode_settings.xml index 717b6c505f6..0f9396840a4 100644 --- a/res/xml/zen_mode_settings.xml +++ b/res/xml/zen_mode_settings.xml @@ -31,8 +31,8 @@ + android:title="@string/zen_mode_restrict_notifications_title" + android:fragment="com.android.settings.notification.ZenModeRestrictNotificationsSettings" /> createPreferenceControllers(Context context) { + return buildPreferenceControllers(context, getLifecycle()); + } + + private static List buildPreferenceControllers(Context context, + Lifecycle lifecycle) { + List controllers = new ArrayList<>(); + controllers.add(new ZenModeVisEffectsNonePreferenceController( + context, lifecycle, "zen_mute_notifications")); + controllers.add(new ZenModeVisEffectsAllPreferenceController( + context, lifecycle, "zen_hide_notifications")); + controllers.add(new ZenModeVisEffectsCustomPreferenceController( + context, lifecycle, "zen_custom")); + controllers.add(new ZenFooterPreferenceController(context, lifecycle, + FooterPreference.KEY_FOOTER)); + return controllers; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.zen_mode_restrict_notifications_settings; + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.SETTINGS_ZEN_NOTIFICATIONS; + } + + /** + * For Search. + */ + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex(Context context, + boolean enabled) { + final ArrayList result = new ArrayList<>(); + + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.zen_mode_restrict_notifications_settings; + result.add(sir); + return result; + } + + @Override + public List getNonIndexableKeys(Context context) { + final List keys = super.getNonIndexableKeys(context); + return keys; + } + + @Override + public List createPreferenceControllers(Context context) { + return buildPreferenceControllers(context, null); + } + }; +} diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java index 15888cbc33c..706bc637502 100644 --- a/src/com/android/settings/notification/ZenModeSettings.java +++ b/src/com/android/settings/notification/ZenModeSettings.java @@ -160,24 +160,15 @@ public class ZenModeSettings extends ZenModeSettingsBase { } String getBlockedEffectsSummary(Policy policy) { - List blockedStrings = new ArrayList<>(); - if (Policy.areAnyScreenOffEffectsSuppressed(policy.suppressedVisualEffects)) { - blockedStrings.add(mContext.getResources().getString( - R.string.zen_mode_block_effect_summary_screen_off)); - } - if (Policy.areAnyScreenOnEffectsSuppressed(policy.suppressedVisualEffects)) { - blockedStrings.add(mContext.getResources().getString( - R.string.zen_mode_block_effect_summary_screen_on)); - } - - if (blockedStrings.size() == 0) { + if (policy.suppressedVisualEffects == 0) { return mContext.getResources().getString( - R.string.zen_mode_block_effect_summary_none); - } else if (blockedStrings.size() == 1) { - return blockedStrings.get(0); + R.string.zen_mode_restrict_notifications_summary_muted); + } else if (Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)) { + return mContext.getResources().getString( + R.string.zen_mode_restrict_notifications_summary_hidden); } else { - return mContext.getResources().getString(R.string.join_two_unrelated_items, - blockedStrings.get(0), blockedStrings.get(1)); + return mContext.getResources().getString( + R.string.zen_mode_restrict_notifications_summary_custom); } } diff --git a/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceController.java b/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceController.java new file mode 100644 index 00000000000..757fde7f731 --- /dev/null +++ b/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceController.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import android.app.NotificationManager.Policy; +import android.content.Context; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class ZenModeVisEffectsAllPreferenceController + extends AbstractZenModePreferenceController + implements ZenCustomRadioButtonPreference.OnRadioButtonClickListener { + + protected static final int EFFECTS = Policy.SUPPRESSED_EFFECT_SCREEN_OFF + | Policy.SUPPRESSED_EFFECT_SCREEN_ON + | Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | Policy.SUPPRESSED_EFFECT_LIGHTS + | Policy.SUPPRESSED_EFFECT_PEEK + | Policy.SUPPRESSED_EFFECT_STATUS_BAR + | Policy.SUPPRESSED_EFFECT_BADGE + | Policy.SUPPRESSED_EFFECT_AMBIENT + | Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; + + public ZenModeVisEffectsAllPreferenceController(Context context, Lifecycle lifecycle, + String key) { + super(context, key, lifecycle); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + boolean everythingBlocked = Policy.areAllVisualEffectsSuppressed( + mBackend.mPolicy.suppressedVisualEffects); + ZenCustomRadioButtonPreference pref = (ZenCustomRadioButtonPreference) preference; + pref.setOnRadioButtonClickListener(this); + pref.setChecked(everythingBlocked); + } + + protected void deselect(PreferenceScreen screen) { + ZenCustomRadioButtonPreference preference = + (ZenCustomRadioButtonPreference) screen.findPreference(getPreferenceKey()); + if (preference != null) { + preference.setChecked(false); + } + } + + @Override + public void onRadioButtonClick(ZenCustomRadioButtonPreference p) { + mMetricsFeatureProvider.action(mContext, + MetricsProto.MetricsEvent.ACTION_ZEN_SOUND_AND_VIS_EFFECTS, true); + mBackend.saveVisualEffectsPolicy(EFFECTS, true); + } +} diff --git a/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java b/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java new file mode 100644 index 00000000000..d0bf9749ceb --- /dev/null +++ b/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import android.app.NotificationManager.Policy; +import android.content.Context; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class ZenModeVisEffectsCustomPreferenceController + extends AbstractZenModePreferenceController { + + protected boolean mShowMenuSelected; + protected static final int INTERRUPTIVE_EFFECTS = Policy.SUPPRESSED_EFFECT_AMBIENT + | Policy.SUPPRESSED_EFFECT_PEEK + | Policy.SUPPRESSED_EFFECT_LIGHTS + | Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; + + public ZenModeVisEffectsCustomPreferenceController(Context context, Lifecycle lifecycle, + String key) { + super(context, key, lifecycle); + } + + @Override + public boolean isAvailable() { + if (mShowMenuSelected) { + return true; + } + + return areCustomOptionsSelected(); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + ZenCustomRadioButtonPreference pref = (ZenCustomRadioButtonPreference) preference; + pref.setChecked(areCustomOptionsSelected()); + + pref.setOnGearClickListener(p -> { + new SubSettingLauncher(mContext) + .setDestination(ZenModeBlockedEffectsSettings.class.getName()) + .setTitle(R.string.zen_mode_what_to_block_title) + .setSourceMetricsCategory(MetricsProto.MetricsEvent.SETTINGS_ZEN_NOTIFICATIONS) + .launch(); + }); + + pref.setOnRadioButtonClickListener(p -> { + select(); + }); + } + + protected void setShownByMenu(boolean shown) { + mShowMenuSelected = shown; + } + + protected boolean areCustomOptionsSelected() { + boolean allEffectsSuppressed = + Policy.areAllVisualEffectsSuppressed(mBackend.mPolicy.suppressedVisualEffects); + boolean noEffectsSuppressed = mBackend.mPolicy.suppressedVisualEffects == 0; + + return !(allEffectsSuppressed || noEffectsSuppressed); + } + + protected void select() { + mMetricsFeatureProvider.action(mContext, + MetricsProto.MetricsEvent.ACTION_ZEN_CUSTOM, true); + mBackend.savePolicy(mBackend.mPolicy.priorityCategories, + mBackend.mPolicy.priorityCallSenders, + mBackend.mPolicy.priorityMessageSenders, + INTERRUPTIVE_EFFECTS); + } +} \ No newline at end of file diff --git a/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceController.java b/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceController.java new file mode 100644 index 00000000000..abfbd01686f --- /dev/null +++ b/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceController.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import android.app.NotificationManager.Policy; +import android.content.Context; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class ZenModeVisEffectsNonePreferenceController + extends AbstractZenModePreferenceController + implements ZenCustomRadioButtonPreference.OnRadioButtonClickListener { + + protected static final int EFFECTS = Policy.SUPPRESSED_EFFECT_SCREEN_OFF + | Policy.SUPPRESSED_EFFECT_SCREEN_ON + | Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | Policy.SUPPRESSED_EFFECT_LIGHTS + | Policy.SUPPRESSED_EFFECT_PEEK + | Policy.SUPPRESSED_EFFECT_STATUS_BAR + | Policy.SUPPRESSED_EFFECT_BADGE + | Policy.SUPPRESSED_EFFECT_AMBIENT + | Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; + + public ZenModeVisEffectsNonePreferenceController(Context context, Lifecycle lifecycle, + String key) { + super(context, key, lifecycle); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + boolean nothingBlocked = mBackend.mPolicy.suppressedVisualEffects == 0; + ZenCustomRadioButtonPreference pref = (ZenCustomRadioButtonPreference) preference; + pref.setOnRadioButtonClickListener(this); + pref.setChecked(nothingBlocked); + } + + @Override + public void onRadioButtonClick(ZenCustomRadioButtonPreference preference) { + mMetricsFeatureProvider.action(mContext, + MetricsProto.MetricsEvent.ACTION_ZEN_SOUND_ONLY, true); + mBackend.saveVisualEffectsPolicy(EFFECTS, false); + } + + protected void deselect(PreferenceScreen screen) { + ZenCustomRadioButtonPreference preference = + (ZenCustomRadioButtonPreference) screen.findPreference(getPreferenceKey()); + if (preference != null) { + preference.setChecked(false); + } + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenCustomRadioButtonPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/ZenCustomRadioButtonPreferenceTest.java new file mode 100644 index 00000000000..40dfc4105e7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenCustomRadioButtonPreferenceTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import androidx.preference.Preference.OnPreferenceChangeListener; +import androidx.preference.PreferenceViewHolder; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.Switch; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.MasterSwitchPreference; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenCustomRadioButtonPreferenceTest { + + private Context mContext; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + } + + @Test + public void createNewPreference_shouldSetLayout() { + final ZenCustomRadioButtonPreference preference + = new ZenCustomRadioButtonPreference(mContext); + + assertThat(preference.getLayoutResource()).isEqualTo(R.layout.preference_two_target_radio); + assertThat(preference.getWidgetLayoutResource()) + .isEqualTo(R.layout.preference_widget_gear); + } + + @Test + public void setChecked_shouldUpdateButtonCheckedState() { + final ZenCustomRadioButtonPreference preference = + new ZenCustomRadioButtonPreference(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate( + R.layout.preference_two_target_radio, null)); + final RadioButton toggle = (RadioButton) holder.findViewById(android.R.id.checkbox); + preference.onBindViewHolder(holder); + + preference.setChecked(true); + assertThat(toggle.isChecked()).isTrue(); + + preference.setChecked(false); + assertThat(toggle.isChecked()).isFalse(); + } + + @Test + public void clickRadioButton_shouldNotifyRadioButtonClicked() { + final ZenCustomRadioButtonPreference preference + = new ZenCustomRadioButtonPreference(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate(R.layout.preference_two_target_radio, null)); + final View toggle = holder.findViewById(R.id.checkbox_frame); + + ZenCustomRadioButtonPreference.OnRadioButtonClickListener l = mock( + ZenCustomRadioButtonPreference.OnRadioButtonClickListener.class); + preference.setOnRadioButtonClickListener(l); + preference.onBindViewHolder(holder); + + toggle.performClick(); + verify(l).onRadioButtonClick(preference); + } + + @Test + public void clickWidgetView_shouldNotifyWidgetClicked() { + final ZenCustomRadioButtonPreference preference = + new ZenCustomRadioButtonPreference(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate(R.layout.preference_two_target_radio, null)); + final View widgetView = holder.findViewById(android.R.id.widget_frame); + + ZenCustomRadioButtonPreference.OnGearClickListener l = mock( + ZenCustomRadioButtonPreference.OnGearClickListener.class); + preference.setOnGearClickListener(l); + preference.onBindViewHolder(holder); + + widgetView.performClick(); + verify(l).onGearClick(preference); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenFooterPreferenceControllerTest.java new file mode 100644 index 00000000000..8c7c32608cb --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenFooterPreferenceControllerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.Context; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.FooterPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenFooterPreferenceControllerTest { + private ZenFooterPreferenceController mController; + + @Mock + private ZenModeBackend mBackend; + @Mock + private FooterPreference mockPref; + private Context mContext; + @Mock + private PreferenceScreen mScreen; + @Mock NotificationManager mNotificationManager; + + private static final String PREF_KEY = "main_pref"; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = shadowApplication.getApplicationContext(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + when(mNotificationManager.getNotificationPolicy()).thenReturn( + mock(NotificationManager.Policy.class)); + mController = new ZenFooterPreferenceController( + mContext, mock(Lifecycle.class), PREF_KEY); + ReflectionHelpers.setField(mController, "mBackend", mBackend); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref); + } + + @Test + public void isAvailable_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_someVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 2); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_allVisEffects() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, allSuppressed); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void updateSummary_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.updateState(mockPref); + verify(mockPref).setTitle(R.string.zen_mode_restrict_notifications_mute_footer); + } + + @Test + public void getSummary_someVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 2); + mController.updateState(mockPref); + verify(mockPref).setTitle(null); + } + + @Test + public void getSummary_allVisEffects() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, allSuppressed); + mController.updateState(mockPref); + verify(mockPref).setTitle(R.string.zen_mode_restrict_notifications_hide_footer); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java index d706d126c07..b26989d3475 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java @@ -74,29 +74,23 @@ public class ZenModeSettingsTest { @Test public void testBlockedEffectsSummary_none() { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0, 0); - assertEquals("Never", mBuilder.getBlockedEffectsSummary(policy)); + assertEquals(mContext.getString(R.string.zen_mode_restrict_notifications_summary_muted), + mBuilder.getBlockedEffectsSummary(policy)); } @Test - public void testBlockedEffectsSummary_screen_on() { + public void testBlockedEffectsSummary_some() { NotificationManager.Policy policy = new NotificationManager.Policy( 0, 0, 0, NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK); - assertEquals("When screen is on", mBuilder.getBlockedEffectsSummary(policy)); + assertEquals(mContext.getString(R.string.zen_mode_restrict_notifications_summary_custom), + mBuilder.getBlockedEffectsSummary(policy)); } @Test - public void testBlockedEffectsSummary_screen_off() { + public void testBlockedEffectsSummary_all() { NotificationManager.Policy policy = new NotificationManager.Policy( - 0, 0, 0, NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT); - assertEquals("When screen is off", mBuilder.getBlockedEffectsSummary(policy)); - } - - @Test - public void testBlockedEffectsSummary_both() { - NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0, - NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST - | NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS); - assertEquals("When screen is off, When screen is on", + 0, 0, 0, 511); + assertEquals(mContext.getString(R.string.zen_mode_restrict_notifications_summary_hidden), mBuilder.getBlockedEffectsSummary(policy)); } diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceControllerTest.java new file mode 100644 index 00000000000..a20b7e16d14 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceControllerTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent + .ACTION_ZEN_SOUND_AND_VIS_EFFECTS; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.Context; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.DisabledCheckBoxPreference; +import com.android.settings.widget.RadioButtonPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenModeVisEffectsAllPreferenceControllerTest { + private ZenModeVisEffectsAllPreferenceController mController; + + @Mock + private ZenModeBackend mBackend; + @Mock + private ZenCustomRadioButtonPreference mockPref; + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + @Mock + private PreferenceScreen mScreen; + @Mock NotificationManager mNotificationManager; + + private static final String PREF_KEY = "main_pref"; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = shadowApplication.getApplicationContext(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + when(mNotificationManager.getNotificationPolicy()).thenReturn( + mock(NotificationManager.Policy.class)); + mController = new ZenModeVisEffectsAllPreferenceController( + mContext, mock(Lifecycle.class), PREF_KEY); + ReflectionHelpers.setField(mController, "mBackend", mBackend); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref); + mController.displayPreference(mScreen); + } + + @Test + public void isAvailable() { + assertTrue(mController.isAvailable()); + } + + @Test + public void updateState_notChecked() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.updateState(mockPref); + + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_checked() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, allSuppressed); + mController.updateState(mockPref); + + verify(mockPref).setChecked(true); + } + + @Test + public void onPreferenceChanged_checkedTrue() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.onRadioButtonClick(mockPref); + verify(mBackend).saveVisualEffectsPolicy(allSuppressed, true); + verify(mFeatureFactory.metricsFeatureProvider).action(eq(mContext), + eq(ACTION_ZEN_SOUND_AND_VIS_EFFECTS), + eq(true)); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java new file mode 100644 index 00000000000..5de7697d1af --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_ZEN_CUSTOM; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent + .ACTION_ZEN_SOUND_AND_VIS_EFFECTS; + +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.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.Context; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenModeVisEffectsCustomPreferenceControllerTest { + private ZenModeVisEffectsCustomPreferenceController mController; + + @Mock + private ZenModeBackend mBackend; + @Mock + private ZenCustomRadioButtonPreference mockPref; + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + @Mock + private PreferenceScreen mScreen; + @Mock NotificationManager mNotificationManager; + + private static final String PREF_KEY = "main_pref"; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = shadowApplication.getApplicationContext(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + when(mNotificationManager.getNotificationPolicy()).thenReturn( + mock(NotificationManager.Policy.class)); + mController = new ZenModeVisEffectsCustomPreferenceController( + mContext, mock(Lifecycle.class), PREF_KEY); + ReflectionHelpers.setField(mController, "mBackend", mBackend); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref); + } + + @Test + public void isAvailable_menuOff_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.mShowMenuSelected = false; + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_menuOn_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.mShowMenuSelected = true; + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_menuOn_visEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.mShowMenuSelected = false; + assertThat(mController.isAvailable()).isTrue(); + } + + + @Test + public void updateState_notChecked_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.updateState(mockPref); + + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_notChecked_allVisEffects() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, allSuppressed); + mController.updateState(mockPref); + + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_checked() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 2); + mController.updateState(mockPref); + + verify(mockPref).setChecked(true); + } + + @Test + public void updateState_listeners() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 2); + mController.updateState(mockPref); + + verify(mockPref).setOnGearClickListener(any()); + verify(mockPref).setOnRadioButtonClickListener(any()); + } + + @Test + public void select() { + int interruptiveSuppressed = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.select(); + verify(mBackend).savePolicy(anyInt(), anyInt(), anyInt(), eq(interruptiveSuppressed)); + verify(mFeatureFactory.metricsFeatureProvider).action(eq(mContext), + eq(ACTION_ZEN_CUSTOM), + eq(true)); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceControllerTest.java new file mode 100644 index 00000000000..fd22166563b --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceControllerTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_ZEN_SOUND_ONLY; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.Context; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.DisabledCheckBoxPreference; +import com.android.settings.widget.RadioButtonPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenModeVisEffectsNonePreferenceControllerTest { + private ZenModeVisEffectsNonePreferenceController mController; + + @Mock + private ZenModeBackend mBackend; + @Mock + private ZenCustomRadioButtonPreference mockPref; + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + @Mock + private PreferenceScreen mScreen; + @Mock NotificationManager mNotificationManager; + + private static final String PREF_KEY = "main_pref"; + private static final int PREF_METRICS = 1; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = shadowApplication.getApplicationContext(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + when(mNotificationManager.getNotificationPolicy()).thenReturn( + mock(NotificationManager.Policy.class)); + mController = new ZenModeVisEffectsNonePreferenceController( + mContext, mock(Lifecycle.class), PREF_KEY); + ReflectionHelpers.setField(mController, "mBackend", mBackend); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref); + mController.displayPreference(mScreen); + } + + @Test + public void isAvailable() { + assertTrue(mController.isAvailable()); + } + + @Test + public void updateState_notChecked() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.updateState(mockPref); + + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_checked() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.updateState(mockPref); + + verify(mockPref).setChecked(true); + } + + @Test + public void onRadioButtonClick() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.onRadioButtonClick(mockPref); + verify(mBackend).saveVisualEffectsPolicy(allSuppressed, false); + verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), + eq(ACTION_ZEN_SOUND_ONLY), + eq(true)); + } +} From 86e6366f6176e9eec0ada10b0abc270475ddef91 Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Tue, 24 Apr 2018 09:57:44 -0700 Subject: [PATCH 2/8] Updated play button icon for paused animations The current play button is mostly white and hard to see with the new animation videos. This updates it to one which has more blue to stand out better. Fixes: 78306799 Test: manual (while watching one of the animations at Settings -> System -> Gestures; tap the video to pause) Change-Id: I48deb6c35eccb3ecd21f2aa6dcc3d275c7436f47 --- res/drawable/ic_gesture_play_button.xml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/res/drawable/ic_gesture_play_button.xml b/res/drawable/ic_gesture_play_button.xml index e2fa6e0ceaf..29ba78e6c7f 100644 --- a/res/drawable/ic_gesture_play_button.xml +++ b/res/drawable/ic_gesture_play_button.xml @@ -16,10 +16,9 @@ - - \ No newline at end of file + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + + + + From 1b6835a86bdd3a6db22903727f6adae60005bf35 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 26 Apr 2018 17:35:24 -0700 Subject: [PATCH 3/8] mark feature flag translateble to avoid loc bugs. Change-Id: I8a5c1e5ce6afbcfb784524a15ad69966ea28b532 Fixes: 78503763 Test: builds --- res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index c25e41a60de..9910fb445cb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4486,8 +4486,8 @@ Downloaded services Experimental - - Feature flags + + Feature flags Talkback From ade2423e3427a1ed7ee663a7d54f6a65c8014d52 Mon Sep 17 00:00:00 2001 From: "Sam.SH Chang" Date: Fri, 27 Apr 2018 10:28:00 +0800 Subject: [PATCH 4/8] Add tint attribute to the icon Bug: 78238313 Test: build pass Change-Id: I805f5a8f1c21f617d3555e135e9d332b77ca0c29 --- res/drawable/ic_devices_other_black.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/drawable/ic_devices_other_black.xml b/res/drawable/ic_devices_other_black.xml index 9974eafa1e8..babcc637652 100644 --- a/res/drawable/ic_devices_other_black.xml +++ b/res/drawable/ic_devices_other_black.xml @@ -17,7 +17,8 @@ android:viewportWidth="24" android:viewportHeight="24" android:width="24dp" - android:height="24dp"> + android:height="24dp" + android:tint="?android:attr/colorControlNormal"> mPreferenceMap; protected Context mPrefContext; + protected DashboardFragment mFragment; private final boolean mShowDeviceWithoutNames; - private DashboardFragment mFragment; private Preference.OnPreferenceClickListener mDevicePreferenceClickListener = null; @VisibleForTesting final GearPreference.OnGearClickListener mDeviceProfilesListener = pref -> { - final CachedBluetoothDevice device = - ((BluetoothDevicePreference) pref).getBluetoothDevice(); - if (device == null) { - return; - } - final Bundle args = new Bundle(); - args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, - device.getDevice().getAddress()); - - new SubSettingLauncher(mFragment.getContext()) - .setDestination(BluetoothDeviceDetailsFragment.class.getName()) - .setArguments(args) - .setTitle(R.string.device_details_title) - .setSourceMetricsCategory(mFragment.getMetricsCategory()) - .launch(); - + launchDeviceDetails(pref); }; private class PreferenceClickListener implements @@ -201,7 +186,7 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, public abstract boolean isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice); /** - * Update whether to show {@cde cachedBluetoothDevice} in the list. + * Update whether to show {@link CachedBluetoothDevice} in the list. */ protected void update(CachedBluetoothDevice cachedBluetoothDevice) { if (isFilterMatched(cachedBluetoothDevice)) { @@ -239,6 +224,28 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, } } + /** + * Get {@link CachedBluetoothDevice} from {@link Preference} and it is used to init + * {@link SubSettingLauncher} to launch {@link BluetoothDeviceDetailsFragment} + */ + protected void launchDeviceDetails(Preference preference) { + final CachedBluetoothDevice device = + ((BluetoothDevicePreference) preference).getBluetoothDevice(); + if (device == null) { + return; + } + final Bundle args = new Bundle(); + args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, + device.getDevice().getAddress()); + + new SubSettingLauncher(mFragment.getContext()) + .setDestination(BluetoothDeviceDetailsFragment.class.getName()) + .setArguments(args) + .setTitle(R.string.device_details_title) + .setSourceMetricsCategory(mFragment.getMetricsCategory()) + .launch(); + } + /** * @return {@code true} if {@code cachedBluetoothDevice} is connected * and the bond state is bonded. diff --git a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java index 588f23df9fe..7559b30fe1e 100644 --- a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java @@ -16,12 +16,19 @@ package com.android.settings.bluetooth; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.media.AudioManager; -import androidx.annotation.VisibleForTesting; +import android.os.Bundle; import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +import com.android.settings.R; import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -116,4 +123,20 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater { } return isFilterMatched; } + + @Override + protected void addPreference(CachedBluetoothDevice cachedDevice) { + super.addPreference(cachedDevice); + final BluetoothDevice device = cachedDevice.getDevice(); + if (mPreferenceMap.containsKey(device)) { + final BluetoothDevicePreference btPreference = + (BluetoothDevicePreference) mPreferenceMap.get(device); + btPreference.setOnGearClickListener(null); + btPreference.hideSecondTarget(true); + btPreference.setOnPreferenceClickListener((Preference p) -> { + launchDeviceDetails(p); + return true; + }); + } + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java index 9d69f59b7d3..56e638a9ed1 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java @@ -15,6 +15,7 @@ */ package com.android.settings.bluetooth; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -207,4 +208,15 @@ public class ConnectedBluetoothDeviceUpdaterTest { verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); } + + @Test + public void addPreference_addPreference_shouldHideSecondTarget() { + BluetoothDevicePreference btPreference = + new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, true); + mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, btPreference); + + mBluetoothDeviceUpdater.addPreference(mCachedBluetoothDevice); + + assertThat(btPreference.shouldHideSecondTarget()).isTrue(); + } } From 4cf8bfd5b2ffdaf181e69113b93bd019b277430c Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Mon, 23 Apr 2018 12:24:21 -0400 Subject: [PATCH 7/8] Redesign zen visual effects screens Test: robotests Bug: 78448988 Change-Id: I3040c1103d76c75601e82e3660a2ace202837ec2 Merged-In: I3040c1103d76c75601e82e3660a2ace202837ec2 --- AndroidManifest.xml | 17 -- res/layout/preference_two_target_radio.xml | 90 ++++++++++ .../preference_widget_zen_master_icon.xml | 26 +++ res/values/strings.xml | 33 +++- ...n_mode_restrict_notifications_settings.xml | 43 +++++ res/xml/zen_mode_settings.xml | 4 +- .../AbstractZenModePreferenceController.java | 28 +-- .../ZenCustomRadioButtonPreference.java | 140 +++++++++++++++ .../ZenFooterPreferenceController.java | 56 ++++++ .../ZenModeRestrictNotificationsSettings.java | 149 ++++++++++++++++ .../notification/ZenModeSettings.java | 23 +-- ...ModeVisEffectsAllPreferenceController.java | 76 ++++++++ ...eVisEffectsCustomPreferenceController.java | 92 ++++++++++ ...odeVisEffectsNonePreferenceController.java | 75 ++++++++ .../search/SearchIndexableResourcesImpl.java | 2 + .../ZenCustomRadioButtonPreferenceTest.java | 113 ++++++++++++ .../ZenFooterPreferenceControllerTest.java | 138 +++++++++++++++ .../notification/ZenModeSettingsTest.java | 22 +-- ...VisEffectsAllPreferenceControllerTest.java | 141 +++++++++++++++ ...EffectsCustomPreferenceControllerTest.java | 167 ++++++++++++++++++ ...isEffectsNonePreferenceControllerTest.java | 132 ++++++++++++++ 21 files changed, 1507 insertions(+), 60 deletions(-) create mode 100644 res/layout/preference_two_target_radio.xml create mode 100644 res/layout/preference_widget_zen_master_icon.xml create mode 100644 res/xml/zen_mode_restrict_notifications_settings.xml create mode 100644 src/com/android/settings/notification/ZenCustomRadioButtonPreference.java create mode 100644 src/com/android/settings/notification/ZenFooterPreferenceController.java create mode 100644 src/com/android/settings/notification/ZenModeRestrictNotificationsSettings.java create mode 100644 src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceController.java create mode 100644 src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java create mode 100644 src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenCustomRadioButtonPreferenceTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenFooterPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index af565d6a02f..38401e8f1ca 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -738,23 +738,6 @@ android:value="true" /> - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/preference_widget_zen_master_icon.xml b/res/layout/preference_widget_zen_master_icon.xml new file mode 100644 index 00000000000..64c0db8d614 --- /dev/null +++ b/res/layout/preference_widget_zen_master_icon.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index b3b068aabf3..7bdb06972eb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7223,8 +7223,39 @@ Allow visual signals + + Restrict notifications + + Notifications + + When Do Not Disturb is turned on + + Mute notifications + + Show notifications but mute sounds & vibrations + + When new notifications arrive your phone won\u2019t make a sound or vibration + + Hide & mute notifications + + Notifications will not appear at all + + You won\u2019t see new or existing notifications when Do Not Disturb is on. However, notifications needed for basic phone activity and status will still appear. + + Custom + + Enable custom setting + + Remove custom setting + + Will appear + + Partially hidden + + Hidden + - Block visual disturbances + Custom restrictions When the screen is on diff --git a/res/xml/zen_mode_restrict_notifications_settings.xml b/res/xml/zen_mode_restrict_notifications_settings.xml new file mode 100644 index 00000000000..d5243f62943 --- /dev/null +++ b/res/xml/zen_mode_restrict_notifications_settings.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + diff --git a/res/xml/zen_mode_settings.xml b/res/xml/zen_mode_settings.xml index 717b6c505f6..0f9396840a4 100644 --- a/res/xml/zen_mode_settings.xml +++ b/res/xml/zen_mode_settings.xml @@ -31,8 +31,8 @@ + android:title="@string/zen_mode_restrict_notifications_title" + android:fragment="com.android.settings.notification.ZenModeRestrictNotificationsSettings" /> createPreferenceControllers(Context context) { + return buildPreferenceControllers(context, getLifecycle()); + } + + private static List buildPreferenceControllers(Context context, + Lifecycle lifecycle) { + List controllers = new ArrayList<>(); + controllers.add(new ZenModeVisEffectsNonePreferenceController( + context, lifecycle, "zen_mute_notifications")); + controllers.add(new ZenModeVisEffectsAllPreferenceController( + context, lifecycle, "zen_hide_notifications")); + controllers.add(new ZenModeVisEffectsCustomPreferenceController( + context, lifecycle, "zen_custom")); + controllers.add(new ZenFooterPreferenceController(context, lifecycle, + FooterPreference.KEY_FOOTER)); + return controllers; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.zen_mode_restrict_notifications_settings; + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.SETTINGS_ZEN_NOTIFICATIONS; + } + + /** + * For Search. + */ + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex(Context context, + boolean enabled) { + final ArrayList result = new ArrayList<>(); + + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.zen_mode_restrict_notifications_settings; + result.add(sir); + return result; + } + + @Override + public List getNonIndexableKeys(Context context) { + final List keys = super.getNonIndexableKeys(context); + return keys; + } + + @Override + public List createPreferenceControllers(Context context) { + return buildPreferenceControllers(context, null); + } + }; +} diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java index 1f0dcc28365..bdc24e3c530 100644 --- a/src/com/android/settings/notification/ZenModeSettings.java +++ b/src/com/android/settings/notification/ZenModeSettings.java @@ -158,24 +158,15 @@ public class ZenModeSettings extends ZenModeSettingsBase { } String getBlockedEffectsSummary(Policy policy) { - List blockedStrings = new ArrayList<>(); - if (Policy.areAnyScreenOffEffectsSuppressed(policy.suppressedVisualEffects)) { - blockedStrings.add(mContext.getResources().getString( - R.string.zen_mode_block_effect_summary_screen_off)); - } - if (Policy.areAnyScreenOnEffectsSuppressed(policy.suppressedVisualEffects)) { - blockedStrings.add(mContext.getResources().getString( - R.string.zen_mode_block_effect_summary_screen_on)); - } - - if (blockedStrings.size() == 0) { + if (policy.suppressedVisualEffects == 0) { return mContext.getResources().getString( - R.string.zen_mode_block_effect_summary_none); - } else if (blockedStrings.size() == 1) { - return blockedStrings.get(0); + R.string.zen_mode_restrict_notifications_summary_muted); + } else if (Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)) { + return mContext.getResources().getString( + R.string.zen_mode_restrict_notifications_summary_hidden); } else { - return mContext.getResources().getString(R.string.join_two_unrelated_items, - blockedStrings.get(0), blockedStrings.get(1)); + return mContext.getResources().getString( + R.string.zen_mode_restrict_notifications_summary_custom); } } diff --git a/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceController.java b/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceController.java new file mode 100644 index 00000000000..70618aac88d --- /dev/null +++ b/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceController.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import android.app.NotificationManager.Policy; +import android.content.Context; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class ZenModeVisEffectsAllPreferenceController + extends AbstractZenModePreferenceController + implements ZenCustomRadioButtonPreference.OnRadioButtonClickListener { + + protected static final int EFFECTS = Policy.SUPPRESSED_EFFECT_SCREEN_OFF + | Policy.SUPPRESSED_EFFECT_SCREEN_ON + | Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | Policy.SUPPRESSED_EFFECT_LIGHTS + | Policy.SUPPRESSED_EFFECT_PEEK + | Policy.SUPPRESSED_EFFECT_STATUS_BAR + | Policy.SUPPRESSED_EFFECT_BADGE + | Policy.SUPPRESSED_EFFECT_AMBIENT + | Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; + + public ZenModeVisEffectsAllPreferenceController(Context context, Lifecycle lifecycle, + String key) { + super(context, key, lifecycle); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + boolean everythingBlocked = Policy.areAllVisualEffectsSuppressed( + mBackend.mPolicy.suppressedVisualEffects); + ZenCustomRadioButtonPreference pref = (ZenCustomRadioButtonPreference) preference; + pref.setOnRadioButtonClickListener(this); + pref.setChecked(everythingBlocked); + } + + protected void deselect(PreferenceScreen screen) { + ZenCustomRadioButtonPreference preference = + (ZenCustomRadioButtonPreference) screen.findPreference(getPreferenceKey()); + if (preference != null) { + preference.setChecked(false); + } + } + + @Override + public void onRadioButtonClick(ZenCustomRadioButtonPreference p) { + mMetricsFeatureProvider.action(mContext, + MetricsProto.MetricsEvent.ACTION_ZEN_SOUND_AND_VIS_EFFECTS, true); + mBackend.saveVisualEffectsPolicy(EFFECTS, true); + } +} diff --git a/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java b/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java new file mode 100644 index 00000000000..fd235e61fcb --- /dev/null +++ b/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceController.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import android.app.NotificationManager.Policy; +import android.content.Context; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class ZenModeVisEffectsCustomPreferenceController + extends AbstractZenModePreferenceController { + + protected boolean mShowMenuSelected; + protected static final int INTERRUPTIVE_EFFECTS = Policy.SUPPRESSED_EFFECT_AMBIENT + | Policy.SUPPRESSED_EFFECT_PEEK + | Policy.SUPPRESSED_EFFECT_LIGHTS + | Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; + + public ZenModeVisEffectsCustomPreferenceController(Context context, Lifecycle lifecycle, + String key) { + super(context, key, lifecycle); + } + + @Override + public boolean isAvailable() { + if (mShowMenuSelected) { + return true; + } + + return areCustomOptionsSelected(); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + ZenCustomRadioButtonPreference pref = (ZenCustomRadioButtonPreference) preference; + pref.setChecked(areCustomOptionsSelected()); + + pref.setOnGearClickListener(p -> { + new SubSettingLauncher(mContext) + .setDestination(ZenModeBlockedEffectsSettings.class.getName()) + .setTitle(R.string.zen_mode_what_to_block_title) + .setSourceMetricsCategory(MetricsProto.MetricsEvent.SETTINGS_ZEN_NOTIFICATIONS) + .launch(); + }); + + pref.setOnRadioButtonClickListener(p -> { + select(); + }); + } + + protected void setShownByMenu(boolean shown) { + mShowMenuSelected = shown; + } + + protected boolean areCustomOptionsSelected() { + boolean allEffectsSuppressed = + Policy.areAllVisualEffectsSuppressed(mBackend.mPolicy.suppressedVisualEffects); + boolean noEffectsSuppressed = mBackend.mPolicy.suppressedVisualEffects == 0; + + return !(allEffectsSuppressed || noEffectsSuppressed); + } + + protected void select() { + mMetricsFeatureProvider.action(mContext, + MetricsProto.MetricsEvent.ACTION_ZEN_CUSTOM, true); + mBackend.savePolicy(mBackend.mPolicy.priorityCategories, + mBackend.mPolicy.priorityCallSenders, + mBackend.mPolicy.priorityMessageSenders, + INTERRUPTIVE_EFFECTS); + } +} \ No newline at end of file diff --git a/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceController.java b/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceController.java new file mode 100644 index 00000000000..8958d081a05 --- /dev/null +++ b/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceController.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import android.app.NotificationManager.Policy; +import android.content.Context; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class ZenModeVisEffectsNonePreferenceController + extends AbstractZenModePreferenceController + implements ZenCustomRadioButtonPreference.OnRadioButtonClickListener { + + protected static final int EFFECTS = Policy.SUPPRESSED_EFFECT_SCREEN_OFF + | Policy.SUPPRESSED_EFFECT_SCREEN_ON + | Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | Policy.SUPPRESSED_EFFECT_LIGHTS + | Policy.SUPPRESSED_EFFECT_PEEK + | Policy.SUPPRESSED_EFFECT_STATUS_BAR + | Policy.SUPPRESSED_EFFECT_BADGE + | Policy.SUPPRESSED_EFFECT_AMBIENT + | Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; + + public ZenModeVisEffectsNonePreferenceController(Context context, Lifecycle lifecycle, + String key) { + super(context, key, lifecycle); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + boolean nothingBlocked = mBackend.mPolicy.suppressedVisualEffects == 0; + ZenCustomRadioButtonPreference pref = (ZenCustomRadioButtonPreference) preference; + pref.setOnRadioButtonClickListener(this); + pref.setChecked(nothingBlocked); + } + + @Override + public void onRadioButtonClick(ZenCustomRadioButtonPreference preference) { + mMetricsFeatureProvider.action(mContext, + MetricsProto.MetricsEvent.ACTION_ZEN_SOUND_ONLY, true); + mBackend.saveVisualEffectsPolicy(EFFECTS, false); + } + + protected void deselect(PreferenceScreen screen) { + ZenCustomRadioButtonPreference preference = + (ZenCustomRadioButtonPreference) screen.findPreference(getPreferenceKey()); + if (preference != null) { + preference.setChecked(false); + } + } +} diff --git a/src/com/android/settings/search/SearchIndexableResourcesImpl.java b/src/com/android/settings/search/SearchIndexableResourcesImpl.java index 3b58c64ed36..d5e4eb9983c 100644 --- a/src/com/android/settings/search/SearchIndexableResourcesImpl.java +++ b/src/com/android/settings/search/SearchIndexableResourcesImpl.java @@ -77,6 +77,7 @@ import com.android.settings.notification.SoundSettings; import com.android.settings.notification.ZenModeAutomationSettings; import com.android.settings.notification.ZenModeBehaviorSettings; import com.android.settings.notification.ZenModeBlockedEffectsSettings; +import com.android.settings.notification.ZenModeRestrictNotificationsSettings; import com.android.settings.notification.ZenModeSettings; import com.android.settings.print.PrintSettingsFragment; import com.android.settings.security.EncryptionAndCredential; @@ -180,6 +181,7 @@ public class SearchIndexableResourcesImpl implements SearchIndexableResources { addIndex(ZenModeBehaviorSettings.class); addIndex(ZenModeBlockedEffectsSettings.class); addIndex(ZenModeAutomationSettings.class); + addIndex(ZenModeRestrictNotificationsSettings.class); addIndex(NightDisplaySettings.class); addIndex(SmartBatterySettings.class); addIndex(MyDeviceInfoFragment.class); diff --git a/tests/robotests/src/com/android/settings/notification/ZenCustomRadioButtonPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/ZenCustomRadioButtonPreferenceTest.java new file mode 100644 index 00000000000..0993f33d3c2 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenCustomRadioButtonPreferenceTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.support.v7.preference.Preference.OnPreferenceChangeListener; +import android.support.v7.preference.PreferenceViewHolder; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.Switch; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.MasterSwitchPreference; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenCustomRadioButtonPreferenceTest { + + private Context mContext; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + } + + @Test + public void createNewPreference_shouldSetLayout() { + final ZenCustomRadioButtonPreference preference + = new ZenCustomRadioButtonPreference(mContext); + + assertThat(preference.getLayoutResource()).isEqualTo(R.layout.preference_two_target_radio); + assertThat(preference.getWidgetLayoutResource()) + .isEqualTo(R.layout.preference_widget_gear); + } + + @Test + public void setChecked_shouldUpdateButtonCheckedState() { + final ZenCustomRadioButtonPreference preference = + new ZenCustomRadioButtonPreference(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate( + R.layout.preference_two_target_radio, null)); + final RadioButton toggle = (RadioButton) holder.findViewById(android.R.id.checkbox); + preference.onBindViewHolder(holder); + + preference.setChecked(true); + assertThat(toggle.isChecked()).isTrue(); + + preference.setChecked(false); + assertThat(toggle.isChecked()).isFalse(); + } + + @Test + public void clickRadioButton_shouldNotifyRadioButtonClicked() { + final ZenCustomRadioButtonPreference preference + = new ZenCustomRadioButtonPreference(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate(R.layout.preference_two_target_radio, null)); + final View toggle = holder.findViewById(R.id.checkbox_frame); + + ZenCustomRadioButtonPreference.OnRadioButtonClickListener l = mock( + ZenCustomRadioButtonPreference.OnRadioButtonClickListener.class); + preference.setOnRadioButtonClickListener(l); + preference.onBindViewHolder(holder); + + toggle.performClick(); + verify(l).onRadioButtonClick(preference); + } + + @Test + public void clickWidgetView_shouldNotifyWidgetClicked() { + final ZenCustomRadioButtonPreference preference = + new ZenCustomRadioButtonPreference(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate(R.layout.preference_two_target_radio, null)); + final View widgetView = holder.findViewById(android.R.id.widget_frame); + + ZenCustomRadioButtonPreference.OnGearClickListener l = mock( + ZenCustomRadioButtonPreference.OnGearClickListener.class); + preference.setOnGearClickListener(l); + preference.onBindViewHolder(holder); + + widgetView.performClick(); + verify(l).onGearClick(preference); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenFooterPreferenceControllerTest.java new file mode 100644 index 00000000000..13f4fb1e5b0 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenFooterPreferenceControllerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.Context; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.FooterPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenFooterPreferenceControllerTest { + private ZenFooterPreferenceController mController; + + @Mock + private ZenModeBackend mBackend; + @Mock + private FooterPreference mockPref; + private Context mContext; + @Mock + private PreferenceScreen mScreen; + @Mock NotificationManager mNotificationManager; + + private static final String PREF_KEY = "main_pref"; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = shadowApplication.getApplicationContext(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + when(mNotificationManager.getNotificationPolicy()).thenReturn( + mock(NotificationManager.Policy.class)); + mController = new ZenFooterPreferenceController( + mContext, mock(Lifecycle.class), PREF_KEY); + ReflectionHelpers.setField(mController, "mBackend", mBackend); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref); + } + + @Test + public void isAvailable_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_someVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 2); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_allVisEffects() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, allSuppressed); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void updateSummary_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.updateState(mockPref); + verify(mockPref).setTitle(R.string.zen_mode_restrict_notifications_mute_footer); + } + + @Test + public void getSummary_someVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 2); + mController.updateState(mockPref); + verify(mockPref).setTitle(null); + } + + @Test + public void getSummary_allVisEffects() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, allSuppressed); + mController.updateState(mockPref); + verify(mockPref).setTitle(R.string.zen_mode_restrict_notifications_hide_footer); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java index d706d126c07..b26989d3475 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java @@ -74,29 +74,23 @@ public class ZenModeSettingsTest { @Test public void testBlockedEffectsSummary_none() { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0, 0); - assertEquals("Never", mBuilder.getBlockedEffectsSummary(policy)); + assertEquals(mContext.getString(R.string.zen_mode_restrict_notifications_summary_muted), + mBuilder.getBlockedEffectsSummary(policy)); } @Test - public void testBlockedEffectsSummary_screen_on() { + public void testBlockedEffectsSummary_some() { NotificationManager.Policy policy = new NotificationManager.Policy( 0, 0, 0, NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK); - assertEquals("When screen is on", mBuilder.getBlockedEffectsSummary(policy)); + assertEquals(mContext.getString(R.string.zen_mode_restrict_notifications_summary_custom), + mBuilder.getBlockedEffectsSummary(policy)); } @Test - public void testBlockedEffectsSummary_screen_off() { + public void testBlockedEffectsSummary_all() { NotificationManager.Policy policy = new NotificationManager.Policy( - 0, 0, 0, NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT); - assertEquals("When screen is off", mBuilder.getBlockedEffectsSummary(policy)); - } - - @Test - public void testBlockedEffectsSummary_both() { - NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0, - NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST - | NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS); - assertEquals("When screen is off, When screen is on", + 0, 0, 0, 511); + assertEquals(mContext.getString(R.string.zen_mode_restrict_notifications_summary_hidden), mBuilder.getBlockedEffectsSummary(policy)); } diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceControllerTest.java new file mode 100644 index 00000000000..30583883464 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsAllPreferenceControllerTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent + .ACTION_ZEN_SOUND_AND_VIS_EFFECTS; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.Context; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.DisabledCheckBoxPreference; +import com.android.settings.widget.RadioButtonPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenModeVisEffectsAllPreferenceControllerTest { + private ZenModeVisEffectsAllPreferenceController mController; + + @Mock + private ZenModeBackend mBackend; + @Mock + private ZenCustomRadioButtonPreference mockPref; + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + @Mock + private PreferenceScreen mScreen; + @Mock NotificationManager mNotificationManager; + + private static final String PREF_KEY = "main_pref"; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = shadowApplication.getApplicationContext(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + when(mNotificationManager.getNotificationPolicy()).thenReturn( + mock(NotificationManager.Policy.class)); + mController = new ZenModeVisEffectsAllPreferenceController( + mContext, mock(Lifecycle.class), PREF_KEY); + ReflectionHelpers.setField(mController, "mBackend", mBackend); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref); + mController.displayPreference(mScreen); + } + + @Test + public void isAvailable() { + assertTrue(mController.isAvailable()); + } + + @Test + public void updateState_notChecked() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.updateState(mockPref); + + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_checked() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, allSuppressed); + mController.updateState(mockPref); + + verify(mockPref).setChecked(true); + } + + @Test + public void onPreferenceChanged_checkedTrue() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.onRadioButtonClick(mockPref); + verify(mBackend).saveVisualEffectsPolicy(allSuppressed, true); + verify(mFeatureFactory.metricsFeatureProvider).action(eq(mContext), + eq(ACTION_ZEN_SOUND_AND_VIS_EFFECTS), + eq(true)); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java new file mode 100644 index 00000000000..56fa1c2dd1a --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsCustomPreferenceControllerTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_ZEN_CUSTOM; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent + .ACTION_ZEN_SOUND_AND_VIS_EFFECTS; + +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.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.Context; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenModeVisEffectsCustomPreferenceControllerTest { + private ZenModeVisEffectsCustomPreferenceController mController; + + @Mock + private ZenModeBackend mBackend; + @Mock + private ZenCustomRadioButtonPreference mockPref; + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + @Mock + private PreferenceScreen mScreen; + @Mock NotificationManager mNotificationManager; + + private static final String PREF_KEY = "main_pref"; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = shadowApplication.getApplicationContext(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + when(mNotificationManager.getNotificationPolicy()).thenReturn( + mock(NotificationManager.Policy.class)); + mController = new ZenModeVisEffectsCustomPreferenceController( + mContext, mock(Lifecycle.class), PREF_KEY); + ReflectionHelpers.setField(mController, "mBackend", mBackend); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref); + } + + @Test + public void isAvailable_menuOff_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.mShowMenuSelected = false; + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_menuOn_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.mShowMenuSelected = true; + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_menuOn_visEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.mShowMenuSelected = false; + assertThat(mController.isAvailable()).isTrue(); + } + + + @Test + public void updateState_notChecked_noVisEffects() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.updateState(mockPref); + + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_notChecked_allVisEffects() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, allSuppressed); + mController.updateState(mockPref); + + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_checked() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 2); + mController.updateState(mockPref); + + verify(mockPref).setChecked(true); + } + + @Test + public void updateState_listeners() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 2); + mController.updateState(mockPref); + + verify(mockPref).setOnGearClickListener(any()); + verify(mockPref).setOnRadioButtonClickListener(any()); + } + + @Test + public void select() { + int interruptiveSuppressed = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.select(); + verify(mBackend).savePolicy(anyInt(), anyInt(), anyInt(), eq(interruptiveSuppressed)); + verify(mFeatureFactory.metricsFeatureProvider).action(eq(mContext), + eq(ACTION_ZEN_CUSTOM), + eq(true)); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceControllerTest.java new file mode 100644 index 00000000000..0f99be83bbf --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ZenModeVisEffectsNonePreferenceControllerTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_ZEN_SOUND_ONLY; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.Context; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.DisabledCheckBoxPreference; +import com.android.settings.widget.RadioButtonPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ZenModeVisEffectsNonePreferenceControllerTest { + private ZenModeVisEffectsNonePreferenceController mController; + + @Mock + private ZenModeBackend mBackend; + @Mock + private ZenCustomRadioButtonPreference mockPref; + private Context mContext; + private FakeFeatureFactory mFeatureFactory; + @Mock + private PreferenceScreen mScreen; + @Mock NotificationManager mNotificationManager; + + private static final String PREF_KEY = "main_pref"; + private static final int PREF_METRICS = 1; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = shadowApplication.getApplicationContext(); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + when(mNotificationManager.getNotificationPolicy()).thenReturn( + mock(NotificationManager.Policy.class)); + mController = new ZenModeVisEffectsNonePreferenceController( + mContext, mock(Lifecycle.class), PREF_KEY); + ReflectionHelpers.setField(mController, "mBackend", mBackend); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref); + mController.displayPreference(mScreen); + } + + @Test + public void isAvailable() { + assertTrue(mController.isAvailable()); + } + + @Test + public void updateState_notChecked() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.updateState(mockPref); + + verify(mockPref).setChecked(false); + } + + @Test + public void updateState_checked() { + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 0); + mController.updateState(mockPref); + + verify(mockPref).setChecked(true); + } + + @Test + public void onRadioButtonClick() { + int allSuppressed = SUPPRESSED_EFFECT_SCREEN_OFF + | SUPPRESSED_EFFECT_SCREEN_ON + | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + | SUPPRESSED_EFFECT_AMBIENT + | SUPPRESSED_EFFECT_STATUS_BAR + | SUPPRESSED_EFFECT_BADGE + | SUPPRESSED_EFFECT_LIGHTS + | SUPPRESSED_EFFECT_PEEK + | SUPPRESSED_EFFECT_NOTIFICATION_LIST; + mBackend.mPolicy = new NotificationManager.Policy(0, 0, 0, 1); + mController.onRadioButtonClick(mockPref); + verify(mBackend).saveVisualEffectsPolicy(allSuppressed, false); + verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), + eq(ACTION_ZEN_SOUND_ONLY), + eq(true)); + } +} From b279b1c0251870a1bb385c8c93ed9ba1792a2790 Mon Sep 17 00:00:00 2001 From: Emily Chuang Date: Thu, 12 Apr 2018 10:12:28 +0800 Subject: [PATCH 8/8] Migrate ChooseAccountActivity to DashboardFragment - Build a controller to generate/manage a list of preferences. - Move some logics to the controller and add tests. - Rename to ChooseAccountFragment. Bug: 73899467 Test: make RunSettingsRoboTests -j atest UniquePreferenceTest SettingsGatewayTest Change-Id: Id2906c4b922ef159d08c803b976671264c54665f --- AndroidManifest.xml | 2 +- res/xml/add_account_settings.xml | 9 +- .../accounts/ChooseAccountFragment.java | 107 +++++++++ ...=> ChooseAccountPreferenceController.java} | 219 ++++++++---------- ...erpriseDisclosurePreferenceController.java | 79 +++++++ .../settings/accounts/ProviderEntry.java | 47 ++++ .../core/gateway/SettingsGateway.java | 4 +- .../grandfather_not_implementing_indexable | 1 - ...ChooseAccountPreferenceControllerTest.java | 205 ++++++++++++++++ ...iseDisclosurePreferenceControllerTest.java | 99 ++++++++ .../shadow/ShadowAccountManager.java | 18 +- .../shadow/ShadowRestrictedLockUtils.java | 75 ++++-- 12 files changed, 712 insertions(+), 153 deletions(-) create mode 100644 src/com/android/settings/accounts/ChooseAccountFragment.java rename src/com/android/settings/accounts/{ChooseAccountActivity.java => ChooseAccountPreferenceController.java} (54%) create mode 100644 src/com/android/settings/accounts/EnterpriseDisclosurePreferenceController.java create mode 100644 src/com/android/settings/accounts/ProviderEntry.java create mode 100644 tests/robotests/src/com/android/settings/accounts/ChooseAccountPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 69416a7223b..5a0f18192f4 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2341,7 +2341,7 @@ android:label="@string/header_add_an_account" android:configChanges="orientation|keyboardHidden|screenSize"> + android:value="com.android.settings.accounts.ChooseAccountFragment" /> - - - + diff --git a/src/com/android/settings/accounts/ChooseAccountFragment.java b/src/com/android/settings/accounts/ChooseAccountFragment.java new file mode 100644 index 00000000000..98b9ee6c5e6 --- /dev/null +++ b/src/com/android/settings/accounts/ChooseAccountFragment.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accounts; + +import android.content.Context; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.SearchIndexableResource; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.search.SearchIndexable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Activity asking a user to select an account to be set up. + */ +@SearchIndexable +public class ChooseAccountFragment extends DashboardFragment { + + private static final String TAG = "ChooseAccountFragment"; + + @Override + public int getMetricsCategory() { + return MetricsEvent.ACCOUNTS_CHOOSE_ACCOUNT_ACTIVITY; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + final String[] authorities = getIntent().getStringArrayExtra( + AccountPreferenceBase.AUTHORITIES_FILTER_KEY); + final String[] accountTypesFilter = getIntent().getStringArrayExtra( + AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY); + final UserManager userManager = UserManager.get(getContext()); + final UserHandle userHandle = Utils.getSecureTargetUser(getActivity().getActivityToken(), + userManager, null /* arguments */, getIntent().getExtras()); + + use(ChooseAccountPreferenceController.class).initialize(authorities, accountTypesFilter, + userHandle, getActivity()); + use(EnterpriseDisclosurePreferenceController.class).setFooterPreferenceMixin( + mFooterPreferenceMixin); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.add_account_settings; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected List createPreferenceControllers(Context context) { + return buildControllers(context); + } + + private static List buildControllers(Context context) { + final List controllers = new ArrayList<>(); + controllers.add(new EnterpriseDisclosurePreferenceController(context)); + return controllers; + } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex(Context context, + boolean enabled) { + final ArrayList result = new ArrayList<>(); + + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.add_account_settings; + result.add(sir); + return result; + } + + @Override + public List createPreferenceControllers( + Context context) { + return buildControllers(context); + } + }; +} diff --git a/src/com/android/settings/accounts/ChooseAccountActivity.java b/src/com/android/settings/accounts/ChooseAccountPreferenceController.java similarity index 54% rename from src/com/android/settings/accounts/ChooseAccountActivity.java rename to src/com/android/settings/accounts/ChooseAccountPreferenceController.java index 35f51afa80d..ded204bde18 100644 --- a/src/com/android/settings/accounts/ChooseAccountActivity.java +++ b/src/com/android/settings/accounts/ChooseAccountPreferenceController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,24 +30,11 @@ import android.content.SyncAdapterType; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; -import android.os.Bundle; import android.os.UserHandle; -import android.os.UserManager; -import androidx.preference.Preference; -import androidx.preference.PreferenceGroup; import android.util.Log; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.util.CharSequences; -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.Utils; -import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider; -import com.android.settings.overlay.FeatureFactory; +import com.android.settings.core.BasePreferenceController; import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; -import com.android.settingslib.widget.FooterPreference; -import com.android.settingslib.widget.FooterPreferenceMixin; import com.google.android.collect.Maps; @@ -55,89 +42,87 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; /** - * Activity asking a user to select an account to be set up. - * * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for * which the action needs to be performed is different to the one the Settings App will run in. */ -public class ChooseAccountActivity extends SettingsPreferenceFragment { +public class ChooseAccountPreferenceController extends BasePreferenceController { - private static final String TAG = "ChooseAccountActivity"; + private static final String TAG = "ChooseAccountPrefCtrler"; - private EnterprisePrivacyFeatureProvider mFeatureProvider; - private FooterPreference mEnterpriseDisclosurePreference = null; + private final List mProviderList; + private final Map mTypeToAuthDescription; private String[] mAuthorities; - private PreferenceGroup mAddAccountGroup; - private final ArrayList mProviderList = new ArrayList(); - public HashSet mAccountTypesFilter; + private Set mAccountTypesFilter; private AuthenticatorDescription[] mAuthDescs; - private HashMap> mAccountTypeToAuthorities = null; - private Map mTypeToAuthDescription - = new HashMap(); + private Map> mAccountTypeToAuthorities; // The UserHandle of the user we are choosing an account for private UserHandle mUserHandle; - private UserManager mUm; + private Activity mActivity; + private PreferenceScreen mScreen; - private static class ProviderEntry implements Comparable { - private final CharSequence name; - private final String type; - ProviderEntry(CharSequence providerName, String accountType) { - name = providerName; - type = accountType; - } + public ChooseAccountPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); - public int compareTo(ProviderEntry another) { - if (name == null) { - return -1; - } - if (another.name == null) { - return +1; - } - return CharSequences.compareToIgnoreCase(name, another.name); - } + mProviderList = new ArrayList<>(); + mTypeToAuthDescription = new HashMap<>(); } - @Override - public int getMetricsCategory() { - return MetricsEvent.ACCOUNTS_CHOOSE_ACCOUNT_ACTIVITY; - } + public void initialize(String[] authorities, String[] accountTypesFilter, UserHandle userHandle, + Activity activity) { + mActivity = activity; + mAuthorities = authorities; + mUserHandle = userHandle; - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - final Activity activity = getActivity(); - mFeatureProvider = FeatureFactory.getFactory(activity) - .getEnterprisePrivacyFeatureProvider(activity); - - addPreferencesFromResource(R.xml.add_account_settings); - mAuthorities = getIntent().getStringArrayExtra( - AccountPreferenceBase.AUTHORITIES_FILTER_KEY); - String[] accountTypesFilter = getIntent().getStringArrayExtra( - AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY); if (accountTypesFilter != null) { - mAccountTypesFilter = new HashSet(); + mAccountTypesFilter = new HashSet<>(); for (String accountType : accountTypesFilter) { mAccountTypesFilter.add(accountType); } } - mAddAccountGroup = getPreferenceScreen(); - mUm = UserManager.get(getContext()); - mUserHandle = Utils.getSecureTargetUser(getActivity().getActivityToken(), mUm, - null /* arguments */, getIntent().getExtras()); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mScreen = screen; updateAuthDescriptions(); } + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!(preference instanceof ProviderPreference)) { + return false; + } + + ProviderPreference pref = (ProviderPreference) preference; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Attempting to add account of type " + pref.getAccountType()); + } + finishWithAccountType(pref.getAccountType()); + return true; + } + /** * Updates provider icons. Subclasses should call this in onCreate() * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated(). */ private void updateAuthDescriptions() { - mAuthDescs = AccountManager.get(getContext()).getAuthenticatorTypesAsUser( + mAuthDescs = AccountManager.get(mContext).getAuthenticatorTypesAsUser( mUserHandle.getIdentifier()); for (int i = 0; i < mAuthDescs.length; i++) { mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); @@ -148,12 +133,12 @@ public class ChooseAccountActivity extends SettingsPreferenceFragment { private void onAuthDescriptionsUpdated() { // Create list of providers to show on preference screen for (int i = 0; i < mAuthDescs.length; i++) { - String accountType = mAuthDescs[i].type; - CharSequence providerName = getLabelForType(accountType); + final String accountType = mAuthDescs[i].type; + final CharSequence providerName = getLabelForType(accountType); // Skip preferences for authorities not specified. If no authorities specified, // then include them all. - ArrayList accountAuths = getAuthoritiesForAccountType(accountType); + final List accountAuths = getAuthoritiesForAccountType(accountType); boolean addAccountPref = true; if (mAuthorities != null && mAuthorities.length > 0 && accountAuths != null) { addAccountPref = false; @@ -169,38 +154,39 @@ public class ChooseAccountActivity extends SettingsPreferenceFragment { addAccountPref = false; } if (addAccountPref) { - mProviderList.add(new ProviderEntry(providerName, accountType)); + mProviderList.add( + new ProviderEntry(providerName, accountType)); } else { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Skipped pref " + providerName + ": has no authority we need"); } } } - - final Context context = getPreferenceScreen().getContext(); + final Context context = mScreen.getContext(); if (mProviderList.size() == 1) { // There's only one provider that matches. If it is disabled by admin show the // support dialog otherwise run it. - EnforcedAdmin admin = RestrictedLockUtils.checkIfAccountManagementDisabled( - context, mProviderList.get(0).type, mUserHandle.getIdentifier()); + final RestrictedLockUtils.EnforcedAdmin admin = + RestrictedLockUtils.checkIfAccountManagementDisabled( + context, mProviderList.get(0).getType(), mUserHandle.getIdentifier()); if (admin != null) { - setResult(RESULT_CANCELED, RestrictedLockUtils.getShowAdminSupportDetailsIntent( - context, admin)); - finish(); + mActivity.setResult(RESULT_CANCELED, + RestrictedLockUtils.getShowAdminSupportDetailsIntent( + context, admin)); + mActivity.finish(); } else { - finishWithAccountType(mProviderList.get(0).type); + finishWithAccountType(mProviderList.get(0).getType()); } } else if (mProviderList.size() > 0) { Collections.sort(mProviderList); - mAddAccountGroup.removeAll(); for (ProviderEntry pref : mProviderList) { - Drawable drawable = getDrawableForType(pref.type); - ProviderPreference p = new ProviderPreference(getPreferenceScreen().getContext(), - pref.type, drawable, pref.name); + final Drawable drawable = getDrawableForType(pref.getType()); + final ProviderPreference p = new ProviderPreference(context, + pref.getType(), drawable, pref.getName()); + p.setKey(pref.getType().toString()); p.checkAccountManagementAndSetDisabled(mUserHandle.getIdentifier()); - mAddAccountGroup.addPreference(p); + mScreen.addPreference(p); } - addEnterpriseDisclosure(); } else { if (Log.isLoggable(TAG, Log.VERBOSE)) { final StringBuilder auths = new StringBuilder(); @@ -210,38 +196,25 @@ public class ChooseAccountActivity extends SettingsPreferenceFragment { } Log.v(TAG, "No providers found for authorities: " + auths); } - setResult(RESULT_CANCELED); - finish(); + mActivity.setResult(RESULT_CANCELED); + mActivity.finish(); } } - private void addEnterpriseDisclosure() { - final CharSequence disclosure = mFeatureProvider.getDeviceOwnerDisclosure(); - if (disclosure == null) { - return; - } - if (mEnterpriseDisclosurePreference == null) { - mEnterpriseDisclosurePreference = mFooterPreferenceMixin.createFooterPreference(); - mEnterpriseDisclosurePreference.setSelectable(false); - } - mEnterpriseDisclosurePreference.setTitle(disclosure); - mAddAccountGroup.addPreference(mEnterpriseDisclosurePreference); - } - - public ArrayList getAuthoritiesForAccountType(String type) { + private List getAuthoritiesForAccountType(String type) { if (mAccountTypeToAuthorities == null) { mAccountTypeToAuthorities = Maps.newHashMap(); - SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( + final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( mUserHandle.getIdentifier()); for (int i = 0, n = syncAdapters.length; i < n; i++) { final SyncAdapterType sa = syncAdapters[i]; - ArrayList authorities = mAccountTypeToAuthorities.get(sa.accountType); + List authorities = mAccountTypeToAuthorities.get(sa.accountType); if (authorities == null) { - authorities = new ArrayList(); + authorities = new ArrayList<>(); mAccountTypeToAuthorities.put(sa.accountType, authorities); } if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.d(TAG, "added authority " + sa.authority + " to accountType " + Log.v(TAG, "added authority " + sa.authority + " to accountType " + sa.accountType); } authorities.add(sa.authority); @@ -252,18 +225,20 @@ public class ChooseAccountActivity extends SettingsPreferenceFragment { /** * Gets an icon associated with a particular account type. If none found, return null. + * * @param accountType the type of account * @return a drawable for the icon or a default icon returned by * {@link PackageManager#getDefaultActivityIcon} if one cannot be found. */ - protected Drawable getDrawableForType(final String accountType) { + @VisibleForTesting + Drawable getDrawableForType(final String accountType) { Drawable icon = null; if (mTypeToAuthDescription.containsKey(accountType)) { try { - AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); - Context authContext = getActivity() + final AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + final Context authContext = mActivity .createPackageContextAsUser(desc.packageName, 0, mUserHandle); - icon = getPackageManager().getUserBadgedIcon( + icon = mContext.getPackageManager().getUserBadgedIcon( authContext.getDrawable(desc.iconId), mUserHandle); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "No icon name for account type " + accountType); @@ -274,21 +249,23 @@ public class ChooseAccountActivity extends SettingsPreferenceFragment { if (icon != null) { return icon; } else { - return getPackageManager().getDefaultActivityIcon(); + return mContext.getPackageManager().getDefaultActivityIcon(); } } /** * Gets the label associated with a particular account type. If none found, return null. + * * @param accountType the type of account * @return a CharSequence for the label or null if one cannot be found. */ - protected CharSequence getLabelForType(final String accountType) { + @VisibleForTesting + CharSequence getLabelForType(final String accountType) { CharSequence label = null; if (mTypeToAuthDescription.containsKey(accountType)) { try { - AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); - Context authContext = getActivity() + final AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + final Context authContext = mActivity .createPackageContextAsUser(desc.packageName, 0, mUserHandle); label = authContext.getResources().getText(desc.labelId); } catch (PackageManager.NameNotFoundException e) { @@ -300,23 +277,11 @@ public class ChooseAccountActivity extends SettingsPreferenceFragment { return label; } - @Override - public boolean onPreferenceTreeClick(Preference preference) { - if (preference instanceof ProviderPreference) { - ProviderPreference pref = (ProviderPreference) preference; - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Attempting to add account of type " + pref.getAccountType()); - } - finishWithAccountType(pref.getAccountType()); - } - return true; - } - private void finishWithAccountType(String accountType) { Intent intent = new Intent(); intent.putExtra(AddAccountSettings.EXTRA_SELECTED_ACCOUNT, accountType); intent.putExtra(EXTRA_USER, mUserHandle); - setResult(RESULT_OK, intent); - finish(); + mActivity.setResult(RESULT_OK, intent); + mActivity.finish(); } } diff --git a/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceController.java b/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceController.java new file mode 100644 index 00000000000..e8a444fdfb5 --- /dev/null +++ b/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceController.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accounts; + +import android.content.Context; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.widget.FooterPreference; +import com.android.settingslib.widget.FooterPreferenceMixin; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +public class EnterpriseDisclosurePreferenceController extends BasePreferenceController { + + private final EnterprisePrivacyFeatureProvider mFeatureProvider; + private FooterPreferenceMixin mFooterPreferenceMixin; + private PreferenceScreen mScreen; + + public EnterpriseDisclosurePreferenceController(Context context) { + // Preference key doesn't matter as we are creating the preference in code. + super(context, "add_account_enterprise_disclosure_footer"); + + mFeatureProvider = FeatureFactory.getFactory(mContext) + .getEnterprisePrivacyFeatureProvider(mContext); + } + + public void setFooterPreferenceMixin(FooterPreferenceMixin footerPreferenceMixin) { + mFooterPreferenceMixin = footerPreferenceMixin; + } + + @Override + public int getAvailabilityStatus() { + if (getDisclosure() == null) { + return DISABLED_UNSUPPORTED; + } + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mScreen = screen; + addEnterpriseDisclosure(); + } + + @VisibleForTesting + CharSequence getDisclosure() { + return mFeatureProvider.getDeviceOwnerDisclosure(); + } + + private void addEnterpriseDisclosure() { + final CharSequence disclosure = getDisclosure(); + if (disclosure == null) { + return; + } + final FooterPreference enterpriseDisclosurePreference = + mFooterPreferenceMixin.createFooterPreference(); + enterpriseDisclosurePreference.setSelectable(false); + enterpriseDisclosurePreference.setTitle(disclosure); + mScreen.addPreference(enterpriseDisclosurePreference); + } +} diff --git a/src/com/android/settings/accounts/ProviderEntry.java b/src/com/android/settings/accounts/ProviderEntry.java new file mode 100644 index 00000000000..cf55e1423fc --- /dev/null +++ b/src/com/android/settings/accounts/ProviderEntry.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accounts; + +import com.android.internal.util.CharSequences; + +public class ProviderEntry implements Comparable { + private final CharSequence name; + private final String type; + + ProviderEntry(CharSequence providerName, String accountType) { + name = providerName; + type = accountType; + } + + public int compareTo(ProviderEntry another) { + if (name == null) { + return -1; + } + if (another.name == null) { + return +1; + } + return CharSequences.compareToIgnoreCase(name, another.name); + } + + public CharSequence getName() { + return name; + } + + public String getType() { + return type; + } +} \ No newline at end of file diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index be9b722a5ac..ced158eb3e0 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -31,7 +31,7 @@ import com.android.settings.accessibility.CaptionPropertiesFragment; import com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment; import com.android.settings.accounts.AccountDashboardFragment; import com.android.settings.accounts.AccountSyncSettings; -import com.android.settings.accounts.ChooseAccountActivity; +import com.android.settings.accounts.ChooseAccountFragment; import com.android.settings.accounts.ManagedProfileSettings; import com.android.settings.applications.AppAndNotificationDashboardFragment; import com.android.settings.applications.DefaultAppSettings; @@ -236,7 +236,7 @@ public class SettingsGateway { PictureInPictureSettings.class.getName(), PictureInPictureDetails.class.getName(), ManagedProfileSettings.class.getName(), - ChooseAccountActivity.class.getName(), + ChooseAccountFragment.class.getName(), IccLockSettings.class.getName(), TestingSettings.class.getName(), WifiAPITest.class.getName(), diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index 8523b2f1ab9..57b75e1eb8b 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -17,7 +17,6 @@ com.android.settings.applications.ManageDomainUrls com.android.settings.applications.appinfo.WriteSettingsDetails com.android.settings.applications.ProcessStatsSummary com.android.settings.users.RestrictedProfileSettings -com.android.settings.accounts.ChooseAccountActivity com.android.settings.accessibility.ToggleAutoclickPreferenceFragment com.android.settings.applications.AppLaunchSettings com.android.settings.applications.ProcessStatsUi diff --git a/tests/robotests/src/com/android/settings/accounts/ChooseAccountPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/ChooseAccountPreferenceControllerTest.java new file mode 100644 index 00000000000..67e01f8817f --- /dev/null +++ b/tests/robotests/src/com/android/settings/accounts/ChooseAccountPreferenceControllerTest.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accounts; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.accounts.AuthenticatorDescription; +import android.app.Activity; +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.SyncAdapterType; +import android.graphics.drawable.ColorDrawable; +import android.os.UserHandle; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowAccountManager; +import com.android.settings.testutils.shadow.ShadowContentResolver; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class, + ShadowRestrictedLockUtils.class}) +public class ChooseAccountPreferenceControllerTest { + + private Context mContext; + private ChooseAccountPreferenceController mController; + private Activity mActivity; + private PreferenceManager mPreferenceManager; + private PreferenceScreen mPreferenceScreen; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = spy(new ChooseAccountPreferenceController(mContext, "controller_key")); + mActivity = Robolectric.setupActivity(Activity.class); + mPreferenceManager = new PreferenceManager(mContext); + mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext); + } + + @After + public void tearDown() { + ShadowContentResolver.reset(); + ShadowAccountManager.resetAuthenticator(); + ShadowRestrictedLockUtils.clearDisabledTypes(); + } + + @Test + public void getAvailabilityStatus_byDefault_shouldBeShown() { + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void handlePreferenceTreeClick_notProviderPreference_shouldReturnFalse() { + final Preference preference = new Preference(mContext); + assertThat(mController.handlePreferenceTreeClick(preference)).isFalse(); + } + + @Test + public void handlePreferenceTreeClick_isProviderPreference_shouldFinishFragment() { + final ProviderPreference providerPreference = new ProviderPreference(mContext, + "account_type", null /* icon */, "provider_name"); + + mController.initialize(null, null, null, mActivity); + mController.handlePreferenceTreeClick(providerPreference); + + assertThat(mActivity.isFinishing()).isTrue(); + } + + @Test + public void updateAuthDescriptions_oneProvider_shouldNotAddPreference() { + final AuthenticatorDescription authDesc = new AuthenticatorDescription("com.acct1", + "com.android.settings", + R.string.header_add_an_account, 0, 0, 0, false); + ShadowAccountManager.addAuthenticator(authDesc); + + final SyncAdapterType[] syncAdapters = {new SyncAdapterType("authority" /* authority */, + "com.acct1" /* accountType */, false /* userVisible */, + true /* supportsUploading */)}; + ShadowContentResolver.setSyncAdapterTypes(syncAdapters); + + ShadowRestrictedLockUtils.setHasSystemFeature(true); + ShadowRestrictedLockUtils.setDevicePolicyManager(mock(DevicePolicyManager.class)); + ShadowRestrictedLockUtils.setDisabledTypes(new String[] {"test_type"}); + + doReturn("label").when(mController).getLabelForType(anyString()); + + mController.initialize(null, null, new UserHandle(3), mActivity); + mController.displayPreference(mPreferenceScreen); + + assertThat(mActivity.isFinishing()).isTrue(); + assertThat(mPreferenceScreen.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void updateAuthDescriptions_oneAdminDisabledProvider_shouldNotAddPreference() { + final AuthenticatorDescription authDesc = new AuthenticatorDescription("com.acct1", + "com.android.settings", + R.string.header_add_an_account, 0, 0, 0, false); + ShadowAccountManager.addAuthenticator(authDesc); + + final SyncAdapterType[] syncAdapters = {new SyncAdapterType("authority" /* authority */, + "com.acct1" /* accountType */, false /* userVisible */, + true /* supportsUploading */)}; + ShadowContentResolver.setSyncAdapterTypes(syncAdapters); + + ShadowRestrictedLockUtils.setHasSystemFeature(true); + ShadowRestrictedLockUtils.setDevicePolicyManager(mock(DevicePolicyManager.class)); + ShadowRestrictedLockUtils.setDisabledTypes(new String[] {"com.acct1"}); + + doReturn("label").when(mController).getLabelForType(anyString()); + + mController.initialize(null, null, new UserHandle(3), mActivity); + mController.displayPreference(mPreferenceScreen); + + assertThat(mActivity.isFinishing()).isTrue(); + assertThat(mPreferenceScreen.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void updateAuthDescriptions_noProvider_shouldNotAddPreference() { + final AuthenticatorDescription authDesc = new AuthenticatorDescription("com.acct1", + "com.android.settings", + R.string.header_add_an_account, 0, 0, 0, false); + ShadowAccountManager.addAuthenticator(authDesc); + + final SyncAdapterType[] syncAdapters = {new SyncAdapterType("authority" /* authority */, + "com.acct1" /* accountType */, false /* userVisible */, + true /* supportsUploading */)}; + ShadowContentResolver.setSyncAdapterTypes(syncAdapters); + + doReturn("label").when(mController).getLabelForType(anyString()); + + mController.initialize(new String[] {"test_authoritiy1"}, null, new UserHandle(3), + mActivity); + mController.displayPreference(mPreferenceScreen); + + assertThat(mActivity.isFinishing()).isTrue(); + assertThat(mPreferenceScreen.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void + updateAuthDescriptions_twoProvider_shouldAddTwoPreference() { + final AuthenticatorDescription authDesc = new AuthenticatorDescription("com.acct1", + "com.android.settings", + R.string.header_add_an_account, 0, 0, 0, false); + final AuthenticatorDescription authDesc2 = new AuthenticatorDescription("com.acct2", + "com.android.settings", + R.string.header_add_an_account, 0, 0, 0, false); + ShadowAccountManager.addAuthenticator(authDesc); + ShadowAccountManager.addAuthenticator(authDesc2); + + final SyncAdapterType[] syncAdapters = {new SyncAdapterType("authority" /* authority */, + "com.acct1" /* accountType */, false /* userVisible */, + true /* supportsUploading */), new SyncAdapterType("authority2" /* authority */, + "com.acct2" /* accountType */, false /* userVisible */, + true /* supportsUploading */)}; + ShadowContentResolver.setSyncAdapterTypes(syncAdapters); + + doReturn("label").when(mController).getLabelForType(anyString()); + doReturn("label2").when(mController).getLabelForType(anyString()); + doReturn(new ColorDrawable()).when(mController).getDrawableForType(anyString()); + doReturn(new ColorDrawable()).when(mController).getDrawableForType(anyString()); + + mController.initialize(null, null, new UserHandle(3), mActivity); + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreferenceScreen.getPreferenceCount()).isEqualTo(2); + } +} diff --git a/tests/robotests/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceControllerTest.java new file mode 100644 index 00000000000..4ad0982d6af --- /dev/null +++ b/tests/robotests/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceControllerTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accounts; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.widget.FooterPreferenceMixin; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + +@RunWith(SettingsRobolectricTestRunner.class) +public class EnterpriseDisclosurePreferenceControllerTest { + + private static final String TEST_DISCLOSURE = "This is a test disclosure."; + + private ChooseAccountFragment mFragment; + private Context mContext; + private EnterpriseDisclosurePreferenceController mController; + private FooterPreferenceMixin mFooterPreferenceMixin; + private PreferenceManager mPreferenceManager; + private PreferenceScreen mPreferenceScreen; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = spy(new EnterpriseDisclosurePreferenceController(mContext)); + mFragment = spy(new ChooseAccountFragment()); + mFooterPreferenceMixin = new FooterPreferenceMixin(mFragment, mFragment.getLifecycle()); + mPreferenceManager = new PreferenceManager(mContext); + mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext); + } + + @Test + public void getAvailabilityStatus_hasDisclosure_shouldBeAvailable() { + doReturn(TEST_DISCLOSURE).when(mController).getDisclosure(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void getAvailabilityStatus_noDisclosure_shouldBeDisabled() { + doReturn(null).when(mController).getDisclosure(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.DISABLED_UNSUPPORTED); + } + + @Test + public void displayPreference_hasDisclosure_shouldSetTitle() { + doReturn(TEST_DISCLOSURE).when(mController).getDisclosure(); + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); + doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); + + mController.setFooterPreferenceMixin(mFooterPreferenceMixin); + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreferenceScreen.getPreferenceCount()).isEqualTo(1); + assertThat(mPreferenceScreen.getPreference(0).getTitle()).isEqualTo(TEST_DISCLOSURE); + } + + @Test + public void displayPreference_noDisclosure_shouldBeInvisible() { + doReturn(null).when(mController).getDisclosure(); + + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreferenceScreen.getPreferenceCount()).isEqualTo(0); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java index 2bccccc74d3..03aabb5dbb5 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java @@ -22,10 +22,24 @@ import android.accounts.AuthenticatorDescription; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import java.util.HashMap; +import java.util.Map; + @Implements(AccountManager.class) -public class ShadowAccountManager { +public class ShadowAccountManager{ + + private static final Map sAuthenticators = new HashMap<>(); + @Implementation public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) { - return null; + return sAuthenticators.values().toArray(new AuthenticatorDescription[sAuthenticators.size()]); + } + + public static void addAuthenticator(AuthenticatorDescription authenticator) { + sAuthenticators.put(authenticator.type, authenticator); + } + + public static void resetAuthenticator() { + sAuthenticators.clear(); } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtils.java index c53f7712bf5..30e6401f53a 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtils.java @@ -16,6 +16,7 @@ package com.android.settings.testutils.shadow; import android.annotation.UserIdInt; +import android.app.admin.DevicePolicyManager; import android.content.Context; import com.android.internal.util.ArrayUtils; @@ -28,26 +29,31 @@ import org.robolectric.annotation.Resetter; @Implements(RestrictedLockUtils.class) public class ShadowRestrictedLockUtils { - private static boolean isRestricted; - private static String[] restrictedPkgs; - private static boolean adminSupportDetailsIntentLaunched; - private static int keyguardDisabledFeatures; + + private static boolean sIsRestricted; + private static boolean sAdminSupportDetailsIntentLaunched; + private static boolean sHasSystemFeature; + private static String[] sRestrictedPkgs; + private static DevicePolicyManager sDevicePolicyManager; + private static String[] sDisabledTypes; + private static int sKeyguardDisabledFeatures; @Resetter public static void reset() { - isRestricted = false; - restrictedPkgs = null; - adminSupportDetailsIntentLaunched = false; - keyguardDisabledFeatures = 0; + sIsRestricted = false; + sRestrictedPkgs = null; + sAdminSupportDetailsIntentLaunched = false; + sKeyguardDisabledFeatures = 0; + sDisabledTypes = new String[0]; } @Implementation public static EnforcedAdmin checkIfMeteredDataRestricted(Context context, String packageName, int userId) { - if (isRestricted) { + if (sIsRestricted) { return new EnforcedAdmin(); } - if (ArrayUtils.contains(restrictedPkgs, packageName)) { + if (ArrayUtils.contains(sRestrictedPkgs, packageName)) { return new EnforcedAdmin(); } return null; @@ -55,32 +61,67 @@ public class ShadowRestrictedLockUtils { @Implementation public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) { - adminSupportDetailsIntentLaunched = true; + sAdminSupportDetailsIntentLaunched = true; + } + + @Implementation + public static EnforcedAdmin checkIfAccountManagementDisabled(Context context, + String accountType, int userId) { + if (accountType == null) { + return null; + } + if (!sHasSystemFeature || sDevicePolicyManager == null) { + return null; + } + boolean isAccountTypeDisabled = false; + if (ArrayUtils.contains(sDisabledTypes, accountType)) { + isAccountTypeDisabled = true; + } + if (!isAccountTypeDisabled) { + return null; + } + return new EnforcedAdmin(); } @Implementation public static EnforcedAdmin checkIfKeyguardFeaturesDisabled(Context context, int features, final @UserIdInt int userId) { - return (keyguardDisabledFeatures & features) == 0 ? null : new EnforcedAdmin(); + return (sKeyguardDisabledFeatures & features) == 0 ? null : new EnforcedAdmin(); } public static boolean hasAdminSupportDetailsIntentLaunched() { - return adminSupportDetailsIntentLaunched; + return sAdminSupportDetailsIntentLaunched; } public static void clearAdminSupportDetailsIntentLaunch() { - adminSupportDetailsIntentLaunched = false; + sAdminSupportDetailsIntentLaunched = false; } public static void setRestricted(boolean restricted) { - isRestricted = restricted; + sIsRestricted = restricted; } public static void setRestrictedPkgs(String... pkgs) { - restrictedPkgs = pkgs; + sRestrictedPkgs = pkgs; + } + + public static void setHasSystemFeature(boolean hasSystemFeature) { + sHasSystemFeature = hasSystemFeature; + } + + public static void setDevicePolicyManager(DevicePolicyManager dpm) { + sDevicePolicyManager = dpm; + } + + public static void setDisabledTypes(String[] disabledTypes) { + sDisabledTypes = disabledTypes; + } + + public static void clearDisabledTypes() { + sDisabledTypes = new String[0]; } public static void setKeyguardDisabledFeatures(int features) { - keyguardDisabledFeatures = features; + sKeyguardDisabledFeatures = features; } }