Adds contextual cards for screen attention in Settings Homepage

Bug: 128527964
Test: atest ContextualAdaptiveSleepSliceTest, maually verified.
Change-Id: Ifaea7d8d4391e91cf6cbde38a2506728f55913d8
This commit is contained in:
Yi Jiang
2019-04-16 14:51:11 -07:00
parent 0fbbad92f9
commit 7605494cc5
8 changed files with 355 additions and 14 deletions

View File

@@ -25,9 +25,9 @@ import com.android.settings.core.TogglePreferenceController;
public class AdaptiveSleepPreferenceController extends TogglePreferenceController {
private final String SYSTEM_KEY = ADAPTIVE_SLEEP;
private final int DEFAULT_VALUE = 0;
public static final String PREF_NAME = "adaptive_sleep";
private static final String SYSTEM_KEY = ADAPTIVE_SLEEP;
private static final int DEFAULT_VALUE = 0;
final boolean hasSufficientPermissions;
@@ -35,9 +35,7 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle
super(context, key);
final PackageManager packageManager = mContext.getPackageManager();
final String attentionPackage = packageManager.getAttentionServicePackageName();
hasSufficientPermissions = attentionPackage != null && packageManager.checkPermission(
Manifest.permission.CAMERA, attentionPackage) == PackageManager.PERMISSION_GRANTED;
hasSufficientPermissions = hasSufficientPermission(packageManager);
}
@Override
@@ -46,7 +44,6 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle
SYSTEM_KEY, DEFAULT_VALUE) != DEFAULT_VALUE;
}
@Override
public boolean setChecked(boolean isChecked) {
Settings.System.putInt(mContext.getContentResolver(), SYSTEM_KEY,
@@ -57,10 +54,7 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle
@Override
@AvailabilityStatus
public int getAvailabilityStatus() {
return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_adaptive_sleep_available)
? AVAILABLE_UNSEARCHABLE
: UNSUPPORTED_ON_DEVICE;
return isControllerAvailable(mContext);
}
@Override
@@ -69,4 +63,17 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle
? R.string.adaptive_sleep_summary_on
: R.string.adaptive_sleep_summary_off);
}
public static int isControllerAvailable(Context context) {
return context.getResources().getBoolean(
com.android.internal.R.bool.config_adaptive_sleep_available)
? AVAILABLE_UNSEARCHABLE
: UNSUPPORTED_ON_DEVICE;
}
private static boolean hasSufficientPermission(PackageManager packageManager) {
final String attentionPackage = packageManager.getAttentionServicePackageName();
return attentionPackage != null && packageManager.checkPermission(
Manifest.permission.CAMERA, attentionPackage) == PackageManager.PERMISSION_GRANTED;
}
}

View File

@@ -16,10 +16,15 @@
package com.android.settings.display;
import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF;
import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF_KEY_INTERACTED;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import android.util.Log;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
@@ -40,8 +45,15 @@ public class AdaptiveSleepSettings extends DashboardFragment {
super.onCreate(icicle);
final FooterPreference footerPreference =
mFooterPreferenceMixin.createFooterPreference();
final Context context = getContext();
footerPreference.setIcon(R.drawable.ic_privacy_shield_24dp);
footerPreference.setTitle(R.string.adaptive_sleep_privacy);
context.getSharedPreferences(PREF, Context.MODE_PRIVATE)
.edit()
.putBoolean(PREF_KEY_INTERACTED, true)
.apply();
}
@Override

View File

