First stab at device idle mode.

Introduce a new device idle controller service that
monitor's the device state and determines when to go
in to idle mode.  When in idle mode, all we do right
now is turn off network access the same as we do for
power save mode.  Many more things should come in the
future -- stopping the alarm manager from scheduling
(most) alarms, telling GmsCore for it to stop doing
stuff, etc.

Battery stats now has state tracking for devie idle
mode, as well as events for the reasons we can come
out of idle mode (significant motion or the device
becoming active).  Also added new events noting when
packages are installed.

Renamed the "low power" event in battery stats to
"power save" because the former was just way too
confusing.

Finally, fix buffer size reading kernel wake locks.
(Stupidly, just increasing the buffer size.  Ideally
we should try to be smarter and grow our buffer to
fit the data available, but I'll leave that for
another time.)

Change-Id: I0be2062466c83ee9d890c6cf5a228d9cc4090eca
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index b077e06..12e1963 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -472,7 +472,8 @@
     /**
      * Creates a virtual display.
      *
-     * @see #createVirtualDisplay(String, int, int, int, Surface, int, VirtualDisplay.Callback)
+     * @see #createVirtualDisplay(String, int, int, int, Surface, int,
+     * VirtualDisplay.Callback, Handler)
      */
     public VirtualDisplay createVirtualDisplay(@NonNull String name,
             int width, int height, int densityDpi, @Nullable Surface surface, int flags) {
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 1129c9e..7e92de2 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -54,7 +54,8 @@
     void setRestrictBackground(boolean restrictBackground);
     boolean getRestrictBackground();
 
+    void setDeviceIdleMode(boolean enabled);
+
     NetworkQuotaInfo getNetworkQuotaInfo(in NetworkState state);
     boolean isNetworkMetered(in NetworkState state);
-
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d96a0e9..cab03da 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -30,7 +30,6 @@
 import android.telephony.SignalStrength;
 import android.text.format.DateFormat;
 import android.util.Printer;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
@@ -1044,14 +1043,15 @@
         public static final int STATE2_WIFI_SIGNAL_STRENGTH_MASK =
                 0x7 << STATE2_WIFI_SIGNAL_STRENGTH_SHIFT;
 
-        public static final int STATE2_LOW_POWER_FLAG = 1<<31;
+        public static final int STATE2_POWER_SAVE_FLAG = 1<<31;
         public static final int STATE2_VIDEO_ON_FLAG = 1<<30;
         public static final int STATE2_WIFI_RUNNING_FLAG = 1<<29;
         public static final int STATE2_WIFI_ON_FLAG = 1<<28;
         public static final int STATE2_FLASHLIGHT_FLAG = 1<<27;
+        public static final int STATE2_DEVICE_IDLE_FLAG = 1<<26;
 
         public static final int MOST_INTERESTING_STATES2 =
-            STATE2_LOW_POWER_FLAG | STATE2_WIFI_ON_FLAG;
+            STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_FLAG;
 
         public int states2;
 
@@ -1086,10 +1086,18 @@
         public static final int EVENT_USER_RUNNING = 0x0007;
         // Events for foreground user.
         public static final int EVENT_USER_FOREGROUND = 0x0008;
-        // Events for connectivity changed.
+        // Event for connectivity changed.
         public static final int EVENT_CONNECTIVITY_CHANGED = 0x0009;
+        // Event for significant motion taking us out of idle mode.
+        public static final int EVENT_SIGNIFICANT_MOTION = 0x000a;
+        // Event for becoming active taking us out of idle mode.
+        public static final int EVENT_ACTIVE = 0x000b;
+        // Event for a package being installed.
+        public static final int EVENT_PACKAGE_INSTALLED = 0x000c;
+        // Event for a package being uninstalled.
+        public static final int EVENT_PACKAGE_UNINSTALLED = 0x000d;
         // Number of event types.
-        public static final int EVENT_COUNT = 0x000a;
+        public static final int EVENT_COUNT = 0x000e;
         // Mask to extract out only the type part of the event.
         public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH);
 
@@ -1486,19 +1494,34 @@
             long elapsedRealtimeUs, int which);
 
     /**
-     * Returns the time in microseconds that low power mode has been enabled while the device was
+     * Returns the time in microseconds that power save mode has been enabled while the device was
      * running on battery.
      *
      * {@hide}
      */
-    public abstract long getLowPowerModeEnabledTime(long elapsedRealtimeUs, int which);
+    public abstract long getPowerSaveModeEnabledTime(long elapsedRealtimeUs, int which);
 
     /**
-     * Returns the number of times that low power mode was enabled.
+     * Returns the number of times that power save mode was enabled.
      *
      * {@hide}
      */
