Merge "Enable experimentation on notification snooze options" into oc-mr1-dev

This commit is contained in:
TreeHugger Robot
2017-10-03 00:02:08 +00:00
committed by Android (Google) Code Review
6 changed files with 272 additions and 10 deletions

View File

@@ -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";
}
/**

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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());
}
}