Merge "Use DevicePolicyData#mUserSetupComplete for DPM's isDeviceProvisioned."
diff --git a/api/current.txt b/api/current.txt
index 2c20982..659c1b8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -601,6 +601,8 @@
     field public static final int font = 16844082; // 0x1010532
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
+    field public static final int fontProviderAuthority = 16844114; // 0x1010552
+    field public static final int fontProviderQuery = 16844115; // 0x1010553
     field public static final int fontStyle = 16844081; // 0x1010531
     field public static final int fontWeight = 16844083; // 0x1010533
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
@@ -52448,6 +52450,13 @@
     field public static final java.lang.Class<java.lang.Boolean> TYPE;
   }
 
+  public class BootstrapMethodError extends java.lang.LinkageError {
+    ctor public BootstrapMethodError();
+    ctor public BootstrapMethodError(java.lang.String);
+    ctor public BootstrapMethodError(java.lang.String, java.lang.Throwable);
+    ctor public BootstrapMethodError(java.lang.Throwable);
+  }
+
   public final class Byte extends java.lang.Number implements java.lang.Comparable {
     ctor public Byte(byte);
     ctor public Byte(java.lang.String) throws java.lang.NumberFormatException;
@@ -54334,6 +54343,21 @@
 
 package java.lang.invoke {
 
+  public abstract class CallSite {
+    method public abstract java.lang.invoke.MethodHandle dynamicInvoker();
+    method public abstract java.lang.invoke.MethodHandle getTarget();
+    method public abstract void setTarget(java.lang.invoke.MethodHandle);
+    method public java.lang.invoke.MethodType type();
+  }
+
+  public class ConstantCallSite extends java.lang.invoke.CallSite {
+    ctor public ConstantCallSite(java.lang.invoke.MethodHandle);
+    ctor protected ConstantCallSite(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle) throws java.lang.Throwable;
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public final void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class LambdaConversionException extends java.lang.Exception {
     ctor public LambdaConversionException();
     ctor public LambdaConversionException(java.lang.String);
@@ -54462,6 +54486,22 @@
     method public java.lang.invoke.MethodType wrap();
   }
 
+  public class MutableCallSite extends java.lang.invoke.CallSite {
+    ctor public MutableCallSite(java.lang.invoke.MethodType);
+    ctor public MutableCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
+  public class VolatileCallSite extends java.lang.invoke.CallSite {
+    ctor public VolatileCallSite(java.lang.invoke.MethodType);
+    ctor public VolatileCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class WrongMethodTypeException extends java.lang.RuntimeException {
     ctor public WrongMethodTypeException();
     ctor public WrongMethodTypeException(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index af1a705..a6de1ea 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -713,6 +713,8 @@
     field public static final int font = 16844082; // 0x1010532
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
+    field public static final int fontProviderAuthority = 16844114; // 0x1010552
+    field public static final int fontProviderQuery = 16844115; // 0x1010553
     field public static final int fontStyle = 16844081; // 0x1010531
     field public static final int fontWeight = 16844083; // 0x1010533
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
@@ -6892,6 +6894,7 @@
     ctor public BackupManager(android.content.Context);
     method public void backupNow();
     method public android.app.backup.RestoreSession beginRestoreSession();
+    method public void cancelBackups();
     method public void dataChanged();
     method public static void dataChanged(java.lang.String);
     method public long getAvailableRestoreToken(java.lang.String);
@@ -6908,6 +6911,7 @@
     method public void setAutoRestore(boolean);
     method public void setBackupEnabled(boolean);
     field public static final int ERROR_AGENT_FAILURE = -1003; // 0xfffffc15
+    field public static final int ERROR_BACKUP_CANCELLED = -2003; // 0xfffff82d
     field public static final int ERROR_BACKUP_NOT_ALLOWED = -2001; // 0xfffff82f
     field public static final int ERROR_PACKAGE_NOT_FOUND = -2002; // 0xfffff82e
     field public static final int ERROR_TRANSPORT_ABORTED = -1000; // 0xfffffc18
@@ -56248,6 +56252,13 @@
     field public static final java.lang.Class<java.lang.Boolean> TYPE;
   }
 
+  public class BootstrapMethodError extends java.lang.LinkageError {
+    ctor public BootstrapMethodError();
+    ctor public BootstrapMethodError(java.lang.String);
+    ctor public BootstrapMethodError(java.lang.String, java.lang.Throwable);
+    ctor public BootstrapMethodError(java.lang.Throwable);
+  }
+
   public final class Byte extends java.lang.Number implements java.lang.Comparable {
     ctor public Byte(byte);
     ctor public Byte(java.lang.String) throws java.lang.NumberFormatException;
@@ -58134,6 +58145,21 @@
 
 package java.lang.invoke {
 
+  public abstract class CallSite {
+    method public abstract java.lang.invoke.MethodHandle dynamicInvoker();
+    method public abstract java.lang.invoke.MethodHandle getTarget();
+    method public abstract void setTarget(java.lang.invoke.MethodHandle);
+    method public java.lang.invoke.MethodType type();
+  }
+
+  public class ConstantCallSite extends java.lang.invoke.CallSite {
+    ctor public ConstantCallSite(java.lang.invoke.MethodHandle);
+    ctor protected ConstantCallSite(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle) throws java.lang.Throwable;
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public final void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class LambdaConversionException extends java.lang.Exception {
     ctor public LambdaConversionException();
     ctor public LambdaConversionException(java.lang.String);
@@ -58262,6 +58288,22 @@
     method public java.lang.invoke.MethodType wrap();
   }
 
+  public class MutableCallSite extends java.lang.invoke.CallSite {
+    ctor public MutableCallSite(java.lang.invoke.MethodType);
+    ctor public MutableCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
+  public class VolatileCallSite extends java.lang.invoke.CallSite {
+    ctor public VolatileCallSite(java.lang.invoke.MethodType);
+    ctor public VolatileCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class WrongMethodTypeException extends java.lang.RuntimeException {
     ctor public WrongMethodTypeException();
     ctor public WrongMethodTypeException(java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 43f1a2e..ebba10e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -601,6 +601,8 @@
     field public static final int font = 16844082; // 0x1010532
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
+    field public static final int fontProviderAuthority = 16844114; // 0x1010552
+    field public static final int fontProviderQuery = 16844115; // 0x1010553
     field public static final int fontStyle = 16844081; // 0x1010531
     field public static final int fontWeight = 16844083; // 0x1010533
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
@@ -52777,6 +52779,13 @@
     field public static final java.lang.Class<java.lang.Boolean> TYPE;
   }
 
+  public class BootstrapMethodError extends java.lang.LinkageError {
+    ctor public BootstrapMethodError();
+    ctor public BootstrapMethodError(java.lang.String);
+    ctor public BootstrapMethodError(java.lang.String, java.lang.Throwable);
+    ctor public BootstrapMethodError(java.lang.Throwable);
+  }
+
   public final class Byte extends java.lang.Number implements java.lang.Comparable {
     ctor public Byte(byte);
     ctor public Byte(java.lang.String) throws java.lang.NumberFormatException;
@@ -54663,6 +54672,21 @@
 
 package java.lang.invoke {
 
+  public abstract class CallSite {
+    method public abstract java.lang.invoke.MethodHandle dynamicInvoker();
+    method public abstract java.lang.invoke.MethodHandle getTarget();
+    method public abstract void setTarget(java.lang.invoke.MethodHandle);
+    method public java.lang.invoke.MethodType type();
+  }
+
+  public class ConstantCallSite extends java.lang.invoke.CallSite {
+    ctor public ConstantCallSite(java.lang.invoke.MethodHandle);
+    ctor protected ConstantCallSite(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle) throws java.lang.Throwable;
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public final void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class LambdaConversionException extends java.lang.Exception {
     ctor public LambdaConversionException();
     ctor public LambdaConversionException(java.lang.String);
@@ -54791,6 +54815,22 @@
     method public java.lang.invoke.MethodType wrap();
   }
 
+  public class MutableCallSite extends java.lang.invoke.CallSite {
+    ctor public MutableCallSite(java.lang.invoke.MethodType);
+    ctor public MutableCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
+  public class VolatileCallSite extends java.lang.invoke.CallSite {
+    ctor public VolatileCallSite(java.lang.invoke.MethodType);
+    ctor public VolatileCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class WrongMethodTypeException extends java.lang.RuntimeException {
     ctor public WrongMethodTypeException();
     ctor public WrongMethodTypeException(java.lang.String);
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index a67e47f..bfcad1b 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -127,6 +127,11 @@
             return;
         }
 
+        if ("cancel".equals(op)) {
+            doCancel();
+            return;
+        }
+
         if ("whitelist".equals(op)) {
             doPrintWhitelist();
             return;
@@ -270,6 +275,8 @@
                 return "Agent error";
             case BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED:
                 return "Size quota exceeded";
+            case BackupManager.ERROR_BACKUP_CANCELLED:
+                return "Backup Cancelled";
             default:
                 return "Unknown error";
         }
@@ -361,6 +368,21 @@
         }
     }
 
+    private void doCancel() {
+        String arg = nextArg();
+        if ("backups".equals(arg)) {
+            try {
+                mBmgr.cancelBackups();
+            } catch (RemoteException e) {
+                System.err.println(e.toString());
+                System.err.println(BMGR_NOT_RUNNING_ERR);
+            }
+            return;
+        }
+
+        System.err.println("Unknown command.");
+    }
+
     private void doTransport() {
         try {
             String which = nextArg();
@@ -721,6 +743,7 @@
         System.err.println("       bmgr wipe TRANSPORT PACKAGE");
         System.err.println("       bmgr fullbackup PACKAGE...");
         System.err.println("       bmgr backupnow --all|PACKAGE...");
+        System.err.println("       bmgr cancel backups");
         System.err.println("");
         System.err.println("The 'backup' command schedules a backup pass for the named package.");
         System.err.println("Note that the backup pass will effectively be a no-op if the package");
@@ -780,5 +803,6 @@
         System.err.println("For each package it will run key/value or full data backup ");
         System.err.println("depending on the package's manifest declarations.");
         System.err.println("The data is sent via the currently active transport.");
+        System.err.println("The 'cancel backups' command cancels all running backups.");
     }
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 50fee83..376823e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5722,6 +5722,24 @@
         } finally {
             StrictMode.setThreadPolicy(savedPolicy);
         }
+
+        // Preload fonts resources
+        try {
+            final ApplicationInfo info =
+                    sPackageManager.getApplicationInfo(
+                            data.appInfo.packageName,
+                            PackageManager.GET_META_DATA /*flags*/,
+                            UserHandle.myUserId());
+            if (info.metaData != null) {
+                final int preloadedFontsResource = info.metaData.getInt(
+                        ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
+                if (preloadedFontsResource != 0) {
+                    data.info.mResources.preloadFonts(preloadedFontsResource);
+                }
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /*package*/ final void finishInstrumentation(int resultCode, Bundle results) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 6719be4..6717491 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -210,6 +210,7 @@
     boolean killPids(in int[] pids, in String reason, boolean secure);
     List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags);
     ActivityManager.TaskThumbnail getTaskThumbnail(int taskId);
+    ActivityManager.TaskDescription getTaskDescription(int taskId);
     // Retrieve running application processes in the system
     List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses();
     // Get device configuration
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index 0ae8505..6ee4780 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -16,197 +16,86 @@
 
 package android.app;
 
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.LinkedList;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
- * Internal utility class to keep track of process-global work that's outstanding and hasn't been
- * finished yet.
+ * Internal utility class to keep track of process-global work that's
+ * outstanding and hasn't been finished yet.
  *
- * New work will be {@link #queue queued}.
- *
- * It is possible to add 'finisher'-runnables that are {@link #waitToFinish guaranteed to be run}.
- * This is used to make sure the work has been finished.
- *
- * This was created for writing SharedPreference edits out asynchronously so we'd have a mechanism
- * to wait for the writes in Activity.onPause and similar places, but we may use this mechanism for
- * other things in the future.
- *
- * The queued asynchronous work is performed on a separate, dedicated thread.
+ * This was created for writing SharedPreference edits out
+ * asynchronously so we'd have a mechanism to wait for the writes in
+ * Activity.onPause and similar places, but we may use this mechanism
+ * for other things in the future.
  *
  * @hide
  */
 public class QueuedWork {
-    private static final String LOG_TAG = QueuedWork.class.getSimpleName();
 
-    /** Delay for delayed runnables */
-    private static final long DELAY = 50;
+    // The set of Runnables that will finish or wait on any async
+    // activities started by the application.
+    private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers =
+            new ConcurrentLinkedQueue<Runnable>();
 
-    /** Lock for this class */
-    private static final Object sLock = new Object();
-
-    /** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */
-    @GuardedBy("sLock")
-    private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
-
-    /** {@link #getHandler() Lazily} created handler */
-    @GuardedBy("sLock")
-    private static Handler sHandler = null;
-
-    /** Work queued via {@link #queue} */
-    @GuardedBy("sLock")
-    private static final LinkedList<Runnable> sWork = new LinkedList<>();
-
-    /** If new work can be delayed or not */
-    @GuardedBy("sLock")
-    private static boolean sCanDelay = true;
+    private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class
 
     /**
-     * Lazily create a handler on a separate thread.
-     *
-     * @return the handler
+     * Returns a single-thread Executor shared by the entire process,
+     * creating it if necessary.
      */
-    private static Handler getHandler() {
-        synchronized (sLock) {
-            if (sHandler == null) {
-                HandlerThread handlerThread = new HandlerThread("queued-work-looper",
-                        Process.THREAD_PRIORITY_BACKGROUND);
-                handlerThread.start();
-
-                sHandler = new QueuedWorkHandler(handlerThread.getLooper());
+    public static ExecutorService singleThreadExecutor() {
+        synchronized (QueuedWork.class) {
+            if (sSingleThreadExecutor == null) {
+                // TODO: can we give this single thread a thread name?
+                sSingleThreadExecutor = Executors.newSingleThreadExecutor();
             }
-            return sHandler;
+            return sSingleThreadExecutor;
         }
     }
 
     /**
-     * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}.
+     * Add a runnable to finish (or wait for) a deferred operation
+     * started in this context earlier.  Typically finished by e.g.
+     * an Activity#onPause.  Used by SharedPreferences$Editor#startCommit().
      *
-     * Used by SharedPreferences$Editor#startCommit().
-     *
-     * Note that this doesn't actually start it running.  This is just a scratch set for callers
-     * doing async work to keep updated with what's in-flight. In the common case, caller code
-     * (e.g. SharedPreferences) will pretty quickly call remove() after an add(). The only time
-     * these Runnables are run is from {@link #waitToFinish}.
-     *
-     * @param finisher The runnable to add as finisher
+     * Note that this doesn't actually start it running.  This is just
+     * a scratch set for callers doing async work to keep updated with
+     * what's in-flight.  In the common case, caller code
+     * (e.g. SharedPreferences) will pretty quickly call remove()
+     * after an add().  The only time these Runnables are run is from
+     * waitToFinish(), below.
      */
-    public static void addFinisher(Runnable finisher) {
-        synchronized (sLock) {
-            sFinishers.add(finisher);
-        }
+    public static void add(Runnable finisher) {
+        sPendingWorkFinishers.add(finisher);
+    }
+
+    public static void remove(Runnable finisher) {
+        sPendingWorkFinishers.remove(finisher);
     }
 
     /**
-     * Remove a previously {@link #addFinisher added} finisher-runnable.
+     * Finishes or waits for async operations to complete.
+     * (e.g. SharedPreferences$Editor#startCommit writes)
      *
-     * @param finisher The runnable to remove.
-     */
-    public static void removeFinisher(Runnable finisher) {
-        synchronized (sLock) {
-            sFinishers.remove(finisher);
-        }
-    }
-
-    /**
-     * Trigger queued work to be processed immediately. The queued work is processed on a separate
-     * thread asynchronous. While doing that run and process all finishers on this thread. The
-     * finishers can be implemented in a way to check weather the queued work is finished.
-     *
-     * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
-     * after Service command handling, etc. (so async work is never lost)
+     * Is called from the Activity base class's onPause(), after
+     * BroadcastReceiver's onReceive, after Service command handling,
+     * etc.  (so async work is never lost)
      */
     public static void waitToFinish() {
-        Handler handler = getHandler();
-
-        synchronized (sLock) {
-            if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
-                // Force the delayed work to be processed now
-                handler.removeMessages(QueuedWorkHandler.MSG_RUN);
-                handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
-            }
-
-            // We should not delay any work as this might delay the finishers
-            sCanDelay = false;
-        }
-
-        try {
-            while (true) {
-                Runnable finisher;
-
-                synchronized (sLock) {
-                    finisher = sFinishers.poll();
-                }
-
-                if (finisher == null) {
-                    break;
-                }
-
-                finisher.run();
-            }
-        } finally {
-            sCanDelay = true;
+        Runnable toFinish;
+        while ((toFinish = sPendingWorkFinishers.poll()) != null) {
+            toFinish.run();
         }
     }
-
+    
     /**
-     * Queue a work-runnable for processing asynchronously.
-     *
-     * @param work The new runnable to process
-     * @param shouldDelay If the message should be delayed
-     */
-    public static void queue(Runnable work, boolean shouldDelay) {
-        Handler handler = getHandler();
-
-        synchronized (sLock) {
-            sWork.add(work);
-
-            if (shouldDelay && sCanDelay) {
-                handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
-            } else {
-                handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
-            }
-        }
-    }
-
-    /**
-     * @return True iff there is any {@link #queue async work queued}.
+     * Returns true if there is pending work to be done.  Note that the
+     * result is out of data as soon as you receive it, so be careful how you
+     * use it.
      */
     public static boolean hasPendingWork() {
-        synchronized (sLock) {
-            return !sWork.isEmpty();
-        }
+        return !sPendingWorkFinishers.isEmpty();
     }
-
-    private static class QueuedWorkHandler extends Handler {
-        static final int MSG_RUN = 1;
-
-        QueuedWorkHandler(Looper looper) {
-            super(looper);
-        }
-
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_RUN) {
-                LinkedList<Runnable> work;
-
-                synchronized (sWork) {
-                    work = (LinkedList<Runnable>) sWork.clone();
-                    sWork.clear();
-
-                    // Remove all msg-s as all work will be processed now
-                    removeMessages(MSG_RUN);
-                }
-
-                work.forEach(Runnable::run);
-            }
-        }
-    }
+    
 }
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 023b4f3..3bb7019 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -406,12 +406,12 @@
                     }
                 };
 
-            QueuedWork.addFinisher(awaitCommit);
+            QueuedWork.add(awaitCommit);
 
             Runnable postWriteRunnable = new Runnable() {
                     public void run() {
                         awaitCommit.run();
-                        QueuedWork.removeFinisher(awaitCommit);
+                        QueuedWork.remove(awaitCommit);
                     }
                 };
 
@@ -588,10 +588,10 @@
         }
 
         if (DEBUG) {
-            Log.d(TAG, "queued " + mcr.memoryStateGeneration + " -> " + mFile.getName());
+            Log.d(TAG, "added " + mcr.memoryStateGeneration + " -> " + mFile.getName());
         }
 
-        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
+        QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
     }
 
     private static FileOutputStream createFileOutputStream(File file) {
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 59bd01f..9d02f53 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -89,6 +89,14 @@
     public static final int ERROR_PACKAGE_NOT_FOUND = -2002;
 
     /**
+     * The backup operation was cancelled.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_BACKUP_CANCELLED = -2003;
+
+    /**
      * The transport for some reason was not in a good state and
      * aborted the entire backup request. This is a transient
      * failure and should not be retried immediately.
@@ -626,6 +634,26 @@
         return -1;
     }
 
+    /**
+     * Cancel all running backups. After this call returns, no currently running backups will
+     * interact with the selected transport.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void cancelBackups() {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                sService.cancelBackups();
+            } catch (RemoteException e) {
+                Log.e(TAG, "cancelBackups() couldn't connect.");
+            }
+        }
+    }
+
     /*
      * We wrap incoming binder calls with a private class implementation that
      * redirects them into main-thread actions.  This serializes the backup
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 393667d..59a941a 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -386,4 +386,10 @@
      */
     int requestBackup(in String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor,
         int flags);
+
+    /**
+     * Cancel all running backups. After this call returns, no currently running backups will
+     * interact with the selected transport.
+     */
+    void cancelBackups();
 }
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index c3d6606..485d078 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -211,16 +211,16 @@
                     // of the list to finish the broadcast, so we don't block this
                     // thread (which may be the main thread) to have it finished.
                     //
-                    // Note that we don't need to use QueuedWork.addFinisher() with the
+                    // Note that we don't need to use QueuedWork.add() with the
                     // runnable, since we know the AM is waiting for us until the
                     // executor gets to it.
-                    QueuedWork.queue(new Runnable() {
+                    QueuedWork.singleThreadExecutor().execute( new Runnable() {
                         @Override public void run() {
                             if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                                     "Finishing broadcast after work to component " + mToken);
                             sendFinished(mgr);
                         }
-                    }, false);
+                    });
                 } else {
                     if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                             "Finishing broadcast to component " + mToken);
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 8465f0f..1fa4181 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -573,6 +573,11 @@
     public int privateFlags;
 
     /**
+     * @hide
+     */
+    public static final String METADATA_PRELOADED_FONTS = "preloaded_fonts";
+
+    /**
      * The required smallest screen width the application can run on.  If 0,
      * nothing has been specified.  Comes from
      * {@link android.R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java
index 7ea62db..3f8f90e 100644
--- a/core/java/android/content/res/FontResourcesParser.java
+++ b/core/java/android/content/res/FontResourcesParser.java
@@ -64,6 +64,17 @@
 
     private static FontConfig.Family readFamily(XmlPullParser parser, Resources resources)
             throws XmlPullParserException, IOException {
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+        TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily);
+        String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
+        String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
+        array.recycle();
+        if (authority != null && query != null) {
+            while (parser.next() != XmlPullParser.END_TAG) {
+                skip(parser);
+            }
+            return new FontConfig.Family(authority, query);
+        }
         List<FontConfig.Font> fonts = new ArrayList<>();
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -84,11 +95,12 @@
         int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight, NORMAL_WEIGHT);
         boolean isItalic = ITALIC == array.getInt(R.styleable.FontFamilyFont_fontStyle, 0);
         String filename = array.getString(R.styleable.FontFamilyFont_font);
+        int resourceId = array.getResourceId(R.styleable.FontFamilyFont_font, 0);
         array.recycle();
         while (parser.next() != XmlPullParser.END_TAG) {
             skip(parser);
         }
-        return new FontConfig.Font(filename, 0, null, weight, isItalic);
+        return new FontConfig.Font(filename, 0, null, weight, isItalic, resourceId);
     }
 
     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 04e4454..21d4b22 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -373,6 +373,20 @@
     }
 
     /**
+     * @hide
+     */
+    public void preloadFonts(@FontRes int id) {
+        final TypedValue value = obtainTempTypedValue();
+        try {
+            final ResourcesImpl impl = mResourcesImpl;
+            impl.getValue(id, value, true);
+            impl.preloadFonts(this, value, id);
+        } finally {
+            releaseTempTypedValue(value);
+        }
+    }
+
+    /**
      * Returns the character sequence necessary for grammatically correct pluralization
      * of the given resource ID for the given quantity.
      * Note that the character sequence is selected based solely on grammatical necessity,
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index bf81096..38efa49 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -32,6 +32,7 @@
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.Configuration.NativeConfig;
 import android.content.res.Resources.NotFoundException;
+import android.graphics.FontFamily;
 import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -52,6 +53,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -782,6 +784,43 @@
     }
 
     /**
+     * @hide
+     */
+    public void preloadFonts(Resources wrapper, TypedValue value, int id) {
+        if (value.string == null) {
+            throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+                    + Integer.toHexString(id) + ") is not a Font: " + value);
+        }
+
+        final String file = value.string.toString();
+
+        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+        try {
+            final XmlResourceParser rp = loadXmlResourceParser(
+                    file, id, value.assetCookie, "font");
+            final FontConfig config = FontResourcesParser.parse(rp, wrapper);
+            final List<FontConfig.Family> families = config.getFamilies();
+            if (families == null || families.isEmpty()) {
+                return;
+            }
+            for (int j = 0; j < families.size(); j++) {
+                final FontConfig.Family family = families.get(j);
+                final List<FontConfig.Font> fonts = family.getFonts();
+                for (int i = 0; i < fonts.size(); i++) {
+                    int resourceId = fonts.get(i).getResourceId();
+                    wrapper.getFont(resourceId);
+                }
+            }
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "Failed to parse xml resource " + file, e);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to read xml resource " + file, e);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+        }
+    }
+
+    /**
      * Given the value and id, we can get the XML filename as in value.data, based on that, we
      * first try to load CSL from the cache. If not found, try to get from the constant state.
      * Last, parse the XML and generate the CSL.
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 3048a38..82e44dc 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -170,14 +170,24 @@
         private final int mWeight;
         private final boolean mIsItalic;
         private ParcelFileDescriptor mFd;
+        private final int mResourceId;
 
-        public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) {
+        /**
+         * @hide
+         */
+        public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic,
+                int resourceId) {
             mFontName = fontName;
             mTtcIndex = ttcIndex;
             mAxes = axes;
             mWeight = weight;
             mIsItalic = isItalic;
             mFd = null;
+            mResourceId = resourceId;
+        }
+
+        public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) {
+            this(fontName, ttcIndex, axes, weight, isItalic, 0);
         }
 
         public Font(Font origin) {
@@ -193,6 +203,7 @@
                     e.printStackTrace();
                 }
             }
+            mResourceId = origin.mResourceId;
         }
 
         /**
@@ -254,6 +265,13 @@
         /**
          * @hide
          */
+        public int getResourceId() {
+            return mResourceId;
+        }
+
+        /**
+         * @hide
+         */
         public Font(Parcel in) {
             mFontName = in.readString();
             mTtcIndex = in.readInt();
@@ -269,6 +287,7 @@
             } else {
                 mFd = null;
             }
+            mResourceId = in.readInt();
         }
 
         @Override
@@ -285,6 +304,7 @@
             if (mFd != null) {
                 mFd.writeToParcel(out, flag);
             }
+            out.writeInt(mResourceId);
         }
 
         @Override
@@ -382,22 +402,40 @@
         private final List<Font> mFonts;
         private final String mLanguage;
         private final String mVariant;
+        private final String mProviderAuthority;
+        private final String mQuery;
 
         public Family(String name, List<Font> fonts, String language, String variant) {
-            this.mName = name;
-            this.mFonts = fonts;
-            this.mLanguage = language;
-            this.mVariant = variant;
+            mName = name;
+            mFonts = fonts;
+            mLanguage = language;
+            mVariant = variant;
+            mProviderAuthority = null;
+            mQuery = null;
+        }
+
+        /**
+         * @hide
+         */
+        public Family(String providerAuthority, String query) {
+            mName = null;
+            mFonts = null;
+            mLanguage = null;
+            mVariant = null;
+            mProviderAuthority = providerAuthority;
+            mQuery = query;
         }
 
         public Family(Family origin) {
-            this.mName = origin.mName;
-            this.mLanguage = origin.mLanguage;
-            this.mVariant = origin.mVariant;
-            this.mFonts = new ArrayList<>();
+            mName = origin.mName;
+            mLanguage = origin.mLanguage;
+            mVariant = origin.mVariant;
+            mFonts = new ArrayList<>();
             for (int i = 0; i < origin.mFonts.size(); i++) {
                 mFonts.add(new Font(origin.mFonts.get(i)));
             }
+            mProviderAuthority = origin.mProviderAuthority;
+            mQuery = origin.mQuery;
         }
 
         /**
@@ -431,6 +469,20 @@
         /**
          * @hide
          */
+        public String getProviderAuthority() {
+            return mProviderAuthority;
+        }
+
+        /**
+         * @hide
+         */
+        public String getQuery() {
+            return mQuery;
+        }
+
+        /**
+         * @hide
+         */
         public Family(Parcel in) {
             mName = in.readString();
             final int size = in.readInt();
@@ -440,6 +492,16 @@
             }
             mLanguage = in.readString();
             mVariant = in.readString();
+            if (in.readInt() == 1) {
+                mProviderAuthority = in.readString();
+            } else {
+                mProviderAuthority = null;
+            }
+            if (in.readInt() == 1) {
+                mQuery = in.readString();
+            } else {
+                mQuery = null;
+            }
         }
 
         @Override
@@ -451,6 +513,14 @@
             }
             out.writeString(mLanguage);
             out.writeString(mVariant);
+            out.writeInt(mProviderAuthority == null ? 0 : 1);
+            if (mProviderAuthority != null) {
+                out.writeString(mProviderAuthority);
+            }
+            out.writeInt(mQuery == null ? 0 : 1);
+            if (mQuery != null) {
+                out.writeString(mQuery);
+            }
         }
 
         @Override
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d8fc1d1..36bb821 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8507,6 +8507,12 @@
         <attr name="fontWeight" format="integer" />
     </declare-styleable>
 
+    <!-- Attributes that are read when parsing a <fontfamily> tag, -->
+    <declare-styleable name="FontFamily">
+        <attr name="fontProviderAuthority" format="string" />
+        <attr name="fontProviderQuery" format="string" />
+    </declare-styleable>
+
     <!-- @hide -->
     <declare-styleable name="RecyclerView">
         <attr name="layoutManager" format="string" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 34659aa..78489eb 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2794,6 +2794,8 @@
         <public name="canCaptureFingerprintGestures" />
         <public name="alphabeticModifiers" />
         <public name="numericModifiers" />
+        <public name="fontProviderAuthority" />
+        <public name="fontProviderQuery" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
@@ -2806,6 +2808,7 @@
     <public type="attr" name="primaryContentAlpha" />
     <public type="attr" name="secondaryContentAlpha" />
 
+
   <!-- ===============================================================
        DO NOT ADD UN-GROUPED ITEMS HERE
 
diff --git a/core/tests/coretests/res/font/samplexmldownloadedfont.xml b/core/tests/coretests/res/font/samplexmldownloadedfont.xml
new file mode 100644
index 0000000..35391bd
--- /dev/null
+++ b/core/tests/coretests/res/font/samplexmldownloadedfont.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fontProviderAuthority="com.example.test.fontprovider"
+        android:fontProviderQuery="MyRequestedFont">
+</font-family>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 380a28774..8b536a7 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -15,6 +15,8 @@
  */
 package android.content.res;
 
+import static junit.framework.Assert.assertNull;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -78,4 +80,19 @@
         assertEquals(true, font4.isItalic());
         assertEquals("res/font/samplefont4.ttf", font4.getFontName());
     }
+
+    @Test
+    public void testParseDownloadableFont() throws IOException, XmlPullParserException {
+        XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfont);
+
+        FontConfig result = FontResourcesParser.parse(parser, mResources);
+
+        assertNotNull(result);
+        List<FontConfig.Family> families = result.getFamilies();
+        assertEquals(1, families.size());
+        FontConfig.Family family = families.get(0);
+        assertEquals("com.example.test.fontprovider", family.getProviderAuthority());
+        assertEquals("MyRequestedFont", family.getQuery());
+        assertNull(family.getFonts());
+    }
 }
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 750ef3f..7eb8099 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -163,36 +163,54 @@
     @Nullable
     public static Typeface createFromResources(FontConfig config, AssetManager mgr, String path) {
         if (sFallbackFonts != null) {
+            Typeface typeface = findFromCache(mgr, path);
+            if (typeface != null) return typeface;
+
+            List<FontConfig.Family> families = config.getFamilies();
+            if (families == null || families.isEmpty()) {
+                throw new RuntimeException(
+                        "Font resource " + path + " contained no font families.");
+            }
+            if (families.size() > 1) {
+                throw new RuntimeException(
+                        "Font resource " + path + " contained more than one family.");
+            }
+            FontConfig.Family family = families.get(0);
+            if (family.getProviderAuthority() != null && family.getQuery() != null) {
+                // Downloadable font
+                typeface = findFromCache(
+                        family.getProviderAuthority(), family.getQuery());
+                if (typeface != null) {
+                    return typeface;
+                }
+                // Downloaded font and it wasn't cached, request it again and return a
+                // default font instead (nothing we can do now).
+                create(new FontRequest(family.getProviderAuthority(), family.getQuery()),
+                        NO_OP_REQUEST_CALLBACK);
+                return DEFAULT;
+            }
+
+            FontFamily fontFamily = new FontFamily();
+            List<FontConfig.Font> fonts = family.getFonts();
+            if (fonts == null || fonts.isEmpty()) {
+                throw new RuntimeException("Font resource " + path + " contained no fonts.");
+            }
+            for (int i = 0; i < fonts.size(); i++) {
+                FontConfig.Font font = fonts.get(i);
+                // TODO: Use style and weight info
+                if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(),
+                        0 /* resourceCookie */, false /* isAsset */)) {
+                    return null;
+                }
+            }
+            fontFamily.freeze();
+            FontFamily[] familyChain = { fontFamily };
+            typeface = createFromFamiliesWithDefault(familyChain);
             synchronized (sDynamicTypefaceCache) {
                 final String key = createAssetUid(mgr, path);
-                Typeface typeface = sDynamicTypefaceCache.get(key);
-                if (typeface != null) return typeface;
-
-                List<FontConfig.Family> families = config.getFamilies();
-                if (families == null || families.isEmpty()) {
-                    throw new RuntimeException("Font resource contained no fonts.");
-                }
-                if (families.size() > 1) {
-                    throw new RuntimeException("Font resource contained more than one family.");
-                }
-                FontConfig.Family family = families.get(0);
-
-                FontFamily fontFamily = new FontFamily();
-                List<FontConfig.Font> fonts = family.getFonts();
-                for (int i = 0; i < fonts.size(); i++) {
-                    FontConfig.Font font = fonts.get(i);
-                    // TODO: Use style and weight info
-                    if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(),
-                            0 /* resourceCookie */, false /* isAsset */)) {
-                        return null;
-                    }
-                }
-                fontFamily.freeze();
-                FontFamily[] familyChain = { fontFamily };
-                typeface = createFromFamiliesWithDefault(familyChain);
                 sDynamicTypefaceCache.put(key, typeface);
-                return typeface;
             }