-    public abstract int getLowPowerModeEnabledCount(int which);
+    public abstract int getPowerSaveModeEnabledCount(int which);
+
+    /**
+     * Returns the time in microseconds that device has been in idle mode while
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getDeviceIdleModeEnabledTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times that the devie has gone in to idle mode.
+     *
+     * {@hide}
+     */
+    public abstract int getDeviceIdleModeEnabledCount(int which);
 
     /**
      * Returns the number of times that connectivity state changed.
@@ -1692,11 +1715,12 @@
 
     public static final BitDescription[] HISTORY_STATE2_DESCRIPTIONS
             = new BitDescription[] {
-        new BitDescription(HistoryItem.STATE2_LOW_POWER_FLAG, "low_power", "lp"),
+        new BitDescription(HistoryItem.STATE2_POWER_SAVE_FLAG, "power_save", "ps"),
         new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"),
         new BitDescription(HistoryItem.STATE2_WIFI_RUNNING_FLAG, "wifi_running", "Wr"),
         new BitDescription(HistoryItem.STATE2_WIFI_ON_FLAG, "wifi", "W"),
         new BitDescription(HistoryItem.STATE2_FLASHLIGHT_FLAG, "flashlight", "fl"),
+        new BitDescription(HistoryItem.STATE2_DEVICE_IDLE_FLAG, "device_idle", "di"),
         new BitDescription(HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK,
                 HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT, "wifi_signal_strength", "Wss",
                 new String[] { "0", "1", "2", "3", "4" },
@@ -1707,11 +1731,13 @@
     };
 
     public static final String[] HISTORY_EVENT_NAMES = new String[] {
-            "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn"
+            "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn",
+            "motion", "active", "pkginst", "pkgunin"
     };
 
     public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
-            "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn"
+            "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn",
+            "Esm", "Eac", "Epi", "Epu"
     };
 
     /**
@@ -2310,7 +2336,8 @@
         final long totalUptime = computeUptime(rawUptime, which);
         final long screenOnTime = getScreenOnTime(rawRealtime, which);
         final long interactiveTime = getInteractiveTime(rawRealtime, which);
-        final long lowPowerModeEnabledTime = getLowPowerModeEnabledTime(rawRealtime, which);
+        final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which);
+        final long deviceIdleModeEnabledTime = getDeviceIdleModeEnabledTime(rawRealtime, which);
         final int connChanges = getNumConnectivityChange(which);
         final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
         final long wifiOnTime = getWifiOnTime(rawRealtime, which);
@@ -2382,7 +2409,8 @@
                 fullWakeLockTimeTotal / 1000, partialWakeLockTimeTotal / 1000,
                 0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which) / 1000,
                 getMobileRadioActiveAdjustedTime(which) / 1000, interactiveTime / 1000,
-                lowPowerModeEnabledTime / 1000, connChanges);
+                powerSaveModeEnabledTime / 1000, connChanges, deviceIdleModeEnabledTime / 1000,
+                getDeviceIdleModeEnabledCount(which));
         
         // Dump screen brightness stats
         Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -2849,7 +2877,8 @@
 
         final long screenOnTime = getScreenOnTime(rawRealtime, which);
         final long interactiveTime = getInteractiveTime(rawRealtime, which);
-        final long lowPowerModeEnabledTime = getLowPowerModeEnabledTime(rawRealtime, which);
+        final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which);
+        final long deviceIdleModeEnabledTime = getDeviceIdleModeEnabledTime(rawRealtime, which);
         final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
         final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which);
         final long wifiOnTime = getWifiOnTime(rawRealtime, which);
@@ -2884,22 +2913,33 @@
         }
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
-        if (lowPowerModeEnabledTime != 0) {
+        if (powerSaveModeEnabledTime != 0) {
             sb.setLength(0);
             sb.append(prefix);
-                    sb.append("  Low power mode enabled: ");
-                    formatTimeMs(sb, lowPowerModeEnabledTime / 1000);
+                    sb.append("  Power save mode enabled: ");
+                    formatTimeMs(sb, powerSaveModeEnabledTime / 1000);
                     sb.append("(");
-                    sb.append(formatRatioLocked(lowPowerModeEnabledTime, whichBatteryRealtime));
+                    sb.append(formatRatioLocked(powerSaveModeEnabledTime, whichBatteryRealtime));
                     sb.append(")");
             pw.println(sb.toString());
         }
+        if (deviceIdleModeEnabledTime != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Device idling: ");
+                    formatTimeMs(sb, deviceIdleModeEnabledTime / 1000);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(deviceIdleModeEnabledTime, whichBatteryRealtime));
+                    sb.append(") "); sb.append(getDeviceIdleModeEnabledCount(which));
+                    sb.append("x");
+            pw.println(sb.toString());
+        }
         if (phoneOnTime != 0) {
             sb.setLength(0);
             sb.append(prefix);
                     sb.append("  Active phone call: "); formatTimeMs(sb, phoneOnTime / 1000);
                     sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime));
-                    sb.append(") "); sb.append(getPhoneOnCount(which));
+                    sb.append(") "); sb.append(getPhoneOnCount(which)); sb.append("x");
         }
         int connChanges = getNumConnectivityChange(which);
         if (connChanges != 0) {
@@ -4721,7 +4761,7 @@
         prepareForDumpLocked();
 
         dumpLine(pw, 0 /* uid */, "i" /* category */, VERSION_DATA,
-                "12", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion());
+                "13", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion());
 
         long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
 
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 87b6ed7..bea4ece 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -111,6 +111,7 @@
     void noteWifiMulticastDisabledFromSource(in WorkSource ws);
     void noteNetworkInterfaceType(String iface, int type);
     void noteNetworkStatsEnabled();
