Initial stab at background check.

Actually, this implementation is more what we want for ephemeral
apps.  I am realizing the two are not really the same thing. :(

For this implementation, we now keep track of how long a uid has
been in the background, and after a certain amount of time
(currently 1 minute) we mark it as "idle".  Any packages associated
with that uid are then no longer allowed to run in the background.
This means, until the app next goes in the foreground:

- No manifest broadcast receivers in the app will execute.
- No services can be started (binding services is still okay,
  as this is outside dependencies on the app that should still
  be represented).
- All alarms for the app are cancelled and no more can be set.
- All jobs for the app are cancelled and no more can be scheduled.
- All syncs for the app are cancelled and no more can be requested.

Change-Id: If53714ca4beed35faf2e89f916ce9eaaabd9290d
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 65de4ca..00bba2d 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -327,12 +327,39 @@
     /** @hide Process is being cached for later use and is empty. */
     public static final int PROCESS_STATE_CACHED_EMPTY = 16;
 
+    /** @hide Should this process state be considered a background state? */
+    public static final boolean isProcStateBackground(int procState) {
+        return procState >= PROCESS_STATE_BACKUP;
+    }
+
     /** @hide requestType for assist context: only basic information. */
     public static final int ASSIST_CONTEXT_BASIC = 0;
 
     /** @hide requestType for assist context: generate full AssistStructure. */
     public static final int ASSIST_CONTEXT_FULL = 1;
 
+    /** @hide Flag for registerUidObserver: report changes in process state. */
+    public static final int UID_OBSERVER_PROCSTATE = 1<<0;
+
+    /** @hide Flag for registerUidObserver: report uid gone. */
+    public static final int UID_OBSERVER_GONE = 1<<1;
+
+    /** @hide Flag for registerUidObserver: report uid has become idle. */
+    public static final int UID_OBSERVER_IDLE = 1<<2;
+
+    /** @hide Flag for registerUidObserver: report uid has become active. */
+    public static final int UID_OBSERVER_ACTIVE = 1<<3;
+
+    /** @hide Mode for {@link IActivityManager#getAppStartMode}: normal free-to-run operation. */
+    public static final int APP_START_MODE_NORMAL = 0;
+
+    /** @hide Mode for {@link IActivityManager#getAppStartMode}: delay running until later. */
+    public static final int APP_START_MODE_DELAYED = 1;
+
+    /** @hide Mode for {@link IActivityManager#getAppStartMode}: disable/cancel pending
+     * launches. */
+    public static final int APP_START_MODE_DISABLED = 2;
+
     /**
      * Lock task mode is not active.
      */
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4449e4f..e246e620 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -756,7 +756,7 @@
             return true;
         }
 
-        case MOVE_TOP_ACTIVITY_TO_PINNED_STACK: {
+        case MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             final int stackId = data.readInt();
             final Rect r = Rect.CREATOR.createFromParcel(data);
@@ -2029,7 +2029,8 @@
             data.enforceInterface(IActivityManager.descriptor);
             IUidObserver observer = IUidObserver.Stub.asInterface(
                     data.readStrongBinder());
-            registerUidObserver(observer);
+            int which = data.readInt();
+            registerUidObserver(observer, which);
             return true;
         }
 
@@ -2698,13 +2699,22 @@
             reply.writeNoException();
             return true;
         }
-        case REMOVE_STACK: {
+        case REMOVE_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             final int stackId = data.readInt();
             removeStack(stackId);
             reply.writeNoException();
             return true;
         }
+        case GET_APP_START_MODE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final int uid = data.readInt();
+            final String pkg = data.readString();
+            int res = getAppStartMode(uid, pkg);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -3579,7 +3589,7 @@
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(stackId);
         r.writeToParcel(data, 0);
-        mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK, data, reply, 0);
+        mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION, data, reply, 0);
         reply.readException();
         final boolean res = reply.readInt() != 0;
         data.recycle();
@@ -5326,11 +5336,12 @@
         reply.recycle();
     }
 
-    public void registerUidObserver(IUidObserver observer) throws RemoteException {
+    public void registerUidObserver(IUidObserver observer, int which) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+        data.writeInt(which);
         mRemote.transact(REGISTER_UID_OBSERVER_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
@@ -6292,11 +6303,26 @@
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(stackId);
-        mRemote.transact(REMOVE_STACK, data, reply, 0);
+        mRemote.transact(REMOVE_STACK_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
         reply.recycle();
     }
 
+    @Override
+    public int getAppStartMode(int uid, String packageName) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(uid);
+        data.writeString(packageName);
+        mRemote.transact(GET_APP_START_MODE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 77a9795..f0453e9 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -237,8 +237,10 @@
     public static final int OP_TURN_SCREEN_ON = 61;
     /** @hide Get device accounts. */
     public static final int OP_GET_ACCOUNTS = 62;
+    /** @hide Control whether an application is allowed to run in the background. */
+    public static final int OP_RUN_IN_BACKGROUND = 63;
     /** @hide */
-    public static final int _NUM_OP = 63;
+    public static final int _NUM_OP = 64;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -409,6 +411,7 @@
             OP_WRITE_EXTERNAL_STORAGE,
             OP_TURN_SCREEN_ON,
             OP_GET_ACCOUNTS,
+            OP_RUN_IN_BACKGROUND,
     };
 
     /**
@@ -478,7 +481,8 @@
             OPSTR_READ_EXTERNAL_STORAGE,
             OPSTR_WRITE_EXTERNAL_STORAGE,
             null,
-            OPSTR_GET_ACCOUNTS
+            OPSTR_GET_ACCOUNTS,
+            null,
     };
 
     /**
@@ -549,6 +553,7 @@
             "WRITE_EXTERNAL_STORAGE",
             "TURN_ON_SCREEN",
             "GET_ACCOUNTS",
+            "RUN_IN_BACKGROUND",
     };
 
     /**
@@ -618,7 +623,8 @@
             Manifest.permission.READ_EXTERNAL_STORAGE,
             Manifest.permission.WRITE_EXTERNAL_STORAGE,
             null, // no permission for turning the screen on
-            Manifest.permission.GET_ACCOUNTS
+            Manifest.permission.GET_ACCOUNTS,
+            null, // no permission for running in background
     };
 
     /**
@@ -690,6 +696,7 @@
             null, // WRITE_EXTERNAL_STORAGE
             null, // TURN_ON_SCREEN
             null, // GET_ACCOUNTS
+            null, // RUN_IN_BACKGROUND
     };
 
     /**
@@ -760,6 +767,7 @@
             false, // WRITE_EXTERNAL_STORAGE
             false, // TURN_ON_SCREEN
             false, // GET_ACCOUNTS
+            false, // RUN_IN_BACKGROUND
     };
 
     /**
@@ -829,6 +837,7 @@
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,  // OP_TURN_ON_SCREEN
             AppOpsManager.MODE_ALLOWED,
+            AppOpsManager.MODE_ALLOWED,  // OP_RUN_IN_BACKGROUND
     };
 
     /**
@@ -901,7 +910,8 @@
             false,
             false,
             false,
-            false
+            false,
+            false,
     };
 
     /**
@@ -1329,7 +1339,7 @@
             IAppOpsCallback cb = mModeWatchers.get(callback);
             if (cb == null) {
                 cb = new IAppOpsCallback.Stub() {
-                    public void opChanged(int op, String packageName) {
+                    public void opChanged(int op, int uid, String packageName) {
                         if (callback instanceof OnOpChangedInternalListener) {
                             ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
                         }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index b69a480..3d0fc92 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -400,7 +400,7 @@
     public void registerProcessObserver(IProcessObserver observer) throws RemoteException;
     public void unregisterProcessObserver(IProcessObserver observer) throws RemoteException;
 
-    public void registerUidObserver(IUidObserver observer) throws RemoteException;
+    public void registerUidObserver(IUidObserver observer, int which) throws RemoteException;
     public void unregisterUidObserver(IUidObserver observer) throws RemoteException;
 
     public boolean isIntentSenderTargetedToPackage(IIntentSender sender) throws RemoteException;
@@ -542,6 +542,8 @@
 
     public void removeStack(int stackId) throws RemoteException;
 
+    public int getAppStartMode(int uid, String packageName) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -899,6 +901,7 @@
     int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345;
     int MOVE_TASK_TO_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346;
     int SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347;
-    int REMOVE_STACK = IBinder.FIRST_CALL_TRANSACTION + 348;
-    int MOVE_TOP_ACTIVITY_TO_PINNED_STACK = IBinder.FIRST_CALL_TRANSACTION + 349;
+    int REMOVE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 348;
+    int MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 349;
+    int GET_APP_START_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 350;
 }
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 308cb94..fa8d0c9 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -18,6 +18,24 @@
 
 /** {@hide} */
 oneway interface IUidObserver {
+    /**
+     * General report of a state change of an uid.
+     */
     void onUidStateChanged(int uid, int procState);
+
+    /**
+     * Report that there are no longer any processes running for a uid.
+     */
     void onUidGone(int uid);
+
+    /**
+     * Report that a uid is now active (no longer idle).
+     */
+    void onUidActive(int uid);
+
+    /**
+     * Report that a uid is idle -- it has either been running in the background for
+     * a sufficient period of time, or all of its processes have gone away.
+     */
+    void onUidIdle(int uid);
 }
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index afbc609..5fdc920 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -19,5 +19,5 @@
 // This interface is also used by native code, so must
 // be kept in sync with frameworks/native/include/binder/IAppOpsCallback.h
 oneway interface IAppOpsCallback {
-    void opChanged(int op, String packageName);
+    void opChanged(int op, int uid, String packageName);
 }
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 1355635..3164930 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -166,7 +166,7 @@
         updateAppOpsPlayAudio();
         // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
         mAppOpsCallback = new IAppOpsCallback.Stub() {
-            public void opChanged(int op, String packageName) {
+            public void opChanged(int op, int uid, String packageName) {
                 synchronized (mLock) {
                     if (op == AppOpsManager.OP_PLAY_AUDIO) {
                         updateAppOpsPlayAudio();
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index f9d9950..504a7ef 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -25,6 +25,7 @@
 import android.app.IAlarmCompleteListener;
 import android.app.IAlarmListener;
 import android.app.IAlarmManager;
+import android.app.IUidObserver;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -41,6 +42,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -459,7 +461,7 @@
             long newStart = 0;  // recalculate endpoints as we go
             long newEnd = Long.MAX_VALUE;
             int newFlags = 0;
-            for (int i = 0; i < alarms.size(); ) {
+            for (int i = alarms.size()-1; i >= 0; i--) {
                 Alarm alarm = alarms.get(i);
                 if (alarm.matches(packageName)) {
                     alarms.remove(i);
@@ -475,7 +477,42 @@
                         newEnd = alarm.maxWhenElapsed;
                     }
                     newFlags |= alarm.flags;
-                    i++;
+                }
+            }
+            if (didRemove) {
+                // commit the new batch bounds
+                start = newStart;
+                end = newEnd;
+                flags = newFlags;
+            }
+            return didRemove;
+        }
+
+        boolean removeForStopped(final int uid) {
+            boolean didRemove = false;
+            long newStart = 0;  // recalculate endpoints as we go
+            long newEnd = Long.MAX_VALUE;
+            int newFlags = 0;
+            for (int i = alarms.size()-1; i >= 0; i--) {
+                Alarm alarm = alarms.get(i);
+                try {
+                    if (alarm.uid == uid && ActivityManagerNative.getDefault().getAppStartMode(
+                            uid, alarm.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+                        alarms.remove(i);
+                        didRemove = true;
+                        if (alarm.alarmClock != null) {
+                            mNextAlarmClockMayChange = true;
+                        }
+                    } else {
+                        if (alarm.whenElapsed > newStart) {
+                            newStart = alarm.whenElapsed;
+                        }
+                        if (alarm.maxWhenElapsed < newEnd) {
+                            newEnd = alarm.maxWhenElapsed;
+                        }
+                        newFlags |= alarm.flags;
+                    }
+                } catch (RemoteException e) {
                 }
             }
             if (didRemove) {
@@ -890,6 +927,13 @@
             Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
         }
 
+        try {
+            ActivityManagerNative.getDefault().registerUidObserver(new UidObserver(),
+                    ActivityManager.UID_OBSERVER_IDLE);
+        } catch (RemoteException e) {
+            // ignored; both services live in system_server
+        }
+
         publishBinderService(Context.ALARM_SERVICE, mService);
     }
 
@@ -1035,6 +1079,15 @@
         Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
                 operation, directReceiver, listenerTag, workSource, flags, alarmClock,
                 callingUid, callingPackage);
+        try {
+            if (ActivityManagerNative.getDefault().getAppStartMode(callingUid, callingPackage)
+                    == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+                        + " -- package not allowed to start");
+                return;
+            }
+        } catch (RemoteException e) {
+        }
         removeLocked(operation, directReceiver);
         setImplLocked(a, false, doValidate);
     }
@@ -1841,6 +1894,37 @@
         }
     }
 
+    void removeForStoppedLocked(int uid) {
+        boolean didRemove = false;
+        for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+            Batch b = mAlarmBatches.get(i);
+            didRemove |= b.removeForStopped(uid);
+            if (b.size() == 0) {
+                mAlarmBatches.remove(i);
+            }
+        }
+        for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+            final Alarm a = mPendingWhileIdleAlarms.get(i);
+            try {
+                if (a.uid == uid && ActivityManagerNative.getDefault().getAppStartMode(
+                        uid, a.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+                    // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                    mPendingWhileIdleAlarms.remove(i);
+                }
+            } catch (RemoteException e) {
+            }
+        }
+
+        if (didRemove) {
+            if (DEBUG_BATCH) {
+                Slog.v(TAG, "remove(package) changed bounds; rebatching");
+            }
+            rebatchAllAlarmsLocked(true);
+            rescheduleKernelAlarmsLocked();
+            updateNextAlarmClockLocked();
+        }
+    }
+
     void removeUserLocked(int userHandle) {
         boolean didRemove = false;
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
@@ -2673,7 +2757,22 @@
             }
         }
     }
