Merge "Fix exception on non face-enabled devices" into qt-r1-dev
diff --git a/res/drawable/ic_settings_adaptive_sleep.xml b/res/drawable/ic_settings_adaptive_sleep.xml
new file mode 100644
index 0000000..765ed94
--- /dev/null
+++ b/res/drawable/ic_settings_adaptive_sleep.xml
@@ -0,0 +1,31 @@
+<!--
+  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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M10.67,21H7v-1h3.13c-0.27-0.63-0.46-1.3-0.56-2H7V6h10v3.5c0.69,0,1.36,0.1,2,0.28V3 c0-1.1-0.9-1.99-2-1.99L7,1C5.9,1,5,1.9,5,3v18c0,1.1,0.9,2,2,2h5.52C11.79,22.45,11.16,21.77,10.67,21z M7,3h10v1H7V3z" />
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M17,12.5c2.48,0,4.5,2.02,4.5,4.5s-2.02,4.5-4.5,4.5s-4.5-2.02-4.5-4.5S14.52,12.5,17,12.5 M17,11 c-3.31,0-6,2.69-6,6s2.69,6,6,6c3.31,0,6-2.69,6-6S20.31,11,17,11L17,11z" />
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M17,14v3l-2.12,2.12C15.42,19.66,16.17,20,17,20c1.66,0,3-1.34,3-3S18.66,14,17,14z" />
+</vector>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index aab91c5..3390831 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -885,7 +885,7 @@
     <!-- Message shown in summary field when face unlock is set up. [CHAR LIMIT=40] -->
     <string name="security_settings_face_preference_summary">Face added</string>
     <!-- Message shown in summary field when face unlock is not set up. [CHAR LIMIT=54] -->
-    <string name="security_settings_face_preference_summary_none">Tap to set up face unlock</string>
+    <string name="security_settings_face_preference_summary_none">Set up face unlock</string>
     <!-- Title shown for menu item that launches face settings or enrollment. [CHAR LIMIT=32] -->
     <string name="security_settings_face_preference_title">Face unlock</string>
     <!-- Introduction title shown in face enrollment education screen [CHAR LIMIT=40] -->
@@ -955,25 +955,25 @@
     <!-- Text shown on a toggle which allows or disallows the device to use face unlock for apps. This will be presented to the user together with the context of security_settings_face_settings_use_face_category. [CHAR LIMIT=30] -->
     <string name="security_settings_face_settings_use_face_for_apps">App sign-in \u0026 payments</string>
     <!-- Title for a category shown for the face settings page, followed by items that the user can toggle on/off to require/disable. -->
-    <string name="security_settings_face_settings_require_category">Require for face unlock</string>
+    <string name="security_settings_face_settings_require_category">Requirements for face unlock</string>
     <!-- Text shown on a toggle which disables/enables face unlock, depending if the user's eyes are open. [CHAR LIMIT=30] -->
-    <string name="security_settings_face_settings_require_attention">Open eyes looking at screen</string>
+    <string name="security_settings_face_settings_require_attention">Require eyes to be open</string>
     <!-- Text shown on the details of a toggle which disables/enables face unlock, depending if the user's eyes are open. [CHAR LIMIT=70] -->
-    <string name="security_settings_face_settings_require_attention_details">To unlock the phone, always require looking at the screen with your eyes open</string>
+    <string name="security_settings_face_settings_require_attention_details">To unlock the phone, your eyes must be open</string>
     <!-- When authenticating in apps, always require confirmation (e.g. confirm button) after a face is authenticated. [CHAR LIMIT=50] -->
-    <string name="security_settings_face_settings_require_confirmation">Confirm button</string>
+    <string name="security_settings_face_settings_require_confirmation">Always require confirmation</string>
     <!-- When authenticating in apps, always require confirmation (e.g. confirm button) after a face is authenticated. [CHAR LIMIT=70] -->
-    <string name="security_settings_face_settings_require_confirmation_details">When authenticating for apps, always require confirmation</string>
+    <string name="security_settings_face_settings_require_confirmation_details">When using face unlock in apps, always require confirmation step</string>
     <!-- Button text in face settings which removes the user's faces from the device [CHAR LIMIT=20] -->
     <string name="security_settings_face_settings_remove_face_data">Delete face data</string>
     <!-- Button text in face settings which lets the user enroll their face [CHAR LIMIT=40] -->
-    <string name="security_settings_face_settings_enroll">Set up new face unlock</string>
+    <string name="security_settings_face_settings_enroll">Set up face unlock</string>
     <!-- Text shown in face settings explaining what your face can be used for. [CHAR LIMIT=NONE] -->
     <string name="security_settings_face_settings_footer">Use face unlock to unlock your device, sign in to apps, and confirm payments.\n\nKeep in mind:\nLooking at the phone can unlock it when you don\u2019t intend to.\n\nYour phone can be unlocked by someone else if it\u2019s held up to your face while your eyes are open.\n\nYour phone can be unlocked by someone who looks a lot like you, say, an identical sibling.</string>
     <!-- Dialog title shown when the user removes an enrollment [CHAR LIMIT=35] -->
     <string name="security_settings_face_settings_remove_dialog_title">Delete face data?</string>
     <!-- Dialog contents shown when the user removes an enrollment [CHAR LIMIT=NONE] -->