+    void noteDeviceIdleMode(boolean enabled, boolean fromActive, boolean fromMotion);
     void setBatteryState(int status, int health, int plugType, int level, int temp, int volt);
     long getAwakeTimeBattery();
     long getAwakeTimePlugged();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f9b1ca1..7d5df46 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -109,7 +109,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 119 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 120 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -307,8 +307,11 @@
     boolean mInteractive;
     StopwatchTimer mInteractiveTimer;
 
-    boolean mLowPowerModeEnabled;
-    StopwatchTimer mLowPowerModeEnabledTimer;
+    boolean mPowerSaveModeEnabled;
+    StopwatchTimer mPowerSaveModeEnabledTimer;
+
+    boolean mDeviceIdleModeEnabled;
+    StopwatchTimer mDeviceIdleModeEnabledTimer;
 
     boolean mPhoneOn;
     StopwatchTimer mPhoneOnTimer;
@@ -1775,17 +1778,18 @@
     private final Map<String, KernelWakelockStats> readKernelWakelockStats() {
 
         FileInputStream is;
-        byte[] buffer = new byte[8192];
+        byte[] buffer = new byte[32*1024];
         int len;
-        boolean wakeup_sources = false;
+        boolean wakeup_sources;
 
         try {
             try {
-                is = new FileInputStream("/proc/wakelocks");
+                is = new FileInputStream("/d/wakeup_sources");
+                wakeup_sources = true;
             } catch (java.io.FileNotFoundException e) {
                 try {
-                    is = new FileInputStream("/d/wakeup_sources");
-                    wakeup_sources = true;
+                    is = new FileInputStream("/proc/wakelocks");
+                    wakeup_sources = false;
                 } catch (java.io.FileNotFoundException e2) {
                     return null;
                 }
@@ -1798,6 +1802,9 @@
         }
 
         if (len > 0) {
+            if (len >= buffer.length) {
+                Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
+            }
             int i;
             for (i=0; i<len; i++) {
                 if (buffer[i] == '\0') {
@@ -3386,29 +3393,73 @@
         }
     }
 
-    public void noteLowPowerMode(boolean enabled) {
-        if (mLowPowerModeEnabled != enabled) {
+    public void notePowerSaveMode(boolean enabled) {
+        if (mPowerSaveModeEnabled != enabled) {
             int stepState = enabled ? STEP_LEVEL_MODE_POWER_SAVE : 0;
             mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_POWER_SAVE) ^ stepState;
             mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState;
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
-            mLowPowerModeEnabled = enabled;
+            mPowerSaveModeEnabled = enabled;
             if (enabled) {
-                mHistoryCur.states2 |= HistoryItem.STATE2_LOW_POWER_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Low power mode enabled to: "
+                mHistoryCur.states2 |= HistoryItem.STATE2_POWER_SAVE_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode enabled to: "
                         + Integer.toHexString(mHistoryCur.states2));
-                mLowPowerModeEnabledTimer.startRunningLocked(elapsedRealtime);
+                mPowerSaveModeEnabledTimer.startRunningLocked(elapsedRealtime);
             } else {
-                mHistoryCur.states2 &= ~HistoryItem.STATE2_LOW_POWER_FLAG;
-                if (DEBUG_HISTORY) Slog.v(TAG, "Low power mode disabled to: "
+                mHistoryCur.states2 &= ~HistoryItem.STATE2_POWER_SAVE_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode disabled to: "
                         + Integer.toHexString(mHistoryCur.states2));
-                mLowPowerModeEnabledTimer.stopRunningLocked(elapsedRealtime);
+                mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtime);
             }
             addHistoryRecordLocked(elapsedRealtime, uptime);
         }
     }
 
+    public void noteDeviceIdleModeLocked(boolean enabled, boolean fromActive, boolean fromMotion) {
+        if (mDeviceIdleModeEnabled != enabled) {
+            final long elapsedRealtime = SystemClock.elapsedRealtime();
+            final long uptime = SystemClock.uptimeMillis();
+            mDeviceIdleModeEnabled = enabled;
+            if (fromMotion) {
+                addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_SIGNIFICANT_MOTION,
+                        "", 0);
+            }
+            if (fromActive) {
+                addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ACTIVE,
+                        "", 0);
+            }
+            if (enabled) {
+                mHistoryCur.states2 |= HistoryItem.STATE2_DEVICE_IDLE_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode enabled to: "
+                        + Integer.toHexString(mHistoryCur.states2));
+                mDeviceIdleModeEnabledTimer.startRunningLocked(elapsedRealtime);
+            } else {
+                mHistoryCur.states2 &= ~HistoryItem.STATE2_DEVICE_IDLE_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode disabled to: "
+                        + Integer.toHexString(mHistoryCur.states2));
+                mDeviceIdleModeEnabledTimer.stopRunningLocked(elapsedRealtime);
+            }
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+    }
+
+    public void notePackageInstalledLocked(String pkgName, int versionCode) {
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long uptime = SystemClock.uptimeMillis();
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PACKAGE_INSTALLED,
+                pkgName, versionCode);
+        mNumConnectivityChange++;
+    }
+
+    public void notePackageUninstalledLocked(String pkgName) {
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long uptime = SystemClock.uptimeMillis();
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PACKAGE_UNINSTALLED,
+                pkgName, 0);
+        mNumConnectivityChange++;
+    }
+
     public void notePhoneOnLocked() {
         if (!mPhoneOn) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -4195,12 +4246,20 @@
         return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
 
-    @Override public long getLowPowerModeEnabledTime(long elapsedRealtimeUs, int which) {
-        return mLowPowerModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    @Override public long getPowerSaveModeEnabledTime(long elapsedRealtimeUs, int which) {
+        return mPowerSaveModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
 
-    @Override public int getLowPowerModeEnabledCount(int which) {
-        return mLowPowerModeEnabledTimer.getCountLocked(which);
+    @Override public int getPowerSaveModeEnabledCount(int which) {
+        return mPowerSaveModeEnabledTimer.getCountLocked(which);
+    }
+
+    @Override public long getDeviceIdleModeEnabledTime(long elapsedRealtimeUs, int which) {
+        return mDeviceIdleModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public int getDeviceIdleModeEnabledCount(int which) {
+        return mDeviceIdleModeEnabledTimer.getCountLocked(which);
     }
 
     @Override public int getNumConnectivityChange(int which) {
@@ -6662,8 +6721,9 @@
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase);
         }
-        mInteractiveTimer = new StopwatchTimer(null, -9, null, mOnBatteryTimeBase);
-        mLowPowerModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase);
+        mInteractiveTimer = new StopwatchTimer(null, -10, null, mOnBatteryTimeBase);
+        mPowerSaveModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase);
+        mDeviceIdleModeEnabledTimer = new StopwatchTimer(null, -11, null, mOnBatteryTimeBase);
         mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
             mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null,
@@ -7233,7 +7293,8 @@
             mScreenBrightnessTimer[i].reset(false);
         }
         mInteractiveTimer.reset(false);
-        mLowPowerModeEnabledTimer.reset(false);
+        mPowerSaveModeEnabledTimer.reset(false);
+        mDeviceIdleModeEnabledTimer.reset(false);
         mPhoneOnTimer.reset(false);
         mAudioOnTimer.reset(false);
         mVideoOnTimer.reset(false);
@@ -8534,7 +8595,8 @@
         mInteractive = false;
         mInteractiveTimer.readSummaryFromParcelLocked(in);
         mPhoneOn = false;
-        mLowPowerModeEnabledTimer.readSummaryFromParcelLocked(in);
+        mPowerSaveModeEnabledTimer.readSummaryFromParcelLocked(in);
+        mDeviceIdleModeEnabledTimer.readSummaryFromParcelLocked(in);
         mPhoneOnTimer.readSummaryFromParcelLocked(in);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
             mPhoneSignalStrengthsTimer[i].readSummaryFromParcelLocked(in);
@@ -8835,7 +8897,8 @@
             mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         }
         mInteractiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