+            return typeface;
         }
         return null;
     }
@@ -372,6 +390,18 @@
         void onTypefaceRequestFailed(@FontRequestFailReason int reason);
     }
 
+    private static final FontRequestCallback NO_OP_REQUEST_CALLBACK = new FontRequestCallback() {
+        @Override
+        public void onTypefaceRetrieved(Typeface typeface) {
+            // Do nothing.
+        }
+
+        @Override
+        public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {
+            // Do nothing.
+        }
+    };
+
     /**
      * Create a typeface object given a family name, and option style information.
      * If null is passed for the name, then the "default" font will be chosen.
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index e566b9d..9981668 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -588,7 +588,7 @@
      * @hide for reuse by CertInstaller and Settings.
      * @see KeyChain#bind
      */
-    public final static class KeyChainConnection implements Closeable {
+    public static class KeyChainConnection implements Closeable {
         private final Context context;
         private final ServiceConnection serviceConnection;
         private final IKeyChainService service;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 9701b0e..988e32c 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -200,6 +200,11 @@
                 }
 
                 if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
+                    if (mKeySizeBits < 64) {
+                        throw new InvalidAlgorithmParameterException(
+                            "HMAC key size must be at least 64 bits.");
+                    }
+
                     // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm
                     // implies SHA-256 digest). Because keymaster HMAC key is authorized only for
                     // one digest, we don't let algorithm parameter spec override the digest implied
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index e03c9e8..a7ebb68 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -48,21 +48,17 @@
         SkBitmap bitmap;
         SkPaint paint;
         hwuiBitmap->getSkBitmapForShaders(&bitmap);
-
-        sk_sp<SkShader> repeatShader = SkMakeBitmapShader(bitmap,
+        sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+        sk_sp<SkShader> repeatShader = image->makeShader(
                 SkShader::TileMode::kRepeat_TileMode,
                 SkShader::TileMode::kRepeat_TileMode,
-                nullptr,
-                kNever_SkCopyPixelsMode,
                 nullptr);
         paint.setShader(std::move(repeatShader));
         canvas.drawRoundRect(0, 0, 500, 500, 50.0f, 50.0f, paint);
 
-        sk_sp<SkShader> mirrorShader = SkMakeBitmapShader(bitmap,
+        sk_sp<SkShader> mirrorShader = image->makeShader(
                 SkShader::TileMode::kMirror_TileMode,
                 SkShader::TileMode::kMirror_TileMode,
-                nullptr,
-                kNever_SkCopyPixelsMode,
                 nullptr);
         paint.setShader(std::move(mirrorShader));
         canvas.drawRoundRect(0, 600, 500, 1100, 50.0f, 50.0f, paint);
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 124f5fa..669f03c 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -748,11 +748,10 @@
         SkPaint paint;
         SkBitmap skBitmap;
         bitmap->getSkBitmap(&skBitmap);
-        sk_sp<SkShader> shader = SkMakeBitmapShader(skBitmap,
+        sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
+        sk_sp<SkShader> shader = image->makeShader(
                 SkShader::TileMode::kClamp_TileMode,
                 SkShader::TileMode::kClamp_TileMode,
-                nullptr,
-                kNever_SkCopyPixelsMode,
                 nullptr);
         paint.setShader(std::move(shader));
         canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
@@ -767,11 +766,10 @@
         SkPaint paint;
         SkBitmap skBitmap;
         bitmap->getSkBitmap(&skBitmap);
-        sk_sp<SkShader> shader1 = SkMakeBitmapShader(skBitmap,
+        sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
+        sk_sp<SkShader> shader1 = image->makeShader(
                 SkShader::TileMode::kClamp_TileMode,
                 SkShader::TileMode::kClamp_TileMode,
-                nullptr,
-                kNever_SkCopyPixelsMode,
                 nullptr);
 
         SkPoint center;
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 03e6b7f..7ae58a6 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -39,12 +39,10 @@
  */
 TEST(SkiaBehavior, CreateBitmapShader1x1) {
     SkBitmap origBitmap = createSkBitmap(1, 1);
-    sk_sp<SkShader> s = SkMakeBitmapShader(
-            origBitmap,
+    sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(origBitmap, kNever_SkCopyPixelsMode);
+    sk_sp<SkShader> s = image->makeShader(
             SkShader::kClamp_TileMode,
             SkShader::kRepeat_TileMode,
-            nullptr,
-            kNever_SkCopyPixelsMode,
             nullptr);
 
     SkBitmap bitmap;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 561d924..1f03b51 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1504,4 +1504,20 @@
             return isMusicApp;
         }
     };
+
+    public static final AppFilter FILTER_OTHER_APPS = new AppFilter() {
+        @Override
+        public void init() {
+        }
+
+        @Override
+        public boolean filterApp(AppEntry entry) {
+            boolean isCategorized;
+            synchronized(entry) {
+                isCategorized = entry.info.category == ApplicationInfo.CATEGORY_AUDIO ||
+                    entry.info.category == ApplicationInfo.CATEGORY_GAME;
+            }
+            return !isCategorized;
+        }
+    };
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 8a6ae86..80e1cbf 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -86,4 +86,25 @@
 
         assertThat(ApplicationsState.FILTER_AUDIO.filterApp(mEntry)).isFalse();
     }
+
+    @Test
+    public void testOtherAppsRejectsAudio() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_AUDIO;
+
+        assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isFalse();
+    }
+
+    @Test
+    public void testOtherAppsRejectsGame() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+        assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isFalse();
+    }
+
+    @Test
+    public void testOtherAppsAcceptsDefaultCategory() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
+
+        assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isTrue();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 23eaed9..32b5862 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard;
 
 import static android.app.ActivityManager.TaskDescription;
-import static android.app.ActivityManager.StackId;
 
 import android.annotation.ColorInt;
 import android.annotation.UserIdInt;
@@ -32,13 +31,14 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Color;
-import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Bouncer between work activities and the activity used to confirm credentials before unlocking
  * a managed profile.
@@ -51,52 +51,39 @@
     private static final String TAG = "WorkLockActivity";
 
     /**
-     * ID of the locked user that this activity blocks access to.
+     * Contains a {@link TaskDescription} for the activity being covered.
      */
-    @UserIdInt
-    private int mUserId;
-
+    static final String EXTRA_TASK_DESCRIPTION =
+            "com.android.systemui.keyguard.extra.TASK_DESCRIPTION";
+  
     /**
-     * {@see KeyguardManager}
+     * Cached keyguard manager instance populated by {@link #getKeyguardManager}.
+     * @see KeyguardManager
      */
     private KeyguardManager mKgm;
 
-    /**
-     * {@see DevicePolicyManager}
-     */
-    private DevicePolicyManager mDpm;
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
-        mDpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
-        mKgm = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
-
-        final IntentFilter lockFilter = new IntentFilter();
-        lockFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
-        registerReceiverAsUser(mLockEventReceiver, UserHandle.ALL, lockFilter,
-                /* permission */ null, /* scheduler */ null);
+        registerReceiverAsUser(mLockEventReceiver, UserHandle.ALL,
+                new IntentFilter(Intent.ACTION_DEVICE_LOCKED_CHANGED), /* permission */ null,
+                /* scheduler */ null);
 
         // Once the receiver is registered, check whether anything happened between now and the time
         // when this activity was launched. If it did and the user is unlocked now, just quit.
-        if (!mKgm.isDeviceLocked(mUserId)) {
+        if (!getKeyguardManager().isDeviceLocked(getTargetUserId())) {
             finish();
             return;
         }
 
-        // Get the organization color; this is a 24-bit integer provided by a DPC, guaranteed to
-        // be completely opaque.
-        final @ColorInt int color = mDpm.getOrganizationColorForUser(mUserId);
-
         // Draw captions overlaid on the content view, so the whole window is one solid color.
         setOverlayWithDecorCaptionEnabled(true);
 
         // Blank out the activity. When it is on-screen it will look like a Recents thumbnail with
         // redaction switched on.
         final View blankView = new View(this);
-        blankView.setBackgroundColor(color);
+        blankView.setBackgroundColor(getPrimaryColor());
         setContentView(blankView);
     }
 
@@ -127,26 +114,28 @@
 
     @Override
     public void setTaskDescription(TaskDescription taskDescription) {
-        // Use the previous activity's task description.
+        // Leave unset so we use the previous activity's task description.
     }
 
     private final BroadcastReceiver mLockEventReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, mUserId);
-            if (userId == mUserId && !mKgm.isDeviceLocked(mUserId)) {
+            final int targetUserId = getTargetUserId();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, targetUserId);
+            if (userId == targetUserId && !getKeyguardManager().isDeviceLocked(targetUserId)) {
                 finish();
             }
         }
     };
 
     private void showConfirmCredentialActivity() {
-        if (isFinishing() || !mKgm.isDeviceLocked(mUserId)) {
+        if (isFinishing() || !getKeyguardManager().isDeviceLocked(getTargetUserId())) {
             // Don't show the confirm credentials screen if we are already unlocked / unlocking.
             return;
         }
 
-        final Intent credential = mKgm.createConfirmDeviceCredentialIntent(null, null, mUserId);
+        final Intent credential = getKeyguardManager()
+                .createConfirmDeviceCredentialIntent(null, null, getTargetUserId());
         if (credential == null) {
             return;
         }
@@ -181,4 +170,32 @@
         final View view = getWindow().getDecorView();
         return ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
     }
+
+    private KeyguardManager getKeyguardManager() {
+        if (mKgm == null) {
+            mKgm = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+        }
+        return mKgm;
+    }
+
+    @VisibleForTesting
+    @UserIdInt
+    final int getTargetUserId() {
+        return getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
+    }
+
+    @VisibleForTesting
+    @ColorInt
+    final int getPrimaryColor() {
+        final TaskDescription taskDescription = (TaskDescription)
+                getIntent().getExtra(EXTRA_TASK_DESCRIPTION);
+        if (taskDescription != null && Color.alpha(taskDescription.getPrimaryColor()) == 255) {
+            return taskDescription.getPrimaryColor();
+        } else {
+            // No task description. Use an organization color set by the policy controller.
+            final DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
+                    getSystemService(Context.DEVICE_POLICY_SERVICE);
+            return devicePolicyManager.getOrganizationColorForUser(getTargetUserId());
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index e6483f6..a49c482 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -31,17 +31,21 @@
 
 public class WorkLockActivityController {
     private final Context mContext;
+    final SystemServicesProxy mSsp;
 
     public WorkLockActivityController(Context context) {
         mContext = context;
+        mSsp = SystemServicesProxy.getInstance(context);
+
         EventBus.getDefault().register(this);
-        SystemServicesProxy.getInstance(context).registerTaskStackListener(mLockListener);
+        mSsp.registerTaskStackListener(mLockListener);
     }
 
     private void startWorkChallengeInTask(int taskId, int userId) {
         Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
                 .setComponent(new ComponentName(mContext, WorkLockActivity.class))
                 .putExtra(Intent.EXTRA_USER_ID, userId)
+                .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, mSsp.getTaskDescription(taskId))
                 .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 49074a6..eae1b81 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -868,6 +868,14 @@
         return null;
     }
 
+    public ActivityManager.TaskDescription getTaskDescription(int taskId) {
+        try {
+            return mIam.getTaskDescription(taskId);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
     /**
      * Returns the given icon for a user, badging if necessary.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 0160eb7..40aad45 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -20,6 +20,8 @@
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
@@ -1496,11 +1498,6 @@
         // Remove the task from the ignored set
         removeIgnoreTask(removedTask);
 
-        // Resize the grid layout task view focus frame
-        if (mTaskViewFocusFrame != null) {
-            mTaskViewFocusFrame.resize();
-        }
-
         // If requested, relayout with the given animation
         if (animation != null) {
             updateLayoutAlgorithm(true /* boundScroll */);
@@ -1838,6 +1835,17 @@
         announceForAccessibility(getContext().getString(
                 R.string.accessibility_recents_item_dismissed, event.task.title));
 
+        if (useGridLayout() && event.animation != null) {
+            event.animation.setListener(new AnimatorListenerAdapter() {
+                public void onAnimationEnd(Animator animator) {
+                    if (mTaskViewFocusFrame != null) {
+                        // Resize the grid layout task view focus frame
+                        mTaskViewFocusFrame.resize();
+                    }
+                }
+            });
+        }
+
         // Remove the task from the stack
         mStack.removeTask(event.task, event.animation, false /* fromDockGesture */);
         EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 9a52a7b..a5f7832 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -270,7 +270,6 @@
         return super.onInterceptTouchEvent(ev);
     }
 
-
     @Override
     protected void measureContents(int width, int height) {
         int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index bd5fb92..0ea56b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -381,12 +381,7 @@
                         if (appShortcuts != null) {
                             result.add(appShortcuts);
                         }
-                        synchronized (sLock) {
-                            // showKeyboardShortcutsDialog only if it has not been dismissed already
-                            if (sInstance != null) {
-                                showKeyboardShortcutsDialog(result);
-                            }
-                        }
+                        showKeyboardShortcutsDialog(result);
                     }
                 }, deviceId);
     }
