Add Night Light Suggestion and tests

Night Light settings suggestion will only show when the user
has not previously interacted with Night Light.

Bug: 37207263
Test: make ROBOTEST_FILTER=\
"(SettingsSuggestionsTest|SuggestionFeatureProviderImplTest)"\
RunSettingsRoboTests
Change-Id: I432d5fef19f5e4a52503da136b044598cb82164a
This commit is contained in:
Sean Stout
2017-05-18 13:39:25 -07:00
parent c97e9bc39d
commit 2bc94d6271
12 changed files with 154 additions and 52 deletions

View File

@@ -921,6 +921,26 @@
android:value="true" /> android:value="true" />
</activity> </activity>
<activity android:name="Settings$NightDisplaySuggestionActivity"
android:enabled="@*android:bool/config_nightDisplayAvailable"
android:taskAffinity=""
android:icon="@drawable/ic_settings_night_display">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.settings.suggested.category.FIRST_IMPRESSION" />
</intent-filter>
<meta-data android:name="com.android.settings.dismiss"
android:value="6,10,30" />
<meta-data android:name="com.android.settings.title"
android:resource="@string/night_display_suggestion_title" />
<meta-data android:name="com.android.settings.summary"
android:resource="@string/night_display_suggestion_summary" />
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.display.NightDisplaySettings" />
</activity>
<activity android:name="Settings$DeviceInfoSettingsActivity" <activity android:name="Settings$DeviceInfoSettingsActivity"
android:label="@string/device_info_settings" android:label="@string/device_info_settings"
android:icon="@drawable/ic_settings_about" android:icon="@drawable/ic_settings_about"

View File

@@ -8108,6 +8108,12 @@
<!-- Summary of condition that work mode is off [CHAR LIMIT=NONE] --> <!-- Summary of condition that work mode is off [CHAR LIMIT=NONE] -->
<string name="condition_work_summary">Apps, background sync, and other features related to your work profile are turned off.</string> <string name="condition_work_summary">Apps, background sync, and other features related to your work profile are turned off.</string>
<!-- Night display: Title for the night display option Suggestion (renamed "Night Light" with title caps). [CHAR LIMIT=30] -->
<string name="night_display_suggestion_title">Set Night Light schedule</string>
<!-- Night display: Summary for the night display option Suggestion (renamed "Night Light" with title caps). [CHAR LIMIT=NONE] -->
<string name="night_display_suggestion_summary">Tint screen amber to help you fall asleep</string>
<!-- Title of condition that night display is on (renamed "Night Light" with title caps). [CHAR LIMIT=30] --> <!-- Title of condition that night display is on (renamed "Night Light" with title caps). [CHAR LIMIT=30] -->
<string name="condition_night_display_title">Night Light is on</string> <string name="condition_night_display_title">Night Light is on</string>

View File

@@ -52,6 +52,7 @@ public class Settings extends SettingsActivity {
public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ } public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ }
public static class DisplaySettingsActivity extends SettingsActivity { /* empty */ } public static class DisplaySettingsActivity extends SettingsActivity { /* empty */ }
public static class NightDisplaySettingsActivity extends SettingsActivity { /* empty */ } public static class NightDisplaySettingsActivity extends SettingsActivity { /* empty */ }
public static class NightDisplaySuggestionActivity extends NightDisplaySettingsActivity { /* empty */ }
public static class DeviceInfoSettingsActivity extends SettingsActivity { /* empty */ } public static class DeviceInfoSettingsActivity extends SettingsActivity { /* empty */ }
public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ } public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ManageApplicationsActivity extends SettingsActivity { /* empty */ } public static class ManageApplicationsActivity extends SettingsActivity { /* empty */ }

View File