-        mLowPowerModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mPowerSaveModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mDeviceIdleModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
             mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
@@ -9132,9 +9195,10 @@
                     in);
         }
         mInteractive = false;
-        mInteractiveTimer = new StopwatchTimer(null, -9, null, mOnBatteryTimeBase, in);
+        mInteractiveTimer = new StopwatchTimer(null, -10, null, mOnBatteryTimeBase, in);
         mPhoneOn = false;
-        mLowPowerModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
+        mPowerSaveModeEnabledTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
+        mDeviceIdleModeEnabledTimer = new StopwatchTimer(null, -11, null, mOnBatteryTimeBase, in);
         mPhoneOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase, in);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
             mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i,
@@ -9299,7 +9363,8 @@
             mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
         }
         mInteractiveTimer.writeToParcel(out, uSecRealtime);
-        mLowPowerModeEnabledTimer.writeToParcel(out, uSecRealtime);
+        mPowerSaveModeEnabledTimer.writeToParcel(out, uSecRealtime);
+        mDeviceIdleModeEnabledTimer.writeToParcel(out, uSecRealtime);
         mPhoneOnTimer.writeToParcel(out, uSecRealtime);
         for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
             mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime);
@@ -9436,8 +9501,10 @@
             }
             pr.println("*** Interactive timer:");
             mInteractiveTimer.logState(pr, "  ");
-            pr.println("*** Low power mode timer:");
-            mLowPowerModeEnabledTimer.logState(pr, "  ");
+            pr.println("*** Power save mode timer:");
+            mPowerSaveModeEnabledTimer.logState(pr, "  ");
+            pr.println("*** Device idle mode timer:");
+            mDeviceIdleModeEnabledTimer.logState(pr, "  ");
             pr.println("*** Phone timer:");
             mPhoneOnTimer.logState(pr, "  ");
             for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
