Be smarter about determining when we can stop idle maintenance.

The device idle service now knows when the system is actively
doing significant things (syncs, jobs, alarms, downloads).  It
uses this, when in an idle maintenance window, to determine when
it can end that window early -- when such work is no longer
happening.

For now this just allows us to shorten the windows.  In the future
we should use this to allow us to expand the windows to a longer
potential time, adjusting future windows to shorter durations if
earlier ones use more time.  This will allow us to batch occasional
long operations (such as downloads) into one window, making up
for that with much shorter later windows.

Change-Id: Ie482abd50bc43be9a8917a769a5175851eee4ec4
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index f55883a..7a1a6a2 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -35,4 +35,6 @@
     long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason);
     long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason);
     void exitIdle(String reason);
+    void downloadServiceActive(IBinder token);
+    void downloadServiceInactive();
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index a5ddc12..f9d9950 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -115,6 +115,7 @@
     final LocalLog mLog = new LocalLog(TAG);
 
     AppOpsManager mAppOps;
+    DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     final Object mLock = new Object();
 
@@ -897,6 +898,8 @@
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             mConstants.start(getContext().getContentResolver());
             mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+            mLocalDeviceIdleController
+                    = LocalServices.getService(DeviceIdleController.LocalService.class);
         }
     }
 
