Implement auto-sleep functionality.

Added a new SLEEP_TIMEOUT setting which governs how long the device will
remain awake or dreaming without user activity.  By default this
value is set to -1 which maintains today's existing behavior.

We basically represent the time we are allowed to be dreaming as a new
kind of user activity summary state called DREAM, similar to BRIGHT
and DIM.  When the sleep timeout expires, the state is cleared and
the dream ends.

Bug: 17665809
Change-Id: I59aa7648dcec215f1285464fc1134934a09230e5
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ec4a53e..e40c88f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2001,7 +2001,10 @@
         public static final String DIM_SCREEN = "dim_screen";
 
         /**
-         * The timeout before the screen turns off.
+         * The amount of time in milliseconds before the device goes to sleep or begins
+         * to dream after a period of inactivity.  This value is also known as the
+         * user activity timeout period since the screen isn't necessarily turned off
+         * when it expires.
          */
         public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
 
@@ -4817,6 +4820,20 @@
                 "usb_audio_automatic_routing_disabled";
 
         /**
+         * The timeout in milliseconds before the device fully goes to sleep after
+         * a period of inactivity.  This value sets an upper bound on how long the device
+         * will stay awake or dreaming without user activity.  It should generally
+         * be longer than {@link #SCREEN_OFF_TIMEOUT} as otherwise the device
+         * will sleep before it ever has a chance to dream.
+         * <p>
+         * Use -1 to disable this timeout.
+         * </p>
+         *
+         * @hide
+         */
+        public static final String SLEEP_TIMEOUT = "sleep_timeout";
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
@@ -4865,7 +4882,8 @@
             MOUNT_UMS_AUTOSTART,
             MOUNT_UMS_PROMPT,
             MOUNT_UMS_NOTIFY_ENABLED,