@@ -585,7 +580,12 @@
         mKeyboardShortcutsDialog.setCanceledOnTouchOutside(true);
         Window keyboardShortcutsWindow = mKeyboardShortcutsDialog.getWindow();
         keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
-        mKeyboardShortcutsDialog.show();
+        synchronized (sLock) {
+            // showKeyboardShortcutsDialog only if it has not been dismissed already
+            if (sInstance != null) {
+                mKeyboardShortcutsDialog.show();
+            }
+        }
     }
 
     private void populateKeyboardShortcuts(LinearLayout keyboardShortcutsLayout,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
new file mode 100644
index 0000000..9b868db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard;
+
+import static android.app.ActivityManager.TaskDescription;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.annotation.ColorInt;
+import android.annotation.UserIdInt;
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Looper;
+
+import com.android.systemui.keyguard.WorkLockActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WorkLockActivityTest {
+    private static final @UserIdInt int USER_ID = 270;
+    private static final String TASK_LABEL = "task label";
+
+    private @Mock DevicePolicyManager mDevicePolicyManager;
+    private @Mock KeyguardManager mKeyguardManager;
+    private @Mock Context mContext;
+
+    private WorkLockActivity mActivity;
+
+    private static class WorkLockActivityTestable extends WorkLockActivity {
+        WorkLockActivityTestable(Context baseContext) {
+            super();
+            attachBaseContext(baseContext);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getSystemService(eq(Context.DEVICE_POLICY_SERVICE)))
+                .thenReturn(mDevicePolicyManager);
+        when(mContext.getSystemService(eq(Context.KEYGUARD_SERVICE)))
+                .thenReturn(mKeyguardManager);
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        mActivity = new WorkLockActivityTestable(mContext);
+    }
+
+    @Test
+    public void testBackgroundAlwaysOpaque() throws Exception {
+        final @ColorInt int orgColor = Color.rgb(250, 199, 67);
+        when(mDevicePolicyManager.getOrganizationColorForUser(eq(USER_ID))).thenReturn(orgColor);
+
+        final @ColorInt int opaqueColor= Color.rgb(164, 198, 57);
+        final @ColorInt int transparentColor = Color.argb(0, 0, 0, 0);
+        TaskDescription opaque = new TaskDescription(null, null, opaqueColor);
+        TaskDescription transparent = new TaskDescription(null, null, transparentColor);
+
+        // When a task description is provided with a suitable (opaque) primaryColor, it should be
+        // used as the scrim's background color.
+        mActivity.setIntent(new Intent()
+                .putExtra(Intent.EXTRA_USER_ID, USER_ID)
+                .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, opaque));
+        assertEquals(opaqueColor, mActivity.getPrimaryColor());
+
+        // When a task description is provided but has no primaryColor / the primaryColor is
+        // transparent, the organization color should be used instead.
+        mActivity.setIntent(new Intent()
+                .putExtra(Intent.EXTRA_USER_ID, USER_ID)
+                .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, transparent));
+        assertEquals(orgColor, mActivity.getPrimaryColor());
+
+        // When no task description is provided at all, it should be treated like a transparent
+        // description and the organization color shown instead.
+        mActivity.setIntent(new Intent()
+                .putExtra(Intent.EXTRA_USER_ID, USER_ID));
+        assertEquals(orgColor, mActivity.getPrimaryColor());
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 6bf0e8d..13e6ae0 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -599,15 +599,42 @@
     static final int OP_ACKNOWLEDGED = 1;
     static final int OP_TIMEOUT = -1;
 
-    class Operation {
-        public int state;
-        public BackupRestoreTask callback;
+    private static final int OP_TYPE_WAIT = 0;    // Waiting for BackupAgent.
+    private static final int OP_TYPE_BACKUP = 1;  // Backup operation in progress.
 
-        Operation(int initialState, BackupRestoreTask callbackObj) {
+    class Operation {
+        int state;
+        final BackupRestoreTask callback;
+        final int type;
+
+        Operation(int initialState, BackupRestoreTask callbackObj, int type) {
             state = initialState;
             callback = callbackObj;
+            this.type = type;
         }
     }
+
+    /**
+     * mCurrentOperations contains the list of currently active operations.
+     *
+     * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
+     * An operation wraps a BackupRestoreTask within it.
+     * It's the responsibility of this task to remove the operation from this array.
+     *
+     * A BackupRestore task gets notified of ack/timeout for the operation via
+     * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
+     * on the mCurrentOpLock. {@link BackupManagerService#waitUntilOperationComplete(int)} is
+     * used in various places to 'wait' for notifyAll and detect change of pending state of an
+     * operation. So typically, an operation will be removed from this array by:
+     *   - BackupRestoreTask#handleCancel and
+     *   - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
+     *     these places because waitUntilOperationComplete relies on the operation being present to
+     *     determine its completion status.
+     *
+     * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
+     * cancel backup tasks.
+     */
+    @GuardedBy("mCurrentOpLock")
     final SparseArray<Operation> mCurrentOperations = new SparseArray<Operation>();
     final Object mCurrentOpLock = new Object();
     final Random mTokenGenerator = new Random();
@@ -967,7 +994,8 @@
 
             case MSG_TIMEOUT:
             {
-                handleTimeout(msg.arg1, msg.obj);
+                Slog.d(TAG, "Timeout message received for token=" + Integer.toHexString(msg.arg1));
+                handleCancel(msg.arg1, false);
                 break;
             }
 
@@ -2364,6 +2392,33 @@
         return BackupManager.SUCCESS;
     }
 
+    // Cancel all running backups.
+    public void cancelBackups(){
+        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups");
+        if (MORE_DEBUG) {
+            Slog.i(TAG, "cancelBackups() called.");
+        }
+        final long oldToken = Binder.clearCallingIdentity();
+        try {
+            synchronized (mCurrentOpLock) {
+                for (int i = 0; i < mCurrentOperations.size(); i++) {
+                    Operation op = mCurrentOperations.valueAt(i);
+                    int token = mCurrentOperations.keyAt(i);
+                    if (op.type == OP_TYPE_BACKUP) {
+                        handleCancel(token, true /* cancelAll */);
+                    }
+                }
+            }
+
+            // We don't want the backup jobs to kick in any time soon.
+            // Reschedules them to run in the distant future.
+            KeyValueBackupJob.schedule(mContext, BUSY_BACKOFF_MIN_MILLIS);
+            FullBackupJob.schedule(mContext, 2 * BUSY_BACKOFF_MIN_MILLIS);
+        } finally {
+            Binder.restoreCallingIdentity(oldToken);
+        }
+    }
+
     // -----
     // Interface and methods used by the asynchronous-with-timeout backup/restore operations
 
@@ -2375,20 +2430,34 @@
         void operationComplete(long result);
 
         // An operation that wanted a callback has timed out
-        void handleTimeout();
+        void handleCancel(boolean cancelAll);
     }
 
-    void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback) {
+    void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
+            int operationType) {
         if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
                 + " interval=" + interval + " callback=" + callback);
         synchronized (mCurrentOpLock) {
-            mCurrentOperations.put(token, new Operation(OP_PENDING, callback));
+            mCurrentOperations.put(token, new Operation(OP_PENDING, callback, operationType));
 
             Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0, callback);
             mBackupHandler.sendMessageDelayed(msg, interval);
         }
     }
 