-    <string name="security_settings_face_settings_remove_dialog_details">Data recorded by face unlock will be permanently and securely deleted. After removal, you will need your PIN, pattern, or password to unlock your phone, sign in to apps, and confirm payments.</string>
+    <string name="security_settings_face_settings_remove_dialog_details">The images and biometric data used by face unlock will be permanently and securely deleted. After removal, you will need your PIN, pattern, or password to unlock your phone, sign in to apps, and confirm payments.</string>
 
 
     <!-- Fingerprint enrollment and settings --><skip />
@@ -1075,11 +1075,11 @@
     <!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set fingerprint. (default) [CHAR LIMIT=NONE] -->
     <string name="fingerprint_lock_screen_setup_skip_dialog_text" product="default">Protect your phone with a screen lock option so no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up fingerprint. Tap Cancel, then set a PIN, pattern, or password.</string>
     <!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set face unlock. (tablet) [CHAR LIMIT=NONE] -->
-    <string name="face_lock_screen_setup_skip_dialog_text" product="tablet">Protect your tablet with a screen lock option so no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. Tap Cancel, then set a PIN, pattern, or password.</string>
+    <string name="face_lock_screen_setup_skip_dialog_text" product="tablet">By protecting your tablet with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel. </string>
     <!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set face unlock. (device) [CHAR LIMIT=NONE] -->
-    <string name="face_lock_screen_setup_skip_dialog_text" product="device">Protect your device with a screen lock option so no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. Tap Cancel, then set a PIN, pattern, or password.</string>
+    <string name="face_lock_screen_setup_skip_dialog_text" product="device">By protecting your device with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel. </string>
     <!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set face unlock. (default) [CHAR LIMIT=NONE] -->
-    <string name="face_lock_screen_setup_skip_dialog_text" product="default">Protect your phone with a screen lock option so no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. Tap Cancel, then set a PIN, pattern, or password.</string>
+    <string name="face_lock_screen_setup_skip_dialog_text" product="default">By protecting your phone with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel.</string>
     <!-- Title of dialog shown when the user tries to skip setting up a PIN, warning them of potential consequences of not doing so [CHAR LIMIT=48]-->
     <string name="lock_screen_pin_skip_title">Skip PIN setup?</string>
     <!-- Title of dialog shown when the user tries to skip setting up a password, warning them of potential consequences of not doing so [CHAR LIMIT=48]-->
@@ -2833,7 +2833,7 @@
     <!-- Setting option summary when adaptive sleep is off [CHAR LIMIT=NONE] -->
     <string name="adaptive_sleep_summary_off">Off</string>
     <!-- Description about the feature adaptive sleep [CHAR LIMIT=NONE]-->
-    <string name="adaptive_sleep_description">Prevents your screen from turning off if you’re looking at it.</string>
+    <string name="adaptive_sleep_description">Keep screen on when viewing it</string>
     <!-- Description feature's privacy sensitive details to make sure users understand what feature users, what it saves/sends etc [CHAR LIMIT=NONE]-->
     <string name="adaptive_sleep_privacy">Screen attention uses the front camera to see if someone is looking at the screen. It works on device, and images are never stored or sent to Google.</string>
 
@@ -10208,7 +10208,7 @@
     <!-- Message for the alert dialog which says that the current default home app does not support gesture navigation. [CHAR LIMIT=NONE] -->
     <string name="gesture_not_supported_dialog_message">Not supported by your default home app, <xliff:g id="default_home_app" example="Pixel Launcher">%s</xliff:g></string>
 
-    <!-- Positive button for the alert dialog when gesture nav not supported by launcher [CHAR LIMIT=40] -->
+    <!-- Positive button for the alert dialog when gesture nav not supported by launcher [CHAR LIMIT=60] -->
     <string name="gesture_not_supported_positive_button">Switch default home app</string>
 
     <!-- Content description for the Information icon [CHAR LIMIT=30] -->
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index 48370d9..05e3778 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -145,7 +145,11 @@
         if (savedInstanceState != null) {
             mToken = savedInstanceState.getByteArray(KEY_TOKEN);
         }
+    }
 
+    @Override
+    public void onResume() {
+        super.onResume();
         if (mToken == null) {
             final long challenge = mFaceManager.generateChallenge();
             ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(getActivity(), this);
@@ -155,13 +159,7 @@
                 Log.e(TAG, "Password not set");
                 finish();
             }
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (mToken != null) {
+        } else {
             mAttentionController.setToken(mToken);
             mEnrollController.setToken(mToken);
         }
@@ -196,13 +194,12 @@
     }
 
     @Override
