Merge "Enable experimentation on notification snooze options" into oc-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9cb14a0c95
@@ -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";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
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));
|
||||
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));
|
||||
|
||||
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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user