Add setting to go to sleep after long user inactivity

The European Commision prescribes an auto-standby feature for TV panels:
After some hours of inactivity, the device has to go from on-mode to
standby-mode or off-mode, or another condition not exceeding the
applicable requirements for standby-mode or for off-mode.

After a long time of no user activity the device should go to sleep,
even if wakelocks are held (eg. during video playback).

Test: 1. Set attentive timeout low, to 35s:
         `adb shell settings put secure attentive_timeout 35000`
      2. Play a YouTube video
      3. Observe warning dialog appearing after 5s
      4. Verify: Clicking a remote button or changing the setting higher hides
         the warning. Remote button press is consumed.
      5. Verify: After 35s of not pressing a button the device goes to sleep
      6. Verify: If "Stay awake" developer option is enabled, then
         warning is not displayed and device does not go to sleep after 35s
      7. Verify: No warning or sleep if setting is set to -1
Test: `atest frameworks/base/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java`
Bug: 137633812
Change-Id: I551b6cffc336437fb1c5a00b4102f68ae0e003e9
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index dd1f8c3..f18b4db 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -426,9 +426,15 @@
     public static final int GO_TO_SLEEP_REASON_FORCE_SUSPEND = 8;
 
     /**
+     * Go to sleep reason code: Going to sleep due to user inattentiveness.
      * @hide
      */
-    public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_FORCE_SUSPEND;
+    public static final int GO_TO_SLEEP_REASON_INATTENTIVE = 9;
+
+    /**
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_INATTENTIVE;
 
     /**
      * @hide
@@ -444,6 +450,7 @@
             case GO_TO_SLEEP_REASON_SLEEP_BUTTON: return "sleep_button";
             case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility";
             case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend";
+            case GO_TO_SLEEP_REASON_INATTENTIVE: return "inattentive";
             default: return Integer.toString(sleepReason);
         }
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 381d492..350cb0a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7677,6 +7677,20 @@
         public static final String SLEEP_TIMEOUT = "sleep_timeout";
 
         /**
+         * The timeout in milliseconds before the device goes to sleep due to user inattentiveness,
+         * even if the system is holding wakelocks. It should generally be longer than {@code
+         * config_attentiveWarningDuration}, as otherwise the device will show the attentive
+         * warning constantly. Small timeouts are discouraged, as they will cause the device to
+         * go to sleep quickly after waking up.
+         * <p>
+         * Use -1 to disable this timeout.
+         * </p>
+         *
+         * @hide
+         */
+        public static final String ATTENTIVE_TIMEOUT = "attentive_timeout";
+
+        /**
          * Controls whether double tap to wake is enabled.
          * @hide
          */
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 73f549a..20706bb 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -188,4 +188,14 @@
      * @param types the internal insets types of the bars are about to abort the transient state.
      */
     void abortTransient(int displayId, in int[] types);
+
+    /**
+     * Show a warning that the device is about to go to sleep due to user inactivity.
+     */
+    void showInattentiveSleepWarning();
+
+    /**
+     * Dismiss the warning that the device is about to go to sleep due to user inactivity.
+     */
+    void dismissInattentiveSleepWarning();
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 3f08710..76235a4 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -113,4 +113,14 @@
     void onBiometricError(int modality, int error, int vendorCode);
     // Used to hide the authentication dialog, e.g. when the application cancels authentication
     void hideAuthenticationDialog();
+
+    /**
+     * Show a warning that the device is about to go to sleep due to user inactivity.
+     */
+    void showInattentiveSleepWarning();
+
+    /**
+     * Dismiss the warning that the device is about to go to sleep due to user inactivity.
+     */
+    void dismissInattentiveSleepWarning();
 }
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 091e1c2..c039570 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -172,6 +172,8 @@
     repeated SuspendBlockerProto suspend_blockers = 48;
     optional WirelessChargerDetectorProto wireless_charger_detector = 49;
     optional BatterySaverStateMachineProto battery_saver_state_machine = 50;
+    // Attentive timeout in ms. The timeout is disabled if it is set to -1.
+    optional sint32 attentive_timeout_ms = 51;
 }
 
 // A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
@@ -310,6 +312,12 @@
     optional bool is_vr_mode_enabled = 35;
     // True if Sidekick is controlling the display and we shouldn't change its power mode.
     optional bool draw_wake_lock_override_from_sidekick = 36;