-    public void onDestroy() {
-        super.onDestroy();
-        if (getActivity().isFinishing()) {
-            final int result = mFaceManager.revokeChallenge();
-            if (result < 0) {
-                Log.w(TAG, "revokeChallenge failed, result: " + result);
-            }
+    public void onStop() {
+        super.onStop();
+        mToken = null;
+        final int result = mFaceManager.revokeChallenge();
+        if (result < 0) {
+            Log.w(TAG, "revokeChallenge failed, result: " + result);
         }
     }
 
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
index d532a76..1c1b81d 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
@@ -64,7 +64,7 @@
 
             builder.setTitle(R.string.security_settings_face_settings_remove_dialog_title)
                     .setMessage(R.string.security_settings_face_settings_remove_dialog_details)
-                    .setPositiveButton(R.string.okay, mOnClickListener)
+                    .setPositiveButton(R.string.delete, mOnClickListener)
                     .setNegativeButton(R.string.cancel, mOnClickListener);
             AlertDialog dialog = builder.create();
             dialog.setCanceledOnTouchOutside(false);
diff --git a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java
index 6b91792..e83410d 100644
--- a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java
+++ b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java
@@ -25,9 +25,9 @@
 
 
 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 @@
         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 @@
                 SYSTEM_KEY, DEFAULT_VALUE) != DEFAULT_VALUE;
     }
 
-
     @Override
     public boolean setChecked(boolean isChecked) {
         Settings.System.putInt(mContext.getContentResolver(), SYSTEM_KEY,
@@ -57,10 +54,7 @@
     @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 @@
                 ? 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;
+    }
 }
diff --git a/src/com/android/settings/display/AdaptiveSleepSettings.java b/src/com/android/settings/display/AdaptiveSleepSettings.java
index 4c17a67..d0f2c9a 100644
--- a/src/com/android/settings/display/AdaptiveSleepSettings.java
+++ b/src/com/android/settings/display/AdaptiveSleepSettings.java
@@ -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 @@
         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
diff --git a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java
index 86fee03..aaae076 100644
--- a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java
+++ b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java
@@ -64,12 +64,21 @@
                         .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;
diff --git a/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSlice.java b/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSlice.java
new file mode 100644
index 0000000..2c091fa
--- /dev/null
+++ b/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSlice.java
@@ -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;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index dc3324b..ebfd7b3 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -26,6 +26,7 @@
 
 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.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;
@@ -65,6 +67,16 @@
             .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.
      */
     public static final Uri BATTERY_FIX_SLICE_URI = new Uri.Builder()
@@ -328,6 +340,7 @@
 
         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 @@
         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) {
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSliceTest.java
new file mode 100644
index 0000000..54fb2c3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSliceTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.DEFERRED_TIME_DAYS;
+import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF;
+import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF_KEY_SETUP_TIME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+
+import androidx.slice.Slice;
+import androidx.slice.SliceProvider;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.settings.slices.CustomSliceRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class ContextualAdaptiveSleepSliceTest {
+
+    private static final String pkgName = "adaptive_sleep";
+    private Context mContext;
+    private ContextualAdaptiveSleepSlice mContextualAdaptiveSleepSlice;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private SharedPreferences mSharedPreferences;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
+        mContext = spy(RuntimeEnvironment.application);
+        mContextualAdaptiveSleepSlice = spy(new ContextualAdaptiveSleepSlice(mContext));
+
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mSharedPreferences).when(mContext).getSharedPreferences(eq(PREF), anyInt());
+        doReturn(true).when(mContextualAdaptiveSleepSlice).isSettingsAvailable();
+        doReturn(pkgName).when(mPackageManager).getAttentionServicePackageName();
+        doReturn(-DEFERRED_TIME_DAYS).when(mSharedPreferences).getLong(eq(PREF_KEY_SETUP_TIME),
+                anyLong());
+    }
+
+    @Test
+    public void getUri_shouldReturnContextualAdaptiveSleepSliceUri() {
+        final Uri uri = mContextualAdaptiveSleepSlice.getUri();
+
+        assertThat(uri).isEqualTo(CustomSliceRegistry.CONTEXTUAL_ADAPTIVE_SLEEP_URI);
+    }
+
+    @Test
+    public void getSlice_ShowIfFeatureIsAvailable() {
+        final Slice slice = mContextualAdaptiveSleepSlice.getSlice();
+
+        assertThat(slice).isNotNull();
+    }
+
+    @Test
+    public void getSlice_DoNotShowIfFeatureIsUnavailable() {
+        doReturn(false).when(mContextualAdaptiveSleepSlice).isSettingsAvailable();
+
+        final Slice slice = mContextualAdaptiveSleepSlice.getSlice();
+
+        assertThat(slice).isNull();
+    }
+
+    @Test
+    public void getSlice_ShowIfNotRecentlySetup() {
+        final Slice slice = mContextualAdaptiveSleepSlice.getSlice();
+
+        assertThat(slice).isNotNull();
+    }
+
+    @Test
+    public void getSlice_DoNotShowIfRecentlySetup() {
+        doReturn(System.currentTimeMillis()).when(mSharedPreferences).getLong(
+                eq(PREF_KEY_SETUP_TIME), anyLong());
+
+        final Slice slice = mContextualAdaptiveSleepSlice.getSlice();
+
+        assertThat(slice).isNull();
+    }
+}