-    
+
+    final class UidObserver extends IUidObserver.Stub {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+            removeForStoppedLocked(uid);
+        }
+    };
+
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
         String pkg = pi.getCreatorPackage();
         int uid = pi.getCreatorUid();
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 96c1e2a..c131628 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -54,7 +54,6 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -557,12 +556,12 @@
                 ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
                 try {
                     if (reportedPackageNames == null) {
-                        callback.mCallback.opChanged(code, null);
+                        callback.mCallback.opChanged(code, uid, null);
                     } else {
                         final int reportedPackageCount = reportedPackageNames.size();
                         for (int j = 0; j < reportedPackageCount; j++) {
                             String reportedPackageName = reportedPackageNames.valueAt(j);
-                            callback.mCallback.opChanged(code, reportedPackageName);
+                            callback.mCallback.opChanged(code, uid, reportedPackageName);
                         }
                     }
                 } catch (RemoteException e) {
@@ -620,7 +619,7 @@
             try {
                 for (int i = 0; i < repCbs.size(); i++) {
                     try {
-                        repCbs.get(i).mCallback.opChanged(code, packageName);
+                        repCbs.get(i).mCallback.opChanged(code, uid, packageName);
                     } catch (RemoteException e) {
                     }
                 }
@@ -630,39 +629,51 @@
         }
     }
 
-    private static HashMap<Callback, ArrayList<Pair<String, Integer>>> addCallbacks(
-            HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks,
-            String packageName, int op, ArrayList<Callback> cbs) {
+    private static HashMap<Callback, ArrayList<ChangeRec>> addCallbacks(
+            HashMap<Callback, ArrayList<ChangeRec>> callbacks,
+            int op, int uid, String packageName, ArrayList<Callback> cbs) {
         if (cbs == null) {
             return callbacks;
         }
         if (callbacks == null) {
-            callbacks = new HashMap<Callback, ArrayList<Pair<String, Integer>>>();
+            callbacks = new HashMap<>();
         }
         boolean duplicate = false;
         for (int i=0; i<cbs.size(); i++) {
             Callback cb = cbs.get(i);
-            ArrayList<Pair<String, Integer>> reports = callbacks.get(cb);
+            ArrayList<ChangeRec> reports = callbacks.get(cb);
             if (reports == null) {
-                reports = new ArrayList<Pair<String, Integer>>();
+                reports = new ArrayList<>();
                 callbacks.put(cb, reports);
             } else {
                 final int reportCount = reports.size();
                 for (int j = 0; j < reportCount; j++) {
-                    Pair<String, Integer> report = reports.get(j);
-                    if (report.second == op && report.first.equals(packageName)) {
+                    ChangeRec report = reports.get(j);
+                    if (report.op == op && report.pkg.equals(packageName)) {
                         duplicate = true;
                         break;
                     }
                 }
             }
             if (!duplicate) {
-                reports.add(new Pair<>(packageName, op));
+                reports.add(new ChangeRec(op, uid, packageName));
             }
         }
         return callbacks;
     }
 
+    static final class ChangeRec {
+        final int op;
+        final int uid;
+        final String pkg;
+
+        ChangeRec(int _op, int _uid, String _pkg) {
+            op = _op;
+            uid = _uid;
+            pkg = _pkg;
+        }
+    }
+
     @Override
     public void resetAllModes(int reqUserId, String reqPackageName) {
         final int callingPid = Binder.getCallingPid();
@@ -682,7 +693,7 @@
             }
         }
 
-        HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks = null;
+        HashMap<Callback, ArrayList<ChangeRec>> callbacks = null;
         synchronized (this) {
             boolean changed = false;
             for (int i = mUidStates.size() - 1; i >= 0; i--) {
@@ -699,9 +710,9 @@
                                 uidState.opModes = null;
                             }
                             for (String packageName : getPackagesForUid(uidState.uid)) {
-                                callbacks = addCallbacks(callbacks, packageName, code,
+                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         mOpModeWatchers.get(code));
-                                callbacks = addCallbacks(callbacks, packageName, code,
+                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         mPackageModeWatchers.get(packageName));
                             }
                         }
@@ -734,9 +745,9 @@
                                 && curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
                             curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
                             changed = true;
-                            callbacks = addCallbacks(callbacks, packageName, curOp.op,
+                            callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
                                     mOpModeWatchers.get(curOp.op));
-                            callbacks = addCallbacks(callbacks, packageName, curOp.op,
+                            callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
                                     mPackageModeWatchers.get(packageName));
                             if (curOp.time == 0 && curOp.rejectTime == 0) {
                                 pkgOps.removeAt(j);
@@ -757,13 +768,13 @@
             }
         }
         if (callbacks != null) {
-            for (Map.Entry<Callback, ArrayList<Pair<String, Integer>>> ent : callbacks.entrySet()) {
+            for (Map.Entry<Callback, ArrayList<ChangeRec>> ent : callbacks.entrySet()) {
                 Callback cb = ent.getKey();
-                ArrayList<Pair<String, Integer>> reports = ent.getValue();
+                ArrayList<ChangeRec> reports = ent.getValue();
                 for (int i=0; i<reports.size(); i++) {
-                    Pair<String, Integer> rep = reports.get(i);
+                    ChangeRec rep = reports.get(i);
                     try {
-                        cb.mCallback.opChanged(rep.second, rep.first);
+                        cb.mCallback.opChanged(rep.op, rep.uid, rep.pkg);
                     } catch (RemoteException e) {
                     }
                 }
@@ -1163,8 +1174,10 @@
                     if (pkgUid != uid) {
                         // Oops!  The package name is not valid for the uid they are calling
                         // under.  Abort.
+                        RuntimeException ex = new RuntimeException("here");
+                        ex.fillInStackTrace();
                         Slog.w(TAG, "Bad call: specified package " + packageName
-                                + " under uid " + uid + " but it is really " + pkgUid);
+                                + " under uid " + uid + " but it is really " + pkgUid, ex);
                         return null;
                     }
                 } finally {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 30565c6..4d05f9a 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -168,13 +168,10 @@
      */
     class ServiceMap extends Handler {
         final int mUserId;
-        final ArrayMap<ComponentName, ServiceRecord> mServicesByName
-                = new ArrayMap<ComponentName, ServiceRecord>();
-        final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent
-                = new ArrayMap<Intent.FilterComparison, ServiceRecord>();
+        final ArrayMap<ComponentName, ServiceRecord> mServicesByName = new ArrayMap<>();
+        final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = new ArrayMap<>();
 
-        final ArrayList<ServiceRecord> mDelayedStartList
-                = new ArrayList<ServiceRecord>();
+        final ArrayList<ServiceRecord> mDelayedStartList = new ArrayList<>();
         /* XXX eventually I'd like to have this based on processes instead of services.
          * That is, if we try to start two services in a row both running in the same
          * process, this should be one entry in mStartingBackground for that one process
@@ -185,8 +182,7 @@
                 = new ArrayList<DelayingProcess>();
         */
 
-        final ArrayList<ServiceRecord> mStartingBackground
-                = new ArrayList<ServiceRecord>();
+        final ArrayList<ServiceRecord> mStartingBackground = new ArrayList<>();
 
         static final int MSG_BG_START_TIMEOUT = 1;
 
@@ -338,7 +334,7 @@
         ServiceRecord r = res.record;
 
         if (!mAm.mUserController.exists(r.userId)) {
-            Slog.d(TAG, "Trying to start service with non-existent user! " + r.userId);
+            Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
             return null;
         }
 
@@ -510,6 +506,35 @@
         return 0;
     }
 
+    void stopInBackgroundLocked(int uid) {
+        // Stop all services associated with this uid due to it going to the background
+        // stopped state.
+        ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
+        ArrayList<ServiceRecord> stopping = null;
+        if (services != null) {
+            for (int i=services.mServicesByName.size()-1; i>=0; i--) {
+                ServiceRecord service = services.mServicesByName.valueAt(i);
+                if (service.appInfo.uid == uid && service.startRequested) {
+                    if (mAm.mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
+                            uid, service.packageName) != AppOpsManager.MODE_ALLOWED) {
+                        if (stopping == null) {
+                            stopping = new ArrayList<>();
+                            stopping.add(service);
+                        }
+                    }
+                }
+            }
+            if (stopping != null) {
+                for (int i=stopping.size()-1; i>=0; i--) {
+                    ServiceRecord service = stopping.get(i);
+                    service.delayed = false;
+                    services.ensureNotStartingBackground(service);
+                    stopServiceLocked(service);
+                }
+            }
+        }
+    }
+
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
                 Binder.getCallingPid(), Binder.getCallingUid(),
@@ -1069,6 +1094,17 @@
                 }
                 r = smap.mServicesByName.get(name);
                 if (r == null && createIfNeeded) {
+                    // Before going further -- if this app is not allowed to run in the background,
+                    // then at this point we aren't going to let it period.
+                    if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
+                            sInfo.packageName, callingPid)) {
+                        Slog.w(TAG, "Background execution not allowed: service "
+                                + r.intent + " to " + name.flattenToShortString()
+                                + " from pid=" + callingPid + " uid=" + callingUid
+                                + " pkg=" + callingPackage);
+                        return null;
+                    }
+
                     Intent.FilterComparison filter
                             = new Intent.FilterComparison(service.cloneFilter());
                     ServiceRestarter res = new ServiceRestarter();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 566065c..f0bcf0c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -60,7 +60,6 @@
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.appwidget.AppWidgetManager;
-import android.content.pm.AppsQueryHelper;
 import android.content.pm.PermissionInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -88,6 +87,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.DumpHeapActivity;