new file mode 100644
index 0000000..062992d
--- /dev/null
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2015 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;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+import android.hardware.display.DisplayManager;
+import android.net.INetworkPolicyManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.TimeUtils;
+import android.view.Display;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.am.BatteryStatsService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Keeps track of device idleness and drives low power mode based on that.
+ */
+public class DeviceIdleController extends SystemService {
+    private static final String TAG = "DeviceIdleController";
+
+    private static final String ACTION_STEP_IDLE_STATE =
+            "com.android.server.device_idle.STEP_IDLE_STATE";
+
+    // TODO: These need to be moved to system settings.
+
+    /**
+     * This is the time, after becoming inactive, at which we start looking at the
+     * motion sensor to determine if the device is being left alone.  We don't do this
+     * immediately after going inactive just because we don't want to be continually running
+     * the significant motion sensor whenever the screen is off.
+     */
+    private static final long DEFAULT_INACTIVE_TIMEOUT = 30*60*1000L;
+    /**
+     * This is the time, after the inactive timeout elapses, that we will wait looking
+     * for significant motion until we truly consider the device to be idle.
+     */
+    private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT = 30*60*1000L;
+    /**
+     * This is the initial time, after being idle, that we will allow ourself to be back
+     * in the IDLE_PENDING state allowing the system to run normally until we return to idle.
+     */
+    private static final long DEFAULT_IDLE_PENDING_TIMEOUT = 5*60*1000L;
+    /**
+     * Maximum pending idle timeout (time spent running) we will be allowed to use.
+     */
+    private static final long DEFAULT_MAX_IDLE_PENDING_TIMEOUT = 10*60*1000L;
+    /**
+     * Scaling factor to apply to current pending idle timeout each time we cycle through
+     * that state.
+     */
+    private static final float DEFAULT_IDLE_PENDING_FACTOR = 2f;
+    /**
+     * This is the initial time that we want to sit in the idle state before waking up
+     * again to return to pending idle and allowing normal work to run.
+     */
+    private static final long DEFAULT_IDLE_TIMEOUT = 60*60*1000L;
+    /**
+     * Maximum idle duration we will be allowed to use.
+     */
+    private static final long DEFAULT_MAX_IDLE_TIMEOUT = 6*60*60*1000L;
+    /**
+     * Scaling factor to apply to current idle timeout each time we cycle through that state.
+     */
+    private static final float DEFAULT_IDLE_FACTOR = 2f;
+
+    private AlarmManager mAlarmManager;
+    private IBatteryStats mBatteryStats;
+    private INetworkPolicyManager mNetworkPolicyManager;
+    private DisplayManager mDisplayManager;
+    private SensorManager mSensorManager;
+    private Sensor mSigMotionSensor;
+    private PendingIntent mAlarmIntent;
+    private Display mCurDisplay;
+    private boolean mScreenOn;
+    private boolean mCharging;
+    private boolean mSigMotionActive;
+
+    /** Device is currently active. */
+    private static final int STATE_ACTIVE = 0;
+    /** Device is inactve (screen off, no motion) and we are waiting to for idle. */
+    private static final int STATE_INACTIVE = 1;
+    /** Device is past the initial inactive period, and waiting for the next idle period. */
+    private static final int STATE_IDLE_PENDING = 2;
+    /** Device is in the idle state, trying to stay asleep as much as possible. */
+    private static final int STATE_IDLE = 3;
+    private static String stateToString(int state) {
+        switch (state) {
+            case STATE_ACTIVE: return "ACTIVE";
+            case STATE_INACTIVE: return "INACTIVE";
+            case STATE_IDLE_PENDING: return "IDLE_PENDING";
+            case STATE_IDLE: return "IDLE";
+            default: return Integer.toString(state);
+        }
+    }
+
+    private int mState;
+
+    private long mNextAlarmTime;
+    private long mNextIdlePendingDelay;
+    private long mNextIdleDelay;
+
+    private final Binder mBinder = new Binder() {
+        @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            DeviceIdleController.this.dump(fd, pw, args);
+        }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+                int plugged = intent.getIntExtra("plugged", 0);
+                updateChargingLocked(plugged != 0);
+            } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
+                synchronized (DeviceIdleController.this) {
+                    stepIdleStateLocked();
+                }
+            }
+        }
+    };
+
+    private final DisplayManager.DisplayListener mDisplayListener
+            = new DisplayManager.DisplayListener() {
+        @Override public void onDisplayAdded(int displayId) {
+        }
+
+        @Override public void onDisplayRemoved(int displayId) {
+        }
+
+        @Override public void onDisplayChanged(int displayId) {
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                synchronized (DeviceIdleController.this) {
+                    updateDisplayLocked();
+                }
+            }
+        }
+    };
+
+    private final TriggerEventListener mSigMotionListener = new TriggerEventListener() {
+        @Override public void onTrigger(TriggerEvent event) {
+            synchronized (DeviceIdleController.this) {
+                significantMotionLocked();
+            }
+        }
+    };
+
+    public DeviceIdleController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        synchronized (this) {
+            mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+            mBatteryStats = BatteryStatsService.getService();
+            mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
+                                ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+            mDisplayManager = (DisplayManager) getContext().getSystemService(
+                    Context.DISPLAY_SERVICE);
+            mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
+            mSigMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+
+            Intent intent = new Intent(ACTION_STEP_IDLE_STATE)
+                    .setPackage("android")
+                    .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+            filter.addAction(ACTION_STEP_IDLE_STATE);
+            getContext().registerReceiver(mReceiver, filter);
+
+            mDisplayManager.registerDisplayListener(mDisplayListener, null);
+
+            mScreenOn = true;
+            // Start out assuming we are charging.  If we aren't, we will at least get
+            // a battery update the next time the level drops.
+            mCharging = true;
+            mState = STATE_ACTIVE;
+            updateDisplayLocked();
+        }
+
+        publishBinderService("deviceidle", mBinder);
+    }
+
+    void updateDisplayLocked() {
+        mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        // We consider any situation where the display is showing something to be it on,
+        // because if there is anything shown we are going to be updating it at some
+        // frequency so can't be allowed to go into deep sleeps.
+        boolean screenOn = mCurDisplay.getState() != Display.STATE_OFF;;
+        if (!screenOn && mScreenOn) {
+            mScreenOn = false;
+            becomeInactiveIfAppropriateLocked();
+        } else if (screenOn) {
+            mScreenOn = true;
+            becomeActiveLocked();
+        }
+    }
+
+    void updateChargingLocked(boolean charging) {
+        if (!charging && mCharging) {
+            mCharging = false;
+            becomeInactiveIfAppropriateLocked();
+        } else if (charging) {
+            mCharging = charging;
+            becomeActiveLocked();
+        }
+    }
+
+    void becomeActiveLocked() {
+        if (mState != STATE_ACTIVE) {
+            try {
+                mNetworkPolicyManager.setDeviceIdleMode(false);
+                mBatteryStats.noteDeviceIdleMode(false, true, false);
+            } catch (RemoteException e) {
+            }
+            mState = STATE_ACTIVE;
+            mNextIdlePendingDelay = 0;
+            mNextIdleDelay = 0;
+            cancelAlarmLocked();
+            stopMonitoringSignificantMotion();
+        }
+    }
+
+    void becomeInactiveIfAppropriateLocked() {
+        if (!mScreenOn && !mCharging && mState == STATE_ACTIVE) {
+            // Screen has turned off; we are now going to become inactive and start
+            // waiting to see if we will ultimately go idle.
+            mState = STATE_INACTIVE;
+            scheduleAlarmLocked(DEFAULT_INACTIVE_TIMEOUT, false);
+        }
+    }
+
+    void stepIdleStateLocked() {
+        switch (mState) {
+            case STATE_INACTIVE:
+                // We have now been inactive long enough, it is time to start looking
+                // for significant motion and sleep some more while doing so.
+                startMonitoringSignificantMotion();
+                scheduleAlarmLocked(DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT, false);
+                // Reset the upcoming idle delays.
+                mNextIdlePendingDelay = DEFAULT_IDLE_PENDING_TIMEOUT;
+                mNextIdleDelay = DEFAULT_IDLE_TIMEOUT;
+                mState = STATE_IDLE_PENDING;
+                break;
+            case STATE_IDLE_PENDING:
+                // We have been waiting to become idle, and now it is time!  This is the
+                // only case where we want to use a wakeup alarm, because we do want to
+                // drag the device out of its sleep state in this case to do the next
+                // scheduled work.
+                scheduleAlarmLocked(mNextIdleDelay, true);
+                mNextIdleDelay = (long)(mNextIdleDelay*DEFAULT_IDLE_FACTOR);
+                if (mNextIdleDelay > DEFAULT_MAX_IDLE_TIMEOUT) {
+                    mNextIdleDelay = DEFAULT_MAX_IDLE_TIMEOUT;
+                }
+                mState = STATE_IDLE;
+                try {
+                    mNetworkPolicyManager.setDeviceIdleMode(true);
+                    mBatteryStats.noteDeviceIdleMode(true, false, false);
+                } catch (RemoteException e) {
+                }
+                break;
+            case STATE_IDLE:
+                // We have been idling long enough, now it is time to do some work.
+                scheduleAlarmLocked(mNextIdlePendingDelay, false);
+                mNextIdlePendingDelay = (long)(mNextIdlePendingDelay*DEFAULT_IDLE_PENDING_FACTOR);
+                if (mNextIdlePendingDelay > DEFAULT_MAX_IDLE_PENDING_TIMEOUT) {
+                    mNextIdlePendingDelay = DEFAULT_MAX_IDLE_PENDING_TIMEOUT;
+                }
+                mState = STATE_IDLE_PENDING;
+                try {
+                    mNetworkPolicyManager.setDeviceIdleMode(false);
+                    mBatteryStats.noteDeviceIdleMode(false, false, false);
+                } catch (RemoteException e) {
+                }
+                break;
+        }
+    }
+
+    void significantMotionLocked() {
+        // When the sensor goes off, its trigger is automatically removed.
+        mSigMotionActive = false;
+        // The device is not yet active, so we want to go back to the pending idle
+        // state to wait again for no motion.  Note that we only monitor for significant
+        // motion after moving out of the inactive state, so no need to worry about that.
+        if (mState != STATE_ACTIVE) {
+            mState = STATE_INACTIVE;
+            try {
+                mNetworkPolicyManager.setDeviceIdleMode(false);
+                mBatteryStats.noteDeviceIdleMode(false, false, true);
+            } catch (RemoteException e) {
+            }
+            stepIdleStateLocked();
+        }
+    }
+
+    void startMonitoringSignificantMotion() {
+        if (mSigMotionSensor != null && !mSigMotionActive) {
+            mSensorManager.requestTriggerSensor(mSigMotionListener, mSigMotionSensor);
+            mSigMotionActive = true;
+        }
+    }
+
+    void stopMonitoringSignificantMotion() {
+        if (mSigMotionActive) {
+            mSensorManager.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor);
+            mSigMotionActive = false;
+        }
+    }
+
+    void cancelAlarmLocked() {
+        if (mNextAlarmTime != 0) {
+            mNextAlarmTime = 0;
+            mAlarmManager.cancel(mAlarmIntent);
+        }
+    }
+
+    void scheduleAlarmLocked(long delay, boolean wakeup) {
+        if (mSigMotionSensor == null) {
+            // If there is no significant motion sensor on this device, then we won't schedule
+            // alarms, because we can't determine if the device is not moving.  This effectively
+            // turns off normal exeuction of device idling, although it is still possible to
+            // manually poke it by pretending like the alarm is going off.
+            return;
+        }
+        mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
+        mAlarmManager.set(wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP
+                : AlarmManager.ELAPSED_REALTIME, mNextAlarmTime, mAlarmIntent);
+    }
+
+    private void dumpHelp(PrintWriter pw) {
+        pw.println("Device idle controller (deviceidle) dump options:");
+        pw.println("  [-h] [CMD]");
+        pw.println("  -h: print this help text.");
+        pw.println("Commands:");
+        pw.println("  step");
+        pw.println("    Immediately step to next state, without waiting for alarm.");
+    }
+
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump DeviceIdleController from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " without permission " + android.Manifest.permission.DUMP);
+            return;
+        }
+
+        if (args != null) {
+            for (int i=0; i<args.length; i++) {
+                String arg = args[i];
+                if ("-h".equals(arg)) {
+                    dumpHelp(pw);
+                    return;
+                } else if ("step".equals(arg)) {
+                    synchronized (this) {
+                        stepIdleStateLocked();
+                        pw.print("Stepped to: "); pw.println(stateToString(mState));
+                    }
+                    return;
+                } else if (arg.length() > 0 && arg.charAt(0) == '-'){
+                    pw.println("Unknown option: " + arg);
+                    dumpHelp(pw);
+                    return;
+                } else {
+                    pw.println("Unknown command: " + arg);
+                    dumpHelp(pw);
+                    return;
+                }
+            }
+        }
+
+        pw.print("  mSigMotionSensor="); pw.println(mSigMotionSensor);
+        pw.print("  mCurDisplay="); pw.println(mCurDisplay);
+        pw.print("  mScreenOn="); pw.println(mScreenOn);
+        pw.print("  mCharging="); pw.println(mCharging);
+        pw.print("  mSigMotionActive="); pw.println(mSigMotionActive);
+        pw.print("  mState="); pw.println(stateToString(mState));
+        if (mNextAlarmTime != 0) {
+            pw.print("  mNextAlarmTime=");
+            TimeUtils.formatDuration(mNextAlarmTime, SystemClock.elapsedRealtime(), pw);
+            pw.println();
+        }
+        if (mNextIdlePendingDelay != 0) {
+            pw.print("  mNextIdlePendingDelay=");
+            TimeUtils.formatDuration(mNextIdlePendingDelay, pw);
+            pw.println();
+        }
+        if (mNextIdleDelay != 0) {
+            pw.print("  mNextIdleDelay=");
+            TimeUtils.formatDuration(mNextIdleDelay, pw);
+            pw.println();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index dcee96a..7f4ccb7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15798,6 +15798,7 @@
                                         if (userId == UserHandle.USER_OWNER) {
                                             mTaskPersister.removeFromPackageCache(ssp);
                                         }
+                                        mBatteryStatsService.notePackageUninstalled(ssp);
                                     }
                                 } else {
                                     removeTasksByRemovedPackageComponentsLocked(ssp, userId);
@@ -15824,6 +15825,13 @@
                         if (userId == UserHandle.USER_OWNER) {
                             mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
                         }
+                        try {
+                            ApplicationInfo ai = AppGlobals.getPackageManager().
+                                    getApplicationInfo(ssp, 0, 0);
+                            mBatteryStatsService.notePackageInstalled(ssp,
+                                    ai != null ? ai.versionCode : 0);
+                        } catch (RemoteException e) {
+                        }
                     }
                     break;
                 case Intent.ACTION_TIMEZONE_CHANGED:
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 6eacfa9..197b51d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -86,7 +86,7 @@
     public void initPowerManagement() {
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
         mPowerManagerInternal.registerLowPowerModeObserver(this);
-        mStats.noteLowPowerMode(mPowerManagerInternal.getLowPowerModeEnabled());
+        mStats.notePowerSaveMode(mPowerManagerInternal.getLowPowerModeEnabled());
         (new WakeupReasonThread()).start();
     }
 
