diff --git a/res/layout/zen_mode_settings_button.xml b/res/layout/zen_mode_settings_button.xml
index 4d4b7d6cbfd..82989fc7863 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 40152f73cb7..ab9e87807c8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6775,19 +6775,16 @@
TURN OFF NOW
- Do Not Disturb is on until %s
+ Do Not Disturb is on until %s
- Do Not Disturb will stay on until you turn it off.
+ Do Not Disturb will stay on until you turn it off
- Do Not Disturb was automatically turned on by a rule %s
+ Do Not Disturb was automatically turned on by a rule (%s)
- Do Not Disturb was automatically turned on by an app %s
-
-
- Do Not Disturb was automatically turned on by a rule or app
+ Do Not Disturb was automatically turned on by an app (%s)
Work profile sounds
@@ -7205,6 +7202,15 @@
Configure rule
+
+ These settings can\'t be changed right now. An app (%1$s) has automatically turned on Do Not Disturb with custom behavior."
+
+
+ These settings can\'t be changed right now. An app has automatically turned on Do Not Disturb with custom behavior."
+
+
+ These settings can\'t be changed right now. Do Not Disturb was manually turned on with custom behavior."
+
Time
diff --git a/res/xml/zen_mode_behavior_settings.xml b/res/xml/zen_mode_behavior_settings.xml
index 2536828966c..c0849daaf48 100644
--- a/res/xml/zen_mode_behavior_settings.xml
+++ b/res/xml/zen_mode_behavior_settings.xml
@@ -75,7 +75,8 @@
-
+
+
diff --git a/res/xml/zen_mode_settings.xml b/res/xml/zen_mode_settings.xml
index dbd1d42a4a8..0a6284d0b7c 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" />
+
+
+
+
diff --git a/src/com/android/settings/notification/AbstractZenModePreferenceController.java b/src/com/android/settings/notification/AbstractZenModePreferenceController.java
index de2e8fd4c06..2642f8129e9 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.net.Uri;
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 @@ abstract public class AbstractZenModePreferenceController extends
@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 @@ abstract public class AbstractZenModePreferenceController extends
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 @@ abstract public class AbstractZenModePreferenceController extends
}
}
}
+
+ /**
+ * 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 00000000000..a1c2b017f17
--- /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 b58ef86a547..bfa95a75703 100644
--- a/src/com/android/settings/notification/ZenModeBehaviorSettings.java
+++ b/src/com/android/settings/notification/ZenModeBehaviorSettings.java
@@ -48,6 +48,7 @@ public class ZenModeBehaviorSettings extends ZenModeSettingsBase implements Inde
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 22d6fcad7ea..f8408fcd273 100644
--- a/src/com/android/settings/notification/ZenModeSettings.java
+++ b/src/com/android/settings/notification/ZenModeSettings.java
@@ -64,6 +64,7 @@ public class ZenModeSettings extends ZenModeSettingsBase {
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 00000000000..752fe447300
--- /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 00000000000..a7c051b7956
--- /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 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 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 00000000000..eed8237d7ff
--- /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 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;
+ }
+}