+    // The attentive timeout setting value in milliseconds. Default value is -1.
+    optional sint32 attentive_timeout_setting_ms = 37;
+    // The attentive timeout config value in milliseconds.
+    optional sint32 attentive_timeout_config_ms = 38;
+    // The attentive warning duration config value in milliseconds.
+    optional sint32 attentive_warning_duration_config_ms = 39;
 }
 
 message BatterySaverStateMachineProto {
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 3ecb1dd..655d4dd 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -42,4 +42,8 @@
 
     <!-- Allow SystemUI to show the shutdown dialog -->
     <bool name="config_showSysuiShutdown">true</bool>
+
+    <!-- The time in milliseconds of prolonged user inactivity after which device goes to sleep,
+         even if wakelocks are held. On TVs, this defaults to 4 hours. -->
+    <integer name="config_attentiveTimeout">14400000</integer>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a810851..e425ef0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -959,6 +959,14 @@
          The default is false. -->
     <bool name="config_suspendWhenScreenOffDueToProximity">false</bool>
 
+    <!-- The time in milliseconds of prolonged user inactivity after which device goes to sleep,
+         even if wakelocks are held. -->
+    <integer name="config_attentiveTimeout">-1</integer>
+
+    <!-- How long to show a warning message to user before the device goes to sleep after prolonged
+         user inactivity. -->
+    <integer name="config_attentiveWarningDuration">30000</integer>
+
     <!-- Control the behavior when the user long presses the power button.
             0 - Nothing
             1 - Global actions menu
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5f2bbac..f8eb90b 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5238,13 +5238,6 @@
     <!-- Application name displayed in notifications [CHAR LIMIT=60] -->
     <string name="notification_app_name_settings">Settings</string>
 
-    <!-- Title of the overlay warning the user to interact with the device or it will go into standby. [CHAR LIMIT=25] -->
-    <string name="standby_warning_title">Standby</string>
-    <!-- Message of the overlay warning the user to interact with the device or it will go into standby. [CHAR LIMIT=NONE] -->
-    <string name="standby_warning_message" product="tv">The Android TV device will soon turn off; press a button to keep it on.</string>
-    <!-- Message of the overlay warning the user to interact with the device or it will go into standby. [CHAR LIMIT=NONE] -->
-    <string name="standby_warning_message" product="default">The device will soon turn off; press to keep it on.</string>
-
     <!-- Active Permission - accessibility support -->
     <!-- Content description of the camera icon in the notification. [CHAR LIMIT=NONE] -->
     <string name="notification_appops_camera_active">Camera</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8d57a43c..a2df060 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2117,6 +2117,8 @@
   <java-symbol type="integer" name="config_minimumScreenOffTimeout" />
   <java-symbol type="integer" name="config_maximumScreenDimDuration" />
   <java-symbol type="fraction" name="config_maximumScreenDimRatio" />
+  <java-symbol type="integer" name="config_attentiveTimeout" />
+  <java-symbol type="integer" name="config_attentiveWarningDuration" />
   <java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" />
   <java-symbol type="string" name="config_customAdbPublicKeyConfirmationSecondaryUserComponent" />
   <java-symbol type="string" name="config_customVpnConfirmDialogComponent" />
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 10d990a..289ac80 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -605,6 +605,7 @@
                  Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE,
                  Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
                  Settings.Secure.ASSIST_STRUCTURE_ENABLED,
+                 Settings.Secure.ATTENTIVE_TIMEOUT,
                  Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION,
                  Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT,
                  Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
diff --git a/packages/SystemUI/res/layout-television/inattentive_sleep_warning.xml b/packages/SystemUI/res/layout-television/inattentive_sleep_warning.xml
new file mode 100644
index 0000000..eb21c43
--- /dev/null
+++ b/packages/SystemUI/res/layout-television/inattentive_sleep_warning.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/sleep_warning_dialog_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:theme="@android:style/Theme.DeviceDefault.Dialog"
+    android:focusable="true">
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/black"
+        android:alpha="?android:backgroundDimAmount" />
+    <LinearLayout
+        android:layout_width="380dp"
+        android:layout_height="wrap_content"
+        android:background="@drawable/rounded_bg_full"
+        android:padding="16dp"
+        android:layout_margin="32dp"
+        android:layout_gravity="bottom|right"
+        android:orientation="vertical">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/inattentive_sleep_warning_title"
+            android:layout_marginBottom="8dp"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/inattentive_sleep_warning_message"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textAppearance="@android:style/TextAppearance.DeviceDefault"/>
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/inattentive_sleep_warning.xml b/packages/SystemUI/res/layout/inattentive_sleep_warning.xml
new file mode 100644
index 0000000..f1f9b1f
--- /dev/null
+++ b/packages/SystemUI/res/layout/inattentive_sleep_warning.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/sleep_warning_dialog_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:theme="@android:style/Theme.Material.Dialog"
+    android:focusable="true">
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/black"
+        android:alpha="?android:backgroundDimAmount" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/rounded_bg_full"
+        android:layout_margin="8dp"
+        android:padding="16dp"
+        android:layout_gravity="top"
+        android:orientation="vertical">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/inattentive_sleep_warning_title"
+            android:layout_marginBottom="8dp"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textAppearance="@android:style/TextAppearance.Material.Large"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/inattentive_sleep_warning_message"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textAppearance="@android:style/TextAppearance.Material"/>
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index dff21cf..c3f410e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -281,6 +281,7 @@
         <item>com.android.systemui.statusbar.phone.StatusBar</item>
         <item>com.android.systemui.usb.StorageNotification</item>
         <item>com.android.systemui.power.PowerUI</item>
+        <item>com.android.systemui.power.InattentiveSleepWarningController</item>
         <item>com.android.systemui.media.RingtonePlayer</item>
         <item>com.android.systemui.keyboard.KeyboardUI</item>
         <item>com.android.systemui.pip.PipUI</item>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0082949..e06380e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2501,4 +2501,11 @@
     <!-- Notification content text when switching to a default launcher that supports gesture navigation [CHAR LIMIT=NONE] -->
     <string name="notification_content_gesture_nav_available">Go to Settings to update system navigation</string>
 
+    <!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] -->
+    <string name="inattentive_sleep_warning_title">Standby</string>
+    <!-- Message of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=NONE] -->
+    <string name="inattentive_sleep_warning_message" product="tv">The Android TV device will soon turn off; press a button to keep it on.</string>
+    <!-- Message of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=NONE] -->
+    <string name="inattentive_sleep_warning_message" product="default">The device will soon turn off; press to keep it on.</string>
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 3cf14d6..25986c5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -25,6 +25,7 @@
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.pip.PipUI;
+import com.android.systemui.power.InattentiveSleepWarningController;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsModule;
@@ -102,6 +103,13 @@
     @ClassKey(PowerUI.class)
     public abstract SystemUI bindPowerUI(PowerUI sysui);
 