+    private void removeOperation(int token) {
+        if (MORE_DEBUG) {
+            Slog.d(TAG, "Removing operation token=" + Integer.toHexString(token));
+        }
+        synchronized (mCurrentOpLock) {
+            if (mCurrentOperations.get(token) == null) {
+                Slog.w(TAG, "Duplicate remove for operation. token=" +
+                        Integer.toHexString(token));
+            }
+            mCurrentOperations.remove(token);
+        }
+    }
+
     // synchronous waiter case
     boolean waitUntilOperationComplete(int token) {
         if (MORE_DEBUG) Slog.i(TAG, "Blocking until operation complete for "
@@ -2405,9 +2474,14 @@
                     if (op.state == OP_PENDING) {
                         try {
                             mCurrentOpLock.wait();
-                        } catch (InterruptedException e) {}
+                        } catch (InterruptedException e) {
+                        }
                         // When the wait is notified we loop around and recheck the current state
                     } else {
+                        if (MORE_DEBUG) {
+                            Slog.d(TAG, "Unblocked waiting for operation token=" +
+                                    Integer.toHexString(token));
+                        }
                         // No longer pending; we're done
                         finalState = op.state;
                         break;
@@ -2416,33 +2490,36 @@
             }
         }
 
+        removeOperation(token);
         mBackupHandler.removeMessages(MSG_TIMEOUT);
         if (MORE_DEBUG) Slog.v(TAG, "operation " + Integer.toHexString(token)
                 + " complete: finalState=" + finalState);
         return finalState == OP_ACKNOWLEDGED;
     }
 
-    void handleTimeout(int token, Object obj) {
+    void handleCancel(int token, boolean cancelAll) {
         // Notify any synchronous waiters
         Operation op = null;
         synchronized (mCurrentOpLock) {
             op = mCurrentOperations.get(token);
             if (MORE_DEBUG) {
-                if (op == null) Slog.w(TAG, "Timeout of token " + Integer.toHexString(token)
+                if (op == null) Slog.w(TAG, "Cancel of token " + Integer.toHexString(token)
                         + " but no op found");
             }
             int state = (op != null) ? op.state : OP_TIMEOUT;
             if (state == OP_ACKNOWLEDGED) {
                 // The operation finished cleanly, so we have nothing more to do.
-                if (MORE_DEBUG) {
-                    Slog.v(TAG, "handleTimeout() after success; cleanup happens now");
+                if (DEBUG) {
+                    Slog.w(TAG, "Operation already got an ack." +
+                            "Should have been removed from mCurrentOperations.");
                 }
                 op = null;
                 mCurrentOperations.delete(token);
             } else if (state == OP_PENDING) {
-                if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + Integer.toHexString(token));
+                if (DEBUG) Slog.v(TAG, "Cancel: token=" + Integer.toHexString(token));
                 op.state = OP_TIMEOUT;
-                // Leaves the object in place for later ack
+                // Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
+                // called after we receive cancel here. We need this op's state there.
             }
             mCurrentOpLock.notifyAll();
         }
@@ -2450,9 +2527,9 @@
         // If there's a TimeoutHandler for this event, call it
         if (op != null && op.callback != null) {
             if (MORE_DEBUG) {
-                Slog.v(TAG, "   Invoking timeout on " + op.callback);
+                Slog.v(TAG, "   Invoking cancel on " + op.callback);
             }
-            op.callback.handleTimeout();
+            op.callback.handleCancel(cancelAll);
         }
     }
 
@@ -2464,9 +2541,36 @@
         FINAL
     }
 
+    /**
+     * This class handles the process of backing up a given list of key/value backup packages.
+     * Also takes in a list of pending dolly backups and kicks them off when key/value backups
+     * are done.
+     *
+     * Flow:
+     * If required, backup @pm@.
+     * For each pending key/value backup package:
+     *     - Bind to agent.
+     *     - Call agent.doBackup()
+     *     - Wait either for cancel/timeout or operationComplete() callback from the agent.
+     * Start task to perform dolly backups.
+     *
+     * There are three entry points into this class:
+     *     - execute() [Called from the handler thread]
+     *     - operationComplete(long result) [Called from the handler thread]
+     *     - handleCancel(boolean cancelAll) [Can be called from any thread]
+     * These methods synchronize on mCancelLock.
+     *
+     * Interaction with mCurrentOperations:
+     *     - An entry for this task is put into mCurrentOperations for the entire lifetime of the
+     *       task. This is useful to cancel the task if required.
+     *     - An ephemeral entry is put into mCurrentOperations each time we are waiting on for
+     *       response from a backup agent. This is used to plumb timeouts and completion callbacks.
+     */
     class PerformBackupTask implements BackupRestoreTask {
         private static final String TAG = "PerformBackupTask";
 
+        private final Object mCancelLock = new Object();
+
         IBackupTransport mTransport;
         ArrayList<BackupRequest> mQueue;
         ArrayList<BackupRequest> mOriginalQueue;
@@ -2477,6 +2581,10 @@
         IBackupObserver mObserver;
         IBackupManagerMonitor mMonitor;
 
+        private final PerformFullTransportBackupTask mFullBackupTask;
+        private final int mCurrentOpToken;
+        private volatile int mEphemeralOpToken;
+
         // carried information about the current in-flight operation
         IBackupAgent mAgentBinder;
         PackageInfo mCurrentPackage;
@@ -2491,6 +2599,8 @@
         final boolean mUserInitiated;
         final boolean mNonIncremental;
 
+        private volatile boolean mCancelAll;
+
         public PerformBackupTask(IBackupTransport transport, String dirName,
                 ArrayList<BackupRequest> queue, File journal, IBackupObserver observer,
                 IBackupManagerMonitor monitor, ArrayList<String> pendingFullBackups,
@@ -2505,33 +2615,63 @@
             mNonIncremental = nonIncremental;
 
             mStateDir = new File(mBaseStateDir, dirName);
+            mCurrentOpToken = generateToken();
 
             mCurrentState = BackupState.INITIAL;
             mFinished = false;
 
+            CountDownLatch latch = new CountDownLatch(1);
+            String[] fullBackups =
+                    mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]);
+            mFullBackupTask =
+                    new PerformFullTransportBackupTask(/*fullBackupRestoreObserver*/ null,
+                            fullBackups, /*updateSchedule*/ false, /*runningJob*/ null, latch,
+                            mObserver, mMonitor,mUserInitiated);
+
+            registerTask();
             addBackupTrace("STATE => INITIAL");
         }
 
+        /**
+         * Put this task in the repository of running tasks.
+         */
+        private void registerTask() {
+            synchronized (mCurrentOpLock) {
+                mCurrentOperations.put(mCurrentOpToken, new Operation(OP_PENDING, this,
+                        OP_TYPE_BACKUP));
+            }
+        }
+
+        /**
+         * Remove this task from repository of running tasks.
+         */
+        private void unregisterTask() {
+            removeOperation(mCurrentOpToken);
+        }
+
         // Main entry point: perform one chunk of work, updating the state as appropriate
         // and reposting the next chunk to the primary backup handler thread.
         @Override
+        @GuardedBy("mCancelLock")
         public void execute() {
-            switch (mCurrentState) {
-                case INITIAL:
-                    beginBackup();
-                    break;
+            synchronized (mCancelLock) {
+                switch (mCurrentState) {
+                    case INITIAL:
+                        beginBackup();
+                        break;
 
-                case RUNNING_QUEUE:
-                    invokeNextAgent();
-                    break;
+                    case RUNNING_QUEUE:
+                        invokeNextAgent();
+                        break;
 
-                case FINAL:
-                    if (!mFinished) finalizeBackup();
-                    else {
-                        Slog.e(TAG, "Duplicate finish");
-                    }
-                    mFinished = true;
-                    break;
+                    case FINAL:
+                        if (!mFinished) finalizeBackup();
+                        else {
+                            Slog.e(TAG, "Duplicate finish");
+                        }
+                        mFinished = true;
+                        break;
+                }
             }
         }
 
@@ -2796,6 +2936,12 @@
         void finalizeBackup() {
             addBackupTrace("finishing");
 
+            // Mark packages that we didn't backup (because backup was cancelled, etc.) as needing
+            // backup.
+            for (BackupRequest req : mQueue) {
+                dataChangedImpl(req.packageName);
+            }
+
             // Either backup was successful, in which case we of course do not need
             // this pass's journal any more; or it failed, in which case we just
             // re-enqueued all of these packages in the current active journal.
@@ -2850,7 +2996,9 @@
 
             clearBackupTrace();
 
-            if (mStatus == BackupTransport.TRANSPORT_OK &&
+            unregisterTask();
+
+            if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK &&
                     mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
                 Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
                 CountDownLatch latch = new CountDownLatch(1);
@@ -2862,8 +3010,12 @@
                                 mObserver, mMonitor, mUserInitiated);
                 // Acquiring wakelock for PerformFullTransportBackupTask before its start.
                 mWakelock.acquire();
-                (new Thread(task, "full-transport-requested")).start();
+                (new Thread(mFullBackupTask, "full-transport-requested")).start();
+            } else if (mCancelAll) {
+                mFullBackupTask.unregisterTask();
+                sendBackupFinished(mObserver, BackupManager.ERROR_BACKUP_CANCELLED);
             } else {
+                mFullBackupTask.unregisterTask();
                 switch (mStatus) {
                     case BackupTransport.TRANSPORT_OK:
                         sendBackupFinished(mObserver, BackupManager.SUCCESS);
@@ -2905,8 +3057,8 @@
             mBackupData = null;
             mNewState = null;
 
-            final int token = generateToken();
             boolean callingAgent = false;
+            mEphemeralOpToken = generateToken();
             try {
                 // Look up the package info & signatures.  This is first so that if it
                 // throws an exception, there's no file setup yet that would need to
@@ -2945,10 +3097,11 @@
 
                 // Initiate the target's backup pass
                 addBackupTrace("setting timeout");
-                prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, this);
+                prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_BACKUP_INTERVAL, this,
+                        OP_TYPE_WAIT);
                 addBackupTrace("calling agent doBackup()");
 
-                agent.doBackup(mSavedState, mBackupData, mNewState, quota, token,
+                agent.doBackup(mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
                         mBackupManagerBinder);
             } catch (Exception e) {
                 Slog.e(TAG, "Error invoking for backup on " + packageName + ". " + e);
@@ -3060,195 +3213,225 @@
         }
 
         @Override
+        @GuardedBy("mCancelLock")
         public void operationComplete(long unusedResult) {
-            // The agent reported back to us!
-
-            if (mBackupData == null) {
-                // This callback was racing with our timeout, so we've cleaned up the
-                // agent state already and are on to the next thing.  We have nothing
-                // further to do here: agent state having been cleared means that we've
-                // initiated the appropriate next operation.
-                final String pkg = (mCurrentPackage != null)
-                        ? mCurrentPackage.packageName : "[none]";
-                if (MORE_DEBUG) {
-                    Slog.i(TAG, "Callback after agent teardown: " + pkg);
+            removeOperation(mEphemeralOpToken);
+            synchronized (mCancelLock) {
+                // The agent reported back to us!
+                if (mFinished) {
+                    Slog.d(TAG, "operationComplete received after task finished.");
+                    return;
                 }
-                addBackupTrace("late opComplete; curPkg = " + pkg);
-                return;
-            }
 
-            final String pkgName = mCurrentPackage.packageName;
-            final long filepos = mBackupDataName.length();
-            FileDescriptor fd = mBackupData.getFileDescriptor();
-            try {
-                // If it's a 3rd party app, see whether they wrote any protected keys
-                // and complain mightily if they are attempting shenanigans.
-                if (mCurrentPackage.applicationInfo != null &&
-                        (mCurrentPackage.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
-                    ParcelFileDescriptor readFd = ParcelFileDescriptor.open(mBackupDataName,
-                            ParcelFileDescriptor.MODE_READ_ONLY);
-                    BackupDataInput in = new BackupDataInput(readFd.getFileDescriptor());
-                    try {
-                        while (in.readNextHeader()) {
-                            final String key = in.getKey();
-                            if (key != null && key.charAt(0) >= 0xff00) {
-                                // Not okay: crash them and bail.
-                                failAgent(mAgentBinder, "Illegal backup key: " + key);
-                                addBackupTrace("illegal key " + key + " from " + pkgName);
-                                EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
-                                        "bad key");
-                                mBackupHandler.removeMessages(MSG_TIMEOUT);
-                                sendBackupOnPackageResult(mObserver, pkgName,
-                                        BackupManager.ERROR_AGENT_FAILURE);
-                                errorCleanup();
-                                // errorCleanup() implicitly executes next state properly
-                                return;
-                            }
-                            in.skipEntityData();
-                        }
-                    } finally {
-                        if (readFd != null) {
-                            readFd.close();
-                        }
+                if (mBackupData == null) {
+                    // This callback was racing with our timeout, so we've cleaned up the
+                    // agent state already and are on to the next thing.  We have nothing
+                    // further to do here: agent state having been cleared means that we've
+                    // initiated the appropriate next operation.
+                    final String pkg = (mCurrentPackage != null)
+                            ? mCurrentPackage.packageName : "[none]";
+                    if (MORE_DEBUG) {
+                        Slog.i(TAG, "Callback after agent teardown: " + pkg);
                     }
+                    addBackupTrace("late opComplete; curPkg = " + pkg);
+                    return;
                 }
 
-                // Piggyback the widget state payload, if any
-                writeWidgetPayloadIfAppropriate(fd, pkgName);
-            } catch (IOException e) {
-                // Hard disk error; recovery/failure policy TBD.  For now roll back,
-                // but we may want to consider this a transport-level failure (i.e.
-                // we're in such a bad state that we can't contemplate doing backup
-                // operations any more during this pass).
-                Slog.w(TAG, "Unable to save widget state for " + pkgName);
+                final String pkgName = mCurrentPackage.packageName;
+                final long filepos = mBackupDataName.length();
+                FileDescriptor fd = mBackupData.getFileDescriptor();
                 try {
-                    Os.ftruncate(fd, filepos);
-                } catch (ErrnoException ee) {
-                    Slog.w(TAG, "Unable to roll back!");
-                }
-            }
-
-            // Spin the data off to the transport and proceed with the next stage.
-            if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
-                    + pkgName);
-            mBackupHandler.removeMessages(MSG_TIMEOUT);
-            clearAgentState();
-            addBackupTrace("operation complete");
-
-            ParcelFileDescriptor backupData = null;
-            mStatus = BackupTransport.TRANSPORT_OK;
-            long size = 0;
-            try {
-                size = mBackupDataName.length();
-                if (size > 0) {
-                    if (mStatus == BackupTransport.TRANSPORT_OK) {
-                        backupData = ParcelFileDescriptor.open(mBackupDataName,
+                    // If it's a 3rd party app, see whether they wrote any protected keys
+                    // and complain mightily if they are attempting shenanigans.
+                    if (mCurrentPackage.applicationInfo != null &&
+                            (mCurrentPackage.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                                    == 0) {
+                        ParcelFileDescriptor readFd = ParcelFileDescriptor.open(mBackupDataName,
                                 ParcelFileDescriptor.MODE_READ_ONLY);
-                        addBackupTrace("sending data to transport");
-                        int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
-                        mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+                        BackupDataInput in = new BackupDataInput(readFd.getFileDescriptor());
+                        try {
+                            while (in.readNextHeader()) {
+                                final String key = in.getKey();
+                                if (key != null && key.charAt(0) >= 0xff00) {
+                                    // Not okay: crash them and bail.
+                                    failAgent(mAgentBinder, "Illegal backup key: " + key);
+                                    addBackupTrace("illegal key " + key + " from " + pkgName);
+                                    EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
+                                            "bad key");
+                                    mBackupHandler.removeMessages(MSG_TIMEOUT);
+                                    sendBackupOnPackageResult(mObserver, pkgName,
+                                            BackupManager.ERROR_AGENT_FAILURE);
+                                    errorCleanup();
+                                    // agentErrorCleanup() implicitly executes next state properly
+                                    return;
+                                }
+                                in.skipEntityData();
+                            }
+                        } finally {
+                            if (readFd != null) {
+                                readFd.close();
+                            }
+                        }
                     }
 
-                    // TODO - We call finishBackup() for each application backed up, because
-                    // we need to know now whether it succeeded or failed.  Instead, we should
-                    // hold off on finishBackup() until the end, which implies holding off on
-                    // renaming *all* the output state files (see below) until that happens.
-
-                    addBackupTrace("data delivered: " + mStatus);
-                    if (mStatus == BackupTransport.TRANSPORT_OK) {
-                        addBackupTrace("finishing op on transport");
-                        mStatus = mTransport.finishBackup();
-                        addBackupTrace("finished: " + mStatus);
-                    } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
-                        addBackupTrace("transport rejected package");
+                    // Piggyback the widget state payload, if any
+                    writeWidgetPayloadIfAppropriate(fd, pkgName);
+                } catch (IOException e) {
+                    // Hard disk error; recovery/failure policy TBD.  For now roll back,
+                    // but we may want to consider this a transport-level failure (i.e.
+                    // we're in such a bad state that we can't contemplate doing backup
+                    // operations any more during this pass).
+                    Slog.w(TAG, "Unable to save widget state for " + pkgName);
+                    try {
+                        Os.ftruncate(fd, filepos);
+                    } catch (ErrnoException ee) {
+                        Slog.w(TAG, "Unable to roll back!");
                     }
-                } else {
-                    if (MORE_DEBUG) Slog.i(TAG, "no backup data written; not calling transport");
-                    addBackupTrace("no data to send");
                 }
 
-                if (mStatus == BackupTransport.TRANSPORT_OK) {
-                    // After successful transport, delete the now-stale data
-                    // and juggle the files so that next time we supply the agent
-                    // with the new state file it just created.
-                    mBackupDataName.delete();
-                    mNewStateName.renameTo(mSavedStateName);
-                    sendBackupOnPackageResult(mObserver, pkgName, BackupManager.SUCCESS);
-                    EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
-                    logBackupComplete(pkgName);
-                } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
-                    // The transport has rejected backup of this specific package.  Roll it
-                    // back but proceed with running the rest of the queue.
-                    mBackupDataName.delete();
-                    mNewStateName.delete();
-                    sendBackupOnPackageResult(mObserver, pkgName,
-                            BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
-                    EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
-                } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
-                    sendBackupOnPackageResult(mObserver, pkgName,
-                            BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
-                    EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
-                } else {
-                    // Actual transport-level failure to communicate the data to the backend
+                // Spin the data off to the transport and proceed with the next stage.
+                if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
+                        + pkgName);
+                mBackupHandler.removeMessages(MSG_TIMEOUT);
+                clearAgentState();
+                addBackupTrace("operation complete");
+
+                ParcelFileDescriptor backupData = null;
+                mStatus = BackupTransport.TRANSPORT_OK;
+                long size = 0;
+                try {
+                    size = mBackupDataName.length();
+                    if (size > 0) {
+                        if (mStatus == BackupTransport.TRANSPORT_OK) {
+                            backupData = ParcelFileDescriptor.open(mBackupDataName,
+                                    ParcelFileDescriptor.MODE_READ_ONLY);
+                            addBackupTrace("sending data to transport");
+                            int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+                            mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+                        }
+
+                        // TODO - We call finishBackup() for each application backed up, because
+                        // we need to know now whether it succeeded or failed.  Instead, we should
+                        // hold off on finishBackup() until the end, which implies holding off on
+                        // renaming *all* the output state files (see below) until that happens.
+
+                        addBackupTrace("data delivered: " + mStatus);
+                        if (mStatus == BackupTransport.TRANSPORT_OK) {
+                            addBackupTrace("finishing op on transport");
+                            mStatus = mTransport.finishBackup();
+                            addBackupTrace("finished: " + mStatus);
+                        } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+                            addBackupTrace("transport rejected package");
+                        }
+                    } else {
+                        if (MORE_DEBUG) Slog.i(TAG,
+                                "no backup data written; not calling transport");
+                        addBackupTrace("no data to send");
+                    }
+
+                    if (mStatus == BackupTransport.TRANSPORT_OK) {
+                        // After successful transport, delete the now-stale data
+                        // and juggle the files so that next time we supply the agent
+                        // with the new state file it just created.
+                        mBackupDataName.delete();
+                        mNewStateName.renameTo(mSavedStateName);
+                        sendBackupOnPackageResult(mObserver, pkgName, BackupManager.SUCCESS);
+                        EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
+                        logBackupComplete(pkgName);
+                    } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+                        // The transport has rejected backup of this specific package.  Roll it
+                        // back but proceed with running the rest of the queue.
+                        mBackupDataName.delete();
+                        mNewStateName.delete();
+                        sendBackupOnPackageResult(mObserver, pkgName,
+                                BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
+                        EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
+                    } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+                        sendBackupOnPackageResult(mObserver, pkgName,
+                                BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
+                        EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
+                    } else {
+                        // Actual transport-level failure to communicate the data to the backend
+                        sendBackupOnPackageResult(mObserver, pkgName,
+                                BackupManager.ERROR_TRANSPORT_ABORTED);
+                        EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
+                    }
+                } catch (Exception e) {
                     sendBackupOnPackageResult(mObserver, pkgName,
                             BackupManager.ERROR_TRANSPORT_ABORTED);
+                    Slog.e(TAG, "Transport error backing up " + pkgName, e);
                     EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
-                }
-            } catch (Exception e) {
-                sendBackupOnPackageResult(mObserver, pkgName,
-                        BackupManager.ERROR_TRANSPORT_ABORTED);
-                Slog.e(TAG, "Transport error backing up " + pkgName, e);
-                EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
-                mStatus = BackupTransport.TRANSPORT_ERROR;
-            } finally {
-                try { if (backupData != null) backupData.close(); } catch (IOException e) {}
-            }
-
-            final BackupState nextState;
-            if (mStatus == BackupTransport.TRANSPORT_OK
-                    || mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
-                // Success or single-package rejection.  Proceed with the next app if any,
-                // otherwise we're done.
-                nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
-            } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
-                if (MORE_DEBUG) {
-                    Slog.d(TAG, "Package " + mCurrentPackage.packageName +
-                            " hit quota limit on k/v backup");
-                }
-                if (mAgentBinder != null) {
+                    mStatus = BackupTransport.TRANSPORT_ERROR;
+                } finally {
                     try {
-                        long quota = mTransport.getBackupQuota(mCurrentPackage.packageName, false);
-                        mAgentBinder.doQuotaExceeded(size, quota);
-                    } catch (Exception e) {
-                        Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
+                        if (backupData != null) backupData.close();
+                    } catch (IOException e) {
                     }
                 }
-                nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
-            } else {
-                // Any other error here indicates a transport-level failure.  That means
-                // we need to halt everything and reschedule everything for next time.
-                revertAndEndBackup();
-                nextState = BackupState.FINAL;
-            }
 
-            executeNextState(nextState);
+                final BackupState nextState;
+                if (mStatus == BackupTransport.TRANSPORT_OK
+                        || mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+                    // Success or single-package rejection.  Proceed with the next app if any,
+                    // otherwise we're done.
+                    nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+                } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+                    if (MORE_DEBUG) {
+                        Slog.d(TAG, "Package " + mCurrentPackage.packageName +
+                                " hit quota limit on k/v backup");
+                    }
+                    if (mAgentBinder != null) {
+                        try {
+                            long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
+                                    false);
+                            mAgentBinder.doQuotaExceeded(size, quota);
+                        } catch (Exception e) {
+                            Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
+                        }
+                    }
+                    nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+                } else {
+                    // Any other error here indicates a transport-level failure.  That means
+                    // we need to halt everything and reschedule everything for next time.
+                    revertAndEndBackup();
+                    nextState = BackupState.FINAL;
+                }
+
+                executeNextState(nextState);
+            }
         }
 
+
         @Override
-        public void handleTimeout() {
-            // Whoops, the current agent timed out running doBackup().  Tidy up and restage
-            // it for the next time we run a backup pass.
-            // !!! TODO: keep track of failure counts per agent, and blacklist those which
-            // fail repeatedly (i.e. have proved themselves to be buggy).
-            Slog.e(TAG, "Timeout backing up " + mCurrentPackage.packageName);
-            EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName,
-                    "timeout");
-            mMonitor = monitorEvent(mMonitor,
-                    BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT,
-                    mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
-            addBackupTrace("timeout of " + mCurrentPackage.packageName);
-            errorCleanup();
-            dataChangedImpl(mCurrentPackage.packageName);
+        @GuardedBy("mCancelLock")
+        public void handleCancel(boolean cancelAll) {
+            removeOperation(mEphemeralOpToken);
+            synchronized (mCancelLock) {
+                if (mFinished) {
+                    // We have already cancelled this operation.
+                    if (MORE_DEBUG) {
+                        Slog.d(TAG, "Ignoring stale cancel. cancelAll=" + cancelAll);
+                    }
+                    return;
+                }
+                mCancelAll = cancelAll;
+                // Whoops, the current agent timed out running doBackup().  Tidy up and restage
+                // it for the next time we run a backup pass.
+                // !!! TODO: keep track of failure counts per agent, and blacklist those which
+                // fail repeatedly (i.e. have proved themselves to be buggy).
+                Slog.e(TAG, "Cancel backing up " + mCurrentPackage.packageName);
+                EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName);
+                addBackupTrace(
+                        "cancel of " + mCurrentPackage.packageName + ", cancelAll=" + cancelAll);
+                errorCleanup();
+                if (!cancelAll) {
+                    executeNextState(
+                            mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
+                    dataChangedImpl(mCurrentPackage.packageName);
+                } else {
+                    finalizeBackup();
+                }
+            }
         }
 
         void revertAndEndBackup() {
@@ -3276,8 +3459,6 @@
             mBackupDataName.delete();
             mNewStateName.delete();
             clearAgentState();
-
-            executeNextState(mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
         }
 
         // Cleanup common to both success and failure cases
@@ -3289,7 +3470,7 @@
                 // Current-operation callback handling requires the validity of these various
                 // bits of internal state as an invariant of the operation still being live.
                 // This means we make sure to clear all of the state in unison inside the lock.
-                mCurrentOperations.clear();
+                mCurrentOperations.remove(mEphemeralOpToken);
                 mSavedState = mBackupData = mNewState = null;
             }
 
@@ -3343,7 +3524,7 @@
             try {
                 pipes = ParcelFileDescriptor.createPipe();
                 int token = generateToken();
-                prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
+                prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null, OP_TYPE_WAIT);
                 mService.backupObbs(pkg.packageName, pipes[1], token, mBackupManagerBinder);
                 routeSocketDataToOutput(pipes[0], out);
                 success = waitUntilOperationComplete(token);
@@ -3481,6 +3662,7 @@
         boolean mIncludeApks;
         PackageInfo mPkg;
         private final long mQuota;
+        private final int mOpToken;
 
         class FullBackupRunner implements Runnable {
             PackageInfo mPackage;
@@ -3535,7 +3717,7 @@
 
                     if (DEBUG) Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName);
                     prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL,
-                            mTimeoutMonitor /* in parent class */);
+                            mTimeoutMonitor /* in parent class */, OP_TYPE_WAIT);
                     mAgent.doFullBackup(mPipe, mQuota, mToken, mBackupManagerBinder);
                 } catch (IOException e) {
                     Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
@@ -3551,7 +3733,7 @@
         }
 
         FullBackupEngine(OutputStream output, FullBackupPreflight preflightHook, PackageInfo pkg,
-                         boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota) {
+                         boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken) {
             mOutput = output;
             mPreflightHook = preflightHook;
             mPkg = pkg;
@@ -3561,6 +3743,7 @@
             mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
             mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
             mQuota = quota;
+            mOpToken = opToken;
         }
 
         public int preflightCheck() throws RemoteException {
@@ -3603,9 +3786,8 @@
                     byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(mPkg.packageName,
                             UserHandle.USER_SYSTEM);
 
-                    final int token = generateToken();
                     FullBackupRunner runner = new FullBackupRunner(mPkg, mAgent, pipes[1],
-                            token, sendApk, !isSharedStorage, widgetBlob);
+                            mOpToken, sendApk, !isSharedStorage, widgetBlob);
                     pipes[1].close();   // the runner has dup'd it
                     pipes[1] = null;
                     Thread t = new Thread(runner, "app-data-runner");