+import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ProcessMap;
@@ -109,15 +109,12 @@
 import com.android.server.IntentResolver;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
-import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityStack.ActivityState;
-import com.android.server.am.ActivityStackSupervisor.ActivityDisplay;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.pm.Installer;
-import com.android.server.pm.UserManagerService;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.AppTransition;
 import com.android.server.wm.WindowManagerService;
@@ -388,6 +385,10 @@
     // Maximum number of users we allow to be running at a time.
     static final int MAX_RUNNING_USERS = 3;
 
+    // This is the amount of time we allow an app to settle after it goes into the background,
+    // before we start restricting what it can do.
+    static final int BACKGROUND_SETTLE_TIME = 1*60*1000;
+
     // How long to wait in getAssistContextExtras for the activity and foreground services
     // to respond with the result.
     static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
@@ -716,6 +717,12 @@
     final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
 
     /**
+     * This is for verifying the UID report flow.
+     */
+    static final boolean VALIDATE_UID_STATES = true;
+    final SparseArray<UidRecord> mValidateUids = new SparseArray<>();
+
+    /**
      * Packages that the user has asked to have run in screen size
      * compatibility mode instead of filling the screen.
      */
@@ -1310,29 +1317,29 @@
         }
     }
 
-    static final int SHOW_ERROR_MSG = 1;
-    static final int SHOW_NOT_RESPONDING_MSG = 2;
-    static final int SHOW_FACTORY_ERROR_MSG = 3;
+    static final int SHOW_ERROR_UI_MSG = 1;
+    static final int SHOW_NOT_RESPONDING_UI_MSG = 2;
+    static final int SHOW_FACTORY_ERROR_UI_MSG = 3;
     static final int UPDATE_CONFIGURATION_MSG = 4;
     static final int GC_BACKGROUND_PROCESSES_MSG = 5;
-    static final int WAIT_FOR_DEBUGGER_MSG = 6;
+    static final int WAIT_FOR_DEBUGGER_UI_MSG = 6;
     static final int SERVICE_TIMEOUT_MSG = 12;
     static final int UPDATE_TIME_ZONE = 13;
-    static final int SHOW_UID_ERROR_MSG = 14;
-    static final int SHOW_FINGERPRINT_ERROR_MSG = 15;
+    static final int SHOW_UID_ERROR_UI_MSG = 14;
+    static final int SHOW_FINGERPRINT_ERROR_UI_MSG = 15;
     static final int PROC_START_TIMEOUT_MSG = 20;
     static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21;
     static final int KILL_APPLICATION_MSG = 22;
     static final int FINALIZE_PENDING_INTENT_MSG = 23;
     static final int POST_HEAVY_NOTIFICATION_MSG = 24;
     static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25;
-    static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26;
+    static final int SHOW_STRICT_MODE_VIOLATION_UI_MSG = 26;
     static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27;
     static final int CLEAR_DNS_CACHE_MSG = 28;
     static final int UPDATE_HTTP_PROXY_MSG = 29;
-    static final int SHOW_COMPAT_MODE_DIALOG_MSG = 30;
-    static final int DISPATCH_PROCESSES_CHANGED = 31;
-    static final int DISPATCH_PROCESS_DIED = 32;
+    static final int SHOW_COMPAT_MODE_DIALOG_UI_MSG = 30;
+    static final int DISPATCH_PROCESSES_CHANGED_UI_MSG = 31;
+    static final int DISPATCH_PROCESS_DIED_UI_MSG = 32;
     static final int REPORT_MEM_USAGE_MSG = 33;
     static final int REPORT_USER_SWITCH_MSG = 34;
     static final int CONTINUE_USER_SWITCH_MSG = 35;
@@ -1346,20 +1353,21 @@
     static final int SYSTEM_USER_CURRENT_MSG = 43;
     static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
     static final int FINISH_BOOTING_MSG = 45;
-    static final int START_USER_SWITCH_MSG = 46;
+    static final int START_USER_SWITCH_UI_MSG = 46;
     static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47;
-    static final int DISMISS_DIALOG_MSG = 48;
+    static final int DISMISS_DIALOG_UI_MSG = 48;
     static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG = 49;
     static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 50;
     static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 51;
     static final int DELETE_DUMPHEAP_MSG = 52;
     static final int FOREGROUND_PROFILE_CHANGED_MSG = 53;
-    static final int DISPATCH_UIDS_CHANGED_MSG = 54;
+    static final int DISPATCH_UIDS_CHANGED_UI_MSG = 54;
     static final int REPORT_TIME_TRACKER_MSG = 55;
     static final int REPORT_USER_SWITCH_COMPLETE_MSG = 56;
     static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 57;
     static final int APP_BOOST_DEACTIVATE_MSG = 58;
     static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 59;
