Added footers to zen mode settings
ZenModeSettings Footer displays when DND will end
ZenModeBehaviorSettings Footer describes why dnd behavior cannot be changed when
in alarms only or total silence mode
Test: make RunSettingsRoboTests -j40
Bug: 63077372
Change-Id: Iefbb3995da4af2b210c8e0c3c3a798d3c613e275
diff --git a/res/layout/zen_mode_settings_button.xml b/res/layout/zen_mode_settings_button.xml
index 4d4b7d6..82989fc 100644
--- a/res/layout/zen_mode_settings_button.xml
+++ b/res/layout/zen_mode_settings_button.xml
@@ -19,7 +19,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:gravity="bottom"
- android:paddingTop="4dp"
android:paddingStart="72dp"
android:paddingEnd="72dp"
android:layout_width="match_parent"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 40152f7..ab9e878 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6775,19 +6775,16 @@
<string name="zen_mode_button_turn_off">TURN OFF NOW</string>
<!-- [CHAR LIMIT=110] Zen mode settings footer: Footer showing end time of DND -->
- <string name="zen_mode_settings_dnd_manual_end_time_next_day">Do Not Disturb is on until <xliff:g id="formatted_time" example="7:00 AM">%s</xliff:g></string>
+ <string name="zen_mode_settings_dnd_manual_end_time">Do Not Disturb is on until <xliff:g id="formatted_time" example="7:00 AM">%s</xliff:g></string>
<!-- [CHAR LIMIT=110] Zen mode settings footer: Footer showing length of DND -->
- <string name="zen_mode_settings_dnd_manual_indefinite">Do Not Disturb will stay on until you turn it off.</string>
+ <string name="zen_mode_settings_dnd_manual_indefinite">Do Not Disturb will stay on until you turn it off</string>
<!-- [CHAR LIMIT=110] Zen mode settings footer: Footer showing how DND was triggered by an automatic DND rule -->
- <string name="zen_mode_settings_dnd_automatic_rule">Do Not Disturb was automatically turned on by a rule <xliff:g id="rule_name" example="Weeknights">%s</xliff:g></string>
+ <string name="zen_mode_settings_dnd_automatic_rule">Do Not Disturb was automatically turned on by a rule (<xliff:g id="rule_name" example="Weeknights">%s</xliff:g>)</string>
<!-- [CHAR LIMIT=110] Zen mode settings footer: Footer how DND was triggered by an app -->
- <string name="zen_mode_settings_dnd_automatic_rule_app">Do Not Disturb was automatically turned on by an app <xliff:g id="app_name" example="Pixel Services">%s</xliff:g></string>
-
- <!-- [CHAR LIMIT=110] Zen mode settings footer: Footer how DND was triggered by multiple rules and/or apps -->
- <string name="zen_mode_settings_dnd_automatic_rule_multiple">Do Not Disturb was automatically turned on by a rule or app</string>
+ <string name="zen_mode_settings_dnd_automatic_rule_app">Do Not Disturb was automatically turned on by an app (<xliff:g id="app_name" example="Android Services">%s</xliff:g>)</string>
<!-- Work Sounds: Work sound settings section header. [CHAR LIMIT=50] -->
<string name="sound_work_settings">Work profile sounds</string>
@@ -7205,6 +7202,15 @@
<!-- [CHAR LIMIT=40] Zen mode settings: Configure external rule -->
<string name="zen_mode_configure_rule">Configure rule</string>
+ <!-- [CHAR LIMIT=NONE] Zen mode behavior settings footer: footer describing why the user cannot change the current do not disturb behavior settings -->
+ <string name="zen_mode_app_set_behavior">These settings can\'t be changed right now. An app (<xliff:g id="app_name" example="Android Services">%1$s</xliff:g>) has automatically turned on Do Not Disturb with custom behavior."</string>
+
+ <!-- [CHAR LIMIT=NONE] Zen mode behavior settings footer: footer describing why the user cannot change the current do not disturb behavior settings -->
+ <string name="zen_mode_unknown_app_set_behavior">These settings can\'t be changed right now. An app has automatically turned on Do Not Disturb with custom behavior."</string>
+
+ <!-- [CHAR LIMIT=NONE] Zen mode behavior settings footer: footer describing why the user cannot change the current do not disturb behavior settings -->
+ <string name="zen_mode_qs_set_behavior">These settings can\'t be changed right now. Do Not Disturb was manually turned on with custom behavior."</string>
+
<!-- [CHAR LIMIT=40] Zen mode settings: Schedule rule type name -->
<string name="zen_schedule_rule_type_name">Time</string>
diff --git a/res/xml/zen_mode_behavior_settings.xml b/res/xml/zen_mode_behavior_settings.xml
index 2536828..c0849da 100644
--- a/res/xml/zen_mode_behavior_settings.xml
+++ b/res/xml/zen_mode_behavior_settings.xml
@@ -75,7 +75,8 @@
<SwitchPreference android:key="zen_mode_screen_off"
android:title="@string/zen_mode_screen_off"
android:summary="@string/zen_mode_screen_off_summary" />
-
</PreferenceCategory>
+ <com.android.settingslib.widget.FooterPreference/>
+
</PreferenceScreen>
diff --git a/res/xml/zen_mode_settings.xml b/res/xml/zen_mode_settings.xml
index dbd1d42..0a6284d 100644
--- a/res/xml/zen_mode_settings.xml
+++ b/res/xml/zen_mode_settings.xml
@@ -41,4 +41,8 @@
android:layout="@layout/zen_mode_settings_button" />
</PreferenceCategory>
+ <PreferenceCategory>
+ <com.android.settingslib.widget.FooterPreference/>
+ </PreferenceCategory>
+
</PreferenceScreen>
diff --git a/src/com/android/settings/notification/AbstractZenModePreferenceController.java b/src/com/android/settings/notification/AbstractZenModePreferenceController.java
index de2e8fd..2642f81 100644
--- a/src/com/android/settings/notification/AbstractZenModePreferenceController.java
+++ b/src/com/android/settings/notification/AbstractZenModePreferenceController.java
@@ -16,6 +16,9 @@
package com.android.settings.notification;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.AlarmManager.AlarmClockInfo;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -24,8 +27,11 @@
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.notification.ScheduleCalendar;
+import android.service.notification.ZenModeConfig;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.core.PreferenceControllerMixin;
@@ -41,12 +47,15 @@
@VisibleForTesting
protected SettingObserver mSettingObserver;
+
private final String KEY;
final private NotificationManager mNotificationManager;
+ protected static ZenModeConfigWrapper mZenModeConfigWrapper;
public AbstractZenModePreferenceController(Context context, String key,
Lifecycle lifecycle) {
super(context);
+ mZenModeConfigWrapper = new ZenModeConfigWrapper(context);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
@@ -79,6 +88,10 @@
return mNotificationManager.getNotificationPolicy();
}
+ protected ZenModeConfig getZenModeConfig() {
+ return mNotificationManager.getZenModeConfig();
+ }
+
protected int getZenMode() {
return Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ZEN_MODE, 0);
@@ -117,4 +130,69 @@
}
}
}
+
+ /**
+ * Wrapper for testing compatibility
+ */
+ @VisibleForTesting
+ static class ZenModeConfigWrapper {
+ private final Context mContext;
+
+ public ZenModeConfigWrapper(Context context) {
+ mContext = context;
+ }
+
+ protected String getOwnerCaption(String owner) {
+ return ZenModeConfig.getOwnerCaption(mContext, owner);
+ }
+
+ protected boolean isTimeRule(Uri id) {
+ return ZenModeConfig.isValidEventConditionId(id) ||
+ ZenModeConfig.isValidScheduleConditionId(id);
+ }
+
+ protected CharSequence getFormattedTime(long time, int userHandle) {
+ return ZenModeConfig.getFormattedTime(mContext, time, isToday(time), userHandle);
+ }
+
+ private boolean isToday(long time) {
+ return ZenModeConfig.isToday(time);
+ }
+
+ protected long parseManualRuleTime(Uri id) {
+ return ZenModeConfig.tryParseCountdownConditionId(id);
+ }
+
+ protected long parseAutomaticRuleEndTime(Uri id) {
+ if (ZenModeConfig.isValidEventConditionId(id)) {
+ // cannot look up end times for events
+ return Long.MAX_VALUE;
+ }
+
+ if (ZenModeConfig.isValidScheduleConditionId(id)) {
+ ScheduleCalendar schedule = ZenModeConfig.toScheduleCalendar(id);
+ long endTimeMs = schedule.getNextChangeTime(System.currentTimeMillis());
+
+ // check if automatic rule will end on next alarm
+ if (schedule.exitAtAlarm()) {
+ long nextAlarm = getNextAlarm(mContext);
+ schedule.maybeSetNextAlarm(System.currentTimeMillis(), nextAlarm);
+ if (schedule.shouldExitForAlarm(endTimeMs)) {
+ return nextAlarm;
+ }
+ }
+
+
+ return endTimeMs;
+ }
+
+ return -1;
+ }
+ }
+
+ private static long getNextAlarm(Context context) {
+ final AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ final AlarmClockInfo info = alarms.getNextAlarmClock(ActivityManager.getCurrentUser());
+ return info != null ? info.getTriggerTime() : 0;
+ }
}
diff --git a/src/com/android/settings/notification/ZenModeBehaviorFooterPreferenceController.java b/src/com/android/settings/notification/ZenModeBehaviorFooterPreferenceController.java
new file mode 100644
index 0000000..a1c2b01
--- /dev/null
+++ b/src/com/android/settings/notification/ZenModeBehaviorFooterPreferenceController.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.content.Context;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.ZenModeConfig;
+import android.support.v7.preference.Preference;
+import android.util.Slog;
+
+import com.android.settings.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+public class ZenModeBehaviorFooterPreferenceController extends AbstractZenModePreferenceController {
+
+ protected static final String KEY = "footer_preference";
+
+ public ZenModeBehaviorFooterPreferenceController(Context context, Lifecycle lifecycle) {
+ super(context, KEY, lifecycle);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return isDeprecatedZenMode(getZenMode());
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+
+ boolean isAvailable = isAvailable();
+ preference.setVisible(isAvailable);
+ if (isAvailable) {
+ preference.setTitle(getFooterText());
+ }
+
+ }
+
+ protected String getFooterText() {
+ ZenModeConfig config = getZenModeConfig();
+
+ // DND turned on by manual rule with deprecated zen mode
+ if (config.manualRule != null &&
+ isDeprecatedZenMode(config.manualRule.zenMode)) {
+ final Uri id = config.manualRule.conditionId;
+ if (config.manualRule.enabler != null) {
+ // app triggered manual rule
+ String appOwner = mZenModeConfigWrapper.getOwnerCaption(config.manualRule.enabler);
+ if (!appOwner.isEmpty()) {
+ return mContext.getString(R.string.zen_mode_app_set_behavior, appOwner);
+ }
+ } else {
+ return mContext.getString(R.string.zen_mode_qs_set_behavior);
+ }
+ }
+
+ // DND turned on by an automatic rule with deprecated zen mode
+ for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive() && isDeprecatedZenMode(automaticRule.zenMode)) {
+ ComponentName component = automaticRule.component;
+ if (component != null) {
+ return mContext.getString(R.string.zen_mode_app_set_behavior,
+ component.getPackageName());
+ }
+ }
+ }
+
+ return mContext.getString(R.string.zen_mode_unknown_app_set_behavior);
+ }
+
+ private boolean isDeprecatedZenMode(int zenMode) {
+ switch (zenMode) {
+ case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+ case Settings.Global.ZEN_MODE_ALARMS:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/ZenModeBehaviorSettings.java b/src/com/android/settings/notification/ZenModeBehaviorSettings.java
index b58ef86..bfa95a7 100644
--- a/src/com/android/settings/notification/ZenModeBehaviorSettings.java
+++ b/src/com/android/settings/notification/ZenModeBehaviorSettings.java
@@ -48,6 +48,7 @@
controllers.add(new ZenModeRepeatCallersPreferenceController(context, lifecycle));
controllers.add(new ZenModeScreenOnPreferenceController(context, lifecycle));
controllers.add(new ZenModeScreenOffPreferenceController(context, lifecycle));
+ controllers.add(new ZenModeBehaviorFooterPreferenceController(context, lifecycle));
return controllers;
}
diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java
index 22d6fca..f8408fc 100644
--- a/src/com/android/settings/notification/ZenModeSettings.java
+++ b/src/com/android/settings/notification/ZenModeSettings.java
@@ -64,6 +64,7 @@
controllers.add(new ZenModeBehaviorPreferenceController(context, lifecycle));
controllers.add(new ZenModeAutomationPreferenceController(context));
controllers.add(new ZenModeButtonPreferenceController(context, lifecycle));
+ controllers.add(new ZenModeSettingsFooterPreferenceController(context, lifecycle));
return controllers;
}
diff --git a/src/com/android/settings/notification/ZenModeSettingsFooterPreferenceController.java b/src/com/android/settings/notification/ZenModeSettingsFooterPreferenceController.java
new file mode 100644
index 0000000..752fe44
--- /dev/null
+++ b/src/com/android/settings/notification/ZenModeSettingsFooterPreferenceController.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.ZenModeConfig;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+public class ZenModeSettingsFooterPreferenceController extends AbstractZenModePreferenceController {
+
+ protected static final String KEY = "footer_preference";
+
+ public ZenModeSettingsFooterPreferenceController(Context context, Lifecycle lifecycle) {
+ super(context, KEY, lifecycle);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ switch(getZenMode()) {
+ case Settings.Global.ZEN_MODE_ALARMS:
+ case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+ case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ return true;
+ case Settings.Global.ZEN_MODE_OFF:
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+
+ boolean isAvailable = isAvailable();
+ preference.setVisible(isAvailable);
+ if (isAvailable) {
+ preference.setTitle(getFooterText());
+ }
+ }
+
+ protected String getFooterText() {
+ ZenModeConfig config = getZenModeConfig();
+ String footerText = "";
+ long latestEndTime = -1;
+
+ // DND turned on by manual rule
+ if (config.manualRule != null) {
+ final Uri id = config.manualRule.conditionId;
+ if (config.manualRule.enabler != null) {
+ // app triggered manual rule
+ String appOwner = mZenModeConfigWrapper.getOwnerCaption(config.manualRule.enabler);
+ if (!appOwner.isEmpty()) {
+ footerText = mContext.getString(
+ R.string.zen_mode_settings_dnd_automatic_rule_app, appOwner);
+ }
+ } else {
+ if (id == null) {
+ return mContext.getString(
+ R.string.zen_mode_settings_dnd_manual_indefinite);
+ } else {
+ latestEndTime = mZenModeConfigWrapper.parseManualRuleTime(id);
+ if (latestEndTime > 0) {
+ final CharSequence formattedTime = mZenModeConfigWrapper.getFormattedTime(
+ latestEndTime, mContext.getUserId());
+ footerText = mContext.getString(
+ R.string.zen_mode_settings_dnd_manual_end_time,
+ formattedTime);
+ }
+ }
+ }
+ }
+
+ // DND turned on by an automatic rule
+ for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive()) {
+ // set footer if 3rd party rule
+ if (!mZenModeConfigWrapper.isTimeRule(automaticRule.conditionId)) {
+ return mContext.getString(R.string.zen_mode_settings_dnd_automatic_rule,
+ automaticRule.name);
+ } else {
+ // set footer if automatic rule end time is the latest active rule end time
+ long endTime = mZenModeConfigWrapper.parseAutomaticRuleEndTime(
+ automaticRule.conditionId);
+ if (endTime > latestEndTime) {
+ latestEndTime = endTime;
+ footerText = mContext.getString(
+ R.string.zen_mode_settings_dnd_automatic_rule, automaticRule.name);
+ }
+ }
+ }
+ }
+ return footerText;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeBehaviorFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeBehaviorFooterPreferenceControllerTest.java
new file mode 100644
index 0000000..a7c051b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/ZenModeBehaviorFooterPreferenceControllerTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import static android.provider.Settings.Global.ZEN_MODE;
+import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_OFF;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ComponentName;
+import android.provider.Settings;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.ArrayMap;
+
+import com.android.settings.notification.AbstractZenModePreferenceController.ZenModeConfigWrapper;
+import com.android.settings.TestConfig;
+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.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O)
+public class ZenModeBehaviorFooterPreferenceControllerTest {
+ private ZenModeBehaviorFooterPreferenceController mController;
+ private final String TEST_APP_NAME = "test_app";
+ private final String MANUAL_RULE_FIELD = "manualRule";
+ private final String AUTOMATIC_RULES_FIELD = "automaticRules";
+
+ @Mock
+ private NotificationManager mNotificationManager;
+ @Mock
+ private Preference mockPref;
+ @Mock
+ private ZenModeConfig mZenModeConfig;
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+ @Mock
+ private ZenModeConfig mConfig;
+ @Mock
+ private ZenModeConfigWrapper mConfigWrapper;
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ ShadowApplication shadowApplication = ShadowApplication.getInstance();
+ shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager);
+
+ mContext = shadowApplication.getApplicationContext();
+ mContentResolver = RuntimeEnvironment.application.getContentResolver();
+ when(mNotificationManager.getZenModeConfig()).thenReturn(mZenModeConfig);
+
+ mController = new ZenModeBehaviorFooterPreferenceController(mContext,
+ mock(Lifecycle.class));
+ ReflectionHelpers.setField(mController, "mZenModeConfigWrapper", mConfigWrapper);
+
+ when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(
+ mockPref);
+ mController.displayPreference(mPreferenceScreen);
+ }
+
+ @Test
+ public void totalSilence_footerIsAvailable() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_NO_INTERRUPTIONS);
+ assertTrue(mController.isAvailable());
+ }
+
+ @Test
+ public void alarmsOnly_footerIsAvailable() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_ALARMS);
+ assertTrue(mController.isAvailable());
+ }
+
+ @Test
+ public void priorityOnly_footerIsAvailable() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertFalse(mController.isAvailable());
+ }
+
+ @Test
+ public void zenModeOff_footerIsNotAvailable() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_OFF);
+ assertFalse(mController.isAvailable());
+ }
+
+ @Test
+ public void zenModeOff_updateState_noFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_OFF);
+ mController.updateState(mockPref);
+
+ verify(mockPref, never()).setTitle(any(String.class));
+ }
+
+ @Test
+ public void zenModeImportantInterruptions_updateState_noFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mController.updateState(mockPref);
+
+ verify(mockPref, never()).setTitle(any(String.class));
+ }
+
+ @Test
+ public void deprecatedZenModeAlarms_qsManualRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_ALARMS);
+
+ ZenRule injectedManualRule = new ZenRule();
+ injectedManualRule.zenMode = ZEN_MODE_ALARMS;
+ ReflectionHelpers.setField(mZenModeConfig, MANUAL_RULE_FIELD, injectedManualRule);
+
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_qs_set_behavior));
+ }
+
+ @Test
+ public void deprecatedZenModeAlarms_appManualRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_ALARMS);
+
+ ZenRule injectedManualRule = new ZenRule();
+ injectedManualRule.zenMode = ZEN_MODE_ALARMS;
+ injectedManualRule.enabler = TEST_APP_NAME;
+ when(mConfigWrapper.getOwnerCaption(injectedManualRule.enabler)).thenReturn(TEST_APP_NAME);
+ ReflectionHelpers.setField(mZenModeConfig, MANUAL_RULE_FIELD, injectedManualRule);
+
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_app_set_behavior, TEST_APP_NAME));
+ }
+
+ @Test
+ public void deprecatedZenModeNoInterruptions_qsManualRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_NO_INTERRUPTIONS);
+
+ ZenRule injectedManualRule = new ZenRule();
+ injectedManualRule.zenMode = ZEN_MODE_NO_INTERRUPTIONS;
+ ReflectionHelpers.setField(mZenModeConfig, MANUAL_RULE_FIELD, injectedManualRule);
+
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_qs_set_behavior));
+ }
+
+ @Test
+ public void deprecatedZenModeNoInterruptions_appManualRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_NO_INTERRUPTIONS);
+
+ ZenRule injectedManualRule = new ZenRule();
+ injectedManualRule.zenMode = ZEN_MODE_NO_INTERRUPTIONS;
+ injectedManualRule.enabler = TEST_APP_NAME;
+ when(mConfigWrapper.getOwnerCaption(injectedManualRule.enabler)).thenReturn(TEST_APP_NAME);
+ ReflectionHelpers.setField(mZenModeConfig, MANUAL_RULE_FIELD, injectedManualRule);
+
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_app_set_behavior, TEST_APP_NAME));
+ }
+
+ @Test
+ public void deprecatedZenModeAlarms_automaticRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_ALARMS);
+
+ ArrayMap<String, ZenRule> injectedAutomaticRules = new ArrayMap<>();
+ ZenRule injectedRule = spy(new ZenRule());
+ injectedRule.zenMode = ZEN_MODE_ALARMS;
+ injectedRule.component = mock(ComponentName.class);
+ when(injectedRule.isAutomaticActive()).thenReturn(true);
+ when(injectedRule.component.getPackageName()).thenReturn(TEST_APP_NAME);
+ injectedAutomaticRules.put("testid", injectedRule);
+
+ ReflectionHelpers.setField(mZenModeConfig, AUTOMATIC_RULES_FIELD, injectedAutomaticRules);
+
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_app_set_behavior, TEST_APP_NAME));
+ }
+
+ @Test
+ public void deprecatedZenModeNoInterruptions_automaticRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_NO_INTERRUPTIONS);
+
+ ArrayMap<String, ZenRule> injectedAutomaticRules = new ArrayMap<>();
+ ZenRule injectedRule = spy(new ZenRule());
+ injectedRule.zenMode = ZEN_MODE_NO_INTERRUPTIONS;
+ injectedRule.component = mock(ComponentName.class);
+ when(injectedRule.isAutomaticActive()).thenReturn(true);
+ when(injectedRule.component.getPackageName()).thenReturn(TEST_APP_NAME);
+ injectedAutomaticRules.put("testid", injectedRule);
+
+ ReflectionHelpers.setField(mZenModeConfig, AUTOMATIC_RULES_FIELD, injectedAutomaticRules);
+
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_app_set_behavior, TEST_APP_NAME));
+ }
+
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsFooterPreferenceControllerTest.java
new file mode 100644
index 0000000..eed8237
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsFooterPreferenceControllerTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import static android.provider.Settings.Global.ZEN_MODE;
+import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_OFF;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.ArrayMap;
+
+import com.android.settings.notification.AbstractZenModePreferenceController.ZenModeConfigWrapper;
+import com.android.settings.TestConfig;
+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.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O)
+public class ZenModeSettingsFooterPreferenceControllerTest {
+ private ZenModeSettingsFooterPreferenceController mController;
+ private final String TEST_APP_NAME = "test_app";
+ private final String TEST_RULE_NAME = "test_rule_name";
+ private final String MANUAL_RULE_FIELD = "manualRule";
+ private final String AUTOMATIC_RULES_FIELD = "automaticRules";
+
+ private final ArrayMap<String, ZenRule> mInjectedAutomaticRules = new ArrayMap<>();
+ ;
+
+ @Mock
+ private NotificationManager mNotificationManager;
+ @Mock
+ private Preference mockPref;
+ @Mock
+ private ZenModeConfig mZenModeConfig;
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+ @Mock
+ private ZenModeConfigWrapper mConfigWrapper;
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ ShadowApplication shadowApplication = ShadowApplication.getInstance();
+ shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager);
+
+ mContext = shadowApplication.getApplicationContext();
+ mContentResolver = RuntimeEnvironment.application.getContentResolver();
+ when(mNotificationManager.getZenModeConfig()).thenReturn(mZenModeConfig);
+
+ mController = new ZenModeSettingsFooterPreferenceController(mContext,
+ mock(Lifecycle.class));
+ ReflectionHelpers.setField(mZenModeConfig, AUTOMATIC_RULES_FIELD, mInjectedAutomaticRules);
+ ReflectionHelpers.setField(mController, "mZenModeConfigWrapper", mConfigWrapper);
+
+ when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(
+ mockPref);
+ mController.displayPreference(mPreferenceScreen);
+ }
+
+ @Test
+ public void totalSilence_footerIsAvailable() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_NO_INTERRUPTIONS);
+ assertTrue(mController.isAvailable());
+ }
+
+ @Test
+ public void alarmsOnly_footerIsAvailable() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_ALARMS);
+ assertTrue(mController.isAvailable());
+ }
+
+ @Test
+ public void priorityOnly_footerIsAvailable() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertTrue(mController.isAvailable());
+ }
+
+ @Test
+ public void zenModeOff_footerIsNotAvailable() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_OFF);
+ assertFalse(mController.isAvailable());
+ }
+
+ @Test
+ public void app_manualRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ injectManualRuleFromApp();
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_settings_dnd_automatic_rule_app,
+ TEST_APP_NAME));
+ }
+
+ @Test
+ public void time_manualRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ String placeholder = "placeholder";
+ injectManualRuleWithTimeCountdown(1000, placeholder);
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_settings_dnd_manual_end_time, placeholder));
+ }
+
+ @Test
+ public void forever_manualRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ injectManualRuleWithIndefiniteEnd();
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_settings_dnd_manual_indefinite));
+ }
+
+ @Test
+ public void automaticRule_noManualRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ // no manual rule
+ ReflectionHelpers.setField(mZenModeConfig, MANUAL_RULE_FIELD, null);
+
+ // adding automatic rule
+ injectNewAutomaticRule(TEST_RULE_NAME, true, false);
+
+ mController.updateState(mockPref);
+
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_settings_dnd_automatic_rule,
+ TEST_RULE_NAME));
+ }
+
+
+ @Test
+ public void manualRuleEndsLast_hasAutomaticRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ // manual rule that ends after automatic rule ends
+ injectManualRuleWithIndefiniteEnd();
+
+ // automatic rule that ends before manual rule ends
+ injectNewAutomaticRule(TEST_RULE_NAME, true, false);
+
+ mController.updateState(mockPref);
+
+ // manual rule end time is after automatic rule end time, so it is displayed
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_settings_dnd_manual_indefinite));
+ }
+
+
+ @Test
+ public void automaticRuleEndsLast_hasManualRule_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ // manual rule that ends before automatic rule ends
+ injectManualRuleWithTimeCountdown(1000, "");
+
+ // automatic rule that ends after manual rule ends
+ ZenRule rule = injectNewAutomaticRule(TEST_RULE_NAME, true, false);
+ when(mConfigWrapper.parseAutomaticRuleEndTime(rule.conditionId)).thenReturn(
+ (long) 2000);
+
+ mController.updateState(mockPref);
+
+ // automatic rule end time is after manual rule end time, so it is displayed
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_settings_dnd_automatic_rule,
+ TEST_RULE_NAME));
+ }
+
+ @Test
+ public void multipleAutomaticRules_appAutoRuleautomaticRuleApp_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ // automatic rule that ends after manual rule ends
+ ZenRule rule1 = injectNewAutomaticRule(TEST_RULE_NAME + "1", false,
+ false);
+ when(mConfigWrapper.parseAutomaticRuleEndTime(rule1.conditionId)).thenReturn(
+ (long) 10000);
+
+ ZenRule rule2 = injectNewAutomaticRule(TEST_RULE_NAME + "2", true,
+ true);
+
+ ZenRule rule3 = injectNewAutomaticRule(TEST_RULE_NAME + "3", true,
+ false);
+ when(mConfigWrapper.parseAutomaticRuleEndTime(rule3.conditionId)).thenReturn(
+ (long) 9000);
+
+ mController.updateState(mockPref);
+
+ // automatic rule from app is displayed
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_settings_dnd_automatic_rule,
+ TEST_RULE_NAME + "2"));
+ }
+
+ @Test
+ public void multipleAutomaticRules_setFooterTitle() {
+ Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ // automatic rule that ends after manual rule ends
+ ZenRule rule1 = injectNewAutomaticRule(TEST_RULE_NAME + "1", true,
+ false);
+ when(mConfigWrapper.parseAutomaticRuleEndTime(rule1.conditionId)).thenReturn(
+ (long) 2000);
+
+ ZenRule rule2 = injectNewAutomaticRule(TEST_RULE_NAME + "2", true,
+ false);
+ when(mConfigWrapper.parseAutomaticRuleEndTime(rule2.conditionId)).thenReturn(
+ (long) 8000);
+
+ ZenRule rule3 = injectNewAutomaticRule(TEST_RULE_NAME + "3", false,
+ false);
+ when(mConfigWrapper.parseAutomaticRuleEndTime(rule3.conditionId)).thenReturn(
+ (long) 12000);
+
+ mController.updateState(mockPref);
+
+ // active automatic rule with the latest end time will display
+ verify(mockPref).setTitle(mContext.getString(
+ com.android.settings.R.string.zen_mode_settings_dnd_automatic_rule,
+ TEST_RULE_NAME + "2"));
+ }
+
+ // manual rule that has no end condition (forever)
+ private void injectManualRuleWithIndefiniteEnd() {
+ ZenRule injectedManualRule = new ZenRule();
+ injectedManualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ injectedManualRule.conditionId = null;
+ injectedManualRule.enabler = null;
+ ReflectionHelpers.setField(mZenModeConfig, MANUAL_RULE_FIELD, injectedManualRule);
+ }
+
+ // manual rule triggered by an app
+ private void injectManualRuleFromApp() {
+ ZenRule injectedManualRule = new ZenRule();
+ injectedManualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ injectedManualRule.enabler = TEST_APP_NAME;
+ when(mConfigWrapper.getOwnerCaption(injectedManualRule.enabler)).thenReturn(TEST_APP_NAME);
+ ReflectionHelpers.setField(mZenModeConfig, MANUAL_RULE_FIELD, injectedManualRule);
+ }
+
+ // manual rule that ends in specified time
+ private void injectManualRuleWithTimeCountdown(long time, String timePlaceholder) {
+ ZenRule injectedManualRule = new ZenRule();
+ injectedManualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ injectedManualRule.enabler = null;
+ injectedManualRule.conditionId = mock(Uri.class);
+ when(mConfigWrapper.parseManualRuleTime(injectedManualRule.conditionId)).thenReturn(
+ time);
+ when(mConfigWrapper.getFormattedTime(time, mContext.getUserId())).thenReturn(
+ timePlaceholder);
+ ReflectionHelpers.setField(mZenModeConfig, MANUAL_RULE_FIELD, injectedManualRule);
+ }
+
+ // manual rule that ends in time
+ private ZenRule injectNewAutomaticRule(String nameAndId, boolean isActive, boolean isApp) {
+ ZenRule injectedRule = spy(new ZenRule());
+ injectedRule.zenMode = ZEN_MODE_NO_INTERRUPTIONS;
+ injectedRule.component = mock(ComponentName.class);
+ injectedRule.name = nameAndId;
+ injectedRule.conditionId = new Uri.Builder().authority(nameAndId).build(); // unique uri
+ when(injectedRule.isAutomaticActive()).thenReturn(isActive);
+ when(mConfigWrapper.isTimeRule(injectedRule.conditionId)).thenReturn(!isApp);
+ if (isApp) {
+ when(injectedRule.component.getPackageName()).thenReturn(TEST_APP_NAME);
+ }
+ mInjectedAutomaticRules.put(nameAndId, injectedRule);
+
+ return injectedRule;
+ }
+}