@@ -109,7 +109,7 @@
     @Override
     public void onLowPowerModeChanged(boolean enabled) {
         synchronized (mStats) {
-            mStats.noteLowPowerMode(enabled);
+            mStats.notePowerSaveMode(enabled);
         }
     }
 
@@ -686,6 +686,28 @@
         }
     }
 
+    @Override
+    public void noteDeviceIdleMode(boolean enabled, boolean fromActive, boolean fromMotion) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteDeviceIdleModeLocked(enabled, fromActive, fromMotion);
+        }
+    }
+
+    public void notePackageInstalled(String pkgName, int versionCode) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.notePackageInstalledLocked(pkgName, versionCode);
+        }
+    }
+
+    public void notePackageUninstalled(String pkgName) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.notePackageUninstalledLocked(pkgName);
+        }
+    }
+
     public boolean isOnBattery() {
         return mStats.isOnBattery();
     }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 83d6986..fe1260d 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -22,6 +22,7 @@
 import java.util.Iterator;
 import java.util.List;
 
+import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
@@ -72,7 +73,8 @@
         implements StateChangedListener, JobCompletedListener {
     static final boolean DEBUG = false;
     /** The number of concurrent jobs we run at one time. */
-    private static final int MAX_JOB_CONTEXTS_COUNT = 3;
+    private static final int MAX_JOB_CONTEXTS_COUNT
+            = ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
     static final String TAG = "JobSchedulerService";
     /** Master list of jobs. */
     final JobStore mJobs;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index cc0fcf5..a69a95f 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -243,9 +243,11 @@
 
     final Object mRulesLock = new Object();
 
+    volatile boolean mSystemReady;
     volatile boolean mScreenOn;
     volatile boolean mRestrictBackground;
     volatile boolean mRestrictPower;
+    volatile boolean mDeviceIdleMode;
 
     private final boolean mSuppressDefaultPolicy;
 
@@ -367,11 +369,12 @@
                 }
             });
             mRestrictPower = mPowerManagerInternal.getLowPowerModeEnabled();