@@ -64,12 +64,21 @@ public class SettingsContextualCardProvider extends ContextualCardProvider {
.setCardName(contextualNotificationChannelSliceUri)
.setCardCategory(ContextualCard.Category.POSSIBLE)
.build();
final String contextualAdaptiveSleepSliceUri =
CustomSliceRegistry.CONTEXTUAL_ADAPTIVE_SLEEP_URI.toString();
final ContextualCard contextualAdaptiveSleepCard =
ContextualCard.newBuilder()
.setSliceUri(contextualAdaptiveSleepSliceUri)
.setCardName(contextualAdaptiveSleepSliceUri)
.setCardCategory(ContextualCard.Category.DEFAULT)
.build();
final ContextualCardList cards = ContextualCardList.newBuilder()
.addCard(wifiCard)
.addCard(connectedDeviceCard)
.addCard(lowStorageCard)
.addCard(batteryFixCard)
.addCard(notificationChannelCard)
.addCard(contextualAdaptiveSleepCard)
.build();
return cards;

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2019 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.homepage.contextualcards.slices;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.display.AdaptiveSleepPreferenceController.PREF_NAME;
import static com.android.settings.display.AdaptiveSleepPreferenceController.isControllerAvailable;
import static com.android.settings.slices.CustomSliceRegistry.CONTEXTUAL_ADAPTIVE_SLEEP_URI;
import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.SliceAction;
import com.android.settings.R;
import com.android.settings.SubSettings;
import com.android.settings.display.AdaptiveSleepSettings;
import com.android.settings.slices.CustomSliceable;
import com.android.settings.slices.SliceBuilderUtils;
import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.TimeUnit;
public class ContextualAdaptiveSleepSlice implements CustomSliceable {
private static final String TAG = "ContextualAdaptiveSleepSlice";
private static final long DEFAULT_SETUP_TIME = 0;
private Context mContext;
@VisibleForTesting
static final long DEFERRED_TIME_DAYS = TimeUnit.DAYS.toMillis(14);
@VisibleForTesting
static final String PREF_KEY_SETUP_TIME = "adaptive_sleep_setup_time";
public static final String PREF_KEY_INTERACTED = "adaptive_sleep_interacted";
public static final String PREF = "adaptive_sleep_slice";
public ContextualAdaptiveSleepSlice(Context context) {
mContext = context;
}
@Override
public Slice getSlice() {
final long setupTime = mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE).getLong(
PREF_KEY_SETUP_TIME, DEFAULT_SETUP_TIME);
if (setupTime == DEFAULT_SETUP_TIME) {
// Set the first setup time.
mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE)
.edit()
.putLong(PREF_KEY_SETUP_TIME, System.currentTimeMillis())
.apply();
return null;
}
// Display the contextual card only if all the following 3 conditions hold:
// 1. The Screen Attention is enabled in Settings.
// 2. The device is not recently set up.
// 3. Current user hasn't opened Screen Attention's settings page before.
if (isSettingsAvailable() && !isUserInteracted() && !isRecentlySetup()) {
final IconCompat icon = IconCompat.createWithResource(mContext,
R.drawable.ic_settings_adaptive_sleep);
final CharSequence title = mContext.getText(R.string.adaptive_sleep_title);
final CharSequence subtitle = mContext.getText(R.string.adaptive_sleep_description);
final SliceAction pAction = SliceAction.createDeeplink(getPrimaryAction(),
icon,
ListBuilder.ICON_IMAGE,
title);
final ListBuilder listBuilder = new ListBuilder(mContext,
CONTEXTUAL_ADAPTIVE_SLEEP_URI,
ListBuilder.INFINITY)
.addRow(new ListBuilder.RowBuilder()
.setTitleItem(icon, ListBuilder.ICON_IMAGE)
.setTitle(title)
.setSubtitle(subtitle)
.setPrimaryAction(pAction));
return listBuilder.build();
} else {
return null;
}
}
@Override
public Uri getUri() {
return CONTEXTUAL_ADAPTIVE_SLEEP_URI;
}
@Override
public Intent getIntent() {
final CharSequence screenTitle = mContext.getText(R.string.adaptive_sleep_title);
final Uri contentUri = new Uri.Builder().appendPath(PREF_NAME).build();
return SliceBuilderUtils.buildSearchResultPageIntent(mContext,
AdaptiveSleepSettings.class.getName(), PREF_NAME, screenTitle.toString(),
SettingsEnums.SLICE).setClassName(mContext.getPackageName(),
SubSettings.class.getName()).setData(contentUri);
}
private PendingIntent getPrimaryAction() {
final Intent intent = getIntent();
return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */);
}
/**
* @return {@code true} if the current user has opened the Screen Attention settings page
* before, otherwise {@code false}.
*/
private boolean isUserInteracted() {
return mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE).getBoolean(
PREF_KEY_INTERACTED, false);
}
/**
* The device is recently set up means its first settings-open time is within 2 weeks ago.
*
* @return {@code true} if the device is recently set up, otherwise {@code false}.
*/
private boolean isRecentlySetup() {
final long endTime = System.currentTimeMillis() - DEFERRED_TIME_DAYS;
final long firstSetupTime = mContext.getSharedPreferences(PREF,
Context.MODE_PRIVATE).getLong(PREF_KEY_SETUP_TIME, DEFAULT_SETUP_TIME);
return firstSetupTime > endTime;
}
/**
* Check whether the screen attention settings is enabled. Contextual card will only appear
* when the screen attention settings is available.
*
* @return {@code true} if screen attention settings is enabled, otherwise {@code false}
*/
@VisibleForTesting
boolean isSettingsAvailable() {
return isControllerAvailable(mContext) == AVAILABLE;
}
}