+    static final int IDLE_UIDS_MSG = 60;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1394,7 +1402,7 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case SHOW_ERROR_MSG: {
+            case SHOW_ERROR_UI_MSG: {
                 HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                 boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                         Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
@@ -1439,7 +1447,7 @@
 
                 ensureBootCompleted();
             } break;
-            case SHOW_NOT_RESPONDING_MSG: {
+            case SHOW_NOT_RESPONDING_UI_MSG: {
                 synchronized (ActivityManagerService.this) {
                     HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                     ProcessRecord proc = (ProcessRecord)data.get("app");
@@ -1471,7 +1479,7 @@
 
                 ensureBootCompleted();
             } break;
-            case SHOW_STRICT_MODE_VIOLATION_MSG: {
+            case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
                 HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                 synchronized (ActivityManagerService.this) {
                     ProcessRecord proc = (ProcessRecord) data.get("app");
@@ -1497,13 +1505,13 @@
                 }
                 ensureBootCompleted();
             } break;
-            case SHOW_FACTORY_ERROR_MSG: {
+            case SHOW_FACTORY_ERROR_UI_MSG: {
                 Dialog d = new FactoryErrorDialog(
                     mContext, msg.getData().getCharSequence("msg"));
                 d.show();
                 ensureBootCompleted();
             } break;
-            case WAIT_FOR_DEBUGGER_MSG: {
+            case WAIT_FOR_DEBUGGER_UI_MSG: {
                 synchronized (ActivityManagerService.this) {
                     ProcessRecord app = (ProcessRecord)msg.obj;
                     if (msg.arg1 != 0) {
@@ -1523,7 +1531,7 @@
                     }
                 }
             } break;
-            case SHOW_UID_ERROR_MSG: {
+            case SHOW_UID_ERROR_UI_MSG: {
                 if (mShowDialogs) {
                     AlertDialog d = new BaseErrorDialog(mContext);
                     d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
@@ -1531,11 +1539,11 @@
                     d.setTitle(mContext.getText(R.string.android_system_label));
                     d.setMessage(mContext.getText(R.string.system_error_wipe_data));
                     d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
-                            obtainMessage(DISMISS_DIALOG_MSG, d));
+                            obtainMessage(DISMISS_DIALOG_UI_MSG, d));
                     d.show();
                 }
             } break;
-            case SHOW_FINGERPRINT_ERROR_MSG: {
+            case SHOW_FINGERPRINT_ERROR_UI_MSG: {
                 if (mShowDialogs) {
                     AlertDialog d = new BaseErrorDialog(mContext);
                     d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
@@ -1543,11 +1551,11 @@
                     d.setTitle(mContext.getText(R.string.android_system_label));
                     d.setMessage(mContext.getText(R.string.system_error_manufacturer));
                     d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
-                            obtainMessage(DISMISS_DIALOG_MSG, d));
+                            obtainMessage(DISMISS_DIALOG_UI_MSG, d));
                     d.show();
                 }
             } break;
-            case SHOW_COMPAT_MODE_DIALOG_MSG: {
+            case SHOW_COMPAT_MODE_DIALOG_UI_MSG: {
                 synchronized (ActivityManagerService.this) {
                     ActivityRecord ar = (ActivityRecord) msg.obj;
                     if (mCompatModeDialog != null) {
@@ -1575,26 +1583,26 @@
                 }
                 break;
             }
-            case START_USER_SWITCH_MSG: {
+            case START_USER_SWITCH_UI_MSG: {
                 mUserController.showUserSwitchDialog(msg.arg1, (String) msg.obj);
                 break;
             }
-            case DISMISS_DIALOG_MSG: {
+            case DISMISS_DIALOG_UI_MSG: {
                 final Dialog d = (Dialog) msg.obj;
                 d.dismiss();
                 break;
             }
-            case DISPATCH_PROCESSES_CHANGED: {
+            case DISPATCH_PROCESSES_CHANGED_UI_MSG: {
                 dispatchProcessesChanged();
                 break;
             }
-            case DISPATCH_PROCESS_DIED: {
+            case DISPATCH_PROCESS_DIED_UI_MSG: {
                 final int pid = msg.arg1;
                 final int uid = msg.arg2;
                 dispatchProcessDied(pid, uid);
                 break;
             }
-            case DISPATCH_UIDS_CHANGED_MSG: {
+            case DISPATCH_UIDS_CHANGED_UI_MSG: {
                 dispatchUidsChanged();
             } break;
             }
@@ -2046,7 +2054,7 @@
                 // it is finished we make sure it is reset to its default.
                 mUserIsMonkey = false;
             } break;
-            case APP_BOOST_DEACTIVATE_MSG : {
+            case APP_BOOST_DEACTIVATE_MSG: {
                 synchronized(ActivityManagerService.this) {
                     if (mIsBoosted) {
                         if (mBoostStartTime < (SystemClock.uptimeMillis() - APP_BOOST_TIMEOUT)) {
@@ -2060,6 +2068,9 @@
                     }
                 }
             } break;
+            case IDLE_UIDS_MSG: {
+                idleUids();
+            } break;
             }
         }
     };
@@ -2352,6 +2363,17 @@
         mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
 
         mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
+        mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
+                new IAppOpsCallback.Stub() {
+                    @Override public void opChanged(int op, int uid, String packageName) {
+                        if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
+                            if (mAppOpsService.checkOperation(op, uid, packageName)
+                                    != AppOpsManager.MODE_ALLOWED) {
+                                runInBackgroundDisabled(uid);
+                            }
+                        }
+                    }
+                });
 
         mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
 
@@ -2766,7 +2788,7 @@
 
     final void showAskCompatModeDialogLocked(ActivityRecord r) {
         Message msg = Message.obtain();
-        msg.what = SHOW_COMPAT_MODE_DIALOG_MSG;
+        msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
         msg.obj = r.task.askedCompatMode ? null : r;
         mUiHandler.sendMessage(msg);
     }
@@ -3797,8 +3819,10 @@
             for (int i=0; i<N; i++) {
                 final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
                 mActiveUidChanges[i] = change;
-                change.uidRecord.pendingChange = null;
-                change.uidRecord = null;
+                if (change.uidRecord != null) {
+                    change.uidRecord.pendingChange = null;
+                    change.uidRecord = null;
+                }
             }
             mPendingUidChanges.clear();
             if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
@@ -3808,7 +3832,8 @@
         if (mLocalPowerManager != null) {
             for (int j=0; j<N; j++) {
                 UidRecord.ChangeItem item = mActiveUidChanges[j];
-                if (item.gone) {
+                if (item.change == UidRecord.CHANGE_GONE
+                        || item.change == UidRecord.CHANGE_GONE_IDLE) {
                     mLocalPowerManager.uidGone(item.uid);
                 } else {
                     mLocalPowerManager.updateUidProcState(item.uid, item.processState);
@@ -3820,19 +3845,66 @@
         while (i > 0) {
             i--;
             final IUidObserver observer = mUidObservers.getBroadcastItem(i);
+            final int which = (Integer)mUidObservers.getBroadcastCookie(i);
             if (observer != null) {
                 try {
                     for (int j=0; j<N; j++) {
                         UidRecord.ChangeItem item = mActiveUidChanges[j];
-                        if (item.gone) {
-                            if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                    "UID gone uid=" + item.uid);
-                            observer.onUidGone(item.uid);
+                        final int change = item.change;
+                        UidRecord validateUid = null;
+                        if (VALIDATE_UID_STATES && i == 0) {
+                            validateUid = mValidateUids.get(item.uid);
+                            if (validateUid == null && change != UidRecord.CHANGE_GONE
+                                    && change != UidRecord.CHANGE_GONE_IDLE) {
+                                validateUid = new UidRecord(item.uid);
+                                mValidateUids.put(item.uid, validateUid);
+                            }
+                        }
+                        if (change == UidRecord.CHANGE_IDLE
+                                || change == UidRecord.CHANGE_GONE_IDLE) {
+                            if ((which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID idle uid=" + item.uid);
+                                observer.onUidIdle(item.uid);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                if (validateUid != null) {
+                                    validateUid.idle = true;
+                                }
+                            }
+                        } else if (change == UidRecord.CHANGE_ACTIVE) {
+                            if ((which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID active uid=" + item.uid);
+                                observer.onUidActive(item.uid);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                validateUid.idle = false;
+                            }
+                        }
+                        if (change == UidRecord.CHANGE_GONE
+                                || change == UidRecord.CHANGE_GONE_IDLE) {
+                            if ((which & ActivityManager.UID_OBSERVER_GONE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID gone uid=" + item.uid);
+                                observer.onUidGone(item.uid);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                if (validateUid != null) {
+                                    mValidateUids.remove(item.uid);
+                                }
+                            }
                         } else {
-                            if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                    "UID CHANGED uid=" + item.uid
-                                    + ": " + item.processState);
-                            observer.onUidStateChanged(item.uid, item.processState);
+                            if ((which & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID CHANGED uid=" + item.uid
+                                                + ": " + item.processState);
+                                observer.onUidStateChanged(item.uid, item.processState);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                validateUid.curProcState = validateUid.setProcState
+                                        = item.processState;
+                            }
                         }
                     }
                 } catch (RemoteException e) {
@@ -5102,7 +5174,7 @@
             // Bring up the infamous App Not Responding dialog
             Message msg = Message.obtain();
             HashMap<String, Object> map = new HashMap<String, Object>();
-            msg.what = SHOW_NOT_RESPONDING_MSG;
+            msg.what = SHOW_NOT_RESPONDING_UI_MSG;
             msg.obj = map;
             msg.arg1 = aboveSystem ? 1 : 0;
             map.put("app", app);
@@ -5881,7 +5953,7 @@
                 // No more processes using this uid, tell clients it is gone.
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "No more processes in " + old.uidRecord);
-                enqueueUidChangeLocked(old.uidRecord, true);
+                enqueueUidChangeLocked(old.uidRecord, -1, UidRecord.CHANGE_GONE);
                 mActiveUids.remove(uid);
             }
             old.uidRecord = null;
@@ -5907,7 +5979,7 @@
             if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                     "Creating new process uid: " + uidRec);
             mActiveUids.put(proc.uid, uidRec);
-            enqueueUidChangeLocked(uidRec, false);
+            enqueueUidChangeLocked(uidRec, -1, UidRecord.CHANGE_ACTIVE);
         }
         proc.uidRecord = uidRec;
         uidRec.numProcs++;
@@ -6715,9 +6787,11 @@
         if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
             activity = ActivityRecord.isInStackLocked(token);
             if (activity == null) {
+                Slog.w(TAG, "Failed createPendingResult: activity " + token + " not in any stack");
                 return null;
             }
             if (activity.finishing) {
+                Slog.w(TAG, "Failed createPendingResult: activity " + activity + " is finishing");
                 return null;
             }
         }
@@ -7269,6 +7343,36 @@
         return readMet && writeMet;
     }
 
+    public int getAppStartMode(int uid, String packageName) {
+        synchronized (this) {
+            boolean bg = checkAllowBackgroundLocked(uid, packageName, -1);
+            return bg ? ActivityManager.APP_START_MODE_NORMAL
+                    : ActivityManager.APP_START_MODE_DISABLED;
+        }
+    }
+
+    boolean checkAllowBackgroundLocked(int uid, String packageName, int callingPid) {
+        UidRecord uidRec = mActiveUids.get(uid);
+        if (uidRec == null || uidRec.idle) {
+            if (callingPid >= 0) {
+                ProcessRecord proc;
+                synchronized (mPidsSelfLocked) {
+                    proc = mPidsSelfLocked.get(callingPid);
+                }
+                if (proc != null && proc.curProcState < ActivityManager.PROCESS_STATE_RECEIVER) {
+                    // Whoever is instigating this is in the foreground, so we will allow it
+                    // to go through.
+                    return true;
+                }
+            }
+            if (mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private ProviderInfo getProviderInfoLocked(String authority, int userHandle) {
         ProviderInfo pi = null;
         ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle);
@@ -8268,7 +8372,7 @@
             if (app == null) return;
 
             Message msg = Message.obtain();
-            msg.what = WAIT_FOR_DEBUGGER_MSG;
+            msg.what = WAIT_FOR_DEBUGGER_UI_MSG;
             msg.obj = app;
             msg.arg1 = waiting ? 1 : 0;
             mUiHandler.sendMessage(msg);
@@ -11213,11 +11317,12 @@
         }
     }
 
-    public void registerUidObserver(IUidObserver observer) {
+    @Override
+    public void registerUidObserver(IUidObserver observer, int which) {
         enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerUidObserver()");
         synchronized (this) {
-            mUidObservers.register(observer);
+            mUidObservers.register(observer, which);
         }
     }
 
@@ -12056,7 +12161,7 @@
                     mTopData = null;
                     mTopComponent = null;
                     Message msg = Message.obtain();
-                    msg.what = SHOW_FACTORY_ERROR_MSG;
+                    msg.what = SHOW_FACTORY_ERROR_UI_MSG;
                     msg.getData().putCharSequence("msg", errorMsg);
                     mUiHandler.sendMessage(msg);
                 }
@@ -12110,14 +12215,14 @@
                 if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
                     Slog.e(TAG, "UIDs on the system are inconsistent, you need to wipe your"
                             + " data partition or your device will be unstable.");
-                    mUiHandler.obtainMessage(SHOW_UID_ERROR_MSG).sendToTarget();
+                    mUiHandler.obtainMessage(SHOW_UID_ERROR_UI_MSG).sendToTarget();
                 }
             } catch (RemoteException e) {
             }
 
             if (!Build.isBuildConsistent()) {
                 Slog.e(TAG, "Build fingerprint is not consistent, warning user");
-                mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_MSG).sendToTarget();
+                mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_UI_MSG).sendToTarget();
             }
 
             long ident = Binder.clearCallingIdentity();
@@ -12398,7 +12503,7 @@
                 final long origId = Binder.clearCallingIdentity();
 
                 Message msg = Message.obtain();
-                msg.what = SHOW_STRICT_MODE_VIOLATION_MSG;
+                msg.what = SHOW_STRICT_MODE_VIOLATION_UI_MSG;
                 HashMap<String, Object> data = new HashMap<String, Object>();
                 data.put("result", result);
                 data.put("app", r);
@@ -12855,7 +12960,7 @@
             }
 
             Message msg = Message.obtain();
-            msg.what = SHOW_ERROR_MSG;
+            msg.what = SHOW_ERROR_UI_MSG;
             HashMap data = new HashMap();
             data.put("result", result);
             data.put("app", r);
@@ -13484,6 +13589,39 @@
         }
     }
 
+    boolean dumpUids(PrintWriter pw, String dumpPackage, SparseArray<UidRecord> uids,
+            String header, boolean needSep) {
+        boolean printed = false;
+        int whichAppId = -1;
+        if (dumpPackage != null) {
+            try {
+                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+                        dumpPackage, 0);
+                whichAppId = UserHandle.getAppId(info.uid);
+            } catch (NameNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+        for (int i=0; i<uids.size(); i++) {
+            UidRecord uidRec = uids.valueAt(i);
+            if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+                continue;
+            }
+            if (!printed) {
+                printed = true;
+                if (needSep) {
+                    pw.println();
+                }
+                pw.print("  ");
+                pw.println(header);
+                needSep = true;
+            }
+            pw.print("    UID "); UserHandle.formatUid(pw, uidRec.uid);
+            pw.print(": "); pw.println(uidRec);
+        }
+        return printed;
+    }
+
     void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
@@ -13540,33 +13678,13 @@
         }
 
         if (mActiveUids.size() > 0) {
-            boolean printed = false;
-            int whichAppId = -1;
-            if (dumpPackage != null) {
-                try {
-                    ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
-                            dumpPackage, 0);
-                    whichAppId = UserHandle.getAppId(info.uid);
-                } catch (NameNotFoundException e) {
-                    e.printStackTrace();
-                }
+            if (dumpUids(pw, dumpPackage, mActiveUids, "UID states:", needSep)) {
+                printedAnything = needSep = true;
             }
-            for (int i=0; i<mActiveUids.size(); i++) {
-                UidRecord uidRec = mActiveUids.valueAt(i);
-                if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
-                    continue;
-                }
-                if (!printed) {
-                    printed = true;
-                    if (needSep) {
-                        pw.println();
-                    }
-                    pw.println("  UID states:");
-                    needSep = true;
-                    printedAnything = true;
-                }
-                pw.print("    UID "); UserHandle.formatUid(pw, uidRec.uid);
-                pw.print(": "); pw.println(uidRec);
+        }
+        if (mValidateUids.size() > 0) {
+            if (dumpUids(pw, dumpPackage, mValidateUids, "UID validation:", needSep)) {
+                printedAnything = needSep = true;
             }
         }
 
@@ -14457,6 +14575,9 @@
                 if (object1.first.setAdj != object2.first.setAdj) {
                     return object1.first.setAdj > object2.first.setAdj ? -1 : 1;
                 }
+                if (object1.first.setProcState != object2.first.setProcState) {
+                    return object1.first.setProcState > object2.first.setProcState ? -1 : 1;
+                }
                 if (object1.second.intValue() != object2.second.intValue()) {
                     return object1.second.intValue() > object2.second.intValue() ? -1 : 1;
                 }
@@ -15829,7 +15950,8 @@
                 mAvailProcessChanges.add(item);
             }
         }