+            mSystemReady = true;
 
             // read policy from disk
             readPolicyLocked();
 
-            if (mRestrictBackground || mRestrictPower) {
+            if (mRestrictBackground || mRestrictPower || mDeviceIdleMode) {
                 updateRulesForGlobalChangeLocked(true);
                 updateNotificationsLocked();
             }
@@ -1031,7 +1034,7 @@
         // will not have a bandwidth limit.  Also only do this if restrict
         // background data use is *not* enabled, since that takes precendence
         // use over those networks can have a cost associated with it).
-        final boolean powerSave = mRestrictPower && !mRestrictBackground;
+        final boolean powerSave = (mRestrictPower || mDeviceIdleMode) && !mRestrictBackground;
 
         // First, generate identities of all connected networks so we can
         // quickly compare them against all defined policies below.
@@ -1696,6 +1699,20 @@
         }
     }
 
+    @Override
+    public void setDeviceIdleMode(boolean enabled) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        synchronized (mRulesLock) {
+            if (mDeviceIdleMode != enabled) {
+                mDeviceIdleMode = enabled;
+                if (mSystemReady) {
+                    updateRulesForGlobalChangeLocked(true);
+                }
+            }
+        }
+    }
+
     private NetworkPolicy findPolicyForNetworkLocked(NetworkIdentity ident) {
         for (int i = mNetworkPolicy.size()-1; i >= 0; i--) {
             NetworkPolicy policy = mNetworkPolicy.valueAt(i);
@@ -1801,8 +1818,10 @@
                 return;
             }
 
+            fout.print("System ready: "); fout.println(mSystemReady);
             fout.print("Restrict background: "); fout.println(mRestrictBackground);
             fout.print("Restrict power: "); fout.println(mRestrictPower);
+            fout.print("Device idle: "); fout.println(mDeviceIdleMode);
             fout.print("Current foreground state: "); fout.println(mCurForegroundState);
             fout.println("Network policies:");
             fout.increaseIndent();
@@ -1952,8 +1971,8 @@
     }
 
     /**
-     * Update rules that might be changed by {@link #mRestrictBackground}
-     * or {@link #mRestrictPower} value.
+     * Update rules that might be changed by {@link #mRestrictBackground},
+     * {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
      */
     void updateRulesForGlobalChangeLocked(boolean restrictedNetworksChanged) {
         final PackageManager pm = mContext.getPackageManager();
@@ -1962,7 +1981,7 @@
         // If we are in restrict power mode, we allow all important apps
         // to have data access.  Otherwise, we restrict data access to only
         // the top apps.
-        mCurForegroundState = (!mRestrictBackground && mRestrictPower)
+        mCurForegroundState = (!mRestrictBackground && (mRestrictPower || mDeviceIdleMode))
                 ? ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
                 : ActivityManager.PROCESS_STATE_TOP;
 
@@ -2015,7 +2034,7 @@
                 // uid in background, and global background disabled
                 uidRules = RULE_REJECT_METERED;
             }
-        } else if (mRestrictPower) {
+        } else if (mRestrictPower || mDeviceIdleMode) {
             final boolean whitelisted = mPowerSaveWhitelistAppIds.get(UserHandle.getAppId(uid));
             if (!whitelisted && !uidForeground
                     && (uidPolicy & POLICY_ALLOW_BACKGROUND_BATTERY_SAVE) == 0) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a0c7f86..ed55c56 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -960,6 +960,7 @@
 
         if (!disableNonCoreServices) {
             mSystemServiceManager.startService(MediaProjectionManagerService.class);
+            mSystemServiceManager.startService(DeviceIdleController.class);
         }
 
         // Before things start rolling, be sure we have decided whether