@@ -3614,7 +3796,7 @@
                     // Now pull data from the app and stuff it into the output
                     routeSocketDataToOutput(pipes[0], mOutput);
 
-                    if (!waitUntilOperationComplete(token)) {
+                    if (!waitUntilOperationComplete(mOpToken)) {
                         Slog.e(TAG, "Full backup failed on package " + mPkg.packageName);
                     } else {
                         if (MORE_DEBUG) {
@@ -3865,12 +4047,14 @@
         PackageInfo mCurrentTarget;
         String mCurrentPassword;
         String mEncryptPassword;
+        private final int mCurrentOpToken;
 
         PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
                 boolean includeApks, boolean includeObbs, boolean includeShared,
                 boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
                 boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) {
             super(observer);
+            mCurrentOpToken = generateToken();
             mLatch = latch;
 
             mOutputFile = fd;
@@ -4156,8 +4340,7 @@
                     final boolean isSharedStorage =
                             pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
 
-                    mBackupEngine = new FullBackupEngine(out, null, pkg, mIncludeApks,
-                            this /* BackupRestoreTask */, Long.MAX_VALUE /* quota */);
+                    mBackupEngine = new FullBackupEngine(out, null, pkg, mIncludeApks, this, Long.MAX_VALUE, mCurrentOpToken);
                     sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
 
                     // Don't need to check preflight result as there is no preflight hook.
@@ -4190,9 +4373,6 @@
                 } catch (IOException e) {
                     /* nothing we can do about this */
                 }
-                synchronized (mCurrentOpLock) {
-                    mCurrentOperations.clear();
-                }
                 synchronized (mLatch) {
                     mLatch.set(true);
                     mLatch.notifyAll();
@@ -4216,29 +4396,68 @@
         }
 
         @Override
-        public void handleTimeout() {
+        public void handleCancel(boolean cancelAll) {
             final PackageInfo target = mCurrentTarget;
             if (DEBUG) {
-                Slog.w(TAG, "adb backup timeout of " + target);
+                Slog.w(TAG, "adb backup cancel of " + target);
             }
             if (target != null) {
                 tearDownAgentAndKill(mCurrentTarget.applicationInfo);
             }
+            removeOperation(mCurrentOpToken);
         }
     }
 
-    // Full backup task extension used for transport-oriented operation
-    class PerformFullTransportBackupTask extends FullBackupTask {
+    /**
+     * Full backup task extension used for transport-oriented operation.
+     *
+     * Flow:
+     * For each requested package:
+     *     - Spin off a new SinglePackageBackupRunner (mBackupRunner) for the current package.
+     *     - Wait until preflight is complete. (mBackupRunner.getPreflightResultBlocking())
+     *     - If preflight data size is within limit, start reading data from agent pipe and writing
+     *       to transport pipe. While there is data to send, call transport.sendBackupData(int) to
+     *       tell the transport how many bytes to expect on its pipe.
+     *     - After sending all data, call transport.finishBackup() if things went well. And
+     *       transport.cancelFullBackup() otherwise.
+     *
+     * Interactions with mCurrentOperations:
+     *     - An entry for this object is added to mCurrentOperations for the entire lifetime of this
+     *       object. Used to cancel the operation.
+     *     - SinglePackageBackupRunner and SinglePackageBackupPreflight will put ephemeral entries
+     *       to get timeouts or operation complete callbacks.
+     *
+     * Handling cancels:
+     *     - The contract we provide is that the task won't interact with the transport after
+     *       handleCancel() is done executing.
+     *     - This task blocks at 3 points: 1. Preflight result check 2. Reading on agent side pipe
+     *       and 3. Get backup result from mBackupRunner.
+     *     - Bubbling up handleCancel to mBackupRunner handles all 3: 1. Calls handleCancel on the
+     *       preflight operation which counts down on the preflight latch. 2. Tears down the agent,
+     *       so read() returns -1. 3. Notifies mCurrentOpLock which unblocks
+     *       mBackupRunner.getBackupResultBlocking().
+     */
+    class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
         static final String TAG = "PFTBT";
+
+        private final Object mCancelLock = new Object();
+
         ArrayList<PackageInfo> mPackages;
         PackageInfo mCurrentPackage;
         boolean mUpdateSchedule;
         CountDownLatch mLatch;
-        AtomicBoolean mKeepRunning;     // signal from job scheduler
         FullBackupJob mJob;             // if a scheduled job needs to be finished afterwards
         IBackupObserver mBackupObserver;
         IBackupManagerMonitor mMonitor;
         boolean mUserInitiated;
+        private volatile IBackupTransport mTransport;
+        SinglePackageBackupRunner mBackupRunner;
+        private final int mBackupRunnerOpToken;
+
+        // This is true when a backup operation for some package is in progress.
+        private volatile boolean mIsDoingBackup;
+        private volatile boolean mCancelAll;
+        private final int mCurrentOpToken;
 
         PerformFullTransportBackupTask(IFullBackupRestoreObserver observer,
                 String[] whichPackages, boolean updateSchedule,
@@ -4247,12 +4466,15 @@
             super(observer);
             mUpdateSchedule = updateSchedule;
             mLatch = latch;
-            mKeepRunning = new AtomicBoolean(true);
             mJob = runningJob;
             mPackages = new ArrayList<PackageInfo>(whichPackages.length);
             mBackupObserver = backupObserver;
             mMonitor = monitor;
             mUserInitiated = userInitiated;
+            mCurrentOpToken = generateToken();
+            mBackupRunnerOpToken = generateToken();
+
+            registerTask();
 
             for (String pkg : whichPackages) {
                 try {
@@ -4298,12 +4520,60 @@
             }
         }
 
-        public void setRunning(boolean running) {
-            mKeepRunning.set(running);
+        private void registerTask() {
+            synchronized (mCurrentOpLock) {
+                Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
+                mCurrentOperations.put(mCurrentOpToken, new Operation(OP_PENDING, this,
+                        OP_TYPE_BACKUP));
+            }
+        }
+
+        private void unregisterTask() {
+            removeOperation(mCurrentOpToken);
+        }
+
+        @Override
+        public void execute() {
+            // Nothing to do.
+        }
+
+        @Override
+        public void handleCancel(boolean cancelAll) {
+            synchronized (mCancelLock) {
+                // We only support 'cancelAll = true' case for this task. Cancelling of a single package
+
+                // due to timeout is handled by SinglePackageBackupRunner and SinglePackageBackupPreflight.
+
+                if (!cancelAll) {
+                    Slog.wtf(TAG, "Expected cancelAll to be true.");
+                }
+
+                if (mCancelAll) {
+                    Slog.d(TAG, "Ignoring duplicate cancel call.");
+                    return;
+                }
+
+                mCancelAll = true;
+                if (mIsDoingBackup) {
+                    BackupManagerService.this.handleCancel(mBackupRunnerOpToken, cancelAll);
+                    try {
+                        mTransport.cancelFullBackup();
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e);
+                        // Can't do much.
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void operationComplete(long result) {
+            // Nothing to do.
         }
 
         @Override
         public void run() {
+
             // data from the app, passed to us for bridging to the transport
             ParcelFileDescriptor[] enginePipes = null;
 
@@ -4325,8 +4595,8 @@
                     return;
                 }
 
-                IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
-                if (transport == null) {
+                mTransport = mTransportManager.getCurrentTransportBinder();
+                if (mTransport == null) {
                     Slog.w(TAG, "Transport not present; full data backup not performed");
                     backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
                     return;
@@ -4347,30 +4617,40 @@
 
                     // Tell the transport the data's coming
                     int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
-                    int backupPackageStatus = transport.performFullBackup(currentPackage,
-                            transportPipes[0], flags);
+                    int backupPackageStatus;
+                    long quota = Long.MAX_VALUE;
+                    synchronized (mCancelLock) {
+                        if (mCancelAll) {
+                            break;
+                        }
+                        backupPackageStatus = mTransport.performFullBackup(currentPackage,
+                                transportPipes[0], flags);
+
+                        if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+                            quota = mTransport.getBackupQuota(currentPackage.packageName,
+                                    true /* isFullBackup */);
+                            // Now set up the backup engine / data source end of things
+                            enginePipes = ParcelFileDescriptor.createPipe();
+                            mBackupRunner =
+                                    new SinglePackageBackupRunner(enginePipes[1], currentPackage,
+                                            mTransport, quota, mBackupRunnerOpToken);
+                            // The runner dup'd the pipe half, so we close it here
+                            enginePipes[1].close();
+                            enginePipes[1] = null;
+
+                            mIsDoingBackup = true;
+                        }
+                    }
                     if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
-                        final long quota = transport.getBackupQuota(currentPackage.packageName,
-                                true /* isFullBackup */);
 
                         // The transport has its own copy of the read end of the pipe,
                         // so close ours now
                         transportPipes[0].close();
                         transportPipes[0] = null;
 
-                        // Now set up the backup engine / data source end of things
-                        enginePipes = ParcelFileDescriptor.createPipe();
-
-                        SinglePackageBackupRunner backupRunner =
-                                new SinglePackageBackupRunner(enginePipes[1], currentPackage,
-                                        transport, quota);
-                        // The runner dup'd the pipe half, so we close it here
-                        enginePipes[1].close();
-                        enginePipes[1] = null;
-
                         // Spin off the runner to fetch the app's data and pipe it
                         // into the engine pipes
-                        (new Thread(backupRunner, "package-backup-bridge")).start();
+                        (new Thread(mBackupRunner, "package-backup-bridge")).start();
 
                         // Read data off the engine pipe and pass it to the transport
                         // pipe until we hit EOD on the input stream.  We do not take
@@ -4380,7 +4660,7 @@
                         FileOutputStream out = new FileOutputStream(
                                 transportPipes[1].getFileDescriptor());
                         long totalRead = 0;
-                        final long preflightResult = backupRunner.getPreflightResultBlocking();
+                        final long preflightResult = mBackupRunner.getPreflightResultBlocking();
                         // Preflight result is negative if some error happened on preflight.
                         if (preflightResult < 0) {
                             if (MORE_DEBUG) {
@@ -4392,19 +4672,17 @@
                         } else {
                             int nRead = 0;
                             do {
-                                if (!mKeepRunning.get()) {
-                                    if (DEBUG_SCHEDULING) {
-                                        Slog.i(TAG, "Full backup task told to stop");
-                                    }
-                                    break;
-                                }
                                 nRead = in.read(buffer);
                                 if (MORE_DEBUG) {
                                     Slog.v(TAG, "in.read(buffer) from app: " + nRead);
                                 }
                                 if (nRead > 0) {
                                     out.write(buffer, 0, nRead);
-                                    backupPackageStatus = transport.sendBackupData(nRead);
+                                    synchronized (mCancelLock) {
+                                        if (!mCancelAll) {
+                                            backupPackageStatus = mTransport.sendBackupData(nRead);
+                                        }
+                                    }
                                     totalRead += nRead;
                                     if (mBackupObserver != null && preflightResult > 0) {
                                         sendBackupOnUpdate(mBackupObserver, packageName,
@@ -4413,29 +4691,32 @@
                                 }
                             } while (nRead > 0
                                     && backupPackageStatus == BackupTransport.TRANSPORT_OK);
-
                             // Despite preflight succeeded, package still can hit quota on flight.
                             if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
                                 Slog.w(TAG, "Package hit quota limit in-flight " + packageName
                                         + ": " + totalRead + " of " + quota);
-                                backupRunner.sendQuotaExceeded(totalRead, quota);
+                                mBackupRunner.sendQuotaExceeded(totalRead, quota);
                             }
                         }
 
-                        // If we've lost our running criteria, tell the transport to cancel
-                        // and roll back this (partial) backup payload; otherwise tell it
-                        // that we've reached the clean finish state.
-                        if (!mKeepRunning.get()) {
-                            backupPackageStatus = BackupTransport.TRANSPORT_ERROR;
-                            transport.cancelFullBackup();
-                        } else {
-                            // If we were otherwise in a good state, now interpret the final
-                            // result based on what finishBackup() returns.  If we're in a
-                            // failure case already, preserve that result and ignore whatever
-                            // finishBackup() reports.
-                            final int finishResult = transport.finishBackup();
-                            if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
-                                backupPackageStatus = finishResult;
+                        final int backupRunnerResult = mBackupRunner.getBackupResultBlocking();
+
+                        synchronized (mCancelLock) {
+                            mIsDoingBackup = false;
+                            // If mCancelCurrent is true, we have already called cancelFullBackup().
+                            if (!mCancelAll) {
+                                if (backupRunnerResult == BackupTransport.TRANSPORT_OK) {
+                                    // If we were otherwise in a good state, now interpret the final
+                                    // result based on what finishBackup() returns.  If we're in a
+                                    // failure case already, preserve that result and ignore whatever
+                                    // finishBackup() reports.
+                                    final int finishResult = mTransport.finishBackup();
+                                    if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+                                        backupPackageStatus = finishResult;
+                                    }
+                                } else {
+                                    mTransport.cancelFullBackup();
+                                }
                             }
                         }
 
@@ -4449,8 +4730,7 @@
                         // errors take precedence over agent/app-specific errors for purposes of
                         // determining our course of action.
                         if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
-                            // We still could fail in backup runner thread, getting result from there.
-                            int backupRunnerResult = backupRunner.getBackupResultBlocking();
+                            // We still could fail in backup runner thread.
                             if (backupRunnerResult != BackupTransport.TRANSPORT_OK) {
                                 // If there was an error in runner thread and
                                 // not TRANSPORT_ERROR here, overwrite it.
@@ -4474,7 +4754,7 @@
 
                         // Also ask the transport how long it wants us to wait before
                         // moving on to the next package, if any.
-                        backoff = transport.requestFullBackupTime();
+                        backoff = mTransport.requestFullBackupTime();
                         if (DEBUG_SCHEDULING) {
                             Slog.i(TAG, "Transport suggested backoff=" + backoff);
                         }
@@ -4513,6 +4793,14 @@
                         EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
                         tearDownAgentAndKill(currentPackage.applicationInfo);
                         // Do nothing, clean up, and continue looping.
+                    } else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) {
+                        sendBackupOnPackageResult(mBackupObserver, packageName,
+                                BackupManager.ERROR_BACKUP_CANCELLED);
+                        Slog.w(TAG, "Backup cancelled. package=" + packageName +
+                                ", cancelAll=" + mCancelAll);
+                        EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
+                        tearDownAgentAndKill(currentPackage.applicationInfo);
+                        // Do nothing, clean up, and continue looping.
                     } else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
                         sendBackupOnPackageResult(mBackupObserver, packageName,
                             BackupManager.ERROR_TRANSPORT_ABORTED);
@@ -4542,6 +4830,11 @@
                 backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
                 Slog.w(TAG, "Exception trying full transport backup", e);
             } finally {
+
+                if (mCancelAll) {
+                    backupRunStatus = BackupManager.ERROR_BACKUP_CANCELLED;
+                }
+
                 if (DEBUG) {
                     Slog.i(TAG, "Full backup completed with status: " + backupRunStatus);
                 }
@@ -4550,6 +4843,8 @@
                 cleanUpPipes(transportPipes);
                 cleanUpPipes(enginePipes);
 
+                unregisterTask();
+
                 if (mJob != null) {
                     mJob.finishBackupPass();
                 }
@@ -4565,6 +4860,7 @@
                 if (mUpdateSchedule) {
                     scheduleNextFullBackupJob(backoff);
                 }
+
                 Slog.i(BackupManagerService.TAG, "Full data backup pass finished.");
                 mWakelock.release();
             }
@@ -4601,23 +4897,24 @@
             final CountDownLatch mLatch = new CountDownLatch(1);
             final IBackupTransport mTransport;
             final long mQuota;
+            private final int mCurrentOpToken;
 
-            public SinglePackageBackupPreflight(IBackupTransport transport, long quota) {
+            SinglePackageBackupPreflight(IBackupTransport transport, long quota, int currentOpToken) {
                 mTransport = transport;
                 mQuota = quota;
+                mCurrentOpToken = currentOpToken;
             }
 
             @Override
             public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) {
                 int result;
                 try {
-                    final int token = generateToken();
-                    prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, this);
+                    prepareOperationTimeout(mCurrentOpToken, TIMEOUT_FULL_BACKUP_INTERVAL, this, OP_TYPE_WAIT);
                     addBackupTrace("preflighting");
                     if (MORE_DEBUG) {
                         Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
                     }
-                    agent.doMeasureFullBackup(mQuota, token, mBackupManagerBinder);
+                    agent.doMeasureFullBackup(mQuota, mCurrentOpToken, mBackupManagerBinder);
 
                     // Now wait to get our result back.  If this backstop timeout is reached without
                     // the latch being thrown, flow will continue as though a result or "normal"
@@ -4652,7 +4949,7 @@
 
             @Override
             public void execute() {
-                // Unused in this case
+                // Unused.
             }
 
             @Override
@@ -4663,15 +4960,17 @@
                 }
                 mResult.set(result);
                 mLatch.countDown();
+                removeOperation(mCurrentOpToken);
             }
 
             @Override
-            public void handleTimeout() {
+            public void handleCancel(boolean cancelAll) {
                 if (MORE_DEBUG) {
-                    Slog.i(TAG, "Preflight timeout; failing");
+                    Slog.i(TAG, "Preflight cancelled; failing");
                 }
                 mResult.set(BackupTransport.AGENT_ERROR);
                 mLatch.countDown();
+                removeOperation(mCurrentOpToken);
             }
 
             @Override
@@ -4688,43 +4987,67 @@
         class SinglePackageBackupRunner implements Runnable, BackupRestoreTask {
             final ParcelFileDescriptor mOutput;
             final PackageInfo mTarget;
-            final FullBackupPreflight mPreflight;
+            final SinglePackageBackupPreflight mPreflight;
             final CountDownLatch mPreflightLatch;
             final CountDownLatch mBackupLatch;
+            private final int mCurrentOpToken;
+            private final int mEphemeralToken;
             private FullBackupEngine mEngine;
             private volatile int mPreflightResult;
             private volatile int mBackupResult;
             private final long mQuota;
+            private volatile boolean mIsCancelled;
 
             SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
-                    IBackupTransport transport, long quota) throws IOException {
+                    IBackupTransport transport, long quota, int currentOpToken) throws IOException {
                 mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
                 mTarget = target;
-                mPreflight = new SinglePackageBackupPreflight(transport, quota);
+                mCurrentOpToken = currentOpToken;
+                mEphemeralToken = generateToken();
+                mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken);
                 mPreflightLatch = new CountDownLatch(1);
                 mBackupLatch = new CountDownLatch(1);
                 mPreflightResult = BackupTransport.AGENT_ERROR;
                 mBackupResult = BackupTransport.AGENT_ERROR;
                 mQuota = quota;
+                registerTask();
+            }
+
+            void registerTask() {
+                synchronized (mCurrentOpLock) {
+                    mCurrentOperations.put(mCurrentOpToken, new Operation(OP_PENDING, this,
+                            OP_TYPE_WAIT));
+                }
+            }
+
+            void unregisterTask() {
+                synchronized (mCurrentOpLock) {
+                    mCurrentOperations.remove(mCurrentOpToken);
+                }
             }
 
             @Override
             public void run() {
                 FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
-                mEngine = new FullBackupEngine(out, mPreflight, mTarget, false, this, mQuota);
+                mEngine = new FullBackupEngine(out, mPreflight, mTarget, false, this, mQuota, mCurrentOpToken);
                 try {
                     try {
-                        mPreflightResult = mEngine.preflightCheck();
+                        if (!mIsCancelled) {
+                            mPreflightResult = mEngine.preflightCheck();
+                        }
                     } finally {
                         mPreflightLatch.countDown();
                     }
                     // If there is no error on preflight, continue backup.
                     if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
-                        mBackupResult = mEngine.backupOnePackage();
+                        if (!mIsCancelled) {
+                            mBackupResult = mEngine.backupOnePackage();
+                        }
                     }
                 } catch (Exception e) {
                     Slog.e(TAG, "Exception during full package backup of " + mTarget.packageName);
                 } finally {
+                    unregisterTask();
                     mBackupLatch.countDown();
                     try {
                         mOutput.close();
@@ -4743,6 +5066,9 @@
             long getPreflightResultBlocking() {
                 try {
                     mPreflightLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+                    if (mIsCancelled) {
+                        return BackupManager.ERROR_BACKUP_CANCELLED;
+                    }
                     if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
                         return mPreflight.getExpectedSizeOrErrorCode();
                     } else {
@@ -4756,6 +5082,9 @@
             int getBackupResultBlocking() {
                 try {
                     mBackupLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+                    if (mIsCancelled) {
+                        return BackupManager.ERROR_BACKUP_CANCELLED;
+                    }
                     return mBackupResult;
                 } catch (InterruptedException e) {
                     return BackupTransport.AGENT_ERROR;
@@ -4772,14 +5101,23 @@
             public void operationComplete(long result) { /* intentionally empty */ }
 
             @Override
-            public void handleTimeout() {
+            public void handleCancel(boolean cancelAll) {
                 if (DEBUG) {
-                    Slog.w(TAG, "Full backup timeout of " + mTarget.packageName);
+                    Slog.w(TAG, "Full backup cancel of " + mTarget.packageName);
                 }
+
                 mMonitor = monitorEvent(mMonitor,
                         BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_TIMEOUT,
                         mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
+                mIsCancelled = true;
+                // Cancel tasks spun off by this task.
+                BackupManagerService.this.handleCancel(mEphemeralToken, cancelAll);
                 tearDownAgentAndKill(mTarget.applicationInfo);
+                // Free up everyone waiting on this task and its children.
+                mPreflightLatch.countDown();
+                mBackupLatch.countDown();
+                // We are done with this operation.
+                removeOperation(mCurrentOpToken);
             }
         }
     }
@@ -5057,7 +5395,7 @@
                 if (DEBUG_SCHEDULING) {
                     Slog.i(TAG, "Telling running backup to stop");
                 }
-                mRunningFullBackupTask.setRunning(false);
+                mRunningFullBackupTask.handleCancel(true);
             }
         }
     }
@@ -5191,6 +5529,8 @@
         // Widget blob to be restored out-of-band
         byte[] mWidgetData = null;
 
+        private final int mEphemeralOpToken;
+
         // Runner that can be placed in a separate thread to do in-process
         // invocations of the full restore API asynchronously. Used by adb restore.
         class RestoreFileRunnable implements Runnable {
@@ -5226,7 +5566,9 @@
         }
 
         public FullRestoreEngine(BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
-                PackageInfo onlyPackage, boolean allowApks, boolean allowObbs) {
+                PackageInfo onlyPackage, boolean allowApks, boolean allowObbs,
+                int ephemeralOpToken) {
+            mEphemeralOpToken = ephemeralOpToken;
             mMonitorTask = monitorTask;
             mObserver = observer;
             mOnlyPackage = onlyPackage;
@@ -5431,17 +5773,16 @@
                         if (okay) {
                             boolean agentSuccess = true;
                             long toCopy = info.size;
-                            final int token = generateToken();
                             try {
-                                prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL,
-                                        mMonitorTask);
+                                prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_FULL_BACKUP_INTERVAL,
+                                        mMonitorTask, OP_TYPE_WAIT);
 
                                 if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
                                     if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
                                             + " : " + info.path);
                                     mObbConnection.restoreObbFile(pkg, mPipes[0],
                                             info.size, info.type, info.path, info.mode,
-                                            info.mtime, token, mBackupManagerBinder);
+                                            info.mtime, mEphemeralOpToken, mBackupManagerBinder);
                                 } else {
                                     if (MORE_DEBUG) Slog.d(TAG, "Invoking agent to restore file "
                                             + info.path);
@@ -5452,12 +5793,12 @@
                                     if (mTargetApp.processName.equals("system")) {
                                         Slog.d(TAG, "system process agent - spinning a thread");
                                         RestoreFileRunnable runner = new RestoreFileRunnable(
-                                                mAgent, info, mPipes[0], token);
+                                                mAgent, info, mPipes[0], mEphemeralOpToken);
                                         new Thread(runner, "restore-sys-runner").start();
                                     } else {
                                         mAgent.doRestoreFile(mPipes[0], info.size, info.type,
                                                 info.domain, info.path, info.mode, info.mtime,
-                                                token, mBackupManagerBinder);
+                                                mEphemeralOpToken, mBackupManagerBinder);
                                     }
                                 }
                             } catch (IOException e) {
@@ -5509,7 +5850,7 @@
 
                                 // and now that we've sent it all, wait for the remote
                                 // side to acknowledge receipt
-                                agentSuccess = waitUntilOperationComplete(token);
+                                agentSuccess = waitUntilOperationComplete(mEphemeralOpToken);
                             }
 
                             // okay, if the remote end failed at any point, deal with
@@ -6354,9 +6695,11 @@
     class AdbRestoreFinishedLatch implements BackupRestoreTask {
         static final String TAG = "AdbRestoreFinishedLatch";
         final CountDownLatch mLatch;
+        private final int mCurrentOpToken;
 
-        AdbRestoreFinishedLatch() {
+        AdbRestoreFinishedLatch(int currentOpToken) {
             mLatch = new CountDownLatch(1);
+            mCurrentOpToken = currentOpToken;
         }
 
         void await() {
@@ -6379,14 +6722,16 @@
                 Slog.w(TAG, "adb onRestoreFinished() complete");
             }
             mLatch.countDown();
+            removeOperation(mCurrentOpToken);
         }
 
         @Override
-        public void handleTimeout() {
+        public void handleCancel(boolean cancelAll) {
             if (DEBUG) {
                 Slog.w(TAG, "adb onRestoreFinished() timed out");
             }
             mLatch.countDown();
+            removeOperation(mCurrentOpToken);
         }
     }
 
@@ -6577,9 +6922,6 @@
                     Slog.w(TAG, "Close of restore data pipe threw", e);
                     /* nothing we can do about this */
                 }
-                synchronized (mCurrentOpLock) {
-                    mCurrentOperations.clear();
-                }
                 synchronized (mLatchObject) {
                     mLatchObject.set(true);
                     mLatchObject.notifyAll();
@@ -6868,7 +7210,8 @@
                             long toCopy = info.size;
                             final int token = generateToken();
                             try {
-                                prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
+                                prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null,
+                                        OP_TYPE_WAIT);
                                 if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
                                     if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
                                             + " : " + info.path);
@@ -7003,8 +7346,9 @@
                     // In the adb restore case, we do restore-finished here
                     if (doRestoreFinished) {
                         final int token = generateToken();
-                        final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch();
-                        prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, latch);
+                        final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(token);
+                        prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, latch,
+                                OP_TYPE_WAIT);
                         if (mTargetApp.processName.equals("system")) {
                             if (MORE_DEBUG) {
                                 Slog.d(TAG, "system agent - restoreFinished on thread");
@@ -7872,11 +8216,14 @@
         ParcelFileDescriptor mBackupData;
         ParcelFileDescriptor mNewState;
 
+        private final int mEphemeralOpToken;
+
         // Invariant: mWakelock is already held, and this task is responsible for
         // releasing it at the end of the restore operation.
         PerformUnifiedRestoreTask(IBackupTransport transport, IRestoreObserver observer,
                 IBackupManagerMonitor monitor, long restoreSetToken, PackageInfo targetPackage,
                 int pmToken, boolean isFullSystemRestore, String[] filterSet) {
+            mEphemeralOpToken = generateToken();
             mState = UnifiedRestoreState.INITIAL;
             mStartRealtime = SystemClock.elapsedRealtime();
 
@@ -8304,7 +8651,6 @@
             ParcelFileDescriptor stage;
             File downloadFile = (staging) ? mStageName : mBackupDataName;
 
-            final int token = generateToken();
             try {
                 // Run the transport's restore pass
                 stage = ParcelFileDescriptor.open(downloadFile,
@@ -8377,9 +8723,9 @@
                 // Kick off the restore, checking for hung agents.  The timeout or
                 // the operationComplete() callback will schedule the next step,
                 // so we do not do that here.
-                prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
+                prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_RESTORE_INTERVAL, this, OP_TYPE_WAIT);
                 mAgent.doRestore(mBackupData, appVersionCode, mNewState,
-                        token, mBackupManagerBinder);
+                        mEphemeralOpToken, mBackupManagerBinder);
             } catch (Exception e) {
                 Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
                 EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
@@ -8426,9 +8772,9 @@
         // state RESTORE_FINISHED : provide the "no more data" signpost callback at the end
         private void restoreFinished() {
             try {
-                final int token = generateToken();
-                prepareOperationTimeout(token, TIMEOUT_RESTORE_FINISHED_INTERVAL, this);
-                mAgent.doRestoreFinished(token, mBackupManagerBinder);
+                prepareOperationTimeout(mEphemeralOpToken, TIMEOUT_RESTORE_FINISHED_INTERVAL, this,
+                        OP_TYPE_WAIT);
+                mAgent.doRestoreFinished(mEphemeralOpToken, mBackupManagerBinder);
                 // If we get this far, the callback or timeout will schedule the
                 // next restore state, so we're done
             } catch (Exception e) {
@@ -8452,7 +8798,10 @@
             // pipe through which the engine will read data.  [0] read, [1] write
             ParcelFileDescriptor[] mEnginePipes;
 
+            private final int mEphemeralOpToken;
+
             public StreamFeederThread() throws IOException {
+                mEphemeralOpToken = generateToken();
                 mTransportPipes = ParcelFileDescriptor.createPipe();
                 mEnginePipes = ParcelFileDescriptor.createPipe();
                 setRunning(true);
@@ -8466,7 +8815,7 @@
                 EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
                         mCurrentPackage.packageName);
 
-                mEngine = new FullRestoreEngine(this, null, mCurrentPackage, false, false);
+                mEngine = new FullRestoreEngine(this, null, mCurrentPackage, false, false, mEphemeralOpToken);
                 mEngineThread = new EngineThread(mEngine, mEnginePipes[0]);
 
                 ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
@@ -8605,7 +8954,8 @@
 
             // The app has timed out handling a restoring file
             @Override
-            public void handleTimeout() {
+            public void handleCancel(boolean cancelAll) {
+                removeOperation(mEphemeralOpToken);
                 if (DEBUG) {
                     Slog.w(TAG, "Full-data restore target timed out; shutting down");
                 }
@@ -8785,13 +9135,11 @@
             // The caller is responsible for reestablishing the state machine; our
             // responsibility here is to clear the decks for whatever comes next.
             mBackupHandler.removeMessages(MSG_TIMEOUT, this);
-            synchronized (mCurrentOpLock) {
-                mCurrentOperations.clear();
-            }
         }
 
         @Override
         public void operationComplete(long unusedResult) {
+            removeOperation(mEphemeralOpToken);
             if (MORE_DEBUG) {
                 Slog.i(TAG, "operationComplete() during restore: target="
                         + mCurrentPackage.packageName
@@ -8852,7 +9200,8 @@
 
         // A call to agent.doRestore() or agent.doRestoreFinished() has timed out
         @Override
-        public void handleTimeout() {
+        public void handleCancel(boolean cancelAll) {
+            removeOperation(mEphemeralOpToken);
             Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
             mMonitor = monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
                     mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
@@ -9966,7 +10315,16 @@
                     // and ignore it; we've already dealt with the timeout.
                     op = null;
                     mCurrentOperations.delete(token);
-                } else {
+                } else if (op.state == OP_ACKNOWLEDGED) {
+                    if (DEBUG) {
+                        Slog.w(TAG, "Received duplicate ack for token=" +
+                                Integer.toHexString(token));
+                    }
+                    op = null;
+                    mCurrentOperations.remove(token);
+                } else if (op.state == OP_PENDING) {
+                    // Can't delete op from mCurrentOperations. waitUntilOperationComplete can be
+                    // called after we we receive this call.
                     op.state = OP_ACKNOWLEDGED;
                 }
             }
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index da0cee5..8855661 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -363,6 +363,14 @@
     }
 
     @Override
+    public void cancelBackups() throws RemoteException {
+        BackupManagerService svc = mService;
+        if (svc != null) {
+            svc.cancelBackups();
+        }
+    }
+
+    @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
 
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 4559254..2e61550 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -132,6 +132,7 @@
 2843 full_backup_success (Package|3)
 2844 full_restore_package (Package|3)
 2845 full_backup_quota_exceeded (Package|3)
+2846 full_backup_cancelled (Package|3),(Message|3)
 
 2850 backup_transport_lifecycle (Transport|3),(Bound|1|1)
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 072b0ea..a73eb18 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9623,6 +9623,20 @@
     }
 
     @Override
+    public ActivityManager.TaskDescription getTaskDescription(int id) {
+        synchronized (this) {
+            enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                    "getTaskDescription()");
+            final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(
+                    id, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
+            if (tr != null) {
+                return tr.lastTaskDescription;
+            }
+        }
+        return null;
+    }
+
+    @Override
     public int addAppTask(IBinder activityToken, Intent intent,
             ActivityManager.TaskDescription description, Bitmap thumbnail) throws RemoteException {
         final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 37221a9..5e51579 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -250,31 +250,33 @@
         }
 
         private void cleanupUpstream() {
-            if (mMyUpstreamIfaceName != null) {
-                // note that we don't care about errors here.
-                // sometimes interfaces are gone before we get
-                // to remove their rules, which generates errors.
-                // just do the best we can.
-                try {
-                    // about to tear down NAT; gather remaining statistics
-                    mStatsService.forceUpdate();
-                } catch (Exception e) {
-                    if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
-                }
-                try {
-                    mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
-                } catch (Exception e) {
-                    if (VDBG) Log.e(
-                            TAG, "Exception in removeInterfaceForward: " + e.toString());
-                }
-                try {
-                    mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
-                } catch (Exception e) {
-                    if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
-                }
-                mMyUpstreamIfaceName = null;
+            if (mMyUpstreamIfaceName == null) return;
+
+            cleanupUpstreamInterface(mMyUpstreamIfaceName);
+            mMyUpstreamIfaceName = null;
+        }
+
+        private void cleanupUpstreamInterface(String upstreamIface) {
+            // Note that we don't care about errors here.
+            // Sometimes interfaces are gone before we get
+            // to remove their rules, which generates errors.
+            // Just do the best we can.
+            try {
+                // About to tear down NAT; gather remaining statistics.
+                mStatsService.forceUpdate();
+            } catch (Exception e) {
+                if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
             }
-            return;
+            try {
+                mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface);
+            } catch (Exception e) {
+                if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString());
+            }
+            try {
+                mNMService.disableNat(mIfaceName, upstreamIface);
+            } catch (Exception e) {
+                if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+            }
         }
 
         @Override
@@ -306,6 +308,7 @@
                                     newUpstreamIfaceName);
                         } catch (Exception e) {
                             Log.e(TAG, "Exception enabling Nat: " + e.toString());
+                            cleanupUpstreamInterface(newUpstreamIfaceName);
                             mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
                             transitionTo(mInitialState);
                             return true;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 19c9d9b..771ae9a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2458,12 +2458,14 @@
                 Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
                 return null;
             }
-            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            try {
-                writePolicyXml(baos, true /*forBackup*/);
-                return baos.toByteArray();
-            } catch (IOException e) {
-                Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+            synchronized(mPolicyFile) {
+                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                try {
+                    writePolicyXml(baos, true /*forBackup*/);
+                    return baos.toByteArray();
+                } catch (IOException e) {
+                    Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+                }
             }
             return null;
         }
@@ -2481,12 +2483,14 @@
                 Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
                 return;
             }
-            final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
-            try {
-                readPolicyXml(bais, true /*forRestore*/);
-                savePolicyFile();
-            } catch (NumberFormatException | XmlPullParserException | IOException e) {
-                Slog.w(TAG, "applyRestore: error reading payload", e);
+            synchronized(mPolicyFile) {
+                final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+                try {
+                    readPolicyXml(bais, true /*forRestore*/);
+                    savePolicyFile();
+                } catch (NumberFormatException | XmlPullParserException | IOException e) {
+                    Slog.w(TAG, "applyRestore: error reading payload", e);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 2781150..7e7de21 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -72,6 +72,9 @@
     // Append ephemeral to existing seinfo label
     private static final String EPHEMERAL_APP_STR = ":ephemeralapp";
 
+    // Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion
+    private static final String TARGETSDKVERSION_STR = ":targetSdkVersion=";
+
     /**
      * Load the mac_permissions.xml file containing all seinfo assignments used to
      * label apps. The loaded mac_permissions.xml file is determined by the
@@ -296,6 +299,8 @@
         if (pkg.applicationInfo.isPrivilegedApp())
             pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
 
+        pkg.applicationInfo.seinfo += TARGETSDKVERSION_STR + pkg.applicationInfo.targetSdkVersion;
+
         if (DEBUG_POLICY_INSTALL) {
             Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
                     "seinfo=" + pkg.applicationInfo.seinfo);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 69d436c..fb28bcf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -99,6 +99,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Color;
@@ -208,7 +209,7 @@
  */
 public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
 
-    private static final String LOG_TAG = "DevicePolicyManager";
+    protected static final String LOG_TAG = "DevicePolicyManager";
 
     private static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
 
@@ -252,7 +253,6 @@
     private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION
             = "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION";
 
-    private static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
     private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001;
     private static final int NETWORK_LOGGING_NOTIFICATION_ID = 1002;
 
@@ -409,6 +409,7 @@
         }
     };
 
+    /** Listens only if mHasFeature == true. */
     private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
 
         @Override
@@ -420,6 +421,7 @@
         }
     };
 
+    /** Listens only if mHasFeature == true. */
     private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {
 
         @Override
@@ -513,7 +515,21 @@
 
     final Handler mHandler;
 
-    BroadcastReceiver mReceiver = new BroadcastReceiver() {
+    /** Listens on any device, even when mHasFeature == false. */
+    final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (StorageManager.inCryptKeeperBounce()) {
+                return;
+            }
+            final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+            new MonitoringCertNotificationTask(DevicePolicyManagerService.this, mInjector)
+                    .execute(userHandle);
+        }
+    };
+
+    /** Listens only if mHasFeature == true. */
+    final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
@@ -559,14 +575,7 @@
                     }
                 });
             }
-            if (Intent.ACTION_USER_UNLOCKED.equals(action)
-                    || Intent.ACTION_USER_STARTED.equals(action)
-                    || KeyChain.ACTION_TRUST_STORE_CHANGED.equals(action)) {
-                if (!StorageManager.inCryptKeeperBounce()) {
-                    new MonitoringCertNotificationTask().execute(
-                            intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL));
-                }
-            }
+
             if (Intent.ACTION_USER_ADDED.equals(action)) {
                 sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle);
                 synchronized (DevicePolicyManagerService.this) {
@@ -1490,6 +1499,15 @@
             mContext = context;
         }
 
+        Context createContextAsUser(UserHandle user) throws PackageManager.NameNotFoundException {
+            final String packageName = mContext.getPackageName();
+            return mContext.createPackageContextAsUser(packageName, 0, user);
+        }
+
+        Resources getResources() {
+            return mContext.getResources();
+        }
+
         Owners newOwners() {
             return new Owners(getUserManager(), getUserManagerInternal(),
                     getPackageManagerInternal());
@@ -1725,6 +1743,10 @@
         boolean securityLogIsLoggingEnabled() {
             return SecurityLog.isLoggingEnabled();
         }
+
+        KeyChainConnection keyChainBindAsUser(UserHandle user) throws InterruptedException {
+            return KeyChain.bindAsUser(mContext, user);
+        }
     }
 
     /**
@@ -1755,18 +1777,27 @@
                 .hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
         mIsWatch = mInjector.getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_WATCH);
+
+        // Broadcast filter for changes to the trusted certificate store. These changes get a
+        // separate intent filter so we can listen to them even when device_admin is off.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_STARTED);
+        filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
+        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiverAsUser(mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
+
         if (!mHasFeature) {
             // Skip the rest of the initialization
             return;
         }
-        IntentFilter filter = new IntentFilter();
+
+        filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BOOT_COMPLETED);
         filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION);
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
-        filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
         filter = new IntentFilter();
@@ -2952,125 +2983,34 @@
         }
     }
 
-    private class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
-        @Override
-        protected Void doInBackground(Integer... params) {
-            int userHandle = params[0];
+    /**
+     * Remove deleted CA certificates from the "approved" list for a particular user, counting
+     * the number still remaining to approve.
+     *
+     * @param userHandle user to check for. This must be a real user and not, for example,
+     *        {@link UserHandle#ALL}.
+     * @param installedCertificates the full set of certificate authorities currently installed for
+     *        {@param userHandle}. After calling this function, {@code mAcceptedCaCertificates} will
+     *        correspond to some subset of this.
+     *
+     * @return number of certificates yet to be approved by {@param userHandle}.
+     */
+    protected synchronized int retainAcceptedCertificates(final UserHandle userHandle,
+            final @NonNull Collection<String> installedCertificates) {
+        enforceManageUsers();
 
-            if (userHandle == UserHandle.USER_ALL) {
-                for (UserInfo userInfo : mUserManager.getUsers(true)) {
-                    manageNotification(userInfo.getUserHandle());
-                }
-            } else {
-                manageNotification(UserHandle.of(userHandle));
-            }
-            return null;
-        }
+        if (!mHasFeature) {
+            return installedCertificates.size();
+        } else {
+            final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
 
-        private void manageNotification(UserHandle userHandle) {
-            if (!mUserManager.isUserUnlocked(userHandle)) {
-                return;
+            // Remove deleted certificates. Flush xml if necessary.
+            if (policy.mAcceptedCaCertificates.retainAll(installedCertificates)) {
+                saveSettingsLocked(userHandle.getIdentifier());
             }
 
-            // Call out to KeyChain to check for CAs which are waiting for approval.
-            final List<String> pendingCertificates;
-            try {
-                pendingCertificates = getInstalledCaCertificates(userHandle);
-            } catch (RemoteException | RuntimeException e) {
-                Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
-                return;
-            }
-
-            synchronized (DevicePolicyManagerService.this) {
-                final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
-
-                // Remove deleted certificates. Flush xml if necessary.
-                if (policy.mAcceptedCaCertificates.retainAll(pendingCertificates)) {
-                    saveSettingsLocked(userHandle.getIdentifier());
-                }
-                // Trim to approved certificates.
-                pendingCertificates.removeAll(policy.mAcceptedCaCertificates);
-            }
-
-            if (pendingCertificates.isEmpty()) {
-                mInjector.getNotificationManager().cancelAsUser(
-                        null, MONITORING_CERT_NOTIFICATION_ID, userHandle);
-                return;
-            }
-
-            // Build and show a warning notification
-            int smallIconId;
-            String contentText;
-            int parentUserId = userHandle.getIdentifier();
-            if (getProfileOwner(userHandle.getIdentifier()) != null) {
-                contentText = mContext.getString(R.string.ssl_ca_cert_noti_managed,
-                        getProfileOwnerName(userHandle.getIdentifier()));
-                smallIconId = R.drawable.stat_sys_certificate_info;
-                parentUserId = getProfileParentId(userHandle.getIdentifier());
-            } else if (getDeviceOwnerUserId() == userHandle.getIdentifier()) {
-                contentText = mContext.getString(R.string.ssl_ca_cert_noti_managed,
-                        getDeviceOwnerName());
-                smallIconId = R.drawable.stat_sys_certificate_info;
-            } else {
-                contentText = mContext.getString(R.string.ssl_ca_cert_noti_by_unknown);
-                smallIconId = android.R.drawable.stat_sys_warning;
-            }
-
-            final int numberOfCertificates = pendingCertificates.size();
-            Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
-            dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-            dialogIntent.setPackage("com.android.settings");
-            dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, numberOfCertificates);
-            dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
-            PendingIntent notifyIntent = PendingIntent.getActivityAsUser(mContext, 0,
-                    dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
-                    new UserHandle(parentUserId));
-
-            final Context userContext;
-            try {
-                final String packageName = mContext.getPackageName();
-                userContext = mContext.createPackageContextAsUser(packageName, 0, userHandle);
-            } catch (PackageManager.NameNotFoundException e) {
-                Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
-                return;
-            }
-            final Notification noti = new Notification.Builder(userContext)
-                .setSmallIcon(smallIconId)
-                .setContentTitle(mContext.getResources().getQuantityText(
-                        R.plurals.ssl_ca_cert_warning, numberOfCertificates))
-                .setContentText(contentText)
-                .setContentIntent(notifyIntent)
-                .setPriority(Notification.PRIORITY_HIGH)
-                .setShowWhen(false)
-                .setColor(mContext.getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
-                .build();
-
-            mInjector.getNotificationManager().notifyAsUser(
-                    null, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
-        }
-
-        private List<String> getInstalledCaCertificates(UserHandle userHandle)
-                throws RemoteException, RuntimeException {
-            KeyChainConnection conn = null;
-            try {
-                conn = KeyChain.bindAsUser(mContext, userHandle);
-                List<ParcelableString> aliases = conn.getService().getUserCaAliases().getList();
-                List<String> result = new ArrayList<>(aliases.size());
-                for (int i = 0; i < aliases.size(); i++) {
-                    result.add(aliases.get(i).string);
-                }
-                return result;
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                return null;
-            } catch (AssertionError e) {
-                throw new RuntimeException(e);
-            } finally {
-                if (conn != null) {
-                    conn.close();
-                }
-            }
+            // Trim approved certificates from the count.
+            return installedCertificates.size() - policy.mAcceptedCaCertificates.size();
         }
     }
 
@@ -4635,7 +4575,7 @@
             }
             saveSettingsLocked(userId);
         }
-        new MonitoringCertNotificationTask().execute(userId);
+        new MonitoringCertNotificationTask(this, mInjector).execute(userId);
         return true;
     }
 
@@ -4659,7 +4599,7 @@
                     saveSettingsLocked(userInfo.id);
                 }
 