+    /** Inject into InattentiveSleepWarningController. */
+    @Binds
+    @IntoMap
+    @ClassKey(InattentiveSleepWarningController.class)
+    public abstract SystemUI bindInattentiveSleepWarningController(
+            InattentiveSleepWarningController sysui);
+
     /** Inject into Recents. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningController.java b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningController.java
new file mode 100644
index 0000000..7169431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningController.java
@@ -0,0 +1,62 @@
+/*
+ * 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.systemui.power;
+
+import android.content.Context;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.statusbar.CommandQueue;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Receives messages sent from {@link com.android.server.power.InattentiveSleepWarningController}
+ * and shows the appropriate inattentive sleep UI (e.g. {@link InattentiveSleepWarningView}).
+ */
+@Singleton
+public class InattentiveSleepWarningController extends SystemUI implements CommandQueue.Callbacks {
+    private final CommandQueue mCommandQueue;
+    private InattentiveSleepWarningView mOverlayView;
+
+    @Inject
+    public InattentiveSleepWarningController(Context context, CommandQueue commandQueue) {
+        super(context);
+        mCommandQueue = commandQueue;
+    }
+
+    @Override
+    public void start() {
+        mCommandQueue.addCallback(this);
+    }
+
+    @Override
+    public void showInattentiveSleepWarning() {
+        if (mOverlayView == null) {
+            mOverlayView = new InattentiveSleepWarningView(mContext);
+        }
+
+        mOverlayView.show();
+    }
+
+    @Override
+    public void dismissInattentiveSleepWarning() {
+        if (mOverlayView != null) {
+            mOverlayView.dismiss();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
new file mode 100644
index 0000000..8ccc679
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
@@ -0,0 +1,85 @@
+/*
+ * 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.systemui.power;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+
+/**
+ * View that shows a warning shortly before the device goes into sleep
+ * after prolonged user inactivity when bound to.
+ */
+public class InattentiveSleepWarningView extends FrameLayout {
+    private final IBinder mWindowToken = new Binder();
+    private final WindowManager mWindowManager;
+
+    InattentiveSleepWarningView(Context context) {
+        super(context);
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+
+        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+        layoutInflater.inflate(R.layout.inattentive_sleep_warning, this, true /* attachToRoot */);
+
+        setFocusable(true);
+        setOnKeyListener((v, keyCode, event) -> {
+            // overlay consumes key presses
+            return true;
+        });
+    }
+
+    /**
+     * Show the warning.
+     */
+    public void show() {
+        if (getParent() == null) {
+            mWindowManager.addView(this, getLayoutParams(mWindowToken));
+        }
+    }
+
+    /**
+     * Dismiss the warning.
+     */
+    public void dismiss() {
+        if (getParent() != null) {
+            mWindowManager.removeView(this);
+        }
+    }
+
+    /**
+     * @param windowToken token for the window
+     */
+    private WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        lp.setTitle("InattentiveSleepWarning");
+        lp.token = windowToken;
+        return lp;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 621f101..88b6fdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -70,53 +70,55 @@
     private static final int OP_SET_ICON    = 1;
     private static final int OP_REMOVE_ICON = 2;
 
-    private static final int MSG_ICON                          = 1 << MSG_SHIFT;
-    private static final int MSG_DISABLE                       = 2 << MSG_SHIFT;
-    private static final int MSG_EXPAND_NOTIFICATIONS          = 3 << MSG_SHIFT;
-    private static final int MSG_COLLAPSE_PANELS               = 4 << MSG_SHIFT;
-    private static final int MSG_EXPAND_SETTINGS               = 5 << MSG_SHIFT;
-    private static final int MSG_SYSTEM_BAR_APPEARANCE_CHANGED = 6 << MSG_SHIFT;
-    private static final int MSG_DISPLAY_READY                 = 7 << MSG_SHIFT;
-    private static final int MSG_SHOW_IME_BUTTON               = 8 << MSG_SHIFT;
-    private static final int MSG_TOGGLE_RECENT_APPS            = 9 << MSG_SHIFT;
-    private static final int MSG_PRELOAD_RECENT_APPS           = 10 << MSG_SHIFT;
-    private static final int MSG_CANCEL_PRELOAD_RECENT_APPS    = 11 << MSG_SHIFT;
-    private static final int MSG_SET_WINDOW_STATE              = 12 << MSG_SHIFT;
-    private static final int MSG_SHOW_RECENT_APPS              = 13 << MSG_SHIFT;
-    private static final int MSG_HIDE_RECENT_APPS              = 14 << MSG_SHIFT;
-    private static final int MSG_SHOW_SCREEN_PIN_REQUEST       = 18 << MSG_SHIFT;
-    private static final int MSG_APP_TRANSITION_PENDING        = 19 << MSG_SHIFT;
-    private static final int MSG_APP_TRANSITION_CANCELLED      = 20 << MSG_SHIFT;
-    private static final int MSG_APP_TRANSITION_STARTING       = 21 << MSG_SHIFT;
-    private static final int MSG_ASSIST_DISCLOSURE             = 22 << MSG_SHIFT;
-    private static final int MSG_START_ASSIST                  = 23 << MSG_SHIFT;
-    private static final int MSG_CAMERA_LAUNCH_GESTURE         = 24 << MSG_SHIFT;
-    private static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS     = 25 << MSG_SHIFT;
-    private static final int MSG_SHOW_PICTURE_IN_PICTURE_MENU  = 26 << MSG_SHIFT;
-    private static final int MSG_ADD_QS_TILE                   = 27 << MSG_SHIFT;
-    private static final int MSG_REMOVE_QS_TILE                = 28 << MSG_SHIFT;
-    private static final int MSG_CLICK_QS_TILE                 = 29 << MSG_SHIFT;
-    private static final int MSG_TOGGLE_APP_SPLIT_SCREEN       = 30 << MSG_SHIFT;
-    private static final int MSG_APP_TRANSITION_FINISHED       = 31 << MSG_SHIFT;
-    private static final int MSG_DISMISS_KEYBOARD_SHORTCUTS    = 32 << MSG_SHIFT;
-    private static final int MSG_HANDLE_SYSTEM_KEY             = 33 << MSG_SHIFT;
-    private static final int MSG_SHOW_GLOBAL_ACTIONS           = 34 << MSG_SHIFT;
-    private static final int MSG_TOGGLE_PANEL                  = 35 << MSG_SHIFT;
-    private static final int MSG_SHOW_SHUTDOWN_UI              = 36 << MSG_SHIFT;
-    private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR  = 37 << MSG_SHIFT;
-    private static final int MSG_ROTATION_PROPOSAL             = 38 << MSG_SHIFT;
-    private static final int MSG_BIOMETRIC_SHOW                = 39 << MSG_SHIFT;
-    private static final int MSG_BIOMETRIC_AUTHENTICATED       = 40 << MSG_SHIFT;
-    private static final int MSG_BIOMETRIC_HELP                = 41 << MSG_SHIFT;
-    private static final int MSG_BIOMETRIC_ERROR               = 42 << MSG_SHIFT;
-    private static final int MSG_BIOMETRIC_HIDE                = 43 << MSG_SHIFT;
-    private static final int MSG_SHOW_CHARGING_ANIMATION       = 44 << MSG_SHIFT;
-    private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
-    private static final int MSG_SHOW_PINNING_TOAST_ESCAPE     = 46 << MSG_SHIFT;
-    private static final int MSG_RECENTS_ANIMATION_STATE_CHANGED = 47 << MSG_SHIFT;
-    private static final int MSG_SHOW_TRANSIENT                = 48 << MSG_SHIFT;
-    private static final int MSG_ABORT_TRANSIENT               = 49 << MSG_SHIFT;
-    private static final int MSG_TOP_APP_WINDOW_CHANGED        = 50 << MSG_SHIFT;
+    private static final int MSG_ICON                              = 1 << MSG_SHIFT;
+    private static final int MSG_DISABLE                           = 2 << MSG_SHIFT;
+    private static final int MSG_EXPAND_NOTIFICATIONS              = 3 << MSG_SHIFT;
+    private static final int MSG_COLLAPSE_PANELS                   = 4 << MSG_SHIFT;
+    private static final int MSG_EXPAND_SETTINGS                   = 5 << MSG_SHIFT;
+    private static final int MSG_SYSTEM_BAR_APPEARANCE_CHANGED     = 6 << MSG_SHIFT;
+    private static final int MSG_DISPLAY_READY                     = 7 << MSG_SHIFT;
+    private static final int MSG_SHOW_IME_BUTTON                   = 8 << MSG_SHIFT;
+    private static final int MSG_TOGGLE_RECENT_APPS                = 9 << MSG_SHIFT;
+    private static final int MSG_PRELOAD_RECENT_APPS               = 10 << MSG_SHIFT;
+    private static final int MSG_CANCEL_PRELOAD_RECENT_APPS        = 11 << MSG_SHIFT;
+    private static final int MSG_SET_WINDOW_STATE                  = 12 << MSG_SHIFT;
+    private static final int MSG_SHOW_RECENT_APPS                  = 13 << MSG_SHIFT;
+    private static final int MSG_HIDE_RECENT_APPS                  = 14 << MSG_SHIFT;
+    private static final int MSG_SHOW_SCREEN_PIN_REQUEST           = 18 << MSG_SHIFT;
+    private static final int MSG_APP_TRANSITION_PENDING            = 19 << MSG_SHIFT;
+    private static final int MSG_APP_TRANSITION_CANCELLED          = 20 << MSG_SHIFT;
+    private static final int MSG_APP_TRANSITION_STARTING           = 21 << MSG_SHIFT;
+    private static final int MSG_ASSIST_DISCLOSURE                 = 22 << MSG_SHIFT;
+    private static final int MSG_START_ASSIST                      = 23 << MSG_SHIFT;
+    private static final int MSG_CAMERA_LAUNCH_GESTURE             = 24 << MSG_SHIFT;
+    private static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS         = 25 << MSG_SHIFT;
+    private static final int MSG_SHOW_PICTURE_IN_PICTURE_MENU      = 26 << MSG_SHIFT;
+    private static final int MSG_ADD_QS_TILE                       = 27 << MSG_SHIFT;
+    private static final int MSG_REMOVE_QS_TILE                    = 28 << MSG_SHIFT;
+    private static final int MSG_CLICK_QS_TILE                     = 29 << MSG_SHIFT;
+    private static final int MSG_TOGGLE_APP_SPLIT_SCREEN           = 30 << MSG_SHIFT;
+    private static final int MSG_APP_TRANSITION_FINISHED           = 31 << MSG_SHIFT;
+    private static final int MSG_DISMISS_KEYBOARD_SHORTCUTS        = 32 << MSG_SHIFT;
+    private static final int MSG_HANDLE_SYSTEM_KEY                 = 33 << MSG_SHIFT;
+    private static final int MSG_SHOW_GLOBAL_ACTIONS               = 34 << MSG_SHIFT;
+    private static final int MSG_TOGGLE_PANEL                      = 35 << MSG_SHIFT;
+    private static final int MSG_SHOW_SHUTDOWN_UI                  = 36 << MSG_SHIFT;
+    private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR      = 37 << MSG_SHIFT;
+    private static final int MSG_ROTATION_PROPOSAL                 = 38 << MSG_SHIFT;
+    private static final int MSG_BIOMETRIC_SHOW                    = 39 << MSG_SHIFT;
+    private static final int MSG_BIOMETRIC_AUTHENTICATED           = 40 << MSG_SHIFT;
+    private static final int MSG_BIOMETRIC_HELP                    = 41 << MSG_SHIFT;
+    private static final int MSG_BIOMETRIC_ERROR                   = 42 << MSG_SHIFT;
+    private static final int MSG_BIOMETRIC_HIDE                    = 43 << MSG_SHIFT;
+    private static final int MSG_SHOW_CHARGING_ANIMATION           = 44 << MSG_SHIFT;
+    private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT     = 45 << MSG_SHIFT;
+    private static final int MSG_SHOW_PINNING_TOAST_ESCAPE         = 46 << MSG_SHIFT;
+    private static final int MSG_RECENTS_ANIMATION_STATE_CHANGED   = 47 << MSG_SHIFT;
+    private static final int MSG_SHOW_TRANSIENT                    = 48 << MSG_SHIFT;
+    private static final int MSG_ABORT_TRANSIENT                   = 49 << MSG_SHIFT;
+    private static final int MSG_TOP_APP_WINDOW_CHANGED            = 50 << MSG_SHIFT;
+    private static final int MSG_SHOW_INATTENTIVE_SLEEP_WARNING    = 51 << MSG_SHIFT;
+    private static final int MSG_DISMISS_INATTENTIVE_SLEEP_WARNING = 52 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -294,6 +296,18 @@
          */
         default void topAppWindowChanged(int displayId, boolean isFullscreen, boolean isImmersive) {
         }
+
+        /**
+         * Called to notify System UI that a warning about the device going to sleep
+         * due to prolonged user inactivity should be shown.
+         */
+        default void showInattentiveSleepWarning() { }
+
+        /**
+         * Called to notify System UI that the warning about the device going to sleep
+         * due to prolonged user inactivity should be dismissed.
+         */
+        default void dismissInattentiveSleepWarning() { }
     }
 
     public CommandQueue(Context context) {
@@ -793,6 +807,22 @@
         }
     }
 
+    @Override
+    public void showInattentiveSleepWarning() {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_SHOW_INATTENTIVE_SLEEP_WARNING)
+                    .sendToTarget();
+        }
+    }
+
+    @Override
+    public void dismissInattentiveSleepWarning() {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_DISMISS_INATTENTIVE_SLEEP_WARNING)
+                    .sendToTarget();
+        }
+    }
+
     private void handleShowImeButton(int displayId, IBinder token, int vis, int backDisposition,
             boolean showImeSwitcher, boolean isMultiClientImeEnabled) {
         if (displayId == INVALID_DISPLAY) return;
@@ -1138,6 +1168,16 @@
                     args.recycle();
                     break;
                 }
