diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml index 7551905f7bc..d2f573c56cf 100644 --- a/res/xml/modes_rule_settings.xml +++ b/res/xml/modes_rule_settings.xml @@ -51,8 +51,7 @@ + android:icon="@drawable/ic_add_24dp" /> { + ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener); + return true; + }); + } + + @VisibleForTesting + final ZenModeScheduleChooserDialog.OnScheduleOptionListener mOnScheduleOptionListener = + conditionId -> saveMode(mode -> { + mode.setCustomModeConditionId(mContext, conditionId); + return mode; + // TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen? + }); +} diff --git a/src/com/android/settings/notification/modes/ZenModeTriggerCategoryPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerCategoryPreferenceController.java new file mode 100644 index 00000000000..5fc3fda80ca --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModeTriggerCategoryPreferenceController.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.modes; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.preference.Preference; + +import com.android.settingslib.notification.modes.ZenMode; + +/** + * Preference controller for the "Turn on automatically" category + */ +class ZenModeTriggerCategoryPreferenceController extends AbstractZenModePreferenceController { + + ZenModeTriggerCategoryPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public boolean isAvailable(@NonNull ZenMode zenMode) { + return !zenMode.isManualDnd(); + } + + @Override + public void updateState(Preference preference, @NonNull ZenMode zenMode) { + // Nothing to update here (except visibility via isAvailable()). + } +} diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java similarity index 74% rename from src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java rename to src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java index 24df931a43b..043a38c1cf8 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java @@ -22,8 +22,6 @@ import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId; -import static com.google.common.base.Preconditions.checkNotNull; - import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.Context; @@ -39,46 +37,36 @@ import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenModesBackend; import com.google.common.base.Strings; -/** - * Preference controller for the link to an individual mode's configuration page. - */ -class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenceController { - private static final String TAG = "ZenModeSetTriggerLink"; +class ZenModeTriggerUpdatePreferenceController extends AbstractZenModePreferenceController { - @VisibleForTesting - static final String AUTOMATIC_TRIGGER_KEY = "zen_automatic_trigger_settings"; - static final String ADD_TRIGGER_KEY = "zen_add_automatic_trigger"; + private static final String TAG = "ZenModeTriggerUpdate"; - private final DashboardFragment mFragment; private final PackageManager mPackageManager; private final ConfigurationActivityHelper mConfigurationActivityHelper; private final ZenServiceListing mServiceListing; - ZenModeSetTriggerLinkPreferenceController(Context context, String key, - DashboardFragment fragment, ZenModesBackend backend) { - this(context, key, fragment, backend, context.getPackageManager(), + ZenModeTriggerUpdatePreferenceController(Context context, String key, + ZenModesBackend backend) { + this(context, key, backend, context.getPackageManager(), new ConfigurationActivityHelper(context.getPackageManager()), new ZenServiceListing(context)); } @VisibleForTesting - ZenModeSetTriggerLinkPreferenceController(Context context, String key, - DashboardFragment fragment, ZenModesBackend backend, PackageManager packageManager, + ZenModeTriggerUpdatePreferenceController(Context context, String key, + ZenModesBackend backend, PackageManager packageManager, ConfigurationActivityHelper configurationActivityHelper, ZenServiceListing serviceListing) { super(context, key, backend); - mFragment = fragment; mPackageManager = packageManager; mConfigurationActivityHelper = configurationActivityHelper; mServiceListing = serviceListing; @@ -86,7 +74,7 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc @Override public boolean isAvailable(@NonNull ZenMode zenMode) { - return !zenMode.isManualDnd(); + return !zenMode.isCustomManual() && !zenMode.isManualDnd(); } @Override @@ -97,39 +85,18 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc } @Override - public void updateState(Preference preference, @NonNull ZenMode zenMode) { - // This controller is expected to govern a preference category so that it controls the - // availability of the entire preference category if the mode doesn't have a way to - // automatically trigger (such as manual DND). - if (zenMode.isManualDnd()) { + void updateState(Preference preference, @NonNull ZenMode zenMode) { + if (!isAvailable(zenMode)) { return; } - PrimarySwitchPreference triggerPref = checkNotNull( - ((PreferenceCategory) preference).findPreference(AUTOMATIC_TRIGGER_KEY)); - Preference addTriggerPref = checkNotNull( - ((PreferenceCategory) preference).findPreference(ADD_TRIGGER_KEY)); - boolean isAddTrigger = zenMode.isSystemOwned() && zenMode.getType() != TYPE_SCHEDULE_TIME - && zenMode.getType() != TYPE_SCHEDULE_CALENDAR; - - if (isAddTrigger) { - triggerPref.setVisible(false); - addTriggerPref.setVisible(true); - addTriggerPref.setOnPreferenceClickListener(unused -> { - ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener); - return true; - }); + PrimarySwitchPreference triggerPref = (PrimarySwitchPreference) preference; + triggerPref.setChecked(zenMode.getRule().isEnabled()); + triggerPref.setOnPreferenceChangeListener(mSwitchChangeListener); + if (zenMode.isSystemOwned()) { + setUpForSystemOwnedTrigger(triggerPref, zenMode); } else { - addTriggerPref.setVisible(false); - triggerPref.setVisible(true); - triggerPref.setChecked(zenMode.getRule().isEnabled()); - triggerPref.setOnPreferenceChangeListener(mSwitchChangeListener); - - if (zenMode.isSystemOwned()) { - setUpForSystemOwnedTrigger(triggerPref, zenMode); - } else { - setUpForAppTrigger(triggerPref, zenMode); - } + setUpForAppTrigger(triggerPref, zenMode); } } @@ -223,14 +190,6 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc preference.setIntent(configurationIntent); } - @VisibleForTesting - final ZenModeScheduleChooserDialog.OnScheduleOptionListener mOnScheduleOptionListener = - conditionId -> saveMode(mode -> { - mode.setCustomModeConditionId(mContext, conditionId); - return mode; - // TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen? - }); - private final Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> { confirmChangeEnabled(p, (boolean) newValue); return true; diff --git a/tests/robotests/src/com/android/settings/notification/modes/CharSequenceTruth.java b/tests/robotests/src/com/android/settings/notification/modes/CharSequenceTruth.java new file mode 100644 index 00000000000..94b932fc2dd --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/modes/CharSequenceTruth.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.modes; + +import androidx.annotation.Nullable; + +import com.google.common.truth.StringSubject; +import com.google.common.truth.Truth; + +class CharSequenceTruth { + /** + * Shortcut version of {@link Truth#assertThat(String)} suitable for {@link CharSequence}. + * {@link CharSequence} doesn't necessarily provide a good {@code equals()} implementation; + * however we don't care about formatting in most cases, and we want to assert on the resulting + * string (without needing to worry that {@code assertThat(x.getText().toString())} can + * throw if the text is null). + */ + static StringSubject assertThat(@Nullable CharSequence actual) { + return Truth.assertThat((String) (actual != null ? actual.toString() : null)); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceControllerTest.java new file mode 100644 index 00000000000..a56e7230bde --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceControllerTest.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.modes; + +import static android.app.AutomaticZenRule.TYPE_OTHER; +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; +import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; + +import static com.android.settings.notification.modes.CharSequenceTruth.assertThat; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; + +import android.app.AutomaticZenRule; +import android.app.Flags; +import android.content.Context; +import android.net.Uri; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.SystemZenRules; +import android.service.notification.ZenModeConfig; + +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.notification.modes.TestModeBuilder; +import com.android.settingslib.notification.modes.ZenMode; +import com.android.settingslib.notification.modes.ZenModesBackend; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +import java.util.Calendar; + +@RunWith(RobolectricTestRunner.class) +@EnableFlags(Flags.FLAG_MODES_UI) +public class ZenModeTriggerAddPreferenceControllerTest { + + private static final ZenMode CUSTOM_MANUAL_MODE = new TestModeBuilder() + .setConditionId(ZenModeConfig.toCustomManualConditionId()) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_OTHER) + .setTriggerDescription("Will not be shown") + .build(); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + + private ZenModeTriggerAddPreferenceController mController; + + private Context mContext; + private Preference mPreference; + @Mock private ZenModesBackend mBackend; + @Mock private DashboardFragment mFragment; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + + PreferenceManager preferenceManager = new PreferenceManager(mContext); + PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(mContext, + R.xml.modes_rule_settings, null); + + mController = new ZenModeTriggerAddPreferenceController(mContext, + "zen_add_automatic_trigger", mFragment, mBackend); + mPreference = preferenceScreen.findPreference("zen_add_automatic_trigger"); + } + + @Test + public void isAvailable_customManualMode_true() { + mController.setZenMode(CUSTOM_MANUAL_MODE); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_systemMode_false() { + ZenMode mode = new TestModeBuilder() + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_SCHEDULE_CALENDAR) + .build(); + mController.setZenMode(mode); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_appProvidedMode_false() { + ZenMode mode = new TestModeBuilder() + .setPackage("com.some.package") + .setType(TYPE_OTHER) + .build(); + mController.setZenMode(mode); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_manualDND_false() { + ZenMode manualMode = ZenMode.manualDndMode(new AutomaticZenRule.Builder("Do Not Disturb", + Uri.parse("manual")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .build(), /* isActive= */ false); + + mController.setZenMode(manualMode); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void updateState_customManualRule() { + ZenMode mode = new TestModeBuilder() + .setConditionId(ZenModeConfig.toCustomManualConditionId()) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_OTHER) + .setTriggerDescription("Will not be shown") + .build(); + + mController.updateState(mPreference, mode); + + assertThat(mPreference.getTitle()).isEqualTo( + mContext.getString(R.string.zen_mode_select_schedule)); + assertThat(mPreference.getSummary()).isNull(); + // Sets up a click listener to open the dialog. + assertThat(mPreference.getOnPreferenceClickListener()).isNotNull(); + } + + @Test + public void onScheduleChosen_updatesMode() { + ZenMode originalMode = new TestModeBuilder() + .setConditionId(ZenModeConfig.toCustomManualConditionId()) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_OTHER) + .setTriggerDescription("") + .build(); + mController.updateZenMode(mPreference, originalMode); + + ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); + scheduleInfo.days = new int[] { Calendar.MONDAY }; + scheduleInfo.startHour = 12; + scheduleInfo.endHour = 15; + Uri scheduleUri = ZenModeConfig.toScheduleConditionId(scheduleInfo); + + mController.mOnScheduleOptionListener.onScheduleSelected(scheduleUri); + + // verify the backend got asked to update the mode to be schedule-based. + ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class); + verify(mBackend).updateMode(captor.capture()); + ZenMode updatedMode = captor.getValue(); + assertThat(updatedMode.getType()).isEqualTo(TYPE_SCHEDULE_TIME); + assertThat(updatedMode.getRule().getConditionId()).isEqualTo(scheduleUri); + assertThat(updatedMode.getRule().getTriggerDescription()).isNotEmpty(); + assertThat(updatedMode.getRule().getOwner()).isEqualTo( + ZenModeConfig.getScheduleConditionProvider()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerCategoryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerCategoryPreferenceControllerTest.java new file mode 100644 index 00000000000..4510e2048f5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerCategoryPreferenceControllerTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.modes; + +import static android.app.AutomaticZenRule.TYPE_OTHER; +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; +import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AutomaticZenRule; +import android.app.Flags; +import android.content.Context; +import android.net.Uri; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.SystemZenRules; +import android.service.notification.ZenModeConfig; + +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.notification.modes.TestModeBuilder; +import com.android.settingslib.notification.modes.ZenMode; +import com.android.settingslib.notification.modes.ZenModesBackend; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +@EnableFlags(Flags.FLAG_MODES_UI) +public class ZenModeTriggerCategoryPreferenceControllerTest { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + + private ZenModeTriggerCategoryPreferenceController mController; + + private Context mContext; + private Preference mPreference; + @Mock private ZenModesBackend mBackend; + @Mock private DashboardFragment mFragment; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + + PreferenceManager preferenceManager = new PreferenceManager(mContext); + PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(mContext, + R.xml.modes_rule_settings, null); + + mController = new ZenModeTriggerCategoryPreferenceController(mContext, + "zen_automatic_trigger_category"); + mPreference = preferenceScreen.findPreference("zen_automatic_trigger_category"); + } + + @Test + public void isAvailable_customManualMode_true() { + ZenMode mode = new TestModeBuilder() + .setConditionId(ZenModeConfig.toCustomManualConditionId()) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_OTHER) + .setTriggerDescription("Will not be shown") + .build(); + mController.setZenMode(mode); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_systemMode_true() { + ZenMode mode = new TestModeBuilder() + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_SCHEDULE_CALENDAR) + .build(); + mController.setZenMode(mode); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_appProvidedMode_true() { + ZenMode mode = new TestModeBuilder() + .setPackage("com.some.package") + .setType(TYPE_OTHER) + .build(); + mController.setZenMode(mode); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_manualDND_false() { + ZenMode manualMode = ZenMode.manualDndMode(new AutomaticZenRule.Builder("Do Not Disturb", + Uri.parse("manual")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .build(), /* isActive= */ false); + + mController.setZenMode(manualMode); + assertThat(mController.isAvailable()).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java similarity index 60% rename from tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java rename to tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java index 93db4bea617..a3fe57e02f5 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java @@ -22,11 +22,8 @@ import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; -import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.ADD_TRIGGER_KEY; -import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.AUTOMATIC_TRIGGER_KEY; -import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceControllerTest.CharSequenceTruth.assertThat; +import static com.android.settings.notification.modes.CharSequenceTruth.assertThat; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -51,24 +48,17 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; -import androidx.annotation.Nullable; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.SettingsActivity; -import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.notification.modes.TestModeBuilder; import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenModesBackend; -import com.google.common.truth.StringSubject; -import com.google.common.truth.Truth; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -84,44 +74,31 @@ import java.util.Calendar; @RunWith(RobolectricTestRunner.class) @EnableFlags(Flags.FLAG_MODES_UI) -public class ZenModeSetTriggerLinkPreferenceControllerTest { +public class ZenModeTriggerUpdatePreferenceControllerTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); - @Mock - private ZenModesBackend mBackend; - private Context mContext; + private ZenModeTriggerUpdatePreferenceController mController; - @Mock - private PackageManager mPm; - @Mock - private ConfigurationActivityHelper mConfigurationActivityHelper; - - private PreferenceCategory mPrefCategory; - private PrimarySwitchPreference mConfigPreference; - private Preference mAddPreference; - - @Mock - private DashboardFragment mFragment; - - private ZenModeSetTriggerLinkPreferenceController mController; + private PrimarySwitchPreference mPreference; + @Mock private ZenModesBackend mBackend; + @Mock private PackageManager mPm; + @Mock private ConfigurationActivityHelper mConfigurationActivityHelper; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mContext = ApplicationProvider.getApplicationContext(); + Context context = ApplicationProvider.getApplicationContext(); - PreferenceManager preferenceManager = new PreferenceManager(mContext); - PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(mContext, + PreferenceManager preferenceManager = new PreferenceManager(context); + PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(context, R.xml.modes_rule_settings, null); - mController = new ZenModeSetTriggerLinkPreferenceController(mContext, - "zen_automatic_trigger_category", mFragment, mBackend, mPm, + mController = new ZenModeTriggerUpdatePreferenceController(context, + "zen_automatic_trigger_settings", mBackend, mPm, mConfigurationActivityHelper, mock(ZenServiceListing.class)); - mPrefCategory = preferenceScreen.findPreference("zen_automatic_trigger_category"); - mConfigPreference = checkNotNull(mPrefCategory).findPreference(AUTOMATIC_TRIGGER_KEY); - mAddPreference = checkNotNull(mPrefCategory).findPreference(ADD_TRIGGER_KEY); + mPreference = preferenceScreen.findPreference("zen_automatic_trigger_settings"); when(mPm.getApplicationInfo(any(), anyInt())).then( (Answer) invocationOnMock -> { @@ -136,19 +113,48 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { } @Test - public void testIsAvailable() { - // should not be available for manual DND + public void isAvailable_systemModeNotCustomManual_true() { + ZenMode mode = new TestModeBuilder() + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_SCHEDULE_CALENDAR) + .build(); + mController.setZenMode(mode); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_appProvidedMode_true() { + ZenMode mode = new TestModeBuilder() + .setPackage("com.some.package") + .setType(TYPE_OTHER) + .build(); + mController.setZenMode(mode); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_customManualMode_false() { + ZenMode mode = new TestModeBuilder() + .setConditionId(ZenModeConfig.toCustomManualConditionId()) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_OTHER) + .build(); + mController.setZenMode(mode); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_manualDND_false() { ZenMode manualMode = ZenMode.manualDndMode(new AutomaticZenRule.Builder("Do Not Disturb", Uri.parse("manual")) .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) - .build(), true); + .build(), /* isActive= */ false); - mController.updateZenMode(mPrefCategory, manualMode); + mController.setZenMode(manualMode); assertThat(mController.isAvailable()).isFalse(); - - // should be available for other modes - mController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE); - assertThat(mController.isAvailable()).isTrue(); } @Test @@ -156,23 +162,23 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { ZenMode zenMode = new TestModeBuilder().setEnabled(false).build(); // Update preference controller with a zen mode that is not enabled - mController.updateZenMode(mPrefCategory, zenMode); - assertThat(mConfigPreference.getCheckedState()).isFalse(); + mController.updateZenMode(mPreference, zenMode); + assertThat(mPreference.getCheckedState()).isFalse(); // Now with the rule enabled zenMode.getRule().setEnabled(true); - mController.updateZenMode(mPrefCategory, zenMode); - assertThat(mConfigPreference.getCheckedState()).isTrue(); + mController.updateZenMode(mPreference, zenMode); + assertThat(mPreference.getCheckedState()).isTrue(); } @Test public void onPreferenceChange_toggleOn_enablesModeAfterConfirmation() { // Start with a disabled mode ZenMode zenMode = new TestModeBuilder().setEnabled(false).build(); - mController.updateZenMode(mPrefCategory, zenMode); + mController.updateZenMode(mPreference, zenMode); // Flip the switch - mConfigPreference.callChangeListener(true); + mPreference.callChangeListener(true); verify(mBackend, never()).updateMode(any()); // Oh wait, I forgot to confirm! Let's do that @@ -193,10 +199,10 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { public void onPreferenceChange_toggleOff_disablesModeAfterConfirmation() { // Start with an enabled mode ZenMode zenMode = new TestModeBuilder().setEnabled(true).build(); - mController.updateZenMode(mPrefCategory, zenMode); + mController.updateZenMode(mPreference, zenMode); // Flip the switch - mConfigPreference.callChangeListener(false); + mPreference.callChangeListener(false); verify(mBackend, never()).updateMode(any()); // Oh wait, I forgot to confirm! Let's do that @@ -217,17 +223,17 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { public void onPreferenceChange_ifPressCancelButton_doesNotUpdateMode() { // Start with a disabled mode ZenMode zenMode = new TestModeBuilder().setEnabled(false).build(); - mController.updateZenMode(mPrefCategory, zenMode); + mController.updateZenMode(mPreference, zenMode); // Flip the switch, then have second thoughts about it - mConfigPreference.callChangeListener(true); + mPreference.callChangeListener(true); ShadowAlertDialog.getLatestAlertDialog() .getButton(AlertDialog.BUTTON_NEGATIVE).performClick(); shadowOf(Looper.getMainLooper()).idle(); // Verify nothing changed, and the switch shows the correct (pre-change) value. verify(mBackend, never()).updateMode(any()); - assertThat(mConfigPreference.isChecked()).isFalse(); + assertThat(mPreference.isChecked()).isFalse(); assertThat(ShadowAlertDialog.getLatestAlertDialog().isShowing()).isFalse(); } @@ -235,16 +241,16 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { public void onPreferenceChange_ifExitingDialog_doesNotUpdateMode() { // Start with a disabled mode ZenMode zenMode = new TestModeBuilder().setEnabled(false).build(); - mController.updateZenMode(mPrefCategory, zenMode); + mController.updateZenMode(mPreference, zenMode); // Flip the switch, but close the dialog without selecting either button. - mConfigPreference.callChangeListener(true); + mPreference.callChangeListener(true); ShadowAlertDialog.getLatestAlertDialog().dismiss(); shadowOf(Looper.getMainLooper()).idle(); // Verify nothing changed, and the switch shows the correct (pre-change) value. verify(mBackend, never()).updateMode(any()); - assertThat(mConfigPreference.isChecked()).isFalse(); + assertThat(mPreference.isChecked()).isFalse(); assertThat(ShadowAlertDialog.getLatestAlertDialog().isShowing()).isFalse(); } @@ -260,15 +266,14 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { .setTriggerDescription("My events") .build(); - mController.updateState(mPrefCategory, mode); + mController.updateState(mPreference, mode); - assertThat(mAddPreference.isVisible()).isFalse(); - assertThat(mConfigPreference.isVisible()).isTrue(); - assertThat(mConfigPreference.getTitle()).isEqualTo("Calendar events"); - assertThat(mConfigPreference.getSummary()).isEqualTo("My events"); + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getTitle()).isEqualTo("Calendar events"); + assertThat(mPreference.getSummary()).isEqualTo("My events"); // Destination as written into the intent by SubSettingLauncher assertThat( - mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) .isEqualTo(ZenModeSetCalendarFragment.class.getName()); } @@ -285,38 +290,17 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { .setTriggerDescription("some schedule") .build(); - mController.updateState(mPrefCategory, mode); + mController.updateState(mPreference, mode); - assertThat(mAddPreference.isVisible()).isFalse(); - assertThat(mConfigPreference.isVisible()).isTrue(); - assertThat(mConfigPreference.getTitle()).isEqualTo("1:00 AM - 3:00 PM"); - assertThat(mConfigPreference.getSummary()).isEqualTo("Mon - Tue, Thu"); + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getTitle()).isEqualTo("1:00 AM - 3:00 PM"); + assertThat(mPreference.getSummary()).isEqualTo("Mon - Tue, Thu"); // Destination as written into the intent by SubSettingLauncher assertThat( - mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) .isEqualTo(ZenModeSetScheduleFragment.class.getName()); } - @Test - public void updateState_customManualRule() { - ZenMode mode = new TestModeBuilder() - .setConditionId(ZenModeConfig.toCustomManualConditionId()) - .setPackage(SystemZenRules.PACKAGE_ANDROID) - .setType(TYPE_OTHER) - .setTriggerDescription("Will not be shown") - .build(); - - mController.updateState(mPrefCategory, mode); - - assertThat(mConfigPreference.isVisible()).isFalse(); - assertThat(mAddPreference.isVisible()).isTrue(); - assertThat(mAddPreference.getTitle()).isEqualTo( - mContext.getString(R.string.zen_mode_select_schedule)); - assertThat(mAddPreference.getSummary()).isNull(); - // Sets up a click listener to open the dialog. - assertThat(mAddPreference.getOnPreferenceClickListener()).isNotNull(); - } - @Test public void updateState_appWithConfigActivity_showsLinkToConfigActivity() { ZenMode mode = new TestModeBuilder() @@ -327,12 +311,12 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any())) .thenReturn(configurationIntent); - mController.updateState(mPrefCategory, mode); + mController.updateState(mPreference, mode); - assertThat(mConfigPreference.isVisible()).isTrue(); - assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app"); - assertThat(mConfigPreference.getSummary()).isEqualTo("When The Music's Over"); - assertThat(mConfigPreference.getIntent()).isEqualTo(configurationIntent); + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getTitle()).isEqualTo("Linked to app"); + assertThat(mPreference.getSummary()).isEqualTo("When The Music's Over"); + assertThat(mPreference.getIntent()).isEqualTo(configurationIntent); } @Test @@ -344,12 +328,12 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any())) .thenReturn(null); - mController.updateState(mPrefCategory, mode); + mController.updateState(mPreference, mode); - assertThat(mConfigPreference.isVisible()).isTrue(); - assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app"); - assertThat(mConfigPreference.getSummary()).isEqualTo("When the saints go marching in"); - assertThat(mConfigPreference.getIntent()).isNull(); + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getTitle()).isEqualTo("Linked to app"); + assertThat(mPreference.getSummary()).isEqualTo("When the saints go marching in"); + assertThat(mPreference.getIntent()).isNull(); } @Test @@ -362,11 +346,11 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { .thenReturn(configurationIntent); when(mPm.getText(any(), anyInt(), any())).thenReturn("The App Name"); - mController.updateState(mPrefCategory, mode); + mController.updateState(mPreference, mode); - assertThat(mConfigPreference.isVisible()).isTrue(); - assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app"); - assertThat(mConfigPreference.getSummary()).isEqualTo("Info and settings in The App Name"); + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getTitle()).isEqualTo("Linked to app"); + assertThat(mPreference.getSummary()).isEqualTo("Info and settings in The App Name"); } @Test @@ -378,52 +362,10 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { .thenReturn(null); when(mPm.getText(any(), anyInt(), any())).thenReturn("The App Name"); - mController.updateState(mPrefCategory, mode); + mController.updateState(mPreference, mode); - assertThat(mConfigPreference.isVisible()).isTrue(); - assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app"); - assertThat(mConfigPreference.getSummary()).isEqualTo("Managed by The App Name"); - } - - @Test - public void onScheduleChosen_updatesMode() { - ZenMode originalMode = new TestModeBuilder() - .setConditionId(ZenModeConfig.toCustomManualConditionId()) - .setPackage(SystemZenRules.PACKAGE_ANDROID) - .setType(TYPE_OTHER) - .setTriggerDescription("") - .build(); - mController.updateZenMode(mPrefCategory, originalMode); - - ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); - scheduleInfo.days = new int[] { Calendar.MONDAY }; - scheduleInfo.startHour = 12; - scheduleInfo.endHour = 15; - Uri scheduleUri = ZenModeConfig.toScheduleConditionId(scheduleInfo); - - mController.mOnScheduleOptionListener.onScheduleSelected(scheduleUri); - - // verify the backend got asked to update the mode to be schedule-based. - ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class); - verify(mBackend).updateMode(captor.capture()); - ZenMode updatedMode = captor.getValue(); - assertThat(updatedMode.getType()).isEqualTo(TYPE_SCHEDULE_TIME); - assertThat(updatedMode.getRule().getConditionId()).isEqualTo(scheduleUri); - assertThat(updatedMode.getRule().getTriggerDescription()).isNotEmpty(); - assertThat(updatedMode.getRule().getOwner()).isEqualTo( - ZenModeConfig.getScheduleConditionProvider()); - } - - static class CharSequenceTruth { - /** - * Shortcut version of {@link Truth#assertThat(String)} suitable for {@link CharSequence}. - * {@link CharSequence} doesn't necessarily provide a good {@code equals()} implementation; - * however we don't care about formatting here, so we want to assert on the resulting - * string (without needing to worry that {@code assertThat(x.getText().toString())} can - * throw if the text is null). - */ - static StringSubject assertThat(@Nullable CharSequence actual) { - return Truth.assertThat((String) (actual != null ? actual.toString() : null)); - } + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getTitle()).isEqualTo("Linked to app"); + assertThat(mPreference.getSummary()).isEqualTo("Managed by The App Name"); } }