diff options
6 files changed, 272 insertions, 10 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 54f587ef28b9..b1a2133168b5 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10839,6 +10839,26 @@ public final class Settings { */ public static final String ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE = "enable_deletion_helper_no_threshold_toggle"; + + /** + * The list of snooze options for notifications + * This is encoded as a key=value list, separated by commas. Ex: + * + * "default=60,options_array=15:30:60:120" + * + * The following keys are supported: + * + * <pre> + * default (int) + * options_array (string) + * </pre> + * + * All delays in integer minutes. Array order is respected. + * Options will be used in order up to the maximum allowed by the UI. + * @hide + */ + public static final String NOTIFICATION_SNOOZE_OPTIONS = + "notification_snooze_options"; } /** diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 43e762d34967..7d9c50dc432e 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -277,6 +277,7 @@ public class SettingsBackupTest { Settings.Global.NEW_CONTACT_AGGREGATOR, Settings.Global.NITZ_UPDATE_DIFF, Settings.Global.NITZ_UPDATE_SPACING, + Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, Settings.Global.NSD_ON, Settings.Global.NTP_SERVER, Settings.Global.NTP_TIMEOUT, diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 87f6306ffb9a..8a1e0b92e4d7 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -422,4 +422,14 @@ increase the rate of unintentional unlocks. --> <bool name="config_lockscreenAntiFalsingClassifierEnabled">true</bool> + <!-- Snooze: default notificaiton snooze time. --> + <integer name="config_notification_snooze_time_default">60</integer> + + <!-- Snooze: List of snooze values in integer minutes. --> + <integer-array name="config_notification_snooze_times"> + <item>15</item> + <item>30</item> + <item>60</item> + <item>120</item> + </integer-array> </resources> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 2148c8047476..e5f802944ff3 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -82,10 +82,10 @@ <!-- Accessibility actions for the notification menu --> <item type="id" name="action_snooze_undo"/> - <item type="id" name="action_snooze_15_min"/> - <item type="id" name="action_snooze_30_min"/> - <item type="id" name="action_snooze_1_hour"/> - <item type="id" name="action_snooze_2_hours"/> + <item type="id" name="action_snooze_shorter"/> + <item type="id" name="action_snooze_short"/> + <item type="id" name="action_snooze_long"/> + <item type="id" name="action_snooze_longer"/> <item type="id" name="action_snooze_assistant_suggestion_1"/> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java index c45ca54024db..54e9ed9e3b76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java @@ -16,8 +16,10 @@ package com.android.systemui.statusbar; */ import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; @@ -29,11 +31,13 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Typeface; import android.os.Bundle; +import android.provider.Settings; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.text.SpannableString; import android.text.style.StyleSpan; import android.util.AttributeSet; +import android.util.KeyValueListParser; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -51,11 +55,14 @@ import com.android.systemui.R; public class NotificationSnooze extends LinearLayout implements NotificationGuts.GutsContent, View.OnClickListener { + private static final String TAG = "NotificationSnooze"; /** * If this changes more number increases, more assistant action resId's should be defined for * accessibility purposes, see {@link #setSnoozeOptions(List)} */ private static final int MAX_ASSISTANT_SUGGESTIONS = 1; + private static final String KEY_DEFAULT_SNOOZE = "default"; + private static final String KEY_OPTIONS = "options_array"; private NotificationGuts mGutsContainer; private NotificationSwipeActionHelper mSnoozeListener; private StatusBarNotification mSbn; @@ -72,9 +79,29 @@ public class NotificationSnooze extends LinearLayout private boolean mSnoozing; private boolean mExpanded; private AnimatorSet mExpandAnimation; + private KeyValueListParser mParser; + + private final static int[] sAccessibilityActions = { + R.id.action_snooze_shorter, + R.id.action_snooze_short, + R.id.action_snooze_long, + R.id.action_snooze_longer, + }; public NotificationSnooze(Context context, AttributeSet attrs) { super(context, attrs); + mParser = new KeyValueListParser(','); + } + + @VisibleForTesting + SnoozeOption getDefaultOption() + { + return mDefaultOption; + } + + @VisibleForTesting + void setKeyValueListParser(KeyValueListParser parser) { + mParser = parser; } @Override @@ -172,17 +199,49 @@ public class NotificationSnooze extends LinearLayout mSbn = sbn; } - private ArrayList<SnoozeOption> getDefaultSnoozeOptions() { + @VisibleForTesting + ArrayList<SnoozeOption> getDefaultSnoozeOptions() { + final Resources resources = getContext().getResources(); ArrayList<SnoozeOption> options = new ArrayList<>(); + try { + final String config = Settings.Global.getString(getContext().getContentResolver(), + Settings.Global.NOTIFICATION_SNOOZE_OPTIONS); + mParser.setString(config); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Bad snooze constants"); + } + + final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE, + resources.getInteger(R.integer.config_notification_snooze_time_default)); + final int[] snoozeTimes = parseIntArray(KEY_OPTIONS, + resources.getIntArray(R.array.config_notification_snooze_times)); - options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min)); - options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min)); - mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour); - options.add(mDefaultOption); - options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours)); + for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) { + int snoozeTime = snoozeTimes[i]; + SnoozeOption option = createOption(snoozeTime, sAccessibilityActions[i]); + if (i == 0 || snoozeTime == defaultSnooze) { + mDefaultOption = option; + } + options.add(option); + } return options; } + @VisibleForTesting + int[] parseIntArray(final String key, final int[] defaultArray) { + final String value = mParser.getString(key, null); + if (value != null) { + try { + return Arrays.stream(value.split(":")).map(String::trim).mapToInt( + Integer::parseInt).toArray(); + } catch (NumberFormatException e) { + return defaultArray; + } + } else { + return defaultArray; + } + } + private SnoozeOption createOption(int minutes, int accessibilityActionId) { Resources res = getResources(); boolean showInHours = minutes >= 60; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java new file mode 100644 index 000000000000..6b31c967a247 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java @@ -0,0 +1,172 @@ +/* + * 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.systemui.statusbar; + +import android.provider.Settings; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableResources; +import android.testing.UiThreadTest; +import android.util.KeyValueListParser; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.anyString; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@UiThreadTest +public class NotificationSnoozeTest extends SysuiTestCase { + private static final int RES_DEFAULT = 2; + private static final int[] RES_OPTIONS = {1, 2, 3}; + private NotificationSnooze mNotificationSnooze; + private KeyValueListParser mMockParser; + + @Before + public void setUp() throws Exception { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, null); + TestableResources resources = mContext.getOrCreateTestableResources(); + resources.addOverride(R.integer.config_notification_snooze_time_default, RES_DEFAULT); + resources.addOverride(R.array.config_notification_snooze_times, RES_OPTIONS); + mNotificationSnooze = new NotificationSnooze(mContext, null); + mMockParser = mock(KeyValueListParser.class); + } + + @Test + public void testParseIntArrayNull() throws Exception { + when(mMockParser.getString(anyString(), isNull())).thenReturn(null); + mNotificationSnooze.setKeyValueListParser(mMockParser); + + int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS); + assertEquals(RES_OPTIONS, result); + } + + @Test + public void testParseIntArrayLeadingSep() throws Exception { + when(mMockParser.getString(anyString(), isNull())).thenReturn(":4:5:6"); + mNotificationSnooze.setKeyValueListParser(mMockParser); + + int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS); + assertEquals(RES_OPTIONS, result); + } + + @Test + public void testParseIntArrayEmptyItem() throws Exception { + when(mMockParser.getString(anyString(), isNull())).thenReturn("4::6"); + mNotificationSnooze.setKeyValueListParser(mMockParser); + + int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS); + assertEquals(RES_OPTIONS, result); + } + + @Test + public void testParseIntArrayTrailingSep() throws Exception { + when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6:"); + mNotificationSnooze.setKeyValueListParser(mMockParser); + + int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS); + assertEquals(3, result.length); + assertEquals(4, result[0]); // respect order + assertEquals(5, result[1]); + assertEquals(6, result[2]); + } + + @Test + public void testParseIntArrayGoodData() throws Exception { + when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6"); + mNotificationSnooze.setKeyValueListParser(mMockParser); + + int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS); + assertEquals(3, result.length); + assertEquals(4, result[0]); // respect order + assertEquals(5, result[1]); + assertEquals(6, result[2]); + } + + @Test + public void testGetOptionsWithNoConfig() throws Exception { + ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + assertEquals(3, result.size()); + assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order + assertEquals(2, result.get(1).getMinutesToSnoozeFor()); + assertEquals(3, result.get(2).getMinutesToSnoozeFor()); + assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor()); + } + + @Test + public void testGetOptionsWithInvalidConfig() throws Exception { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, + "this is garbage"); + ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + assertEquals(3, result.size()); + assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order + assertEquals(2, result.get(1).getMinutesToSnoozeFor()); + assertEquals(3, result.get(2).getMinutesToSnoozeFor()); + assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor()); + } + + @Test + public void testGetOptionsWithValidDefault() throws Exception { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, + "default=10,options_array=4:5:6:7"); + ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + assertNotNull(mNotificationSnooze.getDefaultOption()); // pick one + } + + @Test + public void testGetOptionsWithValidConfig() throws Exception { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, + "default=6,options_array=4:5:6:7"); + ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + assertEquals(4, result.size()); + assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order + assertEquals(5, result.get(1).getMinutesToSnoozeFor()); + assertEquals(6, result.get(2).getMinutesToSnoozeFor()); + assertEquals(7, result.get(3).getMinutesToSnoozeFor()); + assertEquals(6, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor()); + } + + @Test + public void testGetOptionsWithLongConfig() throws Exception { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, + "default=6,options_array=4:5:6:7:8:9:10:11:12:13:14:15:16:17"); + ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + assertTrue(result.size() > 3); + assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order + assertEquals(5, result.get(1).getMinutesToSnoozeFor()); + assertEquals(6, result.get(2).getMinutesToSnoozeFor()); + } +} |