+                case MSG_SHOW_INATTENTIVE_SLEEP_WARNING:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).showInattentiveSleepWarning();
+                    }
+                    break;
+                case MSG_DISMISS_INATTENTIVE_SLEEP_WARNING:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).dismissInattentiveSleepWarning();
+                    }
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/power/InattentiveSleepWarningController.java b/services/core/java/com/android/server/power/InattentiveSleepWarningController.java
new file mode 100644
index 0000000..db8a63f
--- /dev/null
+++ b/services/core/java/com/android/server/power/InattentiveSleepWarningController.java
@@ -0,0 +1,103 @@
+/**
+ * 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.server.power;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
+
+/**
+ * Communicates with System UI to show/hide the inattentive sleep warning overlay.
+ */
+@VisibleForTesting
+public class InattentiveSleepWarningController {
+    private static final String TAG = "InattentiveSleepWarning";
+
+    private final Handler mHandler = new Handler();
+
+    private boolean mIsShown;
+    private IStatusBarService mStatusBarService;
+
+    InattentiveSleepWarningController() {
+    }
+
+    /**
+     * Returns true if the warning is currently being displayed, false otherwise.
+     */
+    @GuardedBy("PowerManagerService.mLock")
+    public boolean isShown() {
+        return mIsShown;
+    }
+
+    /**
+     * Show the warning.
+     */
+    @GuardedBy("PowerManagerService.mLock")
+    public void show() {
+        if (isShown()) {
+            return;
+        }
+
+        mHandler.post(this::showInternal);
+        mIsShown = true;
+    }
+
+    private void showInternal() {
+        try {
+            getStatusBar().showInattentiveSleepWarning();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to show inattentive sleep warning", e);
+            mIsShown = false;
+        }
+    }
+
+    /**
+     * Dismiss the warning.
+     */
+    @GuardedBy("PowerManagerService.mLock")
+    public void dismiss() {
+        if (!isShown()) {
+            return;
+        }
+
+        mHandler.post(this::dismissInternal);
+        mIsShown = false;
+    }
+
+    private void dismissInternal() {
+        try {
+            getStatusBar().dismissInattentiveSleepWarning();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to dismiss inattentive sleep warning", e);
+        }
+    }
+
+    private IStatusBarService getStatusBar() {
+        if (mStatusBarService == null) {
+            mStatusBarService = IStatusBarService.Stub.asInterface(
+                    ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        }
+
+        return mStatusBarService;
+    }
+}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index b67d9b2..7fc9fdc0 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -536,6 +536,7 @@
             case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
                 return WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN;
             case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
+            case PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE:
                 return WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT;
             default:
                 return WindowManagerPolicy.OFF_BECAUSE_OF_USER;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index befe4e9..00e0f71 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -128,6 +128,8 @@
     private static final int MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 3;
     // Message: Polling to look for long held wake locks.
     private static final int MSG_CHECK_FOR_LONG_WAKELOCKS = 4;
+    // Message: Sent when an attentive timeout occurs to update the power state.
+    private static final int MSG_ATTENTIVE_TIMEOUT = 5;
 
     // Dirty bit: mWakeLocks changed
     private static final int DIRTY_WAKE_LOCKS = 1 << 0;
@@ -157,6 +159,8 @@
     private static final int DIRTY_QUIESCENT = 1 << 12;
     // Dirty bit: VR Mode enabled changed
     private static final int DIRTY_VR_MODE_CHANGED = 1 << 13;
+    // Dirty bit: attentive timer may have timed out
+    private static final int DIRTY_ATTENTIVE = 1 << 14;
 
     // Summarizes the state of all active wakelocks.
     private static final int WAKE_LOCK_CPU = 1 << 0;
@@ -249,6 +253,8 @@
     private DreamManagerInternal mDreamManager;
     private Light mAttentionLight;
 
+    private InattentiveSleepWarningController mInattentiveSleepWarningOverlayController;
+
     private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_POWER);
 
     // A bitfield that indicates what parts of the power state have