@@ -21,9 +21,13 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.content.ContentResolver;
import android.provider.Settings.Secure;
import android.support.annotation.VisibleForTesting;
import android.util.Log; import android.util.Log;
import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.Settings.NightDisplaySuggestionActivity;
import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.core.instrumentation.MetricsFeatureProvider;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import com.android.settings.support.NewDeviceIntroSuggestionActivity; import com.android.settings.support.NewDeviceIntroSuggestionActivity;
@@ -50,6 +54,9 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider
@Override @Override
public boolean isSuggestionCompleted(Context context, @NonNull ComponentName component) { public boolean isSuggestionCompleted(Context context, @NonNull ComponentName component) {
final String className = component.getClassName(); final String className = component.getClassName();
if (className.equals(NightDisplaySuggestionActivity.class.getName())) {
return hasUsedNightDisplay(context);
}
if (className.equals(NewDeviceIntroSuggestionActivity.class.getName())) { if (className.equals(NewDeviceIntroSuggestionActivity.class.getName())) {
return NewDeviceIntroSuggestionActivity.isSuggestionComplete(context); return NewDeviceIntroSuggestionActivity.isSuggestionComplete(context);
} }
@@ -119,4 +126,11 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider
return packageName; return packageName;
} }
@VisibleForTesting
boolean hasUsedNightDisplay(Context context) {
final ContentResolver cr = context.getContentResolver();
final long lastActivatedTimeMillis = Secure.getLong(cr,
Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1);
return lastActivatedTimeMillis > 0;
}
} }

View File