-                new MonitoringCertNotificationTask().execute(userInfo.id);
+                new MonitoringCertNotificationTask(this, mInjector).execute(userInfo.id);
             }
         }
     }
@@ -7154,7 +7094,7 @@
         return UserHandle.isSameApp(mInjector.binderGetCallingUid(), Process.SYSTEM_UID);
     }
 
-    private int getProfileParentId(int userHandle) {
+    protected int getProfileParentId(int userHandle) {
         final long ident = mInjector.binderClearCallingIdentity();
         try {
             UserInfo parentUser = mUserManager.getProfileParent(userHandle);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java b/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
new file mode 100644
index 0000000..03c137a
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.security.KeyChain.KeyChainConnection;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.util.ParcelableString;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
+    protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
+    protected static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
+
+    private final DevicePolicyManagerService mService;
+    private final DevicePolicyManagerService.Injector mInjector;
+
+    public MonitoringCertNotificationTask(final DevicePolicyManagerService service,
+            final DevicePolicyManagerService.Injector injector) {
+        super();
+        mService = service;
+        mInjector = injector;
+    }
+
+    @Override
+    protected Void doInBackground(Integer... params) {
+        int userHandle = params[0];
+
+        if (userHandle == UserHandle.USER_ALL) {
+            for (UserInfo userInfo : mInjector.getUserManager().getUsers(true)) {
+                repostOrClearNotification(userInfo.getUserHandle());
+            }
+        } else {
+            repostOrClearNotification(UserHandle.of(userHandle));
+        }
+        return null;
+    }
+
+    private void repostOrClearNotification(UserHandle userHandle) {
+        if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
+            return;
+        }
+
+        // Call out to KeyChain to check for CAs which are waiting for approval.
+        final int pendingCertificateCount;
+        try {
+            pendingCertificateCount = mService.retainAcceptedCertificates(
+                    userHandle, getInstalledCaCertificates(userHandle));
+        } catch (RemoteException | RuntimeException e) {
+            Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
+            return;
+        }
+
+        if (pendingCertificateCount != 0) {
+            showNotification(userHandle, pendingCertificateCount);
+        } else {
+            mInjector.getNotificationManager().cancelAsUser(
+                    LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
+        }
+    }
+
+    private void showNotification(UserHandle userHandle, int pendingCertificateCount) {
+        // Create a context for the target user.
+        final Context userContext;
+        try {
+            userContext = mInjector.createContextAsUser(userHandle);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
+            return;
+        }
+
+        // Build and show a warning notification
+        int smallIconId;
+        String contentText;
+        int parentUserId = userHandle.getIdentifier();
+        Resources resources = mInjector.getResources();
+        if (mService.getProfileOwner(userHandle.getIdentifier()) != null) {
+            contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+                    mService.getProfileOwnerName(userHandle.getIdentifier()));
+            smallIconId = R.drawable.stat_sys_certificate_info;
+            parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
+        } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
+            contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+                    mService.getDeviceOwnerName());
+            smallIconId = R.drawable.stat_sys_certificate_info;
+        } else {
+            contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
+            smallIconId = android.R.drawable.stat_sys_warning;
+        }
+
+        Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
+        dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        // TODO this next line is taken from original notification code in
+        // {@link DevicePolicyManagerService} but not a very good way of doing it. Do it better.
+        dialogIntent.setPackage("com.android.settings");
+        dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
+        dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
+        PendingIntent notifyIntent = PendingIntent.getActivityAsUser(userContext, 0,
+                dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
+                UserHandle.of(parentUserId));
+
+        final Notification noti = new Notification.Builder(userContext)
+            .setSmallIcon(smallIconId)
+            .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
+                    pendingCertificateCount))
+            .setContentText(contentText)
+            .setContentIntent(notifyIntent)
+            .setPriority(Notification.PRIORITY_HIGH)
+            .setShowWhen(false)
+            .setColor(R.color.system_notification_accent_color)
+            .build();
+
+        mInjector.getNotificationManager().notifyAsUser(
+                LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
+    }
+
+    private List<String> getInstalledCaCertificates(UserHandle userHandle)
+            throws RemoteException, RuntimeException {
+        try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
+            List<ParcelableString> aliases = conn.getService().getUserCaAliases().getList();
+            List<String> result = new ArrayList<>(aliases.size());
+            for (int i = 0; i < aliases.size(); i++) {
+                result.add(aliases.get(i).string);
+            }
+            return result;
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            return null;
+        } catch (AssertionError e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 3b92a34..e6dd13f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -29,6 +29,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.security.KeyChain;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.Pair;
@@ -375,5 +376,10 @@
         boolean isBuildDebuggable() {
             return context.buildMock.isDebuggable;
         }
+
+        @Override
+        KeyChain.KeyChainConnection keyChainBindAsUser(UserHandle user) {
+            return context.keyChainConnection;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 6fb65d5..a186b59 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -21,6 +21,8 @@
 
 import android.Manifest.permission;
 import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -32,6 +34,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.net.IIpConnectivityMetrics;
@@ -52,10 +55,13 @@
 import android.util.Pair;
 
 import com.android.internal.R;
+import com.android.internal.util.ParcelableString;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.UserRestrictionsUtils;
 
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
@@ -76,13 +82,16 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -1193,6 +1202,53 @@
         return uid;
     }
 
+    public void testCertificateDisclosure() throws Exception {
+        final int userId = DpmMockContext.CALLER_USER_HANDLE;
+        final UserHandle user = UserHandle.of(userId);
+
+        mContext.applicationInfo = new ApplicationInfo();
+        mContext.callerPermissions.add(permission.MANAGE_USERS);
+        mContext.packageName = "com.android.frameworks.servicestests";
+        mContext.userContexts.put(user, mContext);
+        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+        ParceledListSlice<ParcelableString> oneCert = asSlice(new String[] {"1"});
+        ParceledListSlice<ParcelableString> fourCerts = asSlice(new String[] {"1", "2", "3", "4"});
+
+        final String TEST_STRING = "Test for exactly 2 certs out of 4";
+        doReturn(TEST_STRING).when(mContext.resources).getQuantityText(anyInt(), eq(2));
+
+        // Given that we have exactly one certificate installed,
+        when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(oneCert);
+        // when that certificate is approved,
+        dpms.approveCaCert(oneCert.getList().get(0).string, userId, true);
+        // a notification should not be shown.
+        verify(mContext.notificationManager, timeout(1000))
+                .cancelAsUser(anyString(), anyInt(), eq(user));
+
+        // Given that we have four certificates installed,
+        when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(fourCerts);
+        // when two of them are approved (one of them approved twice hence no action),
+        dpms.approveCaCert(fourCerts.getList().get(0).string, userId, true);
+        dpms.approveCaCert(fourCerts.getList().get(1).string, userId, true);
+        // a notification should be shown saying that there are two certificates left to approve.
+        verify(mContext.notificationManager, timeout(1000))
+                .notifyAsUser(anyString(), anyInt(), argThat(
+                        new BaseMatcher<Notification>() {
+                            @Override
+                            public boolean matches(Object item) {
+                                final Notification noti = (Notification) item;
+                                return TEST_STRING.equals(
+                                        noti.extras.getString(Notification.EXTRA_TITLE));
+                            }
+                            @Override
+                            public void describeTo(Description description) {
+                                description.appendText(
+                                        "Notification{title=\"" + TEST_STRING + "\"}");
+                            }
+                        }), eq(user));
+    }
+
     /**
      * Simple test for delegate set/get and general delegation. Tests verifying that delegated
      * privileges can acually be exercised by a delegate are not covered here.
@@ -3734,4 +3790,20 @@
         assertTrue(dpm.setProfileOwner(admin, null, userId));
         mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
     }
+
+    /**
+     * Convert String[] to ParceledListSlice&lt;ParcelableString&gt;.
+     * <p>
+     * TODO: This shouldn't be necessary. If ParcelableString does need to exist, it also needs
+     * a real constructor.
+     */
+    private static ParceledListSlice<ParcelableString> asSlice(String[] s) {
+        List<ParcelableString> list = new ArrayList<>(s.length);
+        for (int i = 0; i < s.length; i++) {
+            ParcelableString item = new ParcelableString();
+            item.string = s[i];
+            list.add(i, item);
+        }
+        return new ParceledListSlice<ParcelableString>(list);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 22cd135..46aaf83 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -43,9 +43,11 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.security.KeyChain;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
+import android.util.ArrayMap;
 import android.view.IWindowManager;
 
 import com.android.internal.widget.LockPatternUtils;
@@ -58,10 +60,12 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -290,6 +294,7 @@
     public final TelephonyManager telephonyManager;
     public final AccountManager accountManager;
     public final AlarmManager alarmManager;
+    public final KeyChain.KeyChainConnection keyChainConnection;
 
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
@@ -300,6 +305,9 @@
 
     public final BuildMock buildMock = new BuildMock();
 
+    /** Optional mapping of other user contexts for {@link #createPackageContextAsUser} to return */
+    public final Map<UserHandle, Context> userContexts = new ArrayMap<>();
+
     public String packageName = null;
 
     public ApplicationInfo applicationInfo = null;
@@ -335,6 +343,7 @@
         telephonyManager = mock(TelephonyManager.class);
         accountManager = mock(AccountManager.class);
         alarmManager = mock(AlarmManager.class);
+        keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(context.getPackageManager());
@@ -690,6 +699,19 @@
     }
 
     @Override
+    public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+            throws PackageManager.NameNotFoundException {
+        if (!userContexts.containsKey(user)) {
+            return super.createPackageContextAsUser(packageName, flags, user);
+        }
+        if (!getPackageName().equals(packageName)) {
+            throw new UnsupportedOperationException(
+                    "Creating a context as another package is not implemented");
+        }
+        return userContexts.get(user);
+    }
+
+    @Override
     public ContentResolver getContentResolver() {
         return contentResolver;
     }
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
index 0fa4545..32e1b96 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -210,10 +210,9 @@
         inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
-        // TODO: Verify proper cleanup is performed:
-        // inOrder.verify(mStatsService).forceUpdate();
-        // inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        // inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
     }
 
     @Test