@@ -381,6 +387,9 @@
     // True if the device should suspend when the screen is off due to proximity.
     private boolean mSuspendWhenScreenOffDueToProximityConfig;
 
+    // Default value for attentive timeout.
+    private int mAttentiveTimeoutConfig;
+
     // True if dreams are supported on this device.
     private boolean mDreamsSupportedConfig;
 
@@ -441,9 +450,16 @@
     // The screen off timeout setting value in milliseconds.
     private long mScreenOffTimeoutSetting;
 
+    // Default for attentive warning duration.
+    private long mAttentiveWarningDurationConfig;
+
     // The sleep timeout setting value in milliseconds.
     private long mSleepTimeoutSetting;
 
+    // How long to show a warning message to user before the device goes to sleep
+    // after long user inactivity, even if wakelocks are held.
+    private long mAttentiveTimeoutSetting;
+
     // The maximum allowable screen off timeout according to the device
     // administration policy.  Overrides other settings.
     private long mMaximumScreenOffTimeoutFromDeviceAdmin = Long.MAX_VALUE;
@@ -735,6 +751,10 @@
         AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
             return new AmbientDisplayConfiguration(context);
         }
+
+        InattentiveSleepWarningController createInattentiveSleepWarningController() {
+            return new InattentiveSleepWarningController();
+        }
     }
 
     final Constants mConstants;
@@ -779,6 +799,9 @@
         mBatterySaverStateMachine = new BatterySaverStateMachine(
                 mLock, mContext, mBatterySaverController);
 
+        mInattentiveSleepWarningOverlayController =
+                mInjector.createInattentiveSleepWarningController();
+
         synchronized (mLock) {
             mWakeLockSuspendBlocker =
                     mInjector.createSuspendBlocker(this, "PowerManagerService.WakeLocks");
@@ -902,6 +925,9 @@
         resolver.registerContentObserver(Settings.Secure.getUriFor(
                 Settings.Secure.SLEEP_TIMEOUT),
                 false, mSettingsObserver, UserHandle.USER_ALL);
+        resolver.registerContentObserver(Settings.Secure.getUriFor(
+                Settings.Secure.ATTENTIVE_TIMEOUT),
+                false, mSettingsObserver, UserHandle.USER_ALL);
         resolver.registerContentObserver(Settings.Global.getUriFor(
                 Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
                 false, mSettingsObserver, UserHandle.USER_ALL);
@@ -966,6 +992,10 @@
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug);
         mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
+        mAttentiveTimeoutConfig = resources.getInteger(
+                com.android.internal.R.integer.config_attentiveTimeout);
+        mAttentiveWarningDurationConfig = resources.getInteger(
+                com.android.internal.R.integer.config_attentiveWarningDuration);
         mDreamsSupportedConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_dreamsSupported);
         mDreamsEnabledByDefaultConfig = resources.getBoolean(
@@ -1015,6 +1045,9 @@
         mSleepTimeoutSetting = Settings.Secure.getIntForUser(resolver,
                 Settings.Secure.SLEEP_TIMEOUT, DEFAULT_SLEEP_TIMEOUT,
                 UserHandle.USER_CURRENT);
+        mAttentiveTimeoutSetting = Settings.Secure.getIntForUser(resolver,
+                Settings.Secure.ATTENTIVE_TIMEOUT, mAttentiveTimeoutConfig,
+                UserHandle.USER_CURRENT);
         mStayOnWhilePluggedInSetting = Settings.Global.getInt(resolver,
                 Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC);
         mTheaterModeEnabled = Settings.Global.getInt(mContext.getContentResolver(),
@@ -1700,6 +1733,7 @@
 
                 updateWakeLockSummaryLocked(dirtyPhase1);
                 updateUserActivitySummaryLocked(now, dirtyPhase1);
+                updateAttentiveStateLocked(now, dirtyPhase1);
                 if (!updateWakefulnessLocked(dirtyPhase1)) {
                     break;
                 }
@@ -2042,8 +2076,10 @@
             if (mWakefulness == WAKEFULNESS_AWAKE
                     || mWakefulness == WAKEFULNESS_DREAMING
                     || mWakefulness == WAKEFULNESS_DOZING) {
-                final long sleepTimeout = getSleepTimeoutLocked();
-                final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
+                final long attentiveTimeout = getAttentiveTimeoutLocked();
+                final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
+                final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
+                        attentiveTimeout);
                 final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
                 final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
                 final long nextProfileTimeout = getNextProfileTimeoutLocked(now);
@@ -2134,6 +2170,12 @@
         mHandler.sendMessageAtTime(msg, timeMs);
     }
 
+    private void scheduleAttentiveTimeout(long timeMs) {
+        final Message msg = mHandler.obtainMessage(MSG_ATTENTIVE_TIMEOUT);
+        msg.setAsynchronous(true);
+        mHandler.sendMessageAtTime(msg, timeMs);
+    }
+
     /**
      * Finds the next profile timeout time or returns -1 if there are no profiles to be locked.
      */
@@ -2150,6 +2192,69 @@
         return nextTimeout;
     }
 