@@ -2468,10 +2471,9 @@
 
     private class AlarmHandler extends Handler {
         public static final int ALARM_EVENT = 1;
-        public static final int MINUTE_CHANGE_EVENT = 2;
-        public static final int DATE_CHANGE_EVENT = 3;
-        public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4;
-        public static final int LISTENER_TIMEOUT = 5;
+        public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2;
+        public static final int LISTENER_TIMEOUT = 3;
+        public static final int REPORT_ALARMS_ACTIVE = 4;
         
         public AlarmHandler() {
         }
@@ -2511,6 +2513,12 @@
                     mDeliveryTracker.alarmTimedOut((IBinder) msg.obj);
                     break;
 
+                case REPORT_ALARMS_ACTIVE:
+                    if (mLocalDeviceIdleController != null) {
+                        mLocalDeviceIdleController.setAlarmsActive(msg.arg1 != 0);
+                    }
+                    break;
+
                 default:
                     // nope, just ignore it
                     break;
@@ -2740,6 +2748,7 @@
             }
             mBroadcastRefCount--;
             if (mBroadcastRefCount == 0) {
+                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0).sendToTarget();
                 mWakeLock.release();
                 if (mInFlight.size() > 0) {
                     mLog.w("Finished all dispatches with " + mInFlight.size()
@@ -2873,6 +2882,7 @@
                         alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
                         true);
                 mWakeLock.acquire();
+                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
             }
             final InFlight inflight = new InFlight(AlarmManagerService.this,
                     alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 927b995..485e26b 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -48,6 +48,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.IDeviceIdleController;
 import android.os.Looper;
 import android.os.Message;
@@ -196,6 +197,12 @@
     private long mNextIdleDelay;
     private long mNextLightAlarmTime;
 
+    private int mActiveIdleOpCount;
+    private IBinder mDownloadServiceActive;
+    private boolean mSyncActive;
+    private boolean mJobsActive;
+    private boolean mAlarmsActive;
+
     public final AtomicFile mConfigFile;
 
     /**
@@ -282,16 +289,22 @@
                 }
             } else if (ACTION_STEP_LIGHT_IDLE_STATE.equals(intent.getAction())) {
                 synchronized (DeviceIdleController.this) {
-                    stepLightIdleStateLocked();
+                    stepLightIdleStateLocked("s:alarm");
                 }
             } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
                 synchronized (DeviceIdleController.this) {
-                    stepIdleStateLocked();
+                    stepIdleStateLocked("s:alarm");
                 }
             }
         }
     };
 
+    private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() {
+        @Override public void onReceive(Context context, Intent intent) {
+            decActiveIdleOps();
+        }
+    };
+
     private final DisplayManager.DisplayListener mDisplayListener
             = new DisplayManager.DisplayListener() {
         @Override public void onDisplayAdded(int displayId) {
@@ -733,7 +746,7 @@
                 // If we are currently sensing, it is time to move to locating.
                 synchronized (this) {
                     mNotMoving = true;
-                    stepIdleStateLocked();
+                    stepIdleStateLocked("s:stationary");
                 }
             } else if (mState == STATE_LOCATING) {
                 // If we are currently locating, note that we are not moving and step
@@ -741,7 +754,7 @@
                 synchronized (this) {
                     mNotMoving = true;
                     if (mLocated) {
-                        stepIdleStateLocked();
+                        stepIdleStateLocked("s:stationary");
                     }
                 }
             }
@@ -804,11 +817,18 @@
                     } catch (RemoteException e) {
                     }
                     if (fullChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        incActiveIdleOps();
+                        getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null, mIdleStartedDoneReceiver, null, 0, null, null);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        incActiveIdleOps();
+                        getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null, mIdleStartedDoneReceiver, null, 0, null, null);
                     }
+                    // Always start with one active op for the message being sent here.
+                    // Now we we done!
+                    decActiveIdleOps();
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
                 case MSG_REPORT_ACTIVE: {
@@ -913,11 +933,23 @@
         }
 
         @Override public void exitIdle(String reason) {
-            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
                     null);
             exitIdleInternal(reason);
         }
 
+        @Override public void downloadServiceActive(IBinder token) {
+            getContext().enforceCallingOrSelfPermission(
+                    "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
+            DeviceIdleController.this.downloadServiceActive(token);
+        }
+
+        @Override public void downloadServiceInactive() {
+            getContext().enforceCallingOrSelfPermission(
+                    "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
+            DeviceIdleController.this.downloadServiceInactive();
+        }
+
         @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             DeviceIdleController.this.dump(fd, pw, args);
         }
@@ -937,6 +969,19 @@
         public void setNetworkPolicyTempWhitelistCallback(Runnable callback) {
             setNetworkPolicyTempWhitelistCallbackInternal(callback);
         }
+
+        public void setSyncActive(boolean active) {
+            DeviceIdleController.this.setSyncActive(active);
+        }
+
+        public void setJobsActive(boolean active) {
+            DeviceIdleController.this.setJobsActive(active);
+        }
+
+        // Up-call from alarm manager.
+        public void setAlarmsActive(boolean active) {
+            DeviceIdleController.this.setAlarmsActive(active);
+        }
     }
 
     public DeviceIdleController(Context context) {
@@ -1439,7 +1484,7 @@
         }
     }
 
-    void stepLightIdleStateLocked() {
+    void stepLightIdleStateLocked(String reason) {
         if (mLightState == LIGHT_STATE_OVERRIDE) {
             // If we are already in full device idle mode, then
             // there is nothing left to do for light mode.
@@ -1455,22 +1500,23 @@
                 scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
                 if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
                 mLightState = LIGHT_STATE_IDLE;
-                EventLogTags.writeDeviceIdleLight(mLightState, "step");
+                EventLogTags.writeDeviceIdleLight(mLightState, reason);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
                 break;
             case LIGHT_STATE_IDLE:
                 // We have been idling long enough, now it is time to do some work.
+                mActiveIdleOpCount = 1;
                 scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_PENDING_TIMEOUT);
                 if (DEBUG) Slog.d(TAG,
                         "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
                 mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
-                EventLogTags.writeDeviceIdleLight(mLightState, "step");
+                EventLogTags.writeDeviceIdleLight(mLightState, reason);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                 break;
         }
     }
 
-    void stepIdleStateLocked() {
+    void stepIdleStateLocked(String reason) {
         if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
         EventLogTags.writeDeviceIdleStep();
 
@@ -1494,12 +1540,12 @@
                 mNextIdleDelay = mConstants.IDLE_TIMEOUT;
                 mState = STATE_IDLE_PENDING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING.");
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 break;
             case STATE_IDLE_PENDING:
                 mState = STATE_SENSING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false);
                 cancelLocatingLocked();
                 mAnyMotionDetector.checkForAnyMotion();
@@ -1511,7 +1557,7 @@
             case STATE_SENSING:
                 mState = STATE_LOCATING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
                 if (mLocationManager != null
                         && mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
@@ -1553,23 +1599,101 @@
                     mLightState = LIGHT_STATE_OVERRIDE;
                     cancelLightAlarmLocked();
                 }
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
                 break;
             case STATE_IDLE:
                 // We have been idling long enough, now it is time to do some work.
+                mActiveIdleOpCount = 1;
                 scheduleAlarmLocked(mNextIdlePendingDelay, false);
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
                         "Next alarm in " + mNextIdlePendingDelay + " ms.");
                 mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                         (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
                 mState = STATE_IDLE_MAINTENANCE;
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                 break;
         }
     }
 
+    void incActiveIdleOps() {
+        synchronized (this) {
+            mActiveIdleOpCount++;
+        }
+    }
+
+    void decActiveIdleOps() {
+        synchronized (this) {
+            mActiveIdleOpCount--;
+            if (mActiveIdleOpCount <= 0) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void downloadServiceActive(IBinder token) {
+        synchronized (this) {
+            mDownloadServiceActive = token;
+            try {
+                token.linkToDeath(new IBinder.DeathRecipient() {
+                    @Override public void binderDied() {
+                        downloadServiceInactive();
+                    }
+                }, 0);
+            } catch (RemoteException e) {
+                mDownloadServiceActive = null;
+            }
+        }
+    }
+
+    void downloadServiceInactive() {
+        synchronized (this) {
+            mDownloadServiceActive = null;
+            exitMaintenanceEarlyIfNeededLocked();
+        }
+    }
+
+    void setSyncActive(boolean active) {
+        synchronized (this) {
+            mSyncActive = active;
+            if (!active) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void setJobsActive(boolean active) {
+        synchronized (this) {
+            mJobsActive = active;
+            if (!active) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void setAlarmsActive(boolean active) {
+        synchronized (this) {
+            mAlarmsActive = active;
+            if (!active) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void exitMaintenanceEarlyIfNeededLocked() {
+        if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
+            if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
+                    && !mSyncActive && !mJobsActive && !mAlarmsActive) {
+                if (mState == STATE_IDLE_MAINTENANCE) {
+                    stepIdleStateLocked("s:early");
+                } else {
+                    stepLightIdleStateLocked("s:early");
+                }
+            }
+        }
+    }
+
     void motionLocked() {
         if (DEBUG) Slog.d(TAG, "motionLocked()");
         // The motion sensor will have been disabled at this point
@@ -1612,7 +1736,7 @@
         }
         mLocated = true;
         if (mNotMoving) {
-            stepIdleStateLocked();
+            stepIdleStateLocked("s:location");
         }
     }
 
@@ -1628,7 +1752,7 @@
         }
         mLocated = true;
         if (mNotMoving) {
-            stepIdleStateLocked();
+            stepIdleStateLocked("s:gps");
         }
     }
 
@@ -1933,7 +2057,7 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     exitForceIdleLocked();
-                    stepIdleStateLocked();
+                    stepIdleStateLocked("s:shell");
                     pw.print("Stepped to: ");
                     pw.println(stateToString(mState));
                 } finally {
@@ -1947,7 +2071,7 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     exitForceIdleLocked();
-                    stepLightIdleStateLocked();
+                    stepLightIdleStateLocked("s:shell");
                     pw.print("Stepped to: "); pw.println(lightStateToString(mLightState));
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -1967,7 +2091,7 @@
                     becomeInactiveIfAppropriateLocked();
                     int curState = mState;
                     while (curState != STATE_IDLE) {
-                        stepIdleStateLocked();
+                        stepIdleStateLocked("s:shell");
                         if (curState == mState) {
                             pw.print("Unable to go idle; stopped at ");
                             pw.println(stateToString(mState));
@@ -2226,6 +2350,9 @@
             pw.println(lightStateToString(mLightState));
             pw.print("  mInactiveTimeout="); TimeUtils.formatDuration(mInactiveTimeout, pw);
             pw.println();
+            if (mActiveIdleOpCount != 0) {
+                pw.print("  mActiveIdleOpCount="); pw.println(mActiveIdleOpCount);
+            }
             if (mNextAlarmTime != 0) {
                 pw.print("  mNextAlarmTime=");
                 TimeUtils.formatDuration(mNextAlarmTime, SystemClock.elapsedRealtime(), pw);
@@ -2246,6 +2373,18 @@
                 TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
                 pw.println();
             }
+            if (mSyncActive) {
+                pw.print("  mSyncActive="); pw.println(mSyncActive);
+            }
+            if (mJobsActive) {
+                pw.print("  mJobsActive="); pw.println(mJobsActive);
+            }
+            if (mAlarmsActive) {
+                pw.print("  mAlarmsActive="); pw.println(mAlarmsActive);
+            }
+            if (mDownloadServiceActive != null) {
+                pw.print("  mDownloadServiceActive="); pw.println(mDownloadServiceActive);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 82e0eaf..e2a0f82 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -84,6 +84,8 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
 import com.android.server.accounts.AccountManagerService;
 import com.android.server.content.SyncStorageEngine.AuthorityInfo;
 import com.android.server.content.SyncStorageEngine.EndPoint;
@@ -207,6 +209,7 @@
     volatile private boolean mDataConnectionIsConnected = false;
     volatile private boolean mStorageIsLow = false;
     volatile private boolean mDeviceIsIdle = false;
+    volatile private boolean mReportedSyncActive = false;
 
     private final NotificationManager mNotificationMgr;
     private AlarmManager mAlarmService = null;
@@ -267,6 +270,12 @@
                         SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
                         null /* any sync */);
             } else {
+                if (mLocalDeviceIdleController != null) {
+                    if (!mReportedSyncActive) {
+                        mReportedSyncActive = true;
+                        mLocalDeviceIdleController.setSyncActive(true);
+                    }
+                }
                 sendCheckAlarmsMessage();
             }
         }
