Merge "Implement lenient background check option." into nyc-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index c9b519a..984d925 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -48559,6 +48559,7 @@
     method public abstract void onConfigurationChanged(android.content.res.Configuration);
     method public abstract android.view.inputmethod.InputConnection onCreateInputConnection(android.view.inputmethod.EditorInfo);
     method public abstract void onDetachedFromWindow();
+    method public abstract boolean onDragEvent(android.view.DragEvent);
     method public abstract void onDraw(android.graphics.Canvas);
     method public abstract void onDrawVerticalScrollBar(android.graphics.Canvas, android.graphics.drawable.Drawable, int, int, int, int);
     method public abstract void onFinishTemporaryDetach();
diff --git a/core/java/android/text/style/ReplacementSpan.java b/core/java/android/text/style/ReplacementSpan.java
index 26c725f..07190b2 100644
--- a/core/java/android/text/style/ReplacementSpan.java
+++ b/core/java/android/text/style/ReplacementSpan.java
@@ -16,18 +16,49 @@
 
 package android.text.style;
 
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Paint;
 import android.graphics.Canvas;
 import android.text.TextPaint;
 
 public abstract class ReplacementSpan extends MetricAffectingSpan {
 
-    public abstract int getSize(Paint paint, CharSequence text,
-                         int start, int end,
-                         Paint.FontMetricsInt fm);
-    public abstract void draw(Canvas canvas, CharSequence text,
-                     int start, int end, float x,
-                     int top, int y, int bottom, Paint paint);
+    /**
+     * Returns the width of the span. Extending classes can set the height of the span by updating
+     * attributes of {@link android.graphics.Paint.FontMetricsInt}. If the span covers the whole
+     * text, and the height is not set,
+     * {@link #draw(Canvas, CharSequence, int, int, float, int, int, int, Paint)} will not be
+     * called for the span.
+     *
+     * @param paint Paint instance.
+     * @param text Current text.
+     * @param start Start character index for span.
+     * @param end End character index for span.
+     * @param fm Font metrics, can be null.
+     * @return Width of the span.
+     */
+    public abstract int getSize(@NonNull Paint paint, CharSequence text,
+                        @IntRange(from = 0) int start, @IntRange(from = 0) int end,
+                        @Nullable Paint.FontMetricsInt fm);
+
+    /**
+     * Draws the span into the canvas.
+     *
+     * @param canvas Canvas into which the span should be rendered.
+     * @param text Current text.
+     * @param start Start character index for span.
+     * @param end End character index for span.
+     * @param x Edge of the replacement closest to the leading margin.
+     * @param top Top of the line.
+     * @param y Baseline.
+     * @param bottom Bottom of the line.
+     * @param paint Paint instance.
+     */
+    public abstract void draw(@NonNull Canvas canvas, CharSequence text,
+                              @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x,
+                              int top, int y, int bottom, @NonNull Paint paint);
 
     /**
      * This method does nothing, since ReplacementSpans are measured
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 0f58ba3..647d4dc 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -39,6 +39,7 @@
 import android.security.KeyChain;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -2528,6 +2529,11 @@
     }
 
     @Override
+    public boolean onDragEvent(DragEvent event) {
+        return mProvider.getViewDelegate().onDragEvent(event);
+    }
+
+    @Override
     protected void onVisibilityChanged(View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
         // This method may be called in the constructor chain, before the WebView provider is
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 3ce034c..94d231c 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -30,6 +30,7 @@
 import android.os.Bundle;
 import android.os.Message;
 import android.print.PrintDocumentAdapter;
+import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -334,6 +335,8 @@
 
         public InputConnection onCreateInputConnection(EditorInfo outAttrs);
 
+        public boolean onDragEvent(DragEvent event);
+
         public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event);
 
         public boolean onKeyDown(int keyCode, KeyEvent event);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
index 34dfd6c..58c3b7e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -64,6 +64,14 @@
     }
 
     @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Since we control our own bottom, be whatever size we want.
+        // Otherwise the QSPanel ends up with 0 height when the window is only the
+        // size of the status bar.
+        super.onMeasure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         updateBottom();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4be6833..388c8b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2162,8 +2162,13 @@
                 mIntent = new Intent().setComponent(mComponentName);
                 mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                         com.android.internal.R.string.accessibility_binding_label);
-                mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
-                        mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
+                final long idendtity = Binder.clearCallingIdentity();
+                try {
+                    mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
+                            mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
+                } finally {
+                    Binder.restoreCallingIdentity(idendtity);
+                }
             }
             setDynamicallyConfigurableProperties(accessibilityServiceInfo);
         }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 57cede8..25b54ac 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -78,13 +78,15 @@
  * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
  * @hide
  */
-public class JobSchedulerService extends com.android.server.SystemService
+public final class JobSchedulerService extends com.android.server.SystemService
         implements StateChangedListener, JobCompletedListener {
     public static final boolean DEBUG = false;
     /** The number of concurrent jobs we run at one time. */
     private static final int MAX_JOB_CONTEXTS_COUNT
             = ActivityManager.isLowRamDeviceStatic() ? 3 : 6;
     static final String TAG = "JobSchedulerService";
+    /** Global local for all job scheduler state. */
+    final Object mLock = new Object();
     /** Master list of jobs. */
     final JobStore mJobs;
 
@@ -207,6 +209,10 @@
         }
     };
 