+    private void updateAttentiveStateLocked(long now, int dirty) {
+        long attentiveTimeout = getAttentiveTimeoutLocked();
+        long goToSleepTime = mLastUserActivityTime + attentiveTimeout;
+        long showWarningTime = goToSleepTime - mAttentiveWarningDurationConfig;
+
+        boolean warningDismissed = maybeHideInattentiveSleepWarningLocked(now, showWarningTime);
+
+        if (attentiveTimeout >= 0 && (warningDismissed
+                || (dirty & (DIRTY_ATTENTIVE | DIRTY_STAY_ON | DIRTY_SCREEN_BRIGHTNESS_BOOST
+                | DIRTY_PROXIMITY_POSITIVE | DIRTY_WAKEFULNESS | DIRTY_BOOT_COMPLETED
+                | DIRTY_SETTINGS)) != 0)) {
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "Updating attentive state");
+            }
+
+            mHandler.removeMessages(MSG_ATTENTIVE_TIMEOUT);
+
+            if (isBeingKeptFromShowingInattentiveSleepWarningLocked()) {
+                return;
+            }
+
+            long nextTimeout = -1;
+
+            if (now < showWarningTime) {
+                nextTimeout = showWarningTime;
+            } else if (now < goToSleepTime) {
+                if (DEBUG) {
+                    long timeToSleep = goToSleepTime - now;
+                    Slog.d(TAG, "Going to sleep in " + timeToSleep
+                            + "ms if there is no user activity");
+                }
+                mInattentiveSleepWarningOverlayController.show();
+                nextTimeout = goToSleepTime;
+            } else {
+                if (DEBUG && mWakefulness != WAKEFULNESS_ASLEEP) {
+                    Slog.i(TAG, "Going to sleep now due to long user inactivity");
+                }
+            }
+
+            if (nextTimeout >= 0) {
+                scheduleAttentiveTimeout(nextTimeout);
+            }
+        }
+    }
+
+    private boolean maybeHideInattentiveSleepWarningLocked(long now, long showWarningTime) {
+        long attentiveTimeout = getAttentiveTimeoutLocked();
+
+        if (mInattentiveSleepWarningOverlayController.isShown() && (attentiveTimeout < 0
+                || isBeingKeptFromShowingInattentiveSleepWarningLocked()
+                || now < showWarningTime)) {
+            mInattentiveSleepWarningOverlayController.dismiss();
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean isAttentiveTimeoutExpired(long now) {
+        long attentiveTimeout = getAttentiveTimeoutLocked();
+        return attentiveTimeout >= 0 && now > mLastUserActivityTime + attentiveTimeout;
+    }
+
     /**
      * Called when a user activity timeout has occurred.
      * Simply indicates that something about user activity has changed so that the new
@@ -2169,15 +2274,38 @@
         }
     }
 
-    private long getSleepTimeoutLocked() {
-        final long timeout = mSleepTimeoutSetting;
+    private void handleAttentiveTimeout() { // runs on handler thread
+        synchronized (mLock) {
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "handleAttentiveTimeout");
+            }
+
+            mDirty |= DIRTY_ATTENTIVE;
+            updatePowerStateLocked();
+        }
+    }
+
+    private long getAttentiveTimeoutLocked() {
+        long timeout = mAttentiveTimeoutSetting;
         if (timeout <= 0) {
             return -1;
         }
+
+        return Math.max(timeout, mMinimumScreenOffTimeoutConfig);
+    }
+
+    private long getSleepTimeoutLocked(long attentiveTimeout) {
+        long timeout = mSleepTimeoutSetting;
+        if (timeout <= 0) {
+            return -1;
+        }
+        if (attentiveTimeout >= 0) {
+            timeout = Math.min(timeout, attentiveTimeout);
+        }
         return Math.max(timeout, mMinimumScreenOffTimeoutConfig);
     }
 
-    private long getScreenOffTimeoutLocked(long sleepTimeout) {
+    private long getScreenOffTimeoutLocked(long sleepTimeout, long attentiveTimeout) {
         long timeout = mScreenOffTimeoutSetting;
         if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) {
             timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin);
@@ -2188,6 +2316,9 @@
         if (sleepTimeout >= 0) {
             timeout = Math.min(timeout, sleepTimeout);
         }
+        if (attentiveTimeout >= 0) {
+            timeout = Math.min(timeout, attentiveTimeout);
+        }
         return Math.max(timeout, mMinimumScreenOffTimeoutConfig);
     }
 
@@ -2209,13 +2340,16 @@
         boolean changed = false;
         if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED
                 | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
-                | DIRTY_DOCK_STATE)) != 0) {
+                | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE)) != 0) {
             if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) {
                 if (DEBUG_SPEW) {
                     Slog.d(TAG, "updateWakefulnessLocked: Bed time...");
                 }
                 final long time = SystemClock.uptimeMillis();
-                if (shouldNapAtBedTimeLocked()) {
+                if (isAttentiveTimeoutExpired(time)) {
+                    changed = goToSleepNoUpdateLocked(time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
+                            PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
+                } else if (shouldNapAtBedTimeLocked()) {
                     changed = napNoUpdateLocked(time, Process.SYSTEM_UID);
                 } else {
                     changed = goToSleepNoUpdateLocked(time,
@@ -2242,7 +2376,16 @@
      * to being fully awake or else go to sleep for good.
      */
     private boolean isItBedTimeYetLocked() {
-        return mBootCompleted && !isBeingKeptAwakeLocked();
+        if (!mBootCompleted) {
+            return false;
+        }
+
+        long now = SystemClock.uptimeMillis();
+        if (isAttentiveTimeoutExpired(now)) {
+            return !isBeingKeptFromInattentiveSleepLocked();
+        } else {
+            return !isBeingKeptAwakeLocked();
+        }
     }
 
     /**
@@ -2263,11 +2406,29 @@
     }
 
     /**
+     * Returns true if the device is prevented from going into inattentive sleep by the stay on
+     * while powered setting. We also keep the device awake when the proximity sensor returns a
+     * positive result so that the device does not lock while in a phone call. This function only
+     * controls whether the device will go to sleep which is independent of whether it will be
+     * allowed to suspend.
+     */
+    private boolean isBeingKeptFromInattentiveSleepLocked() {
+        return mStayOn || mScreenBrightnessBoostInProgress || mProximityPositive
+                || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT
+                | USER_ACTIVITY_SCREEN_DIM)) != 0;
+    }
+
+    private boolean isBeingKeptFromShowingInattentiveSleepWarningLocked() {
+        return mStayOn || mScreenBrightnessBoostInProgress || mProximityPositive || !mBootCompleted;
+    }
+
+    /**
      * Determines whether to post a message to the sandman to update the dream state.
      */
     private void updateDreamLocked(int dirty, boolean displayBecameReady) {
         if ((dirty & (DIRTY_WAKEFULNESS
                 | DIRTY_USER_ACTIVITY
+                | DIRTY_ATTENTIVE
                 | DIRTY_WAKE_LOCKS
                 | DIRTY_BOOT_COMPLETED
                 | DIRTY_SETTINGS
@@ -2350,6 +2511,7 @@
             }
 
             // Determine whether the dream should continue.
+            long now = SystemClock.uptimeMillis();
             if (wakefulness == WAKEFULNESS_DREAMING) {
                 if (isDreaming && canDreamLocked()) {
                     if (mDreamsBatteryLevelDrainCutoffConfig >= 0
@@ -2371,11 +2533,15 @@
 
                 // Dream has ended or will be stopped.  Update the power state.
                 if (isItBedTimeYetLocked()) {
-                    goToSleepNoUpdateLocked(SystemClock.uptimeMillis(),
-                            PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID);
+                    int flags = 0;
+                    if (isAttentiveTimeoutExpired(now)) {
+                        flags |= PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE;
+                    }
+                    goToSleepNoUpdateLocked(now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, flags,
+                            Process.SYSTEM_UID);
                     updatePowerStateLocked();
                 } else {
-                    wakeUpNoUpdateLocked(SystemClock.uptimeMillis(),
+                    wakeUpNoUpdateLocked(now,
                             PowerManager.WAKE_REASON_UNKNOWN,
                             "android.server.power:DREAM_FINISHED", Process.SYSTEM_UID,
                             mContext.getOpPackageName(), Process.SYSTEM_UID);
@@ -2387,7 +2553,7 @@
                 }
 
                 // Doze has ended or will be stopped.  Update the power state.
-                reallyGoToSleepNoUpdateLocked(SystemClock.uptimeMillis(), Process.SYSTEM_UID);
+                reallyGoToSleepNoUpdateLocked(now, Process.SYSTEM_UID);
                 updatePowerStateLocked();
             }
         }
@@ -3474,6 +3640,9 @@
             pw.println("  mMinimumScreenOffTimeoutConfig=" + mMinimumScreenOffTimeoutConfig);
             pw.println("  mMaximumScreenDimDurationConfig=" + mMaximumScreenDimDurationConfig);
             pw.println("  mMaximumScreenDimRatioConfig=" + mMaximumScreenDimRatioConfig);
+            pw.println("  mAttentiveTimeoutConfig=" + mAttentiveTimeoutConfig);
+            pw.println("  mAttentiveTimeoutSetting=" + mAttentiveTimeoutSetting);
+            pw.println("  mAttentiveWarningDurationConfig=" + mAttentiveWarningDurationConfig);
             pw.println("  mScreenOffTimeoutSetting=" + mScreenOffTimeoutSetting);
             pw.println("  mSleepTimeoutSetting=" + mSleepTimeoutSetting);
             pw.println("  mMaximumScreenOffTimeoutFromDeviceAdmin="
@@ -3501,10 +3670,12 @@
             pw.println("  mForegroundProfile=" + mForegroundProfile);
             pw.println("  mUserId=" + mUserId);
 
-            final long sleepTimeout = getSleepTimeoutLocked();
-            final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
+            final long attentiveTimeout = getAttentiveTimeoutLocked();
+            final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
+            final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, attentiveTimeout);
             final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
             pw.println();
+            pw.println("Attentive timeout: " + attentiveTimeout + " ms");
             pw.println("Sleep timeout: " + sleepTimeout + " ms");
             pw.println("Screen off timeout: " + screenOffTimeout + " ms");
             pw.println("Screen dim duration: " + screenDimDuration + " ms");
@@ -3772,6 +3943,16 @@
                     PowerServiceSettingsAndConfigurationDumpProto.SLEEP_TIMEOUT_SETTING_MS,
                     mSleepTimeoutSetting);
             proto.write(
+                    PowerServiceSettingsAndConfigurationDumpProto.ATTENTIVE_TIMEOUT_SETTING_MS,
+                    mAttentiveTimeoutSetting);
+            proto.write(
+                    PowerServiceSettingsAndConfigurationDumpProto.ATTENTIVE_TIMEOUT_CONFIG_MS,
+                    mAttentiveTimeoutConfig);
+            proto.write(
+                    PowerServiceSettingsAndConfigurationDumpProto
+                            .ATTENTIVE_WARNING_DURATION_CONFIG_MS,
+                    mAttentiveWarningDurationConfig);
+            proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto
                             .MAXIMUM_SCREEN_OFF_TIMEOUT_FROM_DEVICE_ADMIN_MS,
                     // Clamp to int32
@@ -3853,9 +4034,11 @@
                     mIsVrModeEnabled);
             proto.end(settingsAndConfigurationToken);
 
-            final long sleepTimeout = getSleepTimeoutLocked();
-            final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
+            final long attentiveTimeout = getAttentiveTimeoutLocked();
+            final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
+            final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, attentiveTimeout);
             final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+            proto.write(PowerManagerServiceDumpProto.ATTENTIVE_TIMEOUT_MS, attentiveTimeout);
             proto.write(PowerManagerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout);
             proto.write(PowerManagerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout);
             proto.write(PowerManagerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration);
@@ -4009,6 +4192,9 @@
                 case MSG_CHECK_FOR_LONG_WAKELOCKS:
                     checkForLongWakeLocks();
                     break;
+                case MSG_ATTENTIVE_TIMEOUT:
+                    handleAttentiveTimeout();
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 25c41f5..c256b84 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1375,6 +1375,28 @@
                 this, in, out, err, args, callback, resultReceiver);
     }
 