View File

@@ -26,6 +26,7 @@ import android.util.ArrayMap;
import androidx.annotation.VisibleForTesting;
import com.android.settings.display.AdaptiveSleepPreferenceController;
import com.android.settings.flashlight.FlashlightSlice;
import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController;
import com.android.settings.homepage.contextualcards.deviceinfo.DataUsageSlice;
@@ -34,6 +35,7 @@ import com.android.settings.homepage.contextualcards.deviceinfo.EmergencyInfoSli
import com.android.settings.homepage.contextualcards.deviceinfo.StorageSlice;
import com.android.settings.homepage.contextualcards.slices.BatteryFixSlice;
import com.android.settings.homepage.contextualcards.slices.BluetoothDevicesSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice;
import com.android.settings.homepage.contextualcards.slices.LowStorageSlice;
import com.android.settings.homepage.contextualcards.slices.NotificationChannelSlice;
@@ -64,6 +66,16 @@ public class CustomSliceRegistry {
.appendPath(SettingsSlicesContract.KEY_AIRPLANE_MODE)
.build();
/**
* Uri for Contextual Adaptive Sleep Slice
*/
public static final Uri CONTEXTUAL_ADAPTIVE_SLEEP_URI = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(SettingsSliceProvider.SLICE_AUTHORITY)
.appendPath(SettingsSlicesContract.PATH_SETTING_INTENT)
.appendPath(AdaptiveSleepPreferenceController.PREF_NAME)
.build();
/**
* Uri for Battery Fix Slice.
*/
@@ -328,6 +340,7 @@ public class CustomSliceRegistry {
sUriToSlice.put(BATTERY_FIX_SLICE_URI, BatteryFixSlice.class);
sUriToSlice.put(BLUETOOTH_DEVICES_SLICE_URI, BluetoothDevicesSlice.class);
sUriToSlice.put(CONTEXTUAL_ADAPTIVE_SLEEP_URI, ContextualAdaptiveSleepSlice.class);
sUriToSlice.put(CONTEXTUAL_NOTIFICATION_CHANNEL_SLICE_URI,
ContextualNotificationChannelSlice.class);
sUriToSlice.put(CONTEXTUAL_WIFI_SLICE_URI, ContextualWifiSlice.class);
@@ -337,12 +350,12 @@ public class CustomSliceRegistry {
sUriToSlice.put(FLASHLIGHT_SLICE_URI, FlashlightSlice.class);
sUriToSlice.put(LOCATION_SLICE_URI, LocationSlice.class);
sUriToSlice.put(LOW_STORAGE_SLICE_URI, LowStorageSlice.class);
sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class);
sUriToSlice.put(MEDIA_OUTPUT_SLICE_URI, MediaOutputSlice.class);
sUriToSlice.put(MOBILE_DATA_SLICE_URI, MobileDataSlice.class);
sUriToSlice.put(NOTIFICATION_CHANNEL_SLICE_URI, NotificationChannelSlice.class);
sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class);
sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class);
sUriToSlice.put(MEDIA_OUTPUT_SLICE_URI, MediaOutputSlice.class);
sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class);
}
public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) {