-            UI_NIGHT_MODE
+            UI_NIGHT_MODE,
+            SLEEP_TIMEOUT
         };
 
         /**
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 934ed38..efba03d 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -19,6 +19,7 @@
 <resources>
     <bool name="def_dim_screen">true</bool>
     <integer name="def_screen_off_timeout">60000</integer>
+    <integer name="def_sleep_timeout">-1</integer>
     <bool name="def_airplane_mode_on">false</bool>
     <!-- Comma-separated list of bluetooth, wifi, and cell. -->
     <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,wifi,nfc,wimax</string>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 873257c..b17b4cc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -70,7 +70,7 @@
     // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
     // is properly propagated through your change.  Not doing so will result in a loss of user
     // settings.
-    private static final int DATABASE_VERSION = 112;
+    private static final int DATABASE_VERSION = 113;
 
     private Context mContext;
     private int mUserHandle;
@@ -1811,6 +1811,22 @@
             upgradeVersion = 112;
         }
 
+        if (upgradeVersion < 113) {
+            db.beginTransaction();
+            SQLiteStatement stmt = null;
+            try {
+                stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value)"
+                        + " VALUES(?,?);");
+                loadIntegerSetting(stmt, Settings.Secure.SLEEP_TIMEOUT,
+                        R.integer.def_sleep_timeout);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+                if (stmt != null) stmt.close();
+            }
+            upgradeVersion = 113;
+        }
+
         // *** Remember to update DATABASE_VERSION above!
 
         if (upgradeVersion != currentVersion) {
@@ -2382,6 +2398,8 @@
             loadBooleanSetting(stmt, Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
                     R.bool.def_lock_screen_allow_private_notifications);
 
+            loadIntegerSetting(stmt, Settings.Secure.SLEEP_TIMEOUT,
+                    R.integer.def_sleep_timeout);
         } finally {
             if (stmt != null) stmt.close();
         }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 4f41bee..7ad5542f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -142,10 +142,12 @@
     // Summarizes the user activity state.
     private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0;
     private static final int USER_ACTIVITY_SCREEN_DIM = 1 << 1;
+    private static final int USER_ACTIVITY_SCREEN_DREAM = 1 << 2;
 
     // Default timeout in milliseconds.  This is only used until the settings
     // provider populates the actual default value (R.integer.def_screen_off_timeout).
     private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000;
+    private static final int DEFAULT_SLEEP_TIMEOUT = -1;
 
     // Power hints defined in hardware/libhardware/include/hardware/power.h.
     private static final int POWER_HINT_INTERACTION = 2;
@@ -214,7 +216,6 @@
     private long mLastInteractivePowerHintTime;
 
     // A bitfield that summarizes the effect of the user activity timer.
-    // A zero value indicates that the user activity timer has expired.
     private int mUserActivitySummary;
 
     // The desired display power state.  The actual state may lag behind the
@@ -340,6 +341,9 @@
     // The screen off timeout setting value in milliseconds.
     private int mScreenOffTimeoutSetting;
 
+    // The sleep timeout setting value in milliseconds.
+    private int mSleepTimeoutSetting;
+
     // The maximum allowable screen off timeout according to the device
     // administration policy.  Overrides other settings.
     private int mMaximumScreenOffTimeoutFromDeviceAdmin = Integer.MAX_VALUE;
@@ -543,6 +547,9 @@
             resolver.registerContentObserver(Settings.System.getUriFor(
                     Settings.System.SCREEN_OFF_TIMEOUT),
                     false, mSettingsObserver, UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.SLEEP_TIMEOUT),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
                     false, mSettingsObserver, UserHandle.USER_ALL);
@@ -624,6 +631,9 @@
         mScreenOffTimeoutSetting = Settings.System.getIntForUser(resolver,
                 Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT,
                 UserHandle.USER_CURRENT);
+        mSleepTimeoutSetting = Settings.Secure.getIntForUser(resolver,
+                Settings.Secure.SLEEP_TIMEOUT, DEFAULT_SLEEP_TIMEOUT,
+                UserHandle.USER_CURRENT);
         mStayOnWhilePluggedInSetting = Settings.Global.getInt(resolver,
                 Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC);
 
@@ -1431,7 +1441,8 @@
             if (mWakefulness == WAKEFULNESS_AWAKE
                     || mWakefulness == WAKEFULNESS_DREAMING
                     || mWakefulness == WAKEFULNESS_DOZING) {
-                final int screenOffTimeout = getScreenOffTimeoutLocked();
+                final int sleepTimeout = getSleepTimeoutLocked();
+                final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
                 final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
 
                 mUserActivitySummary = 0;
@@ -1439,11 +1450,11 @@
                     nextTimeout = mLastUserActivityTime
                             + screenOffTimeout - screenDimDuration;
                     if (now < nextTimeout) {
-                        mUserActivitySummary |= USER_ACTIVITY_SCREEN_BRIGHT;
+                        mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
                     } else {
                         nextTimeout = mLastUserActivityTime + screenOffTimeout;
                         if (now < nextTimeout) {
-                            mUserActivitySummary |= USER_ACTIVITY_SCREEN_DIM;
+                            mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
                         }
                     }
                 }
@@ -1458,7 +1469,22 @@
                         }
                     }
                 }
-                if (mUserActivitySummary != 0) {
+                if (mUserActivitySummary == 0) {
+                    if (sleepTimeout >= 0) {
+                        final long anyUserActivity = Math.max(mLastUserActivityTime,
+                                mLastUserActivityTimeNoChangeLights);
+                        if (anyUserActivity >= mLastWakeTime) {
+                            nextTimeout = anyUserActivity + sleepTimeout;
+                            if (now < nextTimeout) {
+                                mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
+                            }
+                        }
+                    } else {
+                        mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
+                        nextTimeout = -1;
+                    }
+                }
+                if (mUserActivitySummary != 0 && nextTimeout >= 0) {
                     Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT);
                     msg.setAsynchronous(true);
                     mHandler.sendMessageAtTime(msg, nextTimeout);
@@ -1495,7 +1521,15 @@
         }
     }
 
-    private int getScreenOffTimeoutLocked() {
+    private int getSleepTimeoutLocked() {
+        int timeout = mSleepTimeoutSetting;
+        if (timeout <= 0) {
+            return -1;
+        }
+        return Math.max(timeout, mMinimumScreenOffTimeoutConfig);
+    }
+
+    private int getScreenOffTimeoutLocked(int sleepTimeout) {
         int timeout = mScreenOffTimeoutSetting;
         if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) {
             timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin);
@@ -1503,6 +1537,9 @@
         if (mUserActivityTimeoutOverrideFromWindowManager >= 0) {
             timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager);
         }
+        if (sleepTimeout >= 0) {
+            timeout = Math.min(timeout, sleepTimeout);
+        }
         return Math.max(timeout, mMinimumScreenOffTimeoutConfig);
     }
 
@@ -1619,8 +1656,7 @@
             mSandmanScheduled = false;
             wakefulness = mWakefulness;
             if (mSandmanSummoned && mDisplayReady) {
-                startDreaming = ((wakefulness == WAKEFULNESS_DREAMING && canDreamLocked())
-                        || wakefulness == WAKEFULNESS_DOZING);
+                startDreaming = canDreamLocked() || canDozeLocked();
                 mSandmanSummoned = false;
             } else {
                 startDreaming = false;
@@ -1708,13 +1744,14 @@
 
     /**
      * Returns true if the device is allowed to dream in its current state.
-     * This function is not called when dozing.
      */
     private boolean canDreamLocked() {
         if (mWakefulness != WAKEFULNESS_DREAMING
                 || !mDreamsSupportedConfig
                 || !mDreamsEnabledSetting
                 || !mDisplayPowerRequest.isBrightOrDim()
+                || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT
+                        | USER_ACTIVITY_SCREEN_DIM | USER_ACTIVITY_SCREEN_DREAM)) == 0
                 || !mBootCompleted) {
             return false;
         }
@@ -1737,6 +1774,13 @@
     }
 
     /**
+     * Returns true if the device is allowed to doze in its current state.
+     */
+    private boolean canDozeLocked() {
+        return mWakefulness == WAKEFULNESS_DOZING;
+    }
+
+    /**
      * Updates the display power state asynchronously.
      * When the update is finished, mDisplayReady will be set to true.  The display
      * controller posts a message to tell us when the actual display power state
@@ -2343,6 +2387,7 @@
             pw.println("  mMaximumScreenDimDurationConfig=" + mMaximumScreenDimDurationConfig);
             pw.println("  mMaximumScreenDimRatioConfig=" + mMaximumScreenDimRatioConfig);
             pw.println("  mScreenOffTimeoutSetting=" + mScreenOffTimeoutSetting);
+            pw.println("  mSleepTimeoutSetting=" + mSleepTimeoutSetting);
             pw.println("  mMaximumScreenOffTimeoutFromDeviceAdmin="
                     + mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced="
                     + isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")");
@@ -2367,9 +2412,11 @@
             pw.println("  mScreenBrightnessSettingMaximum=" + mScreenBrightnessSettingMaximum);
             pw.println("  mScreenBrightnessSettingDefault=" + mScreenBrightnessSettingDefault);
 
-            final int screenOffTimeout = getScreenOffTimeoutLocked();
+            final int sleepTimeout = getSleepTimeoutLocked();
+            final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
             final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
             pw.println();
+            pw.println("Sleep timeout: " + sleepTimeout + " ms");
             pw.println("Screen off timeout: " + screenOffTimeout + " ms");
             pw.println("Screen dim duration: " + screenDimDuration + " ms");