@@ -230,10 +229,9 @@
         inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
         inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        // TODO: Verify proper cleanup is performed:
-        // inOrder.verify(mStatsService).forceUpdate();
-        // inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        // inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
     }
 
     @Test
@@ -296,6 +294,18 @@
                 IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
     }
 
+    @Test
+    public void ignoresDuplicateUpstreamNotifications() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+
+        for (int i = 0; i < 5; i++) {
+            dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+            verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+        }
+    }
+
     /**
      * Send a command to the state machine under test, and run the event loop to idle.
      *
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index 77b131f..4ffb2e2 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -132,7 +132,7 @@
     @Override
     public Result insertChild(Object parentView, ILayoutPullParser childXml, int index,
             IAnimationListener listener) {
-        if (parentView instanceof ViewGroup == false) {
+        if (!(parentView instanceof ViewGroup)) {
             throw new IllegalArgumentException("parentView is not a ViewGroup");
         }
 
@@ -155,10 +155,10 @@
     @Override
     public Result moveChild(Object parentView, Object childView, int index,
             Map<String, String> layoutParams, IAnimationListener listener) {
-        if (parentView instanceof ViewGroup == false) {
+        if (!(parentView instanceof ViewGroup)) {
             throw new IllegalArgumentException("parentView is not a ViewGroup");
         }
-        if (childView instanceof View == false) {
+        if (!(childView instanceof View)) {
             throw new IllegalArgumentException("childView is not a View");
         }
 
@@ -179,7 +179,7 @@
 
     @Override
     public Result removeChild(Object childView, IAnimationListener listener) {
-        if (childView instanceof View == false) {
+        if (!(childView instanceof View)) {
             throw new IllegalArgumentException("childView is not a View");
         }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
index d392f21..91668af 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -100,7 +100,7 @@
         mView.setText(text);
     }
 
-    public void setGravity(int gravity) {
+    private void setGravity(int gravity) {
         mView.setGravity(gravity);
     }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
index 2fe3ed5..287334c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
@@ -305,6 +305,7 @@
         return Bridge.getResourceId(ResourceType.ID, ID_PREFIX + name);
     }
 
+    @SuppressWarnings("deprecation")
     @Override
     public void requestFitSystemWindows() {
         // The framework call would usually bubble up to ViewRootImpl but, in layoutlib, Layout will
@@ -416,6 +417,7 @@
             }
         }
 
+        @SuppressWarnings("SameParameterValue")
         private int getDimension(String attr, boolean isFramework, int defaultValue) {
             ResourceValue value = mResources.findItemInTheme(attr, isFramework);
             value = mResources.resolveResValue(value);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 85fe2a4..d21955e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -144,7 +144,7 @@
     private static final class PostInflateException extends Exception {
         private static final long serialVersionUID = 1L;
 
-        public PostInflateException(String message) {
+        private PostInflateException(String message) {
             super(message);
         }
     }
@@ -242,11 +242,13 @@
             // Then measure only the content with UNSPECIFIED to see the size difference
             // and apply this to the screen size.
 
+            View measuredView = mContentRoot.getChildAt(0);
+
             // first measure the full layout, with EXACTLY to get the size of the
             // content as it is inside the decor/dialog
             @SuppressWarnings("deprecation")
             Pair<Integer, Integer> exactMeasure = measureView(
-                    mViewRoot, mContentRoot.getChildAt(0),
+                    mViewRoot, measuredView,
                     mMeasuredScreenWidth, MeasureSpec.EXACTLY,
                     mMeasuredScreenHeight, MeasureSpec.EXACTLY);
 
@@ -258,6 +260,10 @@
                     mMeasuredScreenWidth, widthMeasureSpecMode,
                     mMeasuredScreenHeight, heightMeasureSpecMode);
 
+            // If measuredView is not null, exactMeasure nor result will be null.
+            assert exactMeasure != null;
+            assert result != null;
+
             // now look at the difference and add what is needed.
             if (renderingMode.isHorizExpand()) {
                 int measuredWidth = exactMeasure.getFirst();
@@ -406,8 +412,7 @@
      * @param canvas an optional canvas to render the views to. If null, only the measure and
      * layout steps will be executed.
      */