@@ -78,8 +78,8 @@ public class AssistFlashScreenPreferenceControllerTest {
@Config(shadows = {ShadowSecureSettings.class}) @Config(shadows = {ShadowSecureSettings.class})
public void isAvailable_hasAssistantAndAllowDisclosure_shouldReturnTrue() { public void isAvailable_hasAssistantAndAllowDisclosure_shouldReturnTrue() {
ReflectionHelpers.setField(mController, "mContext", mMockContext); ReflectionHelpers.setField(mController, "mContext", mMockContext);
ShadowSecureSettings.putString(null, Settings.Secure.ASSISTANT, final ContentResolver cr = mContext.getContentResolver();
"com.android.settings/assist"); Settings.Secure.putString(cr, Settings.Secure.ASSISTANT, "com.android.settings/assist");
doReturn(true).when(mController).allowDisablingAssistDisclosure(); doReturn(true).when(mController).allowDisablingAssistDisclosure();
assertThat(mController.isAvailable()).isTrue(); assertThat(mController.isAvailable()).isTrue();
@@ -89,8 +89,8 @@ public class AssistFlashScreenPreferenceControllerTest {
@Config(shadows = {ShadowSecureSettings.class}) @Config(shadows = {ShadowSecureSettings.class})
public void isAvailable_hasAssistantAndDisallowDisclosure_shouldReturnTrue() { public void isAvailable_hasAssistantAndDisallowDisclosure_shouldReturnTrue() {
ReflectionHelpers.setField(mController, "mContext", mMockContext); ReflectionHelpers.setField(mController, "mContext", mMockContext);
ShadowSecureSettings.putString(null, Settings.Secure.ASSISTANT, final ContentResolver cr = mContext.getContentResolver();
"com.android.settings/assist"); Settings.Secure.putString(cr, Settings.Secure.ASSISTANT, "com.android.settings/assist");
doReturn(false).when(mController).allowDisablingAssistDisclosure(); doReturn(false).when(mController).allowDisablingAssistDisclosure();
assertThat(mController.isAvailable()).isFalse(); assertThat(mController.isAvailable()).isFalse();
@@ -98,8 +98,7 @@ public class AssistFlashScreenPreferenceControllerTest {
@Test @Test
public void isAvailable_hasNoAssistant_shouldReturnFalse() { public void isAvailable_hasNoAssistant_shouldReturnFalse() {
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.ASSISTANT, "");
Settings.Secure.ASSISTANT, "");
assertThat(mController.isAvailable()).isFalse(); assertThat(mController.isAvailable()).isFalse();
} }

View File

@@ -78,7 +78,7 @@ public class DefaultAssistPreferenceControllerTest {
@Config(shadows = {ShadowSecureSettings.class}) @Config(shadows = {ShadowSecureSettings.class})
public void getDefaultAppInfo_hasDefaultAssist_shouldReturnKey() { public void getDefaultAppInfo_hasDefaultAssist_shouldReturnKey() {
final String flattenKey = "com.android.settings/assist"; final String flattenKey = "com.android.settings/assist";
ShadowSecureSettings.putString(null, Settings.Secure.ASSISTANT, flattenKey); Settings.Secure.putString(null, Settings.Secure.ASSISTANT, flattenKey);
DefaultAppInfo appInfo = mController.getDefaultAppInfo(); DefaultAppInfo appInfo = mController.getDefaultAppInfo();
assertThat(appInfo.getKey()).isEqualTo(flattenKey); assertThat(appInfo.getKey()).isEqualTo(flattenKey);
@@ -87,7 +87,7 @@ public class DefaultAssistPreferenceControllerTest {
@Test @Test
public void getSettingIntent_noSettingsActivity_shouldNotCrash() { public void getSettingIntent_noSettingsActivity_shouldNotCrash() {
final String flattenKey = "com.android.settings/assist"; final String flattenKey = "com.android.settings/assist";
ShadowSecureSettings.putString(null, Settings.Secure.ASSISTANT, flattenKey); Settings.Secure.putString(null, Settings.Secure.ASSISTANT, flattenKey);
when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mContext.getPackageManager()).thenReturn(mPackageManager);
DefaultAssistPreferenceController controller = DefaultAssistPreferenceController controller =
spy(new DefaultAssistPreferenceController(mContext)); spy(new DefaultAssistPreferenceController(mContext));

View File

@@ -16,14 +16,19 @@
package com.android.settings.dashboard.suggestions; package com.android.settings.dashboard.suggestions;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.provider.Settings.Secure;
import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.Settings.NightDisplaySuggestionActivity;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.Settings;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowSecureSettings;
import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.Tile;
import com.android.settingslib.suggestions.SuggestionParser; import com.android.settingslib.suggestions.SuggestionParser;
@@ -50,7 +55,9 @@ import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @Config(manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
shadows = ShadowSecureSettings.class)
public class SuggestionFeatureProviderImplTest { public class SuggestionFeatureProviderImplTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -172,4 +179,30 @@ public class SuggestionFeatureProviderImplTest {
assertThat(suggestions).hasSize(3); assertThat(suggestions).hasSize(3);
} }
@Test
public void hasUsedNightDisplay_returnsFalse_byDefault() {
assertThat(mProvider.hasUsedNightDisplay(mContext)).isFalse();
}
@Test
public void hasUsedNightDisplay_returnsTrue_ifPreviouslyActivated() {
Secure.putLong(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, 1L);
assertThat(mProvider.hasUsedNightDisplay(mContext)).isTrue();
}
@Test
public void nightDisplaySuggestion_isCompleted_ifPreviouslyActivated() {
Secure.putLong(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, 1L);
final ComponentName componentName =
new ComponentName(mContext, NightDisplaySuggestionActivity.class);
assertThat(mProvider.isSuggestionCompleted(mContext, componentName)).isTrue();
}
@Test
public void nightDisplaySuggestion_isNotCompleted_byDefault() {
final ComponentName componentName =
new ComponentName(mContext, NightDisplaySuggestionActivity.class);
assertThat(mProvider.isSuggestionCompleted(mContext, componentName)).isFalse();
}
} }

View File

@@ -90,7 +90,6 @@ public class SuggestionsChecksTest {
assertThat(mSuggestionsChecks.isSuggestionComplete(tile)).isFalse(); assertThat(mSuggestionsChecks.isSuggestionComplete(tile)).isFalse();
} }
@Test @Test
public void testFingerprintEnrollmentIntroductionIsCompleteWhenFingerprintNotSupported() { public void testFingerprintEnrollmentIntroductionIsCompleteWhenFingerprintNotSupported() {
stubFingerprintSupported(false); stubFingerprintSupported(false);
@@ -115,7 +114,7 @@ public class SuggestionsChecksTest {
} }
private Tile createFingerprintTile() { private Tile createFingerprintTile() {
Tile tile = new Tile(); final Tile tile = new Tile();
tile.intent = new Intent(); tile.intent = new Intent();
tile.intent.setComponent(new ComponentName(mContext, tile.intent.setComponent(new ComponentName(mContext,
Settings.FingerprintEnrollSuggestionActivity.class)); Settings.FingerprintEnrollSuggestionActivity.class));

View File

@@ -26,7 +26,6 @@ import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.shadow.ShadowSecureSettings; import com.android.settings.testutils.shadow.ShadowSecureSettings;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -65,11 +64,6 @@ public class DoubleTwistPreferenceControllerTest {
mController = new DoubleTwistPreferenceController(mContext, null, KEY_DOUBLE_TWIST); mController = new DoubleTwistPreferenceController(mContext, null, KEY_DOUBLE_TWIST);
} }
@After
public void tearDown() {
ShadowSecureSettings.clear();
}
@Test @Test
public void isAvailable_hasSensor_shouldReturnTrue() { public void isAvailable_hasSensor_shouldReturnTrue() {
// Mock sensors // Mock sensors

View File

@@ -19,6 +19,7 @@ package com.android.settings.language;
import android.app.Activity; import android.app.Activity;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.hardware.input.InputManager; import android.hardware.input.InputManager;
@@ -119,7 +120,6 @@ public class LanguageAndInputSettingsTest {
} }
@Test @Test
public void testGetPreferenceControllers_shouldAllBeCreated() { public void testGetPreferenceControllers_shouldAllBeCreated() {
final List<PreferenceController> controllers = final List<PreferenceController> controllers =
mFragment.getPreferenceControllers(mActivity); mFragment.getPreferenceControllers(mActivity);
@@ -135,7 +135,8 @@ public class LanguageAndInputSettingsTest {
final Activity activity = mock(Activity.class); final Activity activity = mock(Activity.class);
final SummaryLoader loader = mock(SummaryLoader.class); final SummaryLoader loader = mock(SummaryLoader.class);
final ComponentName componentName = new ComponentName("pkg", "cls"); final ComponentName componentName = new ComponentName("pkg", "cls");
ShadowSecureSettings.putString(null, Settings.Secure.DEFAULT_INPUT_METHOD, final ContentResolver cr = activity.getContentResolver();
Settings.Secure.putString(cr, Settings.Secure.DEFAULT_INPUT_METHOD,
componentName.flattenToString()); componentName.flattenToString());
when(activity.getSystemService(Context.INPUT_METHOD_SERVICE)) when(activity.getSystemService(Context.INPUT_METHOD_SERVICE))
.thenReturn(mInputMethodManager); .thenReturn(mInputMethodManager);
@@ -168,11 +169,12 @@ public class LanguageAndInputSettingsTest {
SummaryLoader.SummaryProvider provider = mFragment.SUMMARY_PROVIDER_FACTORY SummaryLoader.SummaryProvider provider = mFragment.SUMMARY_PROVIDER_FACTORY
.createSummaryProvider(mActivity, loader); .createSummaryProvider(mActivity, loader);
ShadowSecureSettings.putInt(null, Settings.Secure.ASSIST_GESTURE_ENABLED, 0); final ContentResolver cr = mActivity.getContentResolver();
Settings.Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 0);
provider.setListening(true); provider.setListening(true);
verify(mActivity).getString(R.string.language_input_gesture_summary_off); verify(mActivity).getString(R.string.language_input_gesture_summary_off);
ShadowSecureSettings.putInt(null, Settings.Secure.ASSIST_GESTURE_ENABLED, 1); Settings.Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
provider.setListening(true); provider.setListening(true);
verify(mActivity).getString(R.string.language_input_gesture_summary_on_with_assist); verify(mActivity).getString(R.string.language_input_gesture_summary_on_with_assist);
} }

View File

@@ -79,6 +79,13 @@ public class SettingsSuggestionsTest {
R.string.wifi_calling_suggestion_title, R.string.wifi_calling_suggestion_summary); R.string.wifi_calling_suggestion_title, R.string.wifi_calling_suggestion_summary);
} }
@Test
public void nightDisplaySuggestion_isValid() {
assertSuggestionEquals("Settings$NightDisplaySuggestionActivity",
CATEGORY_FIRST_IMPRESSION,
R.string.night_display_suggestion_title, R.string.night_display_suggestion_summary);
}
private void assertSuggestionEquals(String activityName, String category, @StringRes int title, private void assertSuggestionEquals(String activityName, String category, @StringRes int title,
@StringRes int summary) { @StringRes int summary) {
final AndroidManifest androidManifest = ShadowApplication.getInstance().getAppManifest(); final AndroidManifest androidManifest = ShadowApplication.getInstance().getAppManifest();

View File

@@ -18,58 +18,85 @@ package com.android.settings.testutils.shadow;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.provider.Settings; import android.provider.Settings;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import java.util.Map;
import java.util.WeakHashMap;
import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements; import org.robolectric.annotation.Implements;
import java.util.HashMap;
import java.util.Map;
@Implements(Settings.Secure.class) @Implements(Settings.Secure.class)
public class ShadowSecureSettings { public class ShadowSecureSettings {
private static final Map<String, Object> mValueMap = new HashMap<>(); private static final Map<ContentResolver, Table<Integer, String, Object>> sUserDataMap =
new WeakHashMap<>();
@Implementation @Implementation
public static boolean putInt(ContentResolver resolver, String name, int value) { public static boolean putStringForUser(ContentResolver resolver, String name, String value,
mValueMap.put(name, value); int userHandle) {
final Table<Integer, String, Object> userTable = getUserTable(resolver);
synchronized (userTable) {
userTable.put(userHandle, name, value);
return true; return true;
} }
@Implementation
public static boolean putString(ContentResolver resolver, String name, String value) {
mValueMap.put(name, value);
return true;
}
@Implementation
public static String getString(ContentResolver resolver, String name) {
return (String) mValueMap.get(name);
} }
@Implementation @Implementation
public static String getStringForUser(ContentResolver resolver, String name, int userHandle) { public static String getStringForUser(ContentResolver resolver, String name, int userHandle) {
return getString(resolver, name); final Table<Integer, String, Object> userTable = getUserTable(resolver);
synchronized (userTable) {
return (String) userTable.get(userHandle, name);
}
} }
@Implementation @Implementation
public static boolean putIntForUser(ContentResolver cr, String name, int value, public static boolean putIntForUser(ContentResolver resolver, String name, int value,
int userHandle) { int userHandle) {
return putInt(cr, name, value); final Table<Integer, String, Object> userTable = getUserTable(resolver);
synchronized (userTable) {
userTable.put(userHandle, name, value);
return true;
}
} }
@Implementation @Implementation
public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { public static int getIntForUser(ContentResolver resolver, String name, int def,
return getInt(cr, name, def); int userHandle) {
final Table<Integer, String, Object> userTable = getUserTable(resolver);
synchronized (userTable) {
final Object object = userTable.get(userHandle, name);
return object instanceof Integer ? (Integer) object : def;
}
} }
@Implementation @Implementation
public static int getInt(ContentResolver resolver, String name, int defaultValue) { public static boolean putLongForUser(ContentResolver resolver, String name, long value,
Integer value = (Integer) mValueMap.get(name); int userHandle) {
return value == null ? defaultValue : value; final Table<Integer, String, Object> userTable = getUserTable(resolver);
synchronized (userTable) {
userTable.put(userHandle, name, value);
return true;
}
} }
public static void clear() { @Implementation
mValueMap.clear(); public static long getLongForUser(ContentResolver resolver, String name, long def,
int userHandle) {
final Table<Integer, String, Object> userTable = getUserTable(resolver);
synchronized (userTable) {
final Object object = userTable.get(userHandle, name);
return object instanceof Long ? (Long) object : def;
}
}
private static Table<Integer, String, Object> getUserTable(ContentResolver contentResolver) {
synchronized (sUserDataMap) {
Table<Integer, String, Object> table = sUserDataMap.get(contentResolver);
if (table == null) {
table = HashBasedTable.create();
sUserDataMap.put(contentResolver, table);
}
return table;
}
} }
} }