+    @Override
+    public void showInattentiveSleepWarning() {
+        enforceStatusBarService();
+        if (mBar != null) {
+            try {
+                mBar.showInattentiveSleepWarning();
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    @Override
+    public void dismissInattentiveSleepWarning() {
+        enforceStatusBarService();
+        if (mBar != null) {
+            try {
+                mBar.dismissInattentiveSleepWarning();
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
     public String[] getStatusBarIcons() {
         return mContext.getResources().getStringArray(R.array.config_statusBarIcons);
     }
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 88de250..592f4ec 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -33,9 +33,11 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -62,11 +64,13 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.test.mock.MockContentResolver;
 import android.view.Display;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.lights.LightsManager;
@@ -109,6 +113,9 @@
     @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
     @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
 
+    @Mock
+    private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+
     private PowerManagerService mService;
     private PowerSaveState mPowerSaveState;
     private DisplayPowerRequest mDisplayPowerRequest;
@@ -149,6 +156,9 @@
         when(mBatterySaverPolicyMock.getBatterySaverPolicy(
                 eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS)))
                 .thenReturn(mPowerSaveState);
+        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
+        when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true);
 
         mDisplayPowerRequest = new DisplayPowerRequest();
         addLocalServiceMock(LightsManager.class, mLightsManagerMock);
@@ -161,7 +171,12 @@
         mResourcesSpy = spy(mContextSpy.getResources());
         when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
 
-        when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true);
+        MockContentResolver cr = new MockContentResolver(mContextSpy);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mContextSpy.getContentResolver()).thenReturn(cr);
+
+        Settings.Global.putInt(mContextSpy.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
     }
 
     private PowerManagerService createService() {
@@ -198,6 +213,11 @@
             AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
                 return mAmbientDisplayConfigurationMock;
             }
+
+            @Override
+            InattentiveSleepWarningController createInattentiveSleepWarningController() {
+                return mInattentiveSleepWarningControllerMock;
+            }
         });
         return mService;
     }