-    private static Result renderAndBuildResult(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
-            @Nullable Canvas canvas, int width, int height) {
+    private static Result renderAndBuildResult(@NonNull ViewGroup viewRoot, @Nullable Canvas canvas) {
         if (canvas == null) {
             return SUCCESS.createResult();
         }
@@ -551,7 +556,7 @@
                     long initialTime = System_Delegate.nanoTime();
                     if (!mFirstFrameExecuted) {
                         // We need to run an initial draw call to initialize the animations
-                        renderAndBuildResult(getContext(), mViewRoot, NOP_CANVAS, mMeasuredScreenWidth, mMeasuredScreenHeight);
+                        renderAndBuildResult(mViewRoot, NOP_CANVAS);
 
                         // The first frame will initialize the animations
                         Choreographer_Delegate.doFrame(initialTime);
@@ -560,8 +565,7 @@
                     // Second frame will move the animations
                     Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
                 }
-                renderResult = renderAndBuildResult(getContext(), mViewRoot, mCanvas, mMeasuredScreenWidth,
-                        mMeasuredScreenHeight);
+                renderResult = renderAndBuildResult(mViewRoot, mCanvas);
             }
 
             mSystemViewInfoList =
@@ -1206,7 +1210,7 @@
      * Sets up a {@link TabHost} object.
      * @param tabHost the TabHost to setup.
      * @param layoutlibCallback The project callback object to access the project R class.
-     * @throws PostInflateException
+     * @throws PostInflateException if TabHost is missing the required ids for TabHost
      */
     private void setupTabHost(TabHost tabHost, LayoutlibCallback layoutlibCallback)
             throws PostInflateException {
@@ -1254,12 +1258,7 @@
             TabSpec spec = tabHost.newTabSpec("tag")
                     .setIndicator("Tab Label", tabHost.getResources()
                             .getDrawable(android.R.drawable.ic_menu_info_details, null))
-                    .setContent(new TabHost.TabContentFactory() {
-                        @Override
-                        public View createTabContent(String tag) {
-                            return new LinearLayout(getContext());
-                        }
-                    });
+                    .setContent(tag -> new LinearLayout(getContext()));
             tabHost.addTab(spec);
         } else {
             // for each child of the frameLayout, add a new TabSpec
@@ -1333,8 +1332,8 @@
 
         int childCount = viewGroup.getChildCount();
         if (viewGroup == mContentRoot) {
-            List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
-            List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
+            List<ViewInfo> childrenWithoutOffset = new ArrayList<>(childCount);
+            List<ViewInfo> childrenWithOffset = new ArrayList<>(childCount);
             for (int i = 0; i < childCount; i++) {
                 ViewInfo[] childViewInfo =
                         visitContentRoot(viewGroup.getChildAt(i), hOffset, vOffset,
@@ -1345,7 +1344,7 @@
             mViewInfoList = childrenWithOffset;
             return childrenWithoutOffset;
         } else {
-            List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
+            List<ViewInfo> children = new ArrayList<>(childCount);
             for (int i = 0; i < childCount; i++) {
                 children.add(visit(viewGroup.getChildAt(i), hOffset, vOffset, setExtendedInfo,
                         isContentFrame));
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
index 96b795a..f149b6c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
@@ -34,12 +34,9 @@
 
     @Override
     public boolean markSupported() {
-        if (mFakeMarkSupport) {
-            // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
-            return true;
-        }
+        // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
+        return mFakeMarkSupport || super.markSupported();
 
-        return super.markSupported();
     }
 
     public void disableFakeMarkSupport() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
index 040191e..b89718f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
@@ -52,9 +52,7 @@
         Exception ex;
         try {
             return method.invoke(object, args);
-        } catch (IllegalAccessException e) {
-            ex = e;
-        } catch (InvocationTargetException e) {
+        } catch (IllegalAccessException | InvocationTargetException e) {
             ex = e;
         }
         throw new ReflectionException(ex);