@@ -292,6 +301,7 @@
     };
 
     private final PowerManager mPowerManager;
+    DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     // Use this as a random offset to seed all periodic syncs.
     private int mSyncRandomOffsetMillis;
@@ -1470,6 +1480,7 @@
         }
         pw.print("memory low: "); pw.println(mStorageIsLow);
         pw.print("device idle: "); pw.println(mDeviceIsIdle);
+        pw.print("reported active: "); pw.println(mReportedSyncActive);
 
         final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
 
@@ -2544,6 +2555,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2551,6 +2563,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: memory low, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2558,6 +2571,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: device idle, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2567,6 +2581,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2772,9 +2787,25 @@
                 dispatchSyncOperation(candidate);
             }
 
+            setSyncActive(mActiveSyncContexts.size() > 0);
+
             return nextReadyToRunTime;
         }
 
+        void setSyncActive(boolean active) {
+            if (mLocalDeviceIdleController == null) {
+                mLocalDeviceIdleController
+                        = LocalServices.getService(DeviceIdleController.LocalService.class);
+            }
+            if (mLocalDeviceIdleController != null) {
+                if (mReportedSyncActive != active) {
+                    mReportedSyncActive = active;
+                    mLocalDeviceIdleController.setSyncActive(active);
+                }
+            }
+
+        }
+
         private boolean isSyncNotUsingNetworkH(ActiveSyncContext activeSyncContext) {
             final long bytesTransferredCurrent =
                     getTotalBytesTransferredByUid(activeSyncContext.mSyncAdapterUid);
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 2b535b9..759199c 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -51,6 +51,8 @@
 import android.util.SparseArray;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
 import com.android.server.job.controllers.AppIdleController;
 import com.android.server.job.controllers.BatteryController;
 import com.android.server.job.controllers.ConnectivityController;
@@ -127,6 +129,7 @@
 
     IBatteryStats mBatteryStats;
     PowerManager mPowerManager;
+    DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     /**
      * Set to true once we are allowed to run third party apps.
@@ -139,6 +142,11 @@
     boolean mDeviceIdleMode;
 
     /**
+     * What we last reported to DeviceIdleController about wheter we are active.
+     */
+    boolean mReportedActive;
+
+    /**
      * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
      * still clean up. On reinstall the package will have a new uid.
      */
@@ -268,6 +276,7 @@
             mPendingJobs.remove(cancelled);
             // Cancel if running.
             stopJobOnServiceContextLocked(cancelled);
+            reportActive();
         }
     }
 
@@ -299,12 +308,39 @@
                     }
                 } else {
                     // When coming out of idle, allow thing to start back up.
+                    if (rocking) {
+                        if (mLocalDeviceIdleController != null) {
+                            if (!mReportedActive) {
+                                mReportedActive = true;
+                                mLocalDeviceIdleController.setJobsActive(true);
+                            }
+                        }
+                    }
                     mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
                 }
             }
         }
     }
 
+    void reportActive() {
+        boolean active = false;
+        if (mPendingJobs.size() <= 0) {
+            for (int i=0; i<mActiveServices.size(); i++) {
+                JobServiceContext jsc = mActiveServices.get(i);
+                if (!jsc.isAvailable()) {
+                    active = true;
+                    break;
+                }
+            }
+        }
+        if (mLocalDeviceIdleController != null) {
+            if (mReportedActive != active) {
+                mReportedActive = active;
+                mLocalDeviceIdleController.setJobsActive(active);
+            }
+        }
+    }
+
     /**
      * Initializes the system service.
      * <p>
@@ -354,6 +390,8 @@
                 mReadyToRock = true;
                 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                         BatteryStats.SERVICE_NAME));
+                mLocalDeviceIdleController
+                        = LocalServices.getService(DeviceIdleController.LocalService.class);
                 // Create the "runners".
                 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
                     mActiveServices.add(
@@ -623,6 +661,7 @@
                     stopJobOnServiceContextLocked(job);
                 }
             }
+            reportActive();
             if (DEBUG) {
                 final int queuedJobs = mPendingJobs.size();
                 if (queuedJobs == 0) {
@@ -685,6 +724,7 @@
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
                 }
             }
+            reportActive();
             if (DEBUG) {
                 Slog.d(TAG, "idle=" + idleCount + " connectivity=" +
                 connectivityCount + " charging=" + chargingCount + " tot=" +
@@ -766,6 +806,7 @@
                         it.remove();
                     }
                 }
+                reportActive();
             }
         }
     }
@@ -948,6 +989,7 @@
             pw.println();
             pw.print("mReadyToRock="); pw.println(mReadyToRock);
             pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
+            pw.print("mReportedActive="); pw.println(mReportedActive);
         }
         pw.println();
     }