+    public Object getLock() {
+        return mLock;
+    }
+
     @Override
     public void onStartUser(int userHandle) {
         mStartedUsers.add(userHandle);
@@ -231,7 +237,7 @@
     }
 
     public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId) {
-        JobStatus jobStatus = new JobStatus(job, uId, packageName, userId);
+        JobStatus jobStatus = new JobStatus(getLock(), job, uId, packageName, userId);
         try {
             if (ActivityManagerNative.getDefault().getAppStartMode(uId,
                     job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
@@ -243,7 +249,7 @@
         }
         if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
         JobStatus toCancel;
-        synchronized (mJobs) {
+        synchronized (mLock) {
             toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
         }
         startTrackingJob(jobStatus, toCancel);
@@ -256,7 +262,7 @@
 
     public List<JobInfo> getPendingJobs(int uid) {
         ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
-        synchronized (mJobs) {
+        synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobs();
             for (int i=0; i<jobs.size(); i++) {
                 JobStatus job = jobs.valueAt(i);
@@ -270,7 +276,7 @@
 
     void cancelJobsForUser(int userHandle) {
         List<JobStatus> jobsForUser;
-        synchronized (mJobs) {
+        synchronized (mLock) {
             jobsForUser = mJobs.getJobsByUser(userHandle);
         }
         for (int i=0; i<jobsForUser.size(); i++) {
@@ -289,7 +295,7 @@
      */
     public void cancelJobsForUid(int uid, boolean forceAll) {
         List<JobStatus> jobsForUid;
-        synchronized (mJobs) {
+        synchronized (mLock) {
             jobsForUid = mJobs.getJobsByUid(uid);
         }
         for (int i=0; i<jobsForUid.size(); i++) {
@@ -317,7 +323,7 @@
      */
     public void cancelJob(int uid, int jobId) {
         JobStatus toCancel;
-        synchronized (mJobs) {
+        synchronized (mLock) {
             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
         }
         if (toCancel != null) {
@@ -328,7 +334,7 @@
     private void cancelJobImpl(JobStatus cancelled) {
         if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
         stopTrackingJob(cancelled, true /* writeBack */);
-        synchronized (mJobs) {
+        synchronized (mLock) {
             // Remove from pending queue.
             mPendingJobs.remove(cancelled);
             // Cancel if running.
@@ -340,7 +346,7 @@
     void updateIdleMode(boolean enabled) {
         boolean changed = false;
         boolean rocking;
-        synchronized (mJobs) {
+        synchronized (mLock) {
             if (mDeviceIdleMode != enabled) {
                 changed = true;
             }
@@ -352,7 +358,7 @@
                     mControllers.get(i).deviceIdleModeChanged(enabled);
                 }
             }
-            synchronized (mJobs) {
+            synchronized (mLock) {
                 mDeviceIdleMode = enabled;
                 if (enabled) {
                     // When becoming idle, make sure no jobs are actively running.
@@ -451,7 +457,7 @@
                 // ignored; both services live in system_server
             }
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
-            synchronized (mJobs) {
+            synchronized (mLock) {
                 // Let's go!
                 mReadyToRock = true;
                 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
@@ -487,7 +493,7 @@
     private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
         boolean update;
         boolean rocking;
-        synchronized (mJobs) {
+        synchronized (mLock) {
             update = mJobs.add(jobStatus);
             rocking = mReadyToRock;
         }
@@ -509,7 +515,7 @@
     private boolean stopTrackingJob(JobStatus jobStatus, boolean writeBack) {
         boolean removed;
         boolean rocking;
-        synchronized (mJobs) {
+        synchronized (mLock) {
             // Remove from store as well as controllers.
             removed = mJobs.remove(jobStatus, writeBack);
             rocking = mReadyToRock;
@@ -693,14 +699,14 @@
 
         @Override
         public void handleMessage(Message message) {
-            synchronized (mJobs) {
+            synchronized (mLock) {
                 if (!mReadyToRock) {
                     return;
                 }
             }
             switch (message.what) {
                 case MSG_JOB_EXPIRED:
-                    synchronized (mJobs) {
+                    synchronized (mLock) {
                         JobStatus runNow = (JobStatus) message.obj;
                         // runNow can be null, which is a controller's way of indicating that its
                         // state is such that all ready jobs should be run immediately.
@@ -712,7 +718,7 @@
                     }
                     break;
                 case MSG_CHECK_JOB:
-                    synchronized (mJobs) {
+                    synchronized (mLock) {
                         if (mReportedActive) {
                             // if jobs are currently being run, queue all ready jobs for execution.
                             queueReadyJobsForExecutionLockedH();
@@ -723,7 +729,7 @@
                     }
                     break;
                 case MSG_CHECK_JOB_GREEDY:
-                    synchronized (mJobs) {
+                    synchronized (mLock) {
                         queueReadyJobsForExecutionLockedH();
                     }
                     break;
@@ -879,7 +885,7 @@
          * here is where we decide whether to actually execute it.
          */
         private void maybeRunPendingJobsH() {
-            synchronized (mJobs) {
+            synchronized (mLock) {
                 if (mDeviceIdleMode) {
                     // If device is idle, we will not schedule jobs to run.
                     return;
@@ -1185,7 +1191,7 @@
 
     void dumpInternal(PrintWriter pw) {
         final long now = SystemClock.elapsedRealtime();
-        synchronized (mJobs) {
+        synchronized (mLock) {
             pw.print("Started users: ");
             for (int i=0; i<mStartedUsers.size(); i++) {
                 pw.print("u" + mStartedUsers.get(i) + " ");
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index b249739..48549ce 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -103,6 +103,7 @@
     private final JobCompletedListener mCompletedListener;
     /** Used for service binding, etc. */
     private final Context mContext;
+    private final Object mLock;
     private final IBatteryStats mBatteryStats;
     private PowerManager.WakeLock mWakeLock;
 
@@ -124,7 +125,6 @@
     private int mPreferredUid;
     IJobService service;
 
-    private final Object mLock = new Object();
     /**
      * Whether this context is free. This is set to false at the start of execution, and reset to
      * true when execution is complete.
@@ -137,13 +137,14 @@
     private long mTimeoutElapsed;
 
     JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, Looper looper) {
-        this(service.getContext(), batteryStats, service, looper);
+        this(service.getContext(), service.getLock(), batteryStats, service, looper);
     }
 
     @VisibleForTesting
-    JobServiceContext(Context context, IBatteryStats batteryStats,
+    JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
                       JobCompletedListener completedListener, Looper looper) {
         mContext = context;
+        mLock = lock;
         mBatteryStats = batteryStats;
         mCallbackHandler = new JobServiceHandler(looper);
         mCompletedListener = completedListener;
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 3565fc1..fa4190b 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -70,6 +70,7 @@
     /** Threshold to adjust how often we want to write to the db. */
     private static final int MAX_OPS_BEFORE_WRITE = 1;
     final ArraySet<JobStatus> mJobSet;
+    final Object mLock;
     final Context mContext;
 
     private int mDirtyOperations;
@@ -85,7 +86,7 @@
         synchronized (sSingletonLock) {
             if (sSingleton == null) {
                 sSingleton = new JobStore(jobManagerService.getContext(),
-                        Environment.getDataDirectory());
+                        jobManagerService.getLock(), Environment.getDataDirectory());
             }
             return sSingleton;
         }
@@ -96,7 +97,7 @@
      */
     @VisibleForTesting
     public static JobStore initAndGetForTesting(Context context, File dataDir) {
-        JobStore jobStoreUnderTest = new JobStore(context, dataDir);
+        JobStore jobStoreUnderTest = new JobStore(context, new Object(), dataDir);
         jobStoreUnderTest.clear();
         return jobStoreUnderTest;
     }
@@ -104,7 +105,8 @@
     /**
      * Construct the instance of the job store. This results in a blocking read from disk.
      */
-    private JobStore(Context context, File dataDir) {
+    private JobStore(Context context, Object lock, File dataDir) {
+        mLock = lock;
         mContext = context;
         mDirtyOperations = 0;
 
@@ -266,14 +268,14 @@
 
     /**
      * Runnable that writes {@link #mJobSet} out to xml.
-     * NOTE: This Runnable locks on JobStore.this
+     * NOTE: This Runnable locks on mLock
      */
     private class WriteJobsMapToDiskRunnable implements Runnable {
         @Override
         public void run() {
             final long startElapsed = SystemClock.elapsedRealtime();
             List<JobStatus> mStoreCopy = new ArrayList<JobStatus>();
-            synchronized (JobStore.this) {
+            synchronized (mLock) {
                 // Copy over the jobs so we can release the lock before writing.
                 for (int i=0; i<mJobSet.size(); i++) {
                     JobStatus jobStatus = mJobSet.valueAt(i);
@@ -454,7 +456,7 @@
             try {
                 List<JobStatus> jobs;
                 FileInputStream fis = mJobsFile.openRead();
-                synchronized (JobStore.this) {
+                synchronized (mLock) {
                     jobs = readJobMapImpl(fis);
                     if (jobs != null) {
                         for (int i=0; i<jobs.size(); i++) {
@@ -678,8 +680,8 @@
             parser.nextTag(); // Consume </extras>
 
             JobStatus js = new JobStatus(
-                    jobBuilder.build(), uid, sourcePackageName, sourceUserId, elapsedRuntimes.first,
-                    elapsedRuntimes.second);
+                    mLock, jobBuilder.build(), uid, sourcePackageName, sourceUserId,
+                    elapsedRuntimes.first, elapsedRuntimes.second);
             return js;
         }
 
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 5f3da75..f7f34ae 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -48,14 +48,16 @@
     public static AppIdleController get(JobSchedulerService service) {
         synchronized (sCreationLock) {
             if (sController == null) {
-                sController = new AppIdleController(service, service.getContext());
+                sController = new AppIdleController(service, service.getContext(),
+                        service.getLock());
             }
             return sController;
         }
     }
 
-    private AppIdleController(StateChangedListener stateChangedListener, Context context) {
-        super(stateChangedListener, context);
+    private AppIdleController(StateChangedListener stateChangedListener, Context context,
+            Object lock) {
+        super(stateChangedListener, context, lock);
         mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
         mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn();
         mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
@@ -63,7 +65,7 @@
 
     @Override
     public void maybeStartTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             mTrackedTasks.add(jobStatus);
             String packageName = jobStatus.getSourcePackageName();
             final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
@@ -78,7 +80,7 @@
 
     @Override
     public void maybeStopTrackingJob(JobStatus jobStatus, boolean forUpdate) {
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             mTrackedTasks.remove(jobStatus);
         }
     }
@@ -87,7 +89,7 @@
     public void dumpControllerState(PrintWriter pw) {
         pw.println("AppIdle");
         pw.println("Parole On: " + mAppIdleParoleOn);
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             for (JobStatus task : mTrackedTasks) {
                 pw.print(task.getSourcePackageName());
                 pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get());
@@ -100,7 +102,7 @@
     void setAppIdleParoleOn(boolean isAppIdleParoleOn) {
         // Flag if any app's idle state has changed
         boolean changed = false;
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             if (mAppIdleParoleOn == isAppIdleParoleOn) {
                 return;
             }
@@ -128,7 +130,7 @@
         @Override
         public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
             boolean changed = false;
-            synchronized (mTrackedTasks) {
+            synchronized (mLock) {
                 if (mAppIdleParoleOn) {
                     return;
                 }
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index b322a3e..b101c82 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -53,7 +53,7 @@
         synchronized (sCreationLock) {
             if (sController == null) {
                 sController = new BatteryController(taskManagerService,
-                        taskManagerService.getContext());
+                        taskManagerService.getContext(), taskManagerService.getLock());
             }
         }
         return sController;
@@ -67,11 +67,12 @@
     @VisibleForTesting
     public static BatteryController getForTesting(StateChangedListener stateChangedListener,
                                            Context context) {
-        return new BatteryController(stateChangedListener, context);
+        return new BatteryController(stateChangedListener, context, new Object());
     }
 
-    private BatteryController(StateChangedListener stateChangedListener, Context context) {
-        super(stateChangedListener, context);
+    private BatteryController(StateChangedListener stateChangedListener, Context context,
+            Object lock) {
+        super(stateChangedListener, context, lock);
         mChargeTracker = new ChargingTracker();
         mChargeTracker.startTracking();
     }
@@ -80,7 +81,7 @@
     public void maybeStartTrackingJob(JobStatus taskStatus, JobStatus lastJob) {
         final boolean isOnStablePower = mChargeTracker.isOnStablePower();
         if (taskStatus.hasChargingConstraint()) {
-            synchronized (mTrackedTasks) {
+            synchronized (mLock) {
                 mTrackedTasks.add(taskStatus);
                 taskStatus.chargingConstraintSatisfied.set(isOnStablePower);
             }
@@ -90,7 +91,7 @@
     @Override
     public void maybeStopTrackingJob(JobStatus taskStatus, boolean forUpdate) {
         if (taskStatus.hasChargingConstraint()) {
-            synchronized (mTrackedTasks) {
+            synchronized (mLock) {
                 mTrackedTasks.remove(taskStatus);
             }
         }
@@ -102,7 +103,7 @@
             Slog.d(TAG, "maybeReportNewChargingState: " + stablePower);
         }
         boolean reportChange = false;
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             for (JobStatus ts : mTrackedTasks) {
                 boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower);
                 if (previous != stablePower) {
@@ -200,7 +201,7 @@
     public void dumpControllerState(PrintWriter pw) {
         pw.println("Batt.");
         pw.println("Stable power: " + mChargeTracker.isOnStablePower());
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             Iterator<JobStatus> it = mTrackedTasks.iterator();
             if (it.hasNext()) {
                 pw.print(String.valueOf(it.next().hashCode()));
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index b84658a..29b54c2 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -58,14 +58,15 @@
     public static ConnectivityController get(JobSchedulerService jms) {
         synchronized (sCreationLock) {
             if (mSingleton == null) {
-                mSingleton = new ConnectivityController(jms, jms.getContext());
+                mSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
             }
             return mSingleton;
         }
     }
 
-    private ConnectivityController(StateChangedListener stateChangedListener, Context context) {
-        super(stateChangedListener, context);
+    private ConnectivityController(StateChangedListener stateChangedListener, Context context,
+            Object lock) {
+        super(stateChangedListener, context, lock);
         // Register connectivity changed BR.
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
@@ -84,7 +85,7 @@
     @Override
     public void maybeStartTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
         if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
-            synchronized (mTrackedJobs) {
+            synchronized (mLock) {
                 jobStatus.connectivityConstraintSatisfied.set(mNetworkConnected);
                 jobStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered);
                 mTrackedJobs.add(jobStatus);
@@ -95,7 +96,7 @@
     @Override
     public void maybeStopTrackingJob(JobStatus jobStatus, boolean forUpdate) {
         if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
-            synchronized (mTrackedJobs) {
+            synchronized (mLock) {
                 mTrackedJobs.remove(jobStatus);
             }
         }
@@ -105,7 +106,7 @@
      * @param userId Id of the user for whom we are updating the connectivity state.
      */
     private void updateTrackedJobs(int userId) {
-        synchronized (mTrackedJobs) {
+        synchronized (mLock) {
             boolean changed = false;
             for (JobStatus js : mTrackedJobs) {
                 if (js.getUserId() != userId) {
@@ -128,7 +129,7 @@
      * We know the network has just come up. We want to run any jobs that are ready.
      */
     public synchronized void onNetworkActive() {
-        synchronized (mTrackedJobs) {
+        synchronized (mLock) {
             for (JobStatus js : mTrackedJobs) {
                 if (js.isReady()) {
                     if (DEBUG) {
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index 212cc94..af994f0 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -57,7 +57,7 @@
         synchronized (sCreationLock) {
             if (sController == null) {
                 sController = new ContentObserverController(taskManagerService,
-                        taskManagerService.getContext());
+                        taskManagerService.getContext(), taskManagerService.getLock());
             }
         }
         return sController;
@@ -66,17 +66,18 @@
     @VisibleForTesting
     public static ContentObserverController getForTesting(StateChangedListener stateChangedListener,
                                            Context context) {
-        return new ContentObserverController(stateChangedListener, context);
+        return new ContentObserverController(stateChangedListener, context, new Object());
     }
 
-    private ContentObserverController(StateChangedListener stateChangedListener, Context context) {
-        super(stateChangedListener, context);
+    private ContentObserverController(StateChangedListener stateChangedListener, Context context,
+                Object lock) {
+        super(stateChangedListener, context, lock);
     }
 
     @Override
     public void maybeStartTrackingJob(JobStatus taskStatus, JobStatus lastJob) {
         if (taskStatus.hasContentTriggerConstraint()) {
-            synchronized (mTrackedTasks) {
+            synchronized (mLock) {
                 if (taskStatus.contentObserverJobInstance == null) {
                     taskStatus.contentObserverJobInstance = new JobInstance(taskStatus);
                 }
@@ -128,7 +129,7 @@
     @Override
     public void prepareForExecution(JobStatus taskStatus) {
         if (taskStatus.hasContentTriggerConstraint()) {
-            synchronized (mTrackedTasks) {
+            synchronized (mLock) {
                 if (taskStatus.contentObserverJobInstance != null) {
                     taskStatus.changedUris = taskStatus.contentObserverJobInstance.mChangedUris;
                     taskStatus.changedAuthorities
@@ -143,7 +144,7 @@
     @Override
     public void maybeStopTrackingJob(JobStatus taskStatus, boolean forUpdate) {
         if (taskStatus.hasContentTriggerConstraint()) {
-            synchronized (mTrackedTasks) {
+            synchronized (mLock) {
                 if (!forUpdate) {
                     // We won't do this reset if being called for an update, because
                     // we know it will be immediately followed by maybeStartTrackingJob...
@@ -162,7 +163,7 @@
     public void rescheduleForFailure(JobStatus newJob, JobStatus failureToReschedule) {
         if (failureToReschedule.hasContentTriggerConstraint()
                 && newJob.hasContentTriggerConstraint()) {
-            synchronized (mTrackedTasks) {
+            synchronized (mLock) {
                 // Our job has failed, and we are scheduling a new job for it.
                 // Copy the last reported content changes in to the new job, so when
                 // we schedule the new one we will pick them up and report them again.
@@ -184,7 +185,7 @@
         @Override
         public void onChange(boolean selfChange, Uri uri) {
             boolean reportChange = false;
-            synchronized (mTrackedTasks) {
+            synchronized (mLock) {
                 final int N = mJobs.size();
                 for (int i=0; i<N; i++) {
                     JobInstance inst = mJobs.get(i);
@@ -256,7 +257,7 @@
     @Override
     public void dumpControllerState(PrintWriter pw) {
         pw.println("Content.");
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             Iterator<JobStatus> it = mTrackedTasks.iterator();
             if (it.hasNext()) {
                 pw.print(String.valueOf(it.next().hashCode()));
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 9f4cdef..6b85f22 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -51,14 +51,15 @@
     public static IdleController get(JobSchedulerService service) {
         synchronized (sCreationLock) {
             if (sController == null) {
-                sController = new IdleController(service, service.getContext());
+                sController = new IdleController(service, service.getContext(), service.getLock());
             }
             return sController;
         }
     }
 
-    private IdleController(StateChangedListener stateChangedListener, Context context) {
-        super(stateChangedListener, context);
+    private IdleController(StateChangedListener stateChangedListener, Context context,
+                Object lock) {
+        super(stateChangedListener, context, lock);
         initIdleStateTracking();
     }
 
@@ -68,7 +69,7 @@
     @Override
     public void maybeStartTrackingJob(JobStatus taskStatus, JobStatus lastJob) {
         if (taskStatus.hasIdleConstraint()) {
-            synchronized (mTrackedTasks) {
+            synchronized (mLock) {
                 mTrackedTasks.add(taskStatus);
                 taskStatus.idleConstraintSatisfied.set(mIdleTracker.isIdle());
             }
@@ -77,7 +78,7 @@
 
     @Override
     public void maybeStopTrackingJob(JobStatus taskStatus, boolean forUpdate) {
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             mTrackedTasks.remove(taskStatus);
         }
     }
@@ -86,7 +87,7 @@
      * Interaction with the task manager service
      */
     void reportNewIdleState(boolean isIdle) {
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             for (JobStatus task : mTrackedTasks) {
                 task.idleConstraintSatisfied.set(isIdle);
             }
@@ -194,7 +195,7 @@
 
     @Override
     public void dumpControllerState(PrintWriter pw) {
-        synchronized (mTrackedTasks) {
+        synchronized (mLock) {
             pw.print("Idle: ");
             pw.println(mIdleTracker.isIdle() ? "true" : "false");
             pw.println(mTrackedTasks.size());
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index c4d564c..3cf1054 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -46,6 +46,11 @@
     public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
     public static final long NO_EARLIEST_RUNTIME = 0L;
 
+    /**
+     * Service global lock.  NOTE: this should be removed, having this class just rely on
+     * its callers doing the appropriate locking.
+     */
+    final Object lock;
     final JobInfo job;
     /** Uid of the package requesting this job. */
     final int callingUid;
@@ -94,8 +99,9 @@
         return callingUid;
     }
 
-    private JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId,
-                      int numFailures) {
+    private JobStatus(Object lock, JobInfo job, int callingUid, String sourcePackageName,
+            int sourceUserId, int numFailures) {
+        this.lock = lock;
         this.job = job;
         this.callingUid = callingUid;
         this.name = job.getService().flattenToShortString();
@@ -124,8 +130,9 @@
 
     /** Copy constructor. */
     public JobStatus(JobStatus jobStatus) {
-        this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getSourcePackageName(),
-                jobStatus.getSourceUserId(), jobStatus.getNumFailures());
+        this(jobStatus.lock, jobStatus.getJob(), jobStatus.getUid(),
+                jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
+                jobStatus.getNumFailures());
         this.earliestRunTimeElapsedMillis = jobStatus.getEarliestRunTime();
         this.latestRunTimeElapsedMillis = jobStatus.getLatestRunTimeElapsed();
     }
@@ -138,8 +145,9 @@
      * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
      *                     calling userId.
      */
-    public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId) {
-        this(job, callingUid, sourcePackageName, sourceUserId, 0);
+    public JobStatus(Object lock, JobInfo job, int callingUid, String sourcePackageName,
+            int sourceUserId) {
+        this(lock, job, callingUid, sourcePackageName, sourceUserId, 0);
 
         final long elapsedNow = SystemClock.elapsedRealtime();
 
@@ -161,9 +169,9 @@
      * wallclock runtime rather than resetting it on every boot.
      * We consider a freshly loaded job to no longer be in back-off.
      */
-    public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId,
-                     long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
-        this(job, callingUid, sourcePackageName, sourceUserId, 0);
+    public JobStatus(Object lock, JobInfo job, int callingUid, String sourcePackageName,
+            int sourceUserId, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
+        this(lock, job, callingUid, sourcePackageName, sourceUserId, 0);
 
         this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
         this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
@@ -172,7 +180,8 @@
     /** Create a new job to be rescheduled with the provided parameters. */
     public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
                       long newLatestRuntimeElapsedMillis, int backoffAttempt) {
-        this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(),
+        this(rescheduling.lock, rescheduling.job, rescheduling.getUid(),
+                rescheduling.getSourcePackageName(),
                 rescheduling.getSourceUserId(), backoffAttempt);
 
         earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis;
@@ -275,27 +284,31 @@
      * @return Whether or not this job is ready to run, based on its requirements. This is true if
      * the constraints are satisfied <strong>or</strong> the deadline on the job has expired.
      */
-    public synchronized boolean isReady() {
-        // Deadline constraint trumps other constraints (except for periodic jobs where deadline
-        // (is an implementation detail. A periodic job should only run if it's constraints are
-        // satisfied).
-        // AppNotIdle implicit constraint trumps all!
-        return (isConstraintsSatisfied()
+    public boolean isReady() {
+        synchronized (lock) {
+            // Deadline constraint trumps other constraints (except for periodic jobs where deadline
+            // (is an implementation detail. A periodic job should only run if it's constraints are
+            // satisfied).
+            // AppNotIdle implicit constraint trumps all!
+            return (isConstraintsSatisfied()
                     || (!job.isPeriodic()
-                            && hasDeadlineConstraint() && deadlineConstraintSatisfied.get()))
-                && appNotIdleConstraintSatisfied.get();
+                    && hasDeadlineConstraint() && deadlineConstraintSatisfied.get()))
+                    && appNotIdleConstraintSatisfied.get();
+        }
     }
 
     /**
      * @return Whether the constraints set on this job are satisfied.
      */
-    public synchronized boolean isConstraintsSatisfied() {
-        return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
-                && (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get())
-                && (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get())
-                && (!hasUnmeteredConstraint() || unmeteredConstraintSatisfied.get())
-                && (!hasIdleConstraint() || idleConstraintSatisfied.get())
-                && (!hasContentTriggerConstraint() || contentTriggerConstraintSatisfied.get());
+    public boolean isConstraintsSatisfied() {
+        synchronized (lock) {
+            return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
+                    && (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get())
+                    && (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get())
+                    && (!hasUnmeteredConstraint() || unmeteredConstraintSatisfied.get())
+                    && (!hasIdleConstraint() || idleConstraintSatisfied.get())
+                    && (!hasContentTriggerConstraint() || contentTriggerConstraintSatisfied.get());
+        }
     }
 
     public boolean matches(int uid, int jobId) {
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index b619ea8..8b8611a 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -30,13 +30,16 @@
  */
 public abstract class StateController {
     protected static final boolean DEBUG = JobSchedulerService.DEBUG;
-    protected Context mContext;
-    protected StateChangedListener mStateChangedListener;
+    protected final Context mContext;
+    protected final Object mLock;
+    protected final StateChangedListener mStateChangedListener;
     protected boolean mDeviceIdleMode;
 
-    public StateController(StateChangedListener stateChangedListener, Context context) {
+    public StateController(StateChangedListener stateChangedListener, Context context,
+            Object lock) {
         mStateChangedListener = stateChangedListener;
         mContext = context;
+        mLock = lock;
     }
 
     public void deviceIdleModeChanged(boolean enabled) {
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index a68c3ad..bf17827 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -54,13 +54,14 @@
 
     public static synchronized TimeController get(JobSchedulerService jms) {
         if (mSingleton == null) {
-            mSingleton = new TimeController(jms, jms.getContext());
+            mSingleton = new TimeController(jms, jms.getContext(), jms.getLock());
         }
         return mSingleton;
     }
 
-    private TimeController(StateChangedListener stateChangedListener, Context context) {
-        super(stateChangedListener, context);
+    private TimeController(StateChangedListener stateChangedListener, Context context,
+                Object lock) {
+        super(stateChangedListener, context, lock);
 
         mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
         mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
@@ -71,27 +72,28 @@
      * list.
      */
     @Override
-    public synchronized void maybeStartTrackingJob(JobStatus job, JobStatus lastJob) {
-        if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
-            maybeStopTrackingJob(job, false);
-            boolean isInsert = false;
-            ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
-            while (it.hasPrevious()) {
-                JobStatus ts = it.previous();
-                if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) {
-                    // Insert
-                    isInsert = true;
-                    break;
+    public void maybeStartTrackingJob(JobStatus job, JobStatus lastJob) {
+        synchronized (mLock) {
+            if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
+                maybeStopTrackingJob(job, false);
+                boolean isInsert = false;
+                ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
+                while (it.hasPrevious()) {
+                    JobStatus ts = it.previous();
+                    if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) {
+                        // Insert
+                        isInsert = true;
+                        break;
+                    }
                 }
+                if (isInsert) {
+                    it.next();
+                }
+                it.add(job);
+                maybeUpdateAlarms(
+                        job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
+                        job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE);
             }
-            if(isInsert)
-            {
-                it.next();
-            }
-            it.add(job);
-            maybeUpdateAlarms(
-                    job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
-                    job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE);
         }
     }
 
@@ -101,10 +103,12 @@
      * Really an == comparison should be enough, but why play with fate? We'll do <=.
      */
     @Override
-    public synchronized void maybeStopTrackingJob(JobStatus job, boolean forUpdate) {
-        if (mTrackedJobs.remove(job)) {
-            checkExpiredDelaysAndResetAlarm();
-            checkExpiredDeadlinesAndResetAlarm();
+    public void maybeStopTrackingJob(JobStatus job, boolean forUpdate) {
+        synchronized (mLock) {
+            if (mTrackedJobs.remove(job)) {
+                checkExpiredDelaysAndResetAlarm();
+                checkExpiredDeadlinesAndResetAlarm();
+            }
         }
     }
 
@@ -131,63 +135,67 @@
      * Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler
      * if so, removing them from this list, and updating the alarm for the next expiry time.
      */
-    private synchronized void checkExpiredDeadlinesAndResetAlarm() {
-        long nextExpiryTime = Long.MAX_VALUE;
-        final long nowElapsedMillis = SystemClock.elapsedRealtime();
+    private void checkExpiredDeadlinesAndResetAlarm() {
+        synchronized (mLock) {
+            long nextExpiryTime = Long.MAX_VALUE;
+            final long nowElapsedMillis = SystemClock.elapsedRealtime();
 
-        Iterator<JobStatus> it = mTrackedJobs.iterator();
-        while (it.hasNext()) {
-            JobStatus job = it.next();
-            if (!job.hasDeadlineConstraint()) {
-                continue;
-            }
-            final long jobDeadline = job.getLatestRunTimeElapsed();
+            Iterator<JobStatus> it = mTrackedJobs.iterator();
+            while (it.hasNext()) {
+                JobStatus job = it.next();
+                if (!job.hasDeadlineConstraint()) {
+                    continue;
+                }
+                final long jobDeadline = job.getLatestRunTimeElapsed();
 
-            if (jobDeadline <= nowElapsedMillis) {
-                job.deadlineConstraintSatisfied.set(true);
-                mStateChangedListener.onRunJobNow(job);
-                it.remove();
-            } else {  // Sorted by expiry time, so take the next one and stop.
-                nextExpiryTime = jobDeadline;
-                break;
+                if (jobDeadline <= nowElapsedMillis) {
+                    job.deadlineConstraintSatisfied.set(true);
+                    mStateChangedListener.onRunJobNow(job);
+                    it.remove();
+                } else {  // Sorted by expiry time, so take the next one and stop.
+                    nextExpiryTime = jobDeadline;
+                    break;
+                }
             }
+            setDeadlineExpiredAlarm(nextExpiryTime);
         }
-        setDeadlineExpiredAlarm(nextExpiryTime);
     }
 
     /**
      * Handles alarm that notifies us that a job's delay has expired. Iterates through the list of
      * tracked jobs and marks them as ready as appropriate.
      */
-    private synchronized void checkExpiredDelaysAndResetAlarm() {
-        final long nowElapsedMillis = SystemClock.elapsedRealtime();
-        long nextDelayTime = Long.MAX_VALUE;
-        boolean ready = false;
-        Iterator<JobStatus> it = mTrackedJobs.iterator();
-        while (it.hasNext()) {
-            final JobStatus job = it.next();
-            if (!job.hasTimingDelayConstraint()) {
-                continue;
-            }
-            final long jobDelayTime = job.getEarliestRunTime();
-            if (jobDelayTime <= nowElapsedMillis) {
-                job.timeDelayConstraintSatisfied.set(true);
-                if (canStopTrackingJob(job)) {
-                    it.remove();
+    private void checkExpiredDelaysAndResetAlarm() {
+        synchronized (mLock) {
+            final long nowElapsedMillis = SystemClock.elapsedRealtime();
+            long nextDelayTime = Long.MAX_VALUE;
+            boolean ready = false;
+            Iterator<JobStatus> it = mTrackedJobs.iterator();
+            while (it.hasNext()) {
+                final JobStatus job = it.next();
+                if (!job.hasTimingDelayConstraint()) {
+                    continue;
                 }
-                if (job.isReady()) {
-                    ready = true;
-                }
-            } else {  // Keep going through list to get next delay time.
-                if (nextDelayTime > jobDelayTime) {
-                    nextDelayTime = jobDelayTime;
+                final long jobDelayTime = job.getEarliestRunTime();
+                if (jobDelayTime <= nowElapsedMillis) {
+                    job.timeDelayConstraintSatisfied.set(true);
+                    if (canStopTrackingJob(job)) {
+                        it.remove();
+                    }
+                    if (job.isReady()) {
+                        ready = true;
+                    }
+                } else {  // Keep going through list to get next delay time.
+                    if (nextDelayTime > jobDelayTime) {
+                        nextDelayTime = jobDelayTime;
+                    }
                 }
             }
+            if (ready) {
+                mStateChangedListener.onControllerStateChanged();
+            }
+            setDelayExpiredAlarm(nextDelayTime);
         }
-        if (ready) {
-            mStateChangedListener.onControllerStateChanged();
-        }
-        setDelayExpiredAlarm(nextDelayTime);
     }
 
     private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 14722bf..7378bde 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1087,19 +1087,17 @@
         }
 
         synchronized (mLock) {
-            WallpaperData wallpaper = null;
-            if (which == FLAG_SET_LOCK) {
-                wallpaper = mLockWallpaperMap.get(wallpaperUserId);
+            final SparseArray<WallpaperData> whichSet =
+                    (which == FLAG_SET_LOCK) ? mLockWallpaperMap : mWallpaperMap;
+            WallpaperData wallpaper = whichSet.get(wallpaperUserId);
+            if (wallpaper == null) {
+                // common case, this is the first lookup post-boot of the system or
+                // unified lock, so we bring up the saved state lazily now and recheck.
+                loadSettingsLocked(wallpaperUserId);
+                wallpaper = whichSet.get(wallpaperUserId);
                 if (wallpaper == null) {
-                    // If you ask for the lock wallpaper specifically and there isn't one,
-                    // we say so rather than returning the system wallpaper as fallback.
                     return null;
                 }
-            } else {
-                wallpaper = mWallpaperMap.get(wallpaperUserId);
-            }
-            if (wallpaper == null) {
-                return null;
             }
             try {
                 if (outParams != null) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index ba9e640..5cb7099 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -216,6 +216,9 @@
     private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
     private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
 
+    private int mLastClipRevealMaxTranslation;
+    private boolean mLastHadClipReveal;
+
     AppTransition(Context context, WindowManagerService service) {
         mContext = context;
         mService = service;
@@ -337,6 +340,9 @@
         if (!isRunning()) {
             mAppTransitionState = APP_STATE_IDLE;
             notifyAppTransitionPendingLocked();
+            mLastHadClipReveal = false;
+            mLastClipRevealMaxTranslation = 0;
+            mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;
             return true;
         }
         return false;
@@ -641,11 +647,28 @@
                 bitmap, new Rect(left, top, left + width, top + height));
     }
 
+    /**
+     * @return the duration of the last clip reveal animation
+     */
     long getLastClipRevealTransitionDuration() {
         return mLastClipRevealTransitionDuration;
     }
 
     /**
+     * @return the maximum distance the app surface is traveling of the last clip reveal animation
+     */
+    int getLastClipRevealMaxTranslation() {
+        return mLastClipRevealMaxTranslation;
+    }
+
+    /**
+     * @return true if in the last app transition had a clip reveal animation, false otherwise
+     */
+    boolean hadClipRevealAnimation() {
+        return mLastHadClipReveal;
+    }
+
+    /**
      * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
      * the start rect is outside of the target rect, and there is a lot of movement going on.
      *
@@ -748,7 +771,13 @@
             set.setZAdjustment(Animation.ZORDER_TOP);
             set.initialize(appWidth, appHeight, appWidth, appHeight);
             anim = set;
+            mLastHadClipReveal = true;
             mLastClipRevealTransitionDuration = duration;
+
+            // If the start rect was full inside the target rect (cutOff == false), we don't need
+            // to store the translation, because it's only used if cutOff == true.
+            mLastClipRevealMaxTranslation = cutOff
+                    ? Math.max(Math.abs(translationY), Math.abs(translationX)) : 0;
         } else {
             final long duration;
             switch (transit) {
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index d7c1b32..b6aa3f2 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -21,14 +21,12 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.ArraySet;
-import android.util.Log;
 import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.IDockedStackListener;
 import android.view.SurfaceControl;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
 
 import com.android.server.wm.DimLayer.DimLayerUser;
 
@@ -52,6 +50,30 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
 
+    /**
+     * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip
+     * revealing surface at the earliest.
+     */
+    private static final float CLIP_REVEAL_MEET_EARLIEST = 0.6f;
+
+    /**
+     * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip
+     * revealing surface at the latest.
+     */
+    private static final float CLIP_REVEAL_MEET_LAST = 1f;
+
+    /**
+     * If the app translates at least CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, we start
+     * meet somewhere between {@link #CLIP_REVEAL_MEET_LAST} and {@link #CLIP_REVEAL_MEET_EARLIEST}.
+     */
+    private static final float CLIP_REVEAL_MEET_FRACTION_MIN = 0.4f;
+
+    /**
+     * If the app translates equals or more than CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance,
+     * we meet at {@link #CLIP_REVEAL_MEET_EARLIEST}.
+     */
+    private static final float CLIP_REVEAL_MEET_FRACTION_MAX = 0.8f;
+
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final int mDividerWindowWidth;
@@ -74,6 +96,7 @@
     private float mAnimationTarget;
     private long mAnimationDuration;
     private final Interpolator mMinimizedDockInterpolator;
+    private float mMaximizeMeetFraction;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
@@ -342,6 +365,7 @@
             return false;
         }
 
+        final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked();
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
@@ -350,16 +374,15 @@
                     : DEFAULT_APP_TRANSITION_DURATION;
             mAnimationDuration = (long)
                     (transitionDuration * mService.getTransitionAnimationScaleLocked());
+            mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
             notifyDockedStackMinimizedChanged(mMinimizedDock,
-                    mAnimationDuration);
+                    (long) (mAnimationDuration * mMaximizeMeetFraction));
         }
         float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
         t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator)
                 .getInterpolation(t);
-        final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked();
         if (stack != null) {
-            final float amount = t * mAnimationTarget + (1 - t) * mAnimationStart;
-            if (stack.setAdjustedForMinimizedDock(amount)) {
+            if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) {
                 mService.mWindowPlacerLocked.performSurfacePlacement();
             }
         }
@@ -371,6 +394,54 @@
         }
     }
 
+    /**
+     * Gets the amount how much to minimize a stack depending on the interpolated fraction t.
+     */
+    private float getMinimizeAmount(TaskStack stack, float t) {
+        final float naturalAmount = t * mAnimationTarget + (1 - t) * mAnimationStart;
+        if (isAnimationMaximizing()) {
+            return adjustMaximizeAmount(stack, t, naturalAmount);
+        } else {
+            return naturalAmount;
+        }
+    }
+
+    /**
+     * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount
+     * during the transition such that the edge of the clip reveal rect is met earlier in the
+     * transition so we don't create a visible "hole", but only if both the clip reveal and the
+     * docked stack divider start from about the same portion on the screen.
+     */
+    private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) {
+        if (mMaximizeMeetFraction == 1f) {
+            return naturalAmount;
+        }
+        final int minimizeDistance = stack.getMinimizeDistance();
+        float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation()
+                / (float) minimizeDistance;
+        final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime;
+        final float t2 = Math.min(t / mMaximizeMeetFraction, 1);
+        return amountPrime * t2 + naturalAmount * (1 - t2);
+    }
+
+    /**
+     * Retrieves the animation fraction at which the docked stack has to meet the clip reveal
+     * edge. See {@link #adjustMaximizeAmount}.
+     */
+    private float getClipRevealMeetFraction(TaskStack stack) {
+        if (!isAnimationMaximizing() || stack == null ||
+                !mService.mAppTransition.hadClipRevealAnimation()) {
+            return 1f;
+        }
+        final int minimizeDistance = stack.getMinimizeDistance();
+        final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation())
+                / (float) minimizeDistance;
+        final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN)
+                / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN)));
+        return CLIP_REVEAL_MEET_EARLIEST
+                + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
+    }
+
     @Override
     public boolean isFullscreen() {
         return false;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index c9873a5..2293e4d 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -877,6 +877,26 @@
     }
 
     /**
+     * @return the distance in pixels how much the stack gets minimized from it's original size
+     */
+    int getMinimizeDistance() {
+        final int dockSide = getDockSide();
+        if (dockSide == DOCKED_INVALID) {
+            return 0;
+        }
+
+        if (dockSide == DOCKED_TOP) {
+            mService.getStableInsetsLocked(mTmpRect);
+            int topInset = mTmpRect.top;
+            return mBounds.bottom - topInset;
+        } else if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
+            return mBounds.width() - mDockedStackMinimizeThickness;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
      * Updates the adjustment depending on it's current state.
      */
     void updateAdjustedBounds() {
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 577c3a1..fbcd156 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -58,7 +58,7 @@
                 .setMinimumLatency(runFromMillis)
                 .setPersisted(true)
                 .build();
-        final JobStatus ts = new JobStatus(task, SOME_UID, null, -1);
+        final JobStatus ts = new JobStatus(this, task, SOME_UID, null, -1);
         mTaskStoreUnderTest.add(ts);
         Thread.sleep(IO_WAIT);
         // Manually load tasks from xml file.
@@ -91,8 +91,8 @@
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                 .setPersisted(true)
                 .build();
-        final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID, null, -1);
-        final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID, null, -1);
+        final JobStatus taskStatus1 = new JobStatus(this, task1, SOME_UID, null, -1);
+        final JobStatus taskStatus2 = new JobStatus(this, task2, SOME_UID, null, -1);
         mTaskStoreUnderTest.add(taskStatus1);
         mTaskStoreUnderTest.add(taskStatus2);
         Thread.sleep(IO_WAIT);
@@ -140,7 +140,7 @@
         extras.putInt("into", 3);
         b.setExtras(extras);
         final JobInfo task = b.build();
-        JobStatus taskStatus = new JobStatus(task, SOME_UID, null, -1);
+        JobStatus taskStatus = new JobStatus(this, task, SOME_UID, null, -1);
 
         mTaskStoreUnderTest.add(taskStatus);
         Thread.sleep(IO_WAIT);
@@ -157,7 +157,8 @@
                 .setPeriodic(10000L)
                 .setRequiresCharging(true)
                 .setPersisted(true);
-        JobStatus taskStatus = new JobStatus(b.build(), SOME_UID, "com.google.android.gms", 0);
+        JobStatus taskStatus = new JobStatus(this, b.build(), SOME_UID,
+                "com.google.android.gms", 0);
 
         mTaskStoreUnderTest.add(taskStatus);
         Thread.sleep(IO_WAIT);
@@ -178,7 +179,7 @@
                 .setPeriodic(5*60*60*1000, 1*60*60*1000)
                 .setRequiresCharging(true)
                 .setPersisted(true);
-        JobStatus taskStatus = new JobStatus(b.build(), SOME_UID, null, -1);
+        JobStatus taskStatus = new JobStatus(this, b.build(), SOME_UID, null, -1);
 
         mTaskStoreUnderTest.add(taskStatus);
         Thread.sleep(IO_WAIT);
@@ -203,7 +204,8 @@
                 SystemClock.elapsedRealtime() + (TWO_HOURS * ONE_HOUR) + TWO_HOURS;  // > period+flex
         final long invalidEarlyRuntimeElapsedMillis =
                 invalidLateRuntimeElapsedMillis - TWO_HOURS;  // Early is (late - period).
-        final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage", 0 /* sourceUserId */,
+        final JobStatus js = new JobStatus(this, b.build(), SOME_UID, "somePackage",
+                0 /* sourceUserId */,
                 invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis);
 
         mTaskStoreUnderTest.add(js);
@@ -229,7 +231,7 @@
                 .setOverrideDeadline(5000)
                 .setPriority(42)
                 .setPersisted(true);
-        final JobStatus js = new JobStatus(b.build(), SOME_UID, null, -1);
+        final JobStatus js = new JobStatus(this, b.build(), SOME_UID, null, -1);
         mTaskStoreUnderTest.add(js);
         Thread.sleep(IO_WAIT);
         final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
@@ -245,12 +247,12 @@
         JobInfo.Builder b = new Builder(42, mComponent)
                 .setOverrideDeadline(10000)
                 .setPersisted(false);
-        JobStatus jsNonPersisted = new JobStatus(b.build(), SOME_UID, null, -1);
+        JobStatus jsNonPersisted = new JobStatus(this, b.build(), SOME_UID, null, -1);
         mTaskStoreUnderTest.add(jsNonPersisted);
         b = new Builder(43, mComponent)
                 .setOverrideDeadline(10000)
                 .setPersisted(true);
-        JobStatus jsPersisted = new JobStatus(b.build(), SOME_UID, null, -1);
+        JobStatus jsPersisted = new JobStatus(this, b.build(), SOME_UID, null, -1);
         mTaskStoreUnderTest.add(jsPersisted);
         Thread.sleep(IO_WAIT);
         final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();