-        mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
+        mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED_UI_MSG, app.pid, app.info.uid,
+                null).sendToTarget();
 
         // If the caller is restarting this app, then leave it in its
         // current lists and let the caller take care of it.
@@ -19020,7 +19142,7 @@
                 if (mPendingProcessChanges.size() == 0) {
                     if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
                             "*** Enqueueing dispatch processes changed!");
-                    mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
+                    mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG).sendToTarget();
                 }
                 mPendingProcessChanges.add(item);
             }
@@ -19039,29 +19161,46 @@
         return success;
     }
 
-    private final void enqueueUidChangeLocked(UidRecord uidRec, boolean gone) {
-        if (uidRec.pendingChange == null) {
+    private final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+        final UidRecord.ChangeItem pendingChange;
+        if (uidRec == null || uidRec.pendingChange == null) {
             if (mPendingUidChanges.size() == 0) {
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "*** Enqueueing dispatch uid changed!");
-                mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_MSG).sendToTarget();
+                mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_UI_MSG).sendToTarget();
             }
             final int NA = mAvailUidChanges.size();
             if (NA > 0) {
-                uidRec.pendingChange = mAvailUidChanges.remove(NA-1);
+                pendingChange = mAvailUidChanges.remove(NA-1);
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Retrieving available item: " + uidRec.pendingChange);
+                        "Retrieving available item: " + pendingChange);
             } else {
-                uidRec.pendingChange = new UidRecord.ChangeItem();
+                pendingChange = new UidRecord.ChangeItem();
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Allocating new item: " + uidRec.pendingChange);
+                        "Allocating new item: " + pendingChange);
             }
-            uidRec.pendingChange.uidRecord = uidRec;
-            uidRec.pendingChange.uid = uidRec.uid;
-            mPendingUidChanges.add(uidRec.pendingChange);
+            if (uidRec != null) {
+                uidRec.pendingChange = pendingChange;
+                if (change == UidRecord.CHANGE_GONE && !uidRec.idle) {
+                    // If this uid is going away, and we haven't yet reported it is gone,
+                    // then do so now.
+                    change = UidRecord.CHANGE_GONE_IDLE;
+                }
+            } else if (uid < 0) {
+                throw new IllegalArgumentException("No UidRecord or uid");
+            }
+            pendingChange.uidRecord = uidRec;
+            pendingChange.uid = uidRec != null ? uidRec.uid : uid;
+            mPendingUidChanges.add(pendingChange);
+        } else {
+            pendingChange = uidRec.pendingChange;
+            if (change == UidRecord.CHANGE_GONE && pendingChange.change == UidRecord.CHANGE_IDLE) {
+                change = UidRecord.CHANGE_GONE_IDLE;
+            }
         }
-        uidRec.pendingChange.gone = gone;
-        uidRec.pendingChange.processState = uidRec.setProcState;
+        pendingChange.change = change;
+        pendingChange.processState = uidRec != null
+                ? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
     }
 
     private void maybeUpdateProviderUsageStatsLocked(ProcessRecord app, String providerPkgName,
@@ -19608,12 +19747,31 @@
         // Update from any uid changes.
         for (int i=mActiveUids.size()-1; i>=0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
+            int uidChange = UidRecord.CHANGE_PROCSTATE;
             if (uidRec.setProcState != uidRec.curProcState) {
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "Changes in " + uidRec + ": proc state from " + uidRec.setProcState
                         + " to " + uidRec.curProcState);
+                if (ActivityManager.isProcStateBackground(uidRec.curProcState)) {
+                    if (!ActivityManager.isProcStateBackground(uidRec.setProcState)) {
+                        uidRec.lastBackgroundTime = nowElapsed;
+                        if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {
+                            // Note: the background settle time is in elapsed realtime, while
+                            // the handler time base is uptime.  All this means is that we may
+                            // stop background uids later than we had intended, but that only
+                            // happens because the device was sleeping so we are okay anyway.
+                            mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, BACKGROUND_SETTLE_TIME);
+                        }
+                    }
+                } else {
+                    if (uidRec.idle) {
+                        uidChange = UidRecord.CHANGE_ACTIVE;
+                        uidRec.idle = false;
+                    }
+                    uidRec.lastBackgroundTime = 0;
+                }
                 uidRec.setProcState = uidRec.curProcState;
-                enqueueUidChangeLocked(uidRec, false);
+                enqueueUidChangeLocked(uidRec, -1, uidChange);
             }
         }
 
@@ -19638,6 +19796,53 @@
         }
     }
 
+    final void idleUids() {
+        synchronized (this) {
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long maxBgTime = nowElapsed - BACKGROUND_SETTLE_TIME;
+            long nextTime = 0;
+            for (int i=mActiveUids.size()-1; i>=0; i--) {
+                final UidRecord uidRec = mActiveUids.valueAt(i);
+                final long bgTime = uidRec.lastBackgroundTime;
+                if (bgTime > 0 && !uidRec.idle) {
+                    if (bgTime <= maxBgTime) {
+                        uidRec.idle = true;
+                        doStopUidLocked(uidRec.uid, uidRec);
+                    } else {
+                        if (nextTime == 0 || nextTime > bgTime) {
+                            nextTime = bgTime;
+                        }
+                    }
+                }
+            }
+            if (nextTime > 0) {
+                mHandler.removeMessages(IDLE_UIDS_MSG);
+                mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+                        nextTime + BACKGROUND_SETTLE_TIME - nowElapsed);
+            }
+        }
+    }
+
+    final void runInBackgroundDisabled(int uid) {
+        synchronized (this) {
+            UidRecord uidRec = mActiveUids.get(uid);
+            if (uidRec != null) {
+                // This uid is actually running...  should it be considered background now?
+                if (uidRec.idle) {
+                    doStopUidLocked(uidRec.uid, uidRec);
+                }
+            } else {
+                // This uid isn't actually running...  still send a report about it being "stopped".
+                doStopUidLocked(uid, null);
+            }
+        }
+    }
+
+    final void doStopUidLocked(int uid, final UidRecord uidRec) {
+        mServices.stopInBackgroundLocked(uid);
+        enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
+    }
+
     final void trimApplications() {
         synchronized (this) {
             int i;
@@ -19973,8 +20178,9 @@
             userName = userInfo.name;
             mUserController.setTargetUserIdLocked(userId);
         }
-        mUiHandler.removeMessages(START_USER_SWITCH_MSG);
-        mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
+        mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
+        mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_UI_MSG, userId, 0,
+                userName));
         return true;
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index d317791..fb37eda 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -556,6 +556,17 @@
                     + " (uid " + r.callingUid + ")");
             skip = true;
         }
+        if (!skip) {
+            if (!mService.checkAllowBackgroundLocked(filter.receiverList.uid, filter.packageName,
+                    -1)) {
+                Slog.w(TAG, "Background execution not allowed: receiving "
+                        + r.intent
+                        + " to " + filter.receiverList.app
+                        + " (pid=" + filter.receiverList.pid
+                        + ", uid=" + filter.receiverList.uid + ")");
+                skip = true;
+            }
+        }
 
         if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                 r.callingPid, r.resolvedType, filter.receiverList.uid)) {
@@ -938,6 +949,15 @@
                 skip = true;
             }
             if (!skip) {
+                if (!mService.checkAllowBackgroundLocked(info.activityInfo.applicationInfo.uid,
+                        info.activityInfo.packageName, -1)) {
+                    Slog.w(TAG, "Background execution not allowed: receiving "
+                            + r.intent + " to "
+                            + component.flattenToShortString());
+                    skip = true;
+                }
+            }
+            if (!skip) {
                 skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                         r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
             }
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index b4efbf0..d24c3a5 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -17,7 +17,9 @@
 package com.android.server.am;
 
 import android.app.ActivityManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.util.TimeUtils;
 
 /**
  * Overall information about a uid that has actively running processes.
@@ -26,12 +28,20 @@
     final int uid;
     int curProcState;
     int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+    long lastBackgroundTime;
+    boolean idle;
     int numProcs;
 
+    static final int CHANGE_PROCSTATE = 0;
+    static final int CHANGE_GONE = 1;
+    static final int CHANGE_GONE_IDLE = 2;
+    static final int CHANGE_IDLE = 3;
+    static final int CHANGE_ACTIVE = 4;
+
     static final class ChangeItem {
         UidRecord uidRecord;
         int uid;
-        boolean gone;
+        int change;
         int processState;
     }
 
@@ -54,9 +64,16 @@
         UserHandle.formatUid(sb, uid);
         sb.append(' ');
         sb.append(ProcessList.makeProcStateString(curProcState));
-        sb.append(" / ");
+        if (lastBackgroundTime > 0) {
+            sb.append(" bg:");
+            TimeUtils.formatDuration(SystemClock.elapsedRealtime()-lastBackgroundTime, sb);
+        }
+        if (idle) {
+            sb.append(" idle");
+        }
+        sb.append(" procs:");
         sb.append(numProcs);
-        sb.append(" procs}");
+        sb.append("}");
         return sb.toString();
     }
 }
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index b766894..75a74c0 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -466,7 +466,7 @@
                 if (cname == null) {
                     info = new SyncStorageEngine.EndPoint(account, authority, userId);
                 } else {
-                    info = new SyncStorageEngine.EndPoint(cname, userId);
+                    info = new SyncStorageEngine.EndPoint(cname, userId, -1);
                 }
                 syncManager.clearScheduledSyncOperations(info);
                 syncManager.cancelActiveSync(info, null /* all syncs for this adapter */);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index e2a0f82..4f53882 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -21,8 +21,10 @@
 import android.accounts.AccountManager;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
+import android.app.IUidObserver;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -79,6 +81,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import android.util.Slog;
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
@@ -237,7 +240,7 @@
 
     private final AppIdleMonitor mAppIdleMonitor;
 
-    private BroadcastReceiver mStorageIntentReceiver =
+    private final BroadcastReceiver mStorageIntentReceiver =
             new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
@@ -260,7 +263,7 @@
                 }
             };
 
-    private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             boolean idle = mPowerManager.isDeviceIdleMode()
                     || mPowerManager.isLightDeviceIdleMode();
@@ -281,7 +284,7 @@
         }
     };
 
-    private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             mBootCompleted = true;
@@ -289,7 +292,7 @@
         }
     };
 
-    private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             updateRunningAccounts();
@@ -300,6 +303,21 @@
         }
     };
 
+    private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+            cancelSyncsForUid(uid);
+        }
+    };
+
     private final PowerManager mPowerManager;
     DeviceIdleController.LocalService mLocalDeviceIdleController;
 
@@ -504,6 +522,13 @@
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
 
+        try {
+            ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
+                    ActivityManager.UID_OBSERVER_IDLE);
+        } catch (RemoteException e) {
+            // ignored; both services live in system_server
+        }
+
         if (!factoryTest) {
             mNotificationMgr = (NotificationManager)
                 context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -661,6 +686,26 @@
             Log.d(TAG, "one off sync for: " + cname + " " + extras.toString());
         }
 
+        final android.content.pm.ServiceInfo sinfo;
+        try {
+            sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "Not scheduling sync " + cname
+                    + " -- can't find service for user " + userId);
+            return;
+        }
+        final int sUid = sinfo.applicationInfo.uid;
+
+        try {
+            if (ActivityManagerNative.getDefault().getAppStartMode(sUid, cname.getPackageName())
+                    == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Not scheduling sync " + sUid + ":" + cname
+                        + " -- package not allowed to start");
+                return;
+            }
+        } catch (RemoteException e) {
+        }
+
         Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
         if (expedited) {
             runtimeMillis = -1; // this means schedule at the front of the queue
@@ -688,7 +733,7 @@
             }
             return;
         }
-        SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId);
+        SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId, sUid);
         Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
         long delayUntil = mSyncStorageEngine.getDelayUntilTime(info);
         final long backoffTime = backoff != null ? backoff.first : 0;
@@ -702,7 +747,7 @@
                         + ", extras " + extras);
         }
         scheduleSyncOperation(
-                new SyncOperation(cname, userId, uid, source, extras,
+                new SyncOperation(cname, userId, sUid, cname.getPackageName(), uid, source, extras,
                         runtimeMillis /* runtime */,
                         beforeRunTimeMillis /* flextime */,
                         backoffTime,
@@ -837,6 +882,18 @@
                 if (syncAdapterInfo == null) {
                     continue;
                 }
+                final int owningUid = syncAdapterInfo.uid;
+                final String owningPackage = syncAdapterInfo.componentName.getPackageName();
+                try {
+                    if (ActivityManagerNative.getDefault().getAppStartMode(owningUid,
+                            owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+                        Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":"
+                                + syncAdapterInfo.componentName
+                                + " -- package not allowed to start");
+                        return;
+                    }
+                } catch (RemoteException e) {
+                }
                 final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
                 final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
                 if (isSyncable < 0 && isAlwaysSyncable) {
@@ -886,7 +943,8 @@
                                 + ", extras " + newExtras);
                     }
                     scheduleSyncOperation(
-                            new SyncOperation(account.account, account.userId, reason, source,
+                            new SyncOperation(account.account, account.userId,
+                                    owningUid, owningPackage, reason, source,
                                     authority, newExtras, 0 /* immediate */, 0 /* No flex time*/,
                                     backoffTime, delayUntil, allowParallelSyncs));
                 }
@@ -902,7 +960,8 @@
                                 + ", extras " + extras);
                     }
                     scheduleSyncOperation(
-                            new SyncOperation(account.account, account.userId, reason, source,
+                            new SyncOperation(account.account, account.userId,
+                                    owningUid, owningPackage, reason, source,
                                     authority, extras, runtimeMillis, beforeRuntimeMillis,
                                     backoffTime, delayUntil, allowParallelSyncs));
                 }
@@ -1309,6 +1368,14 @@
         }
     }
 
+    void cancelSyncsForUid(int uid) {
+        synchronized (mSyncQueue) {
+            if (mSyncQueue.removeUidIfNeededLocked(uid)) {
+                sendCheckAlarmsMessage();
+            }
+        }
+    }
+
     /**
      * @hide
      */
@@ -2166,8 +2233,8 @@
         /**
          * Stash any messages that come to the handler before boot is complete or before the device
          * is properly provisioned (i.e. out of set-up wizard).
-         * {@link #onBootCompleted()} and {@link #onDeviceProvisioned(boolean)} both need to come
-         * in before we start syncing.
+         * {@link #onBootCompleted()} and {@link SyncHandler#onDeviceProvisioned} both
+         * need to come in before we start syncing.
          * @param msg Message to dispatch at a later point.
          * @return true if a message was enqueued, false otherwise. This is to avoid losing the
          * message if we manage to acquire the lock but by the time we do boot has completed.
@@ -2503,6 +2570,8 @@
                             }
                             scheduleSyncOperation(
                                     new SyncOperation(target.account, target.userId,
+                                            syncAdapterInfo.uid,
+                                            syncAdapterInfo.componentName.getPackageName(),
                                             SyncOperation.REASON_PERIODIC,
                                             SyncStorageEngine.SOURCE_PERIODIC,
                                             target.provider, extras,
@@ -2513,6 +2582,7 @@
                         } else if (target.target_service) {
                             scheduleSyncOperation(
                                     new SyncOperation(target.service, target.userId,
+                                            target.serviceUid, target.service.getPackageName(),
                                             SyncOperation.REASON_PERIODIC,
                                             SyncStorageEngine.SOURCE_PERIODIC,
                                             extras,
@@ -3145,6 +3215,7 @@
                 if (syncResult != null && syncResult.fullSyncRequested) {
                     scheduleSyncOperation(
                             new SyncOperation(info.account, info.userId,
+                                    syncOperation.owningUid, syncOperation.owningPackage,
                                 syncOperation.reason,
                                 syncOperation.syncSource, info.provider, new Bundle(),
                                 0 /* delay */, 0 /* flex */,
@@ -3155,6 +3226,7 @@
                 if (syncResult != null && syncResult.fullSyncRequested) {
                     scheduleSyncOperation(
                             new SyncOperation(info.service, info.userId,
+                                    syncOperation.owningUid, syncOperation.owningPackage,
                                 syncOperation.reason,
                                 syncOperation.syncSource, new Bundle(),
                                 0 /* delay */, 0 /* flex */,
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 10efe81..ab777ae 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -22,6 +22,7 @@
 import android.content.ContentResolver;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Log;
 
 /**
@@ -62,6 +63,8 @@
 
     /** Identifying info for the target for this operation. */
     public final SyncStorageEngine.EndPoint target;
+    public final int owningUid;
+    public final String owningPackage;
     /** Why this sync was kicked off. {@link #REASON_NAMES} */
     public final int reason;
     /** Where this sync was initiated. */
@@ -93,25 +96,28 @@
     /** Whether this sync op was recently skipped due to the app being idle */
     public boolean appIdle;
 
-    public SyncOperation(Account account, int userId, int reason, int source, String provider,
-            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
-            long delayUntil, boolean allowParallelSyncs) {
-        this(new SyncStorageEngine.EndPoint(account, provider, userId),
+    public SyncOperation(Account account, int userId, int owningUid, String owningPackage,
+            int reason, int source, String provider, Bundle extras, long runTimeFromNow,
+            long flexTime, long backoff, long delayUntil, boolean allowParallelSyncs) {
+        this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage,
                 reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
                 allowParallelSyncs);
     }
 
-    public SyncOperation(ComponentName service, int userId, int reason, int source,
-            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
+    public SyncOperation(ComponentName service, int userId, int owningUid, String owningPackage,
+            int reason, int source, Bundle extras, long runTimeFromNow, long flexTime, long backoff,
             long delayUntil) {
-        this(new SyncStorageEngine.EndPoint(service, userId), reason, source, extras,
-                runTimeFromNow, flexTime, backoff, delayUntil, true /* allowParallelSyncs */);
+        this(new SyncStorageEngine.EndPoint(service, userId, owningUid), owningUid, owningPackage,
+                reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
+                true /* allowParallelSyncs */);
     }
 
-    private SyncOperation(SyncStorageEngine.EndPoint info, int reason, int source, Bundle extras,
-            long runTimeFromNow, long flexTime, long backoff, long delayUntil,
-            boolean allowParallelSyncs) {
+    private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
+            int reason, int source, Bundle extras, long runTimeFromNow, long flexTime,
+            long backoff, long delayUntil, boolean allowParallelSyncs) {
         this.target = info;
+        this.owningUid = owningUid;
+        this.owningPackage = owningPackage;
         this.reason = reason;
         this.syncSource = source;
         this.extras = new Bundle(extras);
@@ -142,7 +148,8 @@
 
     /** Used to reschedule a sync at a new point in time. */
     public SyncOperation(SyncOperation other, long newRunTimeFromNow) {
-        this(other.target, other.reason, other.syncSource, new Bundle(other.extras),
+        this(other.target, other.owningUid, other.owningPackage, other.reason, other.syncSource,
+                new Bundle(other.extras),
                 newRunTimeFromNow,
                 0L /* In back-off so no flex */,
                 other.backoff,
@@ -228,6 +235,13 @@
         }
         sb.append(", reason: ");
         sb.append(reasonToString(pm, reason));
+        if (!useOneLine) {
+            sb.append("\n    ");
+            sb.append("owningUid=");
+            UserHandle.formatUid(sb, owningUid);
+            sb.append(" owningPackage=");
+            sb.append(owningPackage);
+        }
         if (!useOneLine && !extras.keySet().isEmpty()) {
             sb.append("\n    ");
             extrasToStringBuilder(extras, sb);
diff --git a/services/core/java/com/android/server/content/SyncQueue.java b/services/core/java/com/android/server/content/SyncQueue.java
index 587de1c..b15d0d8 100644
--- a/services/core/java/com/android/server/content/SyncQueue.java
+++ b/services/core/java/com/android/server/content/SyncQueue.java
@@ -16,16 +16,20 @@
 
 package com.android.server.content;
 
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.content.pm.PackageManager;
 import android.content.SyncAdapterType;
 import android.content.SyncAdaptersCache;
 import android.content.pm.RegisteredServicesCache.ServiceInfo;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Pair;
 
+import android.util.Slog;
 import com.google.android.collect.Maps;
 
 import java.util.ArrayList;
@@ -74,8 +78,9 @@
                     continue;
                 }
                 operationToAdd = new SyncOperation(
-                        info.account, info.userId, op.reason, op.syncSource, info.provider,
-                        op.extras,
+                        info.account, info.userId, syncAdapterInfo.uid,
+                        syncAdapterInfo.componentName.getPackageName(), op.reason,
+                        op.syncSource, info.provider, op.extras,
                         op.expedited ? -1 : 0 /* delay */,
                         0 /* flex */,
                         backoff != null ? backoff.first : 0L,
@@ -84,16 +89,24 @@
                 operationToAdd.pendingOperation = op;
                 add(operationToAdd, op);
             } else if (info.target_service) {
+                android.content.pm.ServiceInfo sinfo;
                 try {
-                    mPackageManager.getServiceInfo(info.service, 0);
+                    sinfo = mPackageManager.getServiceInfo(info.service, info.userId);
                 } catch (PackageManager.NameNotFoundException e) {
                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
                         Log.w(TAG, "Missing sync service for authority " + op.target);
                     }
                     continue;
                 }
+                if (sinfo == null) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.w(TAG, "Missing sync service for authority " + op.target);
+                    }
+                    continue;
+                }
                 operationToAdd = new SyncOperation(
-                        info.service, info.userId, op.reason, op.syncSource,
+                        info.service, info.userId, sinfo.applicationInfo.uid,
+                        info.service.getPackageName(), op.reason, op.syncSource,
                         op.extras,
                         op.expedited ? -1 : 0 /* delay */,
                         0 /* flex */,
@@ -161,9 +174,38 @@
                 opsToRemove.add(op);
             }
         }
-            for (SyncOperation op : opsToRemove) {
-                remove(op);
+        for (SyncOperation op : opsToRemove) {
+            remove(op);
+        }
+    }
+
+    public boolean removeUidIfNeededLocked(int uid) {
+        ArrayList<SyncOperation> opsToRemove = null;
+        for (SyncOperation op : mOperationsMap.values()) {
+            if (op.owningUid != uid) {
+                continue;
             }
+            try {
+                if (ActivityManagerNative.getDefault().getAppStartMode(op.owningUid,
+                        op.owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+                    Slog.w(TAG, "Removing sync " + op.owningUid + ":" + op
+                            + " -- package not allowed to start");
+                    continue;
+                }
+            } catch (RemoteException e) {
+            }
+            if (opsToRemove == null) {
+                opsToRemove = new ArrayList<SyncOperation>();
+            }
+            opsToRemove.add(op);
+        }
+        if (opsToRemove == null) {
+            return false;
+        }
+        for (SyncOperation op : opsToRemove) {
+            remove(op);
+        }
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index cca0c16..8266c08 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -27,6 +27,7 @@
 import android.content.SyncInfo;
 import android.content.SyncRequest;
 import android.content.SyncStatusInfo;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
@@ -42,6 +43,7 @@
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.ArrayMap;
 import android.util.Xml;
@@ -227,14 +229,16 @@
         public final static EndPoint USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL =
                 new EndPoint(null, null, UserHandle.USER_ALL);
         final ComponentName service;
+        final int serviceUid;           // -1 for "any"
         final Account account;
         final int userId;
         final String provider;
         final boolean target_service;
         final boolean target_provider;
 
-        public EndPoint(ComponentName service, int userId) {
+        public EndPoint(ComponentName service, int userId, int uid) {
             this.service = service;
+            this.serviceUid = uid;
             this.userId = userId;
             this.account = null;
             this.provider = null;
@@ -247,6 +251,7 @@
             this.provider = provider;
             this.userId = userId;
             this.service = null;
+            this.serviceUid = -1;
             this.target_service = false;
             this.target_provider = true;
         }
@@ -264,6 +269,11 @@
                 return false;
             }
             if (target_service && spec.target_service) {
+                if (serviceUid != spec.serviceUid
+                    && serviceUid >= 0
+                    && spec.serviceUid >= 0) {
+                    return false;
+                }
                 return service.equals(spec.service);
             } else if (target_provider && spec.target_provider) {
                 boolean accountsMatch;
@@ -290,8 +300,9 @@
                     .append("/")
                     .append(provider == null ? "ALL PDRS" : provider);
             } else if (target_service) {
-                sb.append(service.getPackageName() + "/")
-                  .append(service.getClassName());
+                service.appendShortString(sb);
+                sb.append(":");
+                UserHandle.formatUid(sb,serviceUid);
             } else {
                 sb.append("invalid target");
             }
@@ -737,7 +748,7 @@
         synchronized (mAuthorities) {
             if (cname != null) {
                 AuthorityInfo authority = getAuthorityLocked(
-                        new EndPoint(cname, userId),
+                        new EndPoint(cname, userId, -1),
                         "get service active");
                 if (authority == null) {
                     return false;
@@ -749,7 +760,7 @@
     }
 
     public void setIsTargetServiceActive(ComponentName cname, int userId, boolean active) {
-        setSyncableStateForEndPoint(new EndPoint(cname, userId), active ?
+        setSyncableStateForEndPoint(new EndPoint(cname, userId, -1), active ?
                 AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE);
     }
 
@@ -2064,18 +2075,30 @@
                             new Account(accountName, accountType),
                             authorityName, userId);
                 } else {
-                    info = new EndPoint(
-                            new ComponentName(packageName, className),
-                            userId);
+                    final ComponentName cname = new ComponentName(packageName, className);
+                    android.content.pm.ServiceInfo sinfo = null;
+                    try {
+                        sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.w(TAG, "Not restoring sync " + cname
+                                + " -- can't find service for user " + userId);
+                    }
+                    if (sinfo != null) {
+                        info = new EndPoint(cname, userId, sinfo.applicationInfo.uid);
+                    } else {
+                        info = null;
+                    }
                 }
-                authority = getOrCreateAuthorityLocked(info, id, false);
-                // If the version is 0 then we are upgrading from a file format that did not
-                // know about periodic syncs. In that case don't clear the list since we
-                // want the default, which is a daily periodic sync.
-                // Otherwise clear out this default list since we will populate it later with
-                // the periodic sync descriptions that are read from the configuration file.
-                if (version > 0) {
-                    authority.periodicSyncs.clear();
+                if (info != null) {
+                    authority = getOrCreateAuthorityLocked(info, id, false);
+                    // If the version is 0 then we are upgrading from a file format that did not
+                    // know about periodic syncs. In that case don't clear the list since we
+                    // want the default, which is a daily periodic sync.
+                    // Otherwise clear out this default list since we will populate it later with
+                    // the periodic sync descriptions that are read from the configuration file.
+                    if (version > 0) {
+                        authority.periodicSyncs.clear();
+                    }
                 }
             }
             if (authority != null) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 759199c..4d7df9c 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -23,7 +23,9 @@
 import java.util.List;
 
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
+import android.app.IUidObserver;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
@@ -85,6 +87,7 @@
 
     static final int MSG_JOB_EXPIRED = 0;
     static final int MSG_CHECK_JOB = 1;
+    static final int MSG_STOP_JOB = 2;
 
     // Policy constants
     /**
@@ -162,7 +165,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
                     }
-                    cancelJobsForUid(uidRemoved);
+                    cancelJobsForUid(uidRemoved, true);
                 }
             } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -180,6 +183,21 @@
         }
     };
 
+    final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+            cancelJobsForUid(uid, false);
+        }
+    };
+
     @Override
     public void onStartUser(int userHandle) {
         mStartedUsers.add(userHandle);
@@ -202,6 +220,15 @@
     public int schedule(JobInfo job, int uId) {
         JobStatus jobStatus = new JobStatus(job, uId);
         cancelJob(uId, job.getId());
+        try {
+            if (ActivityManagerNative.getDefault().getAppStartMode(uId,
+                    job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+                        + " -- package not allowed to start");
+                return JobScheduler.RESULT_FAILURE;
+            }
+        } catch (RemoteException e) {
+        }
         startTrackingJob(jobStatus);
         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
         return JobScheduler.RESULT_SUCCESS;
@@ -237,14 +264,26 @@
      * This will remove the job from the master list, and cancel the job if it was staged for
      * execution or being executed.
      * @param uid Uid to check against for removal of a job.
+     * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
+     * whose apps are stopped.
      */
-    public void cancelJobsForUid(int uid) {
+    public void cancelJobsForUid(int uid, boolean forceAll) {
         List<JobStatus> jobsForUid;
         synchronized (mJobs) {
             jobsForUid = mJobs.getJobsByUid(uid);
         }
         for (int i=0; i<jobsForUid.size(); i++) {
             JobStatus toRemove = jobsForUid.get(i);
+            if (!forceAll) {
+                String packageName = toRemove.getServiceComponent().getPackageName();
+                try {
+                    if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
+                            != ActivityManager.APP_START_MODE_DISABLED) {
+                        continue;
+                    }
+                } catch (RemoteException e) {
+                }
+            }
             cancelJobImpl(toRemove);
         }
     }
@@ -384,6 +423,12 @@
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
             mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
+            try {
+                ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
+                        ActivityManager.UID_OBSERVER_IDLE);
+            } catch (RemoteException e) {
+                // ignored; both services live in system_server
+            }
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             synchronized (mJobs) {
                 // Let's go!
@@ -635,6 +680,9 @@
                         maybeQueueReadyJobsForExecutionLockedH();
                     }
                     break;
+                case MSG_STOP_JOB:
+                    cancelJobImpl((JobStatus)message.obj);
+                    break;
             }
             maybeRunPendingJobsH();
             // Don't remove JOB_EXPIRED in case one came along while processing the queue.
@@ -686,11 +734,22 @@
             int idleCount =  0;
             int backoffCount = 0;
             int connectivityCount = 0;
-            List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
+            List<JobStatus> runnableJobs = null;
             ArraySet<JobStatus> jobs = mJobs.getJobs();
             for (int i=0; i<jobs.size(); i++) {
                 JobStatus job = jobs.valueAt(i);
                 if (isReadyToBeExecutedLocked(job)) {
+                    try {
+                        if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
+                                job.getJob().getService().getPackageName())
+                                == ActivityManager.APP_START_MODE_DISABLED) {
+                            Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+                                    + job.getJob().toString() + " -- package not allowed to start");
+                            mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
+                            continue;
+                        }
+                    } catch (RemoteException e) {
+                    }
                     if (job.getNumFailures() > 0) {
                         backoffCount++;
                     }
@@ -703,6 +762,9 @@
                     if (job.hasChargingConstraint()) {
                         chargingCount++;
                     }
+                    if (runnableJobs == null) {
+                        runnableJobs = new ArrayList<>();
+                    }
                     runnableJobs.add(job);
                 } else if (isReadyToBeCancelledLocked(job)) {
                     stopJobOnServiceContextLocked(job);
@@ -712,7 +774,7 @@
                     idleCount >= MIN_IDLE_COUNT ||
                     connectivityCount >= MIN_CONNECTIVITY_COUNT ||
                     chargingCount >= MIN_CHARGING_COUNT ||
-                    runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
+                    (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
                 if (DEBUG) {
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
                 }
@@ -908,7 +970,7 @@
 
             long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJobsForUid(uid);
+                JobSchedulerService.this.cancelJobsForUid(uid, true);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 41aea04..2ac0ba6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -459,7 +459,8 @@
         updateScreenOn();
 
         try {
-            mActivityManager.registerUidObserver(mUidObserver);
+            mActivityManager.registerUidObserver(mUidObserver,
+                    ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE);
             mNetworkManager.registerObserver(mAlertObserver);
         } catch (RemoteException e) {
             // ignored; both services live in system_server
@@ -541,6 +542,12 @@
                 removeUidStateLocked(uid);
             }
         }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+        }
     };
 
     final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
index b0296a0..c174a92 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -61,7 +61,7 @@
         b2.putBoolean("b2", true);
 
         SyncOperation op1 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b1,
@@ -73,7 +73,7 @@
 
         // Same as op1 but different time infos
         SyncOperation op2 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b1,
@@ -85,7 +85,7 @@
 
         // Same as op1 but different authority
         SyncOperation op3 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority2",
                 b1,
@@ -97,7 +97,7 @@
 
         // Same as op1 but different account
         SyncOperation op4 = new SyncOperation(account2, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b1,
@@ -109,7 +109,7 @@
 
         // Same as op1 but different bundle
         SyncOperation op5 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b2,
@@ -131,21 +131,21 @@
         long soonFlex = 50;
         long after = 1500;
         long afterFlex = 100;
-        SyncOperation op1 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+        SyncOperation op1 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
                 "authority1", mEmpty, soon, soonFlex, mUnimportantLong, mUnimportantLong, true);
 
         // Interval disjoint from and after op1.
-        SyncOperation op2 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+        SyncOperation op2 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
                 "authority1", mEmpty, after, afterFlex, mUnimportantLong, mUnimportantLong, true);
 
         // Interval equivalent to op1, but expedited.
         Bundle b2 = new Bundle();
         b2.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
-        SyncOperation op3 = new SyncOperation(mDummy, 0, 0, 0,
+        SyncOperation op3 = new SyncOperation(mDummy, 0, 0, "foo", 0, 0,
                 "authority1", b2, -1, soonFlex, mUnimportantLong, mUnimportantLong, true);
 
         // Interval overlaps but not equivalent to op1.
-        SyncOperation op4 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+        SyncOperation op4 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
                 "authority1", mEmpty, soon + 100, soonFlex + 100, mUnimportantLong, mUnimportantLong, true);
 
         assertTrue(op1.compareTo(op2) == -1);
@@ -165,7 +165,8 @@
 
         Bundle withExpedited = new Bundle();
         withExpedited.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
-        SyncOperation op = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_USER_START,
+        SyncOperation op = new SyncOperation(mDummy, 0, 0, "foo", 0,
+                SyncOperation.REASON_USER_START,
                 mAuthority, withExpedited, fiveSecondsFromNow, twoSecondsFlex,
                 eightSeconds /* backoff */, fourSeconds /* delayUntil */, true);
         // Create another sync op to be rerun in 5 minutes.
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
index ae1967e4..b22eb53 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
@@ -41,8 +41,6 @@
 import java.io.FileOutputStream;
 import java.util.List;
 
-import com.android.server.content.SyncStorageEngine.EndPoint;
-
 public class SyncStorageEngineTest extends AndroidTestCase {
 
     protected Account account1;
@@ -96,7 +94,7 @@
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
                 new TestContext(mockResolver, getContext()));
         long time0 = 1000;
-        SyncOperation op = new SyncOperation(account, 0,
+        SyncOperation op = new SyncOperation(account, 0, 0, "foo",
                 SyncOperation.REASON_PERIODIC,
                 SyncStorageEngine.SOURCE_LOCAL,
                 authority,
@@ -112,7 +110,7 @@
     @MediumTest
     public void testAppendPending() throws Exception {
         SyncOperation sop = new SyncOperation(account1,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_PERIODIC,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
                 0 /* runtime */, 0 /* flex */, 0 /* backoff */, 0 /* delayuntil */,
@@ -140,19 +138,19 @@
      */
     public void testWritePendingOperationsLocked() throws Exception {
         SyncOperation sop = new SyncOperation(account1,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_IS_SYNCABLE,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
                 1000L /* runtime */, 57L /* flex */, 0 /* backoff */, 0 /* delayuntil */,
                 true /* expedited */);
         SyncOperation sop1 = new SyncOperation(account2,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_PERIODIC,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, defaultBundle,
                 0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
                 false /* expedited */);
         SyncOperation deleted = new SyncOperation(account2,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_SYNC_AUTO,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
                 0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
@@ -456,14 +454,14 @@
 
         // Test service component read
         List<PeriodicSync> syncs = engine.getPeriodicSyncs(
-                new SyncStorageEngine.EndPoint(syncService1, 0));
+                new SyncStorageEngine.EndPoint(syncService1, 0, 0));
         assertEquals(1, syncs.size());
         assertEquals(true, engine.getIsTargetServiceActive(syncService1, 0));
     }
 
     @SmallTest
     public void testComponentSettings() throws Exception {
-        EndPoint target1 = new EndPoint(syncService1, 0);
+        EndPoint target1 = new EndPoint(syncService1, 0, 0);
         engine.updateOrAddPeriodicSync(target1, dayPoll, dayFuzz, Bundle.EMPTY);
         
         engine.setIsTargetServiceActive(target1.service, 0, true);