@@ -208,8 +228,12 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.removeServiceForTest(BatteryManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+
         Settings.Global.putInt(
                 mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0);
+        setAttentiveTimeout(-1);
+        Settings.Global.putInt(mContextSpy.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
     }
 
     /**
@@ -263,12 +287,30 @@
 
     private void setPluggedIn(boolean isPluggedIn) {
         // Set the callback to return the new state
-        when(mBatteryManagerInternalMock.isPowered(BatteryManager.BATTERY_PLUGGED_ANY))
+        when(mBatteryManagerInternalMock.isPowered(anyInt()))
                 .thenReturn(isPluggedIn);
         // Trigger PowerManager to reread the plug-in state
         mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
     }
 
+    private void setAttentiveTimeout(int attentiveTimeoutMillis) {
+        Settings.Secure.putInt(
+                mContextSpy.getContentResolver(), Settings.Secure.ATTENTIVE_TIMEOUT,
+                attentiveTimeoutMillis);
+    }
+
+    private void setAttentiveWarningDuration(int attentiveWarningDurationMillis) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_attentiveWarningDuration))
+                .thenReturn(attentiveWarningDurationMillis);
+    }
+
+    private void setMinimumScreenOffTimeoutConfig(int minimumScreenOffTimeoutConfigMillis) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_minimumScreenOffTimeout))
+                .thenReturn(minimumScreenOffTimeoutConfigMillis);
+    }
+
     @Test
     public void testUpdatePowerScreenPolicy_UpdateDisplayPowerRequest() {
         createService();
@@ -615,4 +657,97 @@
                 .setDozeOverrideFromDreamManager(Display.STATE_ON, PowerManager.BRIGHTNESS_DEFAULT);
         assertTrue(isAcquired[0]);
     }
+
+    @Test
+    public void testInattentiveSleep_hideWarningIfStayOnIsEnabledAndPluggedIn() throws Exception {
+        setAttentiveTimeout(15000);
+        Settings.Global.putInt(mContextSpy.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC);
+
+        createService();
+        startSystem();
+
+        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss();
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        setPluggedIn(true);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss();
+    }
+
+    @Test
+    public void testInattentive_userActivityDismissesWarning() throws Exception {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(30);
+        setAttentiveTimeout(100);
+
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().userActivity(SystemClock.uptimeMillis(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+        verify(mInattentiveSleepWarningControllerMock, never()).show();
+
+        SystemClock.sleep(70);
+        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss();
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        mService.getBinderServiceInstance().userActivity(SystemClock.uptimeMillis(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+        verify(mInattentiveSleepWarningControllerMock, times(1)).dismiss();
+    }
+
+    @Test
+    public void testInattentiveSleep_warningHiddenAfterWakingUp() throws Exception {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(20);
+        setAttentiveTimeout(30);
+
+        createService();
+        startSystem();
+        SystemClock.sleep(10);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+        SystemClock.sleep(30);
+        forceAwake();
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss();
+    }
+
+    @Test
+    public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() throws Exception {
+        setAttentiveTimeout(-1);
+        createService();
+        startSystem();
+        verify(mInattentiveSleepWarningControllerMock, never()).show();
+    }
+
+    @Test
+    public void testInattentiveSleep_goesToSleepAfterTimeout() throws Exception {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(5);
+        createService();
+        startSystem();
+        SystemClock.sleep(8);
+        assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+    @Test
+    public void testInattentiveSleep_goesToSleepWithWakeLock() throws Exception {
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "sleep_testWithWakeLock";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(10);
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */);
+
+        SystemClock.sleep(11);
+        assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP);
+    }
 }