Merge "Use correct rounding in View for potentially negative values"
diff --git a/api/current.txt b/api/current.txt
index 96ecad6..e7627f7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -332,6 +332,7 @@
     field public static final int calendarTextColor = 16843931; // 0x101049b
     field public static final int calendarViewShown = 16843596; // 0x101034c
     field public static final int calendarViewStyle = 16843613; // 0x101035d
+    field public static final int canControlMagnification = 16844040; // 0x1010508
     field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
     field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
     field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2619,6 +2620,7 @@
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+    method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
     method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
     method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
@@ -2656,6 +2658,23 @@
     field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
   }
 
+  public static final class AccessibilityService.MagnificationController {
+    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
+    method public float getCenterX();
+    method public float getCenterY();
+    method public android.graphics.Region getMagnifiedRegion();
+    method public float getScale();
+    method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+    method public boolean reset(boolean);
+    method public boolean setCenter(float, float, boolean);
+    method public boolean setScale(float, boolean);
+  }
+
+  public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
+    method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
+  }
+
   public class AccessibilityServiceInfo implements android.os.Parcelable {
     ctor public AccessibilityServiceInfo();
     method public static java.lang.String capabilityToString(int);
@@ -2670,6 +2689,7 @@
     method public java.lang.String getSettingsActivityName();
     method public java.lang.String loadDescription(android.content.pm.PackageManager);
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
     field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
     field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
     field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -5146,10 +5166,12 @@
   }
 
   public static class NotificationManager.Policy implements android.os.Parcelable {
-    ctor public NotificationManager.Policy(int, int, int);
+    ctor public deprecated NotificationManager.Policy(int, int, int);
+    ctor public NotificationManager.Policy(int, int, int, int);
     method public int describeContents();
     method public static java.lang.String priorityCategoriesToString(int);
     method public static java.lang.String prioritySendersToString(int);
+    method public static java.lang.String suppressedEffectsToString(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
     field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
@@ -5160,9 +5182,13 @@
     field public static final int PRIORITY_SENDERS_ANY = 0; // 0x0
     field public static final int PRIORITY_SENDERS_CONTACTS = 1; // 0x1
     field public static final int PRIORITY_SENDERS_STARRED = 2; // 0x2
+    field public static final int SUPPRESSED_EFFECTS_UNSET = -1; // 0xffffffff
+    field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+    field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
     field public final int priorityCallSenders;
     field public final int priorityCategories;
     field public final int priorityMessageSenders;
+    field public final int suppressedVisualEffects;
   }
 
   public final class PendingIntent implements android.os.Parcelable {
@@ -9920,6 +9946,7 @@
   public static class Resources.NotFoundException extends java.lang.RuntimeException {
     ctor public Resources.NotFoundException();
     ctor public Resources.NotFoundException(java.lang.String);
+    ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception);
   }
 
   public final class Resources.Theme {
@@ -17776,6 +17803,7 @@
 package android.media.tv {
 
   public final class TvContentRating {
+    method public final boolean contains(android.media.tv.TvContentRating);
     method public static android.media.tv.TvContentRating createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String...);
     method public java.lang.String flattenToString();
     method public java.lang.String getDomain();
@@ -28972,12 +29000,15 @@
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
+    field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+    field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
   }
 
   public static class NotificationListenerService.Ranking {
     ctor public NotificationListenerService.Ranking();
     method public java.lang.String getKey();
     method public int getRank();
+    method public int getSuppressedVisualEffects();
     method public boolean isAmbient();
     method public boolean matchesInterruptionFilter();
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 0605851..203df90 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -424,6 +424,7 @@
     field public static final int calendarTextColor = 16843931; // 0x101049b
     field public static final int calendarViewShown = 16843596; // 0x101034c
     field public static final int calendarViewStyle = 16843613; // 0x101035d
+    field public static final int canControlMagnification = 16844040; // 0x1010508
     field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
     field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
     field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2718,6 +2719,7 @@
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+    method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
     method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
     method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
@@ -2755,6 +2757,23 @@
     field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
   }
 
+  public static final class AccessibilityService.MagnificationController {
+    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
+    method public float getCenterX();
+    method public float getCenterY();
+    method public android.graphics.Region getMagnifiedRegion();
+    method public float getScale();
+    method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+    method public boolean reset(boolean);
+    method public boolean setCenter(float, float, boolean);
+    method public boolean setScale(float, boolean);
+  }
+
+  public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
+    method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
+  }
+
   public class AccessibilityServiceInfo implements android.os.Parcelable {
     ctor public AccessibilityServiceInfo();
     method public static java.lang.String capabilityToString(int);
@@ -2769,6 +2788,7 @@
     method public java.lang.String getSettingsActivityName();
     method public java.lang.String loadDescription(android.content.pm.PackageManager);
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
     field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
     field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
     field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -5264,10 +5284,12 @@
   }
 
   public static class NotificationManager.Policy implements android.os.Parcelable {
-    ctor public NotificationManager.Policy(int, int, int);
+    ctor public deprecated NotificationManager.Policy(int, int, int);
+    ctor public NotificationManager.Policy(int, int, int, int);
     method public int describeContents();
     method public static java.lang.String priorityCategoriesToString(int);
     method public static java.lang.String prioritySendersToString(int);
+    method public static java.lang.String suppressedEffectsToString(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
     field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
@@ -5278,9 +5300,13 @@
     field public static final int PRIORITY_SENDERS_ANY = 0; // 0x0
     field public static final int PRIORITY_SENDERS_CONTACTS = 1; // 0x1
     field public static final int PRIORITY_SENDERS_STARRED = 2; // 0x2
+    field public static final int SUPPRESSED_EFFECTS_UNSET = -1; // 0xffffffff
+    field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+    field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
     field public final int priorityCallSenders;
     field public final int priorityCategories;
     field public final int priorityMessageSenders;
+    field public final int suppressedVisualEffects;
   }
 
   public final class PendingIntent implements android.os.Parcelable {
@@ -10261,6 +10287,7 @@
   public static class Resources.NotFoundException extends java.lang.RuntimeException {
     ctor public Resources.NotFoundException();
     ctor public Resources.NotFoundException(java.lang.String);
+    ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception);
   }
 
   public final class Resources.Theme {
@@ -21494,10 +21521,13 @@
     method public void writeToParcel(android.os.Parcel, int);
     field public int band;
     field public android.net.wifi.WifiScanner.ChannelSpec[] channels;
+    field public int exponent;
+    field public int maxPeriodInMs;
     field public int maxScansToCache;
     field public int numBssidsPerScan;
     field public int periodInMs;
     field public int reportEvents;
+    field public int stepCount;
   }
 
   public static abstract interface WifiScanner.WifiChangeListener implements android.net.wifi.WifiScanner.ActionListener {
@@ -31089,6 +31119,8 @@
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
+    field public static final int SUPPRESSED_EFFECT_LIGHTS = 1; // 0x1
+    field public static final int SUPPRESSED_EFFECT_PEEK = 2; // 0x2
     field public static final int TRIM_FULL = 0; // 0x0
     field public static final int TRIM_LIGHT = 1; // 0x1
   }
@@ -31097,6 +31129,7 @@
     ctor public NotificationListenerService.Ranking();
     method public java.lang.String getKey();
     method public int getRank();
+    method public int getSuppressedVisualEffects();
     method public boolean isAmbient();
     method public boolean matchesInterruptionFilter();
   }
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 7f33cb5..62e0919a 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -55,6 +55,7 @@
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.ServiceManager;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -72,6 +73,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
+import java.io.PrintWriter;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -117,7 +119,8 @@
 
     @Override
     public void onShowUsage(PrintStream out) {
-        out.println(
+        PrintWriter pw = new PrintWriter(out);
+        pw.println(
                 "usage: am [subcommand] [options]\n" +
                 "usage: am start [-D] [-W] [-P <FILE>] [--start-profiler <FILE>]\n" +
                 "               [--sampling INTERVAL] [-R COUNT] [-S]\n" +
@@ -337,52 +340,10 @@
                 "am send-trim-memory: send a memory trim event to a <PROCESS>.\n" +
                 "\n" +
                 "am get-current-user: returns id of the current foreground user.\n" +
-                "\n" +
-                "<INTENT> specifications include these flags and arguments:\n" +
-                "    [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
-                "    [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
-                "    [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
-                "    [--esn <EXTRA_KEY> ...]\n" +
-                "    [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
-                "    [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
-                "    [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
-                "    [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]\n" +
-                "    [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]\n" +
-                "    [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]\n" +
-                "    [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
-                "        (mutiple extras passed as Integer[])\n" +
-                "    [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
-                "        (mutiple extras passed as List<Integer>)\n" +
-                "    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
-                "        (mutiple extras passed as Long[])\n" +
-                "    [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
-                "        (mutiple extras passed as List<Long>)\n" +
-                "    [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
-                "        (mutiple extras passed as Float[])\n" +
-                "    [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
-                "        (mutiple extras passed as List<Float>)\n" +
-                "    [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]\n" +
-                "        (mutiple extras passed as String[]; to embed a comma into a string,\n" +
-                "         escape it using \"\\,\")\n" +
-                "    [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]\n" +
-                "        (mutiple extras passed as List<String>; to embed a comma into a string,\n" +
-                "         escape it using \"\\,\")\n" +
-                "    [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
-                "    [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]\n" +
-                "    [--debug-log-resolution] [--exclude-stopped-packages]\n" +
-                "    [--include-stopped-packages]\n" +
-                "    [--activity-brought-to-front] [--activity-clear-top]\n" +
-                "    [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" +
-                "    [--activity-launched-from-history] [--activity-multiple-task]\n" +
-                "    [--activity-no-animation] [--activity-no-history]\n" +
-                "    [--activity-no-user-action] [--activity-previous-is-top]\n" +
-                "    [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" +
-                "    [--activity-single-top] [--activity-clear-task]\n" +
-                "    [--activity-task-on-home]\n" +
-                "    [--receiver-registered-only] [--receiver-replace-pending]\n" +
-                "    [--selector]\n" +
-                "    [<URI> | <PACKAGE> | <COMPONENT>]\n"
-                );
+                "\n"
+        );
+        Intent.printIntentArgsHelp(pw, "");
+        pw.flush();
     }
 
     @Override
@@ -486,10 +447,6 @@
     }
 
     private Intent makeIntent(int defUser) throws URISyntaxException {
-        Intent intent = new Intent();
-        Intent baseIntent = intent;
-        boolean hasIntentInfo = false;
-
         mStartFlags = 0;
         mWaitOption = false;
         mStopOption = false;
@@ -498,316 +455,38 @@
         mSamplingInterval = 0;
         mAutoStop = false;
         mUserId = defUser;
-        Uri data = null;
-        String type = null;
 
-        String opt;
-        while ((opt=nextOption()) != null) {
-            if (opt.equals("-a")) {
-                intent.setAction(nextArgRequired());
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-d")) {
-                data = Uri.parse(nextArgRequired());
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-t")) {
-                type = nextArgRequired();
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-c")) {
-                intent.addCategory(nextArgRequired());
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-e") || opt.equals("--es")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, value);
-            } else if (opt.equals("--esn")) {
-                String key = nextArgRequired();
-                intent.putExtra(key, (String) null);
-            } else if (opt.equals("--ei")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, Integer.decode(value));
-            } else if (opt.equals("--eu")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, Uri.parse(value));
-            } else if (opt.equals("--ecn")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                ComponentName cn = ComponentName.unflattenFromString(value);
-                if (cn == null) throw new IllegalArgumentException("Bad component name: " + value);
-                intent.putExtra(key, cn);
-            } else if (opt.equals("--eia")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                int[] list = new int[strings.length];
-                for (int i = 0; i < strings.length; i++) {
-                    list[i] = Integer.decode(strings[i]);
-                }
-                intent.putExtra(key, list);
-            } else if (opt.equals("--eial")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                ArrayList<Integer> list = new ArrayList<>(strings.length);
-                for (int i = 0; i < strings.length; i++) {
-                    list.add(Integer.decode(strings[i]));
-                }
-                intent.putExtra(key, list);
-            } else if (opt.equals("--el")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, Long.valueOf(value));
-            } else if (opt.equals("--ela")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                long[] list = new long[strings.length];
-                for (int i = 0; i < strings.length; i++) {
-                    list[i] = Long.valueOf(strings[i]);
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--elal")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                ArrayList<Long> list = new ArrayList<>(strings.length);
-                for (int i = 0; i < strings.length; i++) {
-                    list.add(Long.valueOf(strings[i]));
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--ef")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                intent.putExtra(key, Float.valueOf(value));
-                hasIntentInfo = true;
-            } else if (opt.equals("--efa")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                float[] list = new float[strings.length];
-                for (int i = 0; i < strings.length; i++) {
-                    list[i] = Float.valueOf(strings[i]);
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--efal")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                String[] strings = value.split(",");
-                ArrayList<Float> list = new ArrayList<>(strings.length);
-                for (int i = 0; i < strings.length; i++) {
-                    list.add(Float.valueOf(strings[i]));
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--esa")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                // Split on commas unless they are preceeded by an escape.
-                // The escape character must be escaped for the string and
-                // again for the regex, thus four escape characters become one.
-                String[] strings = value.split("(?<!\\\\),");
-                intent.putExtra(key, strings);
-                hasIntentInfo = true;
-            } else if (opt.equals("--esal")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired();
-                // Split on commas unless they are preceeded by an escape.
-                // The escape character must be escaped for the string and
-                // again for the regex, thus four escape characters become one.
-                String[] strings = value.split("(?<!\\\\),");
-                ArrayList<String> list = new ArrayList<>(strings.length);
-                for (int i = 0; i < strings.length; i++) {
-                    list.add(strings[i]);
-                }
-                intent.putExtra(key, list);
-                hasIntentInfo = true;
-            } else if (opt.equals("--ez")) {
-                String key = nextArgRequired();
-                String value = nextArgRequired().toLowerCase();
-                // Boolean.valueOf() results in false for anything that is not "true", which is
-                // error-prone in shell commands
-                boolean arg;
-                if ("true".equals(value) || "t".equals(value)) {
-                    arg = true;
-                } else if ("false".equals(value) || "f".equals(value)) {
-                    arg = false;
+        return Intent.parseCommandArgs(mArgs, new Intent.CommandOptionHandler() {
+            @Override
+            public boolean handleOption(String opt, ShellCommand cmd) {
+                if (opt.equals("-D")) {
+                    mStartFlags |= ActivityManager.START_FLAG_DEBUG;
+                } else if (opt.equals("-W")) {
+                    mWaitOption = true;
+                } else if (opt.equals("-P")) {
+                    mProfileFile = nextArgRequired();
+                    mAutoStop = true;
+                } else if (opt.equals("--start-profiler")) {
+                    mProfileFile = nextArgRequired();
+                    mAutoStop = false;
+                } else if (opt.equals("--sampling")) {
+                    mSamplingInterval = Integer.parseInt(nextArgRequired());
+                } else if (opt.equals("-R")) {
+                    mRepeat = Integer.parseInt(nextArgRequired());
+                } else if (opt.equals("-S")) {
+                    mStopOption = true;
+                } else if (opt.equals("--track-allocation")) {
+                    mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
+                } else if (opt.equals("--user")) {
+                    mUserId = parseUserArg(nextArgRequired());
+                } else if (opt.equals("--receiver-permission")) {
+                    mReceiverPermission = nextArgRequired();
                 } else {
-                    try {
-                        arg = Integer.decode(value) != 0;
-                    } catch (NumberFormatException ex) {
-                        throw new IllegalArgumentException("Invalid boolean value: " + value);
-                    }
+                    return false;
                 }
-
-                intent.putExtra(key, arg);
-            } else if (opt.equals("-n")) {
-                String str = nextArgRequired();
-                ComponentName cn = ComponentName.unflattenFromString(str);
-                if (cn == null) throw new IllegalArgumentException("Bad component name: " + str);
-                intent.setComponent(cn);
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-p")) {
-                String str = nextArgRequired();
-                intent.setPackage(str);
-                if (intent == baseIntent) {
-                    hasIntentInfo = true;
-                }
-            } else if (opt.equals("-f")) {
-                String str = nextArgRequired();
-                intent.setFlags(Integer.decode(str).intValue());
-            } else if (opt.equals("--grant-read-uri-permission")) {
-                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            } else if (opt.equals("--grant-write-uri-permission")) {
-                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-            } else if (opt.equals("--grant-persistable-uri-permission")) {
-                intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
-            } else if (opt.equals("--grant-prefix-uri-permission")) {
-                intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
-            } else if (opt.equals("--exclude-stopped-packages")) {
-                intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
-            } else if (opt.equals("--include-stopped-packages")) {
-                intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
-            } else if (opt.equals("--debug-log-resolution")) {
-                intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
-            } else if (opt.equals("--activity-brought-to-front")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-            } else if (opt.equals("--activity-clear-top")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            } else if (opt.equals("--activity-clear-when-task-reset")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
-            } else if (opt.equals("--activity-exclude-from-recents")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            } else if (opt.equals("--activity-launched-from-history")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
-            } else if (opt.equals("--activity-multiple-task")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-            } else if (opt.equals("--activity-no-animation")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-            } else if (opt.equals("--activity-no-history")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-            } else if (opt.equals("--activity-no-user-action")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
-            } else if (opt.equals("--activity-previous-is-top")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
-            } else if (opt.equals("--activity-reorder-to-front")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-            } else if (opt.equals("--activity-reset-task-if-needed")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-            } else if (opt.equals("--activity-single-top")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-            } else if (opt.equals("--activity-clear-task")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
-            } else if (opt.equals("--activity-task-on-home")) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-            } else if (opt.equals("--receiver-registered-only")) {
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            } else if (opt.equals("--receiver-replace-pending")) {
-                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-            } else if (opt.equals("--selector")) {
-                intent.setDataAndType(data, type);
-                intent = new Intent();
-            } else if (opt.equals("-D")) {
-                mStartFlags |= ActivityManager.START_FLAG_DEBUG;
-            } else if (opt.equals("-W")) {
-                mWaitOption = true;
-            } else if (opt.equals("-P")) {
-                mProfileFile = nextArgRequired();
-                mAutoStop = true;
-            } else if (opt.equals("--start-profiler")) {
-                mProfileFile = nextArgRequired();
-                mAutoStop = false;
-            } else if (opt.equals("--sampling")) {
-                mSamplingInterval = Integer.parseInt(nextArgRequired());
-            } else if (opt.equals("-R")) {
-                mRepeat = Integer.parseInt(nextArgRequired());
-            } else if (opt.equals("-S")) {
-                mStopOption = true;
-            } else if (opt.equals("--track-allocation")) {
-                mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
-            } else if (opt.equals("--user")) {
-                mUserId = parseUserArg(nextArgRequired());
-            } else if (opt.equals("--receiver-permission")) {
-                mReceiverPermission = nextArgRequired();
-            } else {
-                throw new IllegalArgumentException("Unknown option: " + opt);
+                return true;
             }
-        }
-        intent.setDataAndType(data, type);
-
-        final boolean hasSelector = intent != baseIntent;
-        if (hasSelector) {
-            // A selector was specified; fix up.
-            baseIntent.setSelector(intent);
-            intent = baseIntent;
-        }
-
-        String arg = nextArg();
-        baseIntent = null;
-        if (arg == null) {
-            if (hasSelector) {
-                // If a selector has been specified, and no arguments
-                // have been supplied for the main Intent, then we can
-                // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't
-                // need to have a component name specified yet, the
-                // selector will take care of that.
-                baseIntent = new Intent(Intent.ACTION_MAIN);
-                baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-            }
-        } else if (arg.indexOf(':') >= 0) {
-            // The argument is a URI.  Fully parse it, and use that result
-            // to fill in any data not specified so far.
-            baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME
-                    | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE);
-        } else if (arg.indexOf('/') >= 0) {
-            // The argument is a component name.  Build an Intent to launch
-            // it.
-            baseIntent = new Intent(Intent.ACTION_MAIN);
-            baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-            baseIntent.setComponent(ComponentName.unflattenFromString(arg));
-        } else {
-            // Assume the argument is a package name.
-            baseIntent = new Intent(Intent.ACTION_MAIN);
-            baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-            baseIntent.setPackage(arg);
-        }
-        if (baseIntent != null) {
-            Bundle extras = intent.getExtras();
-            intent.replaceExtras((Bundle)null);
-            Bundle uriExtras = baseIntent.getExtras();
-            baseIntent.replaceExtras((Bundle)null);
-            if (intent.getAction() != null && baseIntent.getCategories() != null) {
-                HashSet<String> cats = new HashSet<String>(baseIntent.getCategories());
-                for (String c : cats) {
-                    baseIntent.removeCategory(c);
-                }
-            }
-            intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR);
-            if (extras == null) {
-                extras = uriExtras;
-            } else if (uriExtras != null) {
-                uriExtras.putAll(extras);
-                extras = uriExtras;
-            }
-            intent.replaceExtras(extras);
-            hasIntentInfo = true;
-        }
-
-        if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied");
-        return intent;
+        });
     }
 
     private void runStartService() throws Exception {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 9d6aa13..273483a 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,14 +17,19 @@
 package android.accessibilityservice;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Region;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Slog;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
@@ -36,7 +41,10 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
 
 /**
  * An accessibility service runs in the background and receives callbacks by the system
@@ -373,6 +381,8 @@
         public void init(int connectionId, IBinder windowToken);
         public boolean onGesture(int gestureId);
         public boolean onKeyEvent(KeyEvent event);
+        public void onMagnificationChanged(@NonNull Region region,
+                float scale, float centerX, float centerY);
     }
 
     private int mConnectionId;
@@ -383,6 +393,8 @@
 
     private WindowManager mWindowManager;
 
+    private MagnificationController mMagnificationController;
+
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
      *
@@ -396,6 +408,20 @@
     public abstract void onInterrupt();
 
     /**
+     * Dispatches service connection to internal components first, then the
+     * client code.
+     */
+    private void dispatchServiceConnected() {
+        if (mMagnificationController != null) {
+            mMagnificationController.onServiceConnected();
+        }
+
+        // The client gets to handle service connection last, after we've set
+        // up any state upon which their code may rely.
+        onServiceConnected();
+    }
+
+    /**
      * This method is a part of the {@link AccessibilityService} lifecycle and is
      * called after the system has successfully bound to the service. If is
      * convenient to use this method for setting the {@link AccessibilityServiceInfo}.
@@ -513,6 +539,385 @@
     }
 
     /**
+     * Returns the magnification controller, which may be used to query and
+     * modify the state of display magnification.
+     * <p>
+     * <strong>Note:</strong> In order to control magnification, your service
+     * must declare the capability by setting the
+     * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+     * property in its meta-data. For more information, see
+     * {@link #SERVICE_META_DATA}.
+     *
+     * @return the magnification controller
+     */
+    @NonNull
+    public final MagnificationController getMagnificationController() {
+        if (mMagnificationController == null) {
+            mMagnificationController = new MagnificationController(this);
+        }
+        return mMagnificationController;
+    }
+
+    private void onMagnificationChanged(@NonNull Region region, float scale,
+            float centerX, float centerY) {
+        if (mMagnificationController != null) {
+            mMagnificationController.dispatchMagnificationChanged(
+                    region, scale, centerX, centerY);
+        }
+    }
+
+    /**
+     * Used to control and query the state of display magnification.
+     */
+    public static final class MagnificationController {
+        private final AccessibilityService mService;
+
+        /**
+         * Map of listeners to their handlers. Lazily created when adding the
+         * first magnification listener.
+         */
+        private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
+
+        MagnificationController(@NonNull AccessibilityService service) {
+            mService = service;
+        }
+
+        /**
+         * Called when the service is connected.
+         */
+        void onServiceConnected() {
+            if (mListeners != null && !mListeners.isEmpty()) {
+                setMagnificationCallbackEnabled(true);
+            }
+        }
+
+        /**
+         * Adds the specified change listener to the list of magnification
+         * change listeners. The callback will occur on the service's main
+         * thread.
+         *
+         * @param listener the listener to add, must be non-{@code null}
+         */
+        public void addListener(@NonNull OnMagnificationChangedListener listener) {
+            addListener(listener, null);
+        }
+
+        /**
+         * Adds the specified change listener to the list of magnification
+         * change listeners. The callback will occur on the specified
+         * {@link Handler}'s thread, or on the service's main thread if the
+         * handler is {@code null}.
+         *
+         * @param listener the listener to add, must be non-null
+         * @param handler the handler on which the callback should execute, or
+         *        {@code null} to execute on the service's main thread
+         */
+        public void addListener(@NonNull OnMagnificationChangedListener listener,
+                @Nullable Handler handler) {
+            if (mListeners == null) {
+                mListeners = new ArrayMap<>();
+            }
+
+            final boolean shouldEnableCallback = mListeners.isEmpty();
+            mListeners.put(listener, handler);
+
+            if (shouldEnableCallback) {
+                // This may fail if the service is not connected yet, but if we
+                // still have listeners when it connects then we can try again.
+                setMagnificationCallbackEnabled(true);
+            }
+        }
+
+        /**
+         * Removes all instances of the specified change listener from the list
+         * of magnification change listeners.
+         *
+         * @param listener the listener to remove, must be non-null
+         * @return {@code true} if at least one instance of the listener was
+         *         removed
+         */
+        public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
+            if (mListeners == null) {
+                return false;
+            }
+
+            final int keyIndex = mListeners.indexOfKey(listener);
+            final boolean hasKey = keyIndex >= 0;
+            if (hasKey) {
+                mListeners.removeAt(keyIndex);
+            }
+
+            if (hasKey && mListeners.isEmpty()) {
+                // We just removed the last listener, so we don't need
+                // callbacks from the service anymore.
+                setMagnificationCallbackEnabled(false);
+            }
+
+            return hasKey;
+        }
+
+        private void setMagnificationCallbackEnabled(boolean enabled) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    connection.setMagnificationCallbackEnabled(enabled);
+                } catch (RemoteException re) {
+                    throw new RuntimeException(re);
+                }
+            }
+        }
+
+        /**
+         * Dispatches magnification changes to any registered listeners. This
+         * should be called on the service's main thread.
+         */
+        void dispatchMagnificationChanged(final @NonNull Region region, final float scale,
+                final float centerX, final float centerY) {
+            if (mListeners == null || mListeners.isEmpty()) {
+                Slog.d(LOG_TAG, "Received magnification changed "
+                        + "callback with no listeners registered!");
+                setMagnificationCallbackEnabled(false);
+                return;
+            }
+
+            // Listeners may remove themselves. Perform a shallow copy to avoid
+            // concurrent modification.
+            final ArrayMap<OnMagnificationChangedListener, Handler> entries =
+                    new ArrayMap<>(mListeners);
+
+            for (int i = 0, count = entries.size(); i < count; i++) {
+                final OnMagnificationChangedListener listener = entries.keyAt(i);
+                final Handler handler = entries.valueAt(i);
+                if (handler != null) {
+                    handler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            listener.onMagnificationChanged(MagnificationController.this,
+                                    region, scale, centerX, centerY);
+                        }
+                    });
+                } else {
+                    // We're already on the main thread, just run the listener.
+                    listener.onMagnificationChanged(this, region, scale, centerX, centerY);
+                }
+            }
+        }
+
+        /**
+         * Returns the current magnification scale.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will
+         * return a default value of {@code 1.0f}.
+         *
+         * @return the current magnification scale
+         */
+        public float getScale() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.getMagnificationScale();
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to obtain scale", re);
+                }
+            }
+            return 1.0f;
+        }
+
+        /**
+         * Returns the unscaled screen-relative X coordinate of the focal
+         * center of the magnified region. This is the point around which
+         * zooming occurs and is guaranteed to lie within the magnified
+         * region.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will
+         * return a default value of {@code 0.0f}.
+         *
+         * @return the unscaled screen-relative X coordinate of the center of
+         *         the magnified region
+         */
+        public float getCenterX() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.getMagnificationCenterX();
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to obtain center X", re);
+                }
+            }
+            return 0.0f;
+        }
+
+        /**
+         * Returns the unscaled screen-relative Y coordinate of the focal
+         * center of the magnified region. This is the point around which
+         * zooming occurs and is guaranteed to lie within the magnified
+         * region.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will
+         * return a default value of {@code 0.0f}.
+         *
+         * @return the unscaled screen-relative Y coordinate of the center of
+         *         the magnified region
+         */
+        public float getCenterY() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.getMagnificationCenterY();
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to obtain center Y", re);
+                }
+            }
+            return 0.0f;
+        }
+
+        /**
+         * Returns the region of the screen currently being magnified. If
+         * magnification is not enabled, the returned region will be empty.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will
+         * return an empty region.
+         *
+         * @return the screen-relative bounds of the magnified region
+         */
+        @NonNull
+        public Region getMagnifiedRegion() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.getMagnifiedRegion();
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to obtain magnified region", re);
+                }
+            }
+            return Region.obtain();
+        }
+
+        /**
+         * Resets magnification scale and center to their default (e.g. no
+         * magnification) values.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will have
+         * no effect and return {@code false}.
+         *
+         * @param animate {@code true} to animate from the current scale and
+         *                center or {@code false} to reset the scale and center
+         *                immediately
+         * @return {@code true} on success, {@code false} on failure
+         */
+        public boolean reset(boolean animate) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.resetMagnification(animate);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to reset", re);
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Sets the magnification scale.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will have
+         * no effect and return {@code false}.
+         *
+         * @param scale the magnification scale to set, must be >= 1 and <= 5
+         * @param animate {@code true} to animate from the current scale or
+         *                {@code false} to set the scale immediately
+         * @return {@code true} on success, {@code false} on failure
+         */
+        public boolean setScale(float scale, boolean animate) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.setMagnificationScaleAndCenter(
+                            scale, Float.NaN, Float.NaN, animate);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to set scale", re);
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Sets the center of the magnified viewport.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will have
+         * no effect and return {@code false}.
+         *
+         * @param centerX the unscaled screen-relative X coordinate on which to
+         *                center the viewport
+         * @param centerY the unscaled screen-relative Y coordinate on which to
+         *                center the viewport
+         * @param animate {@code true} to animate from the current viewport
+         *                center or {@code false} to set the center immediately
+         * @return {@code true} on success, {@code false} on failure
+         */
+        public boolean setCenter(float centerX, float centerY, boolean animate) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.setMagnificationScaleAndCenter(
+                            Float.NaN, centerX, centerY, animate);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to set center", re);
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Listener for changes in the state of magnification.
+         */
+        public interface OnMagnificationChangedListener {
+            /**
+             * Called when the magnified region, scale, or center changes.
+             *
+             * @param controller the magnification controller
+             * @param region the new magnified region, may be empty if
+             *               magnification is not enabled (e.g. scale is 1)
+             * @param scale the new scale
+             * @param centerX the new X coordinate around which magnification is focused
+             * @param centerY the new Y coordinate around which magnification is focused
+             */
+            void onMagnificationChanged(@NonNull MagnificationController controller,
+                    @NonNull Region region, float scale, float centerX, float centerY);
+        }
+    }
+
+    /**
      * Performs a global action. Such an action can be performed
      * at any moment regardless of the current application or user
      * location in that application. For example going back, going
@@ -645,7 +1050,7 @@
         return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
             @Override
             public void onServiceConnected() {
-                AccessibilityService.this.onServiceConnected();
+                AccessibilityService.this.dispatchServiceConnected();
             }
 
             @Override
@@ -678,6 +1083,12 @@
             public boolean onKeyEvent(KeyEvent event) {
                 return AccessibilityService.this.onKeyEvent(event);
             }
+
+            @Override
+            public void onMagnificationChanged(@NonNull Region region,
+                    float scale, float centerX, float centerY) {
+                AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
+            }
         });
     }
 
@@ -695,6 +1106,7 @@
         private static final int DO_ON_GESTURE = 4;
         private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
         private static final int DO_ON_KEY_EVENT = 6;
+        private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
 
         private final HandlerCaller mCaller;
 
@@ -741,6 +1153,18 @@
             mCaller.sendMessage(message);
         }
 
+        public void onMagnificationChanged(@NonNull Region region,
+                float scale, float centerX, float centerY) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = region;
+            args.arg2 = scale;
+            args.arg3 = centerX;
+            args.arg4 = centerY;
+
+            final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
+            mCaller.sendMessage(message);
+        }
+
         @Override
         public void executeMessage(Message message) {
             switch (message.what) {
@@ -816,6 +1240,15 @@
                     }
                 } return;
 
+                case DO_ON_MAGNIFICATION_CHANGED: {
+                    final SomeArgs args = (SomeArgs) message.obj;
+                    final Region region = (Region) args.arg1;
+                    final float scale = (float) args.arg2;
+                    final float centerX = (float) args.arg3;
+                    final float centerY = (float) args.arg4;
+                    mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+                } return;
+
                 default :
                     Log.w(LOG_TAG, "Unknown message type " + message.what);
             }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 4edb0c6..2c98fef 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -104,6 +104,12 @@
      */
     public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 0x00000008;
 
+    /**
+     * Capability: This accessibility service can control display magnification.
+     * @see android.R.styleable#AccessibilityService_canControlMagnification
+     */
+    public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010;
+
     private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos =
             new SparseArray<CapabilityInfo>();
     static {
@@ -123,6 +129,10 @@
                 new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
                         R.string.capability_title_canRequestFilterKeyEvents,
                         R.string.capability_desc_canRequestFilterKeyEvents));
+        sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+                new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+                        R.string.capability_title_canControlMagnification,
+                        R.string.capability_desc_canControlMagnification));
     }
 
     /**
@@ -502,6 +512,10 @@
                     .AccessibilityService_canRequestFilterKeyEvents, false)) {
                 mCapabilities |= CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
             }
+            if (asAttributes.getBoolean(com.android.internal.R.styleable
+                    .AccessibilityService_canControlMagnification, false)) {
+                mCapabilities |= CAPABILITY_CAN_CONTROL_MAGNIFICATION;
+            }
             TypedValue peekedValue = asAttributes.peekValue(
                     com.android.internal.R.styleable.AccessibilityService_description);
             if (peekedValue != null) {
@@ -917,6 +931,8 @@
                 return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
             case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
                 return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
+            case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
+                return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
             default:
                 return "UNKNOWN";
         }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 8b503dd..15666bf 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -17,6 +17,7 @@
 package android.accessibilityservice;
 
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Region;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.KeyEvent;
@@ -39,4 +40,6 @@
     void clearAccessibilityCache();
 
     void onKeyEvent(in KeyEvent event, int sequence);
+
+    void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
 }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 5f7a17d..6ac50bd 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,6 +18,7 @@
 
 import android.os.Bundle;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.graphics.Region;
 import android.view.MagnificationSpec;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
@@ -63,4 +64,19 @@
     boolean performGlobalAction(int action);
 
     oneway void setOnKeyEventResult(boolean handled, int sequence);
+
+    float getMagnificationScale();
+
+    float getMagnificationCenterX();
+
+    float getMagnificationCenterY();
+
+    Region getMagnifiedRegion();
+
+    boolean resetMagnification(boolean animate);
+
+    boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+        boolean animate);
+
+    void setMagnificationCallbackEnabled(boolean enabled);
 }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 65de4ca..00bba2d 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -327,12 +327,39 @@
     /** @hide Process is being cached for later use and is empty. */
     public static final int PROCESS_STATE_CACHED_EMPTY = 16;
 
+    /** @hide Should this process state be considered a background state? */
+    public static final boolean isProcStateBackground(int procState) {
+        return procState >= PROCESS_STATE_BACKUP;
+    }
+
     /** @hide requestType for assist context: only basic information. */
     public static final int ASSIST_CONTEXT_BASIC = 0;
 
     /** @hide requestType for assist context: generate full AssistStructure. */
     public static final int ASSIST_CONTEXT_FULL = 1;
 
+    /** @hide Flag for registerUidObserver: report changes in process state. */
+    public static final int UID_OBSERVER_PROCSTATE = 1<<0;
+
+    /** @hide Flag for registerUidObserver: report uid gone. */
+    public static final int UID_OBSERVER_GONE = 1<<1;
+
+    /** @hide Flag for registerUidObserver: report uid has become idle. */
+    public static final int UID_OBSERVER_IDLE = 1<<2;
+
+    /** @hide Flag for registerUidObserver: report uid has become active. */
+    public static final int UID_OBSERVER_ACTIVE = 1<<3;
+
+    /** @hide Mode for {@link IActivityManager#getAppStartMode}: normal free-to-run operation. */
+    public static final int APP_START_MODE_NORMAL = 0;
+
+    /** @hide Mode for {@link IActivityManager#getAppStartMode}: delay running until later. */
+    public static final int APP_START_MODE_DELAYED = 1;
+
+    /** @hide Mode for {@link IActivityManager#getAppStartMode}: disable/cancel pending
+     * launches. */
+    public static final int APP_START_MODE_DISABLED = 2;
+
     /**
      * Lock task mode is not active.
      */
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4449e4f..e246e62 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -756,7 +756,7 @@
             return true;
         }
 
-        case MOVE_TOP_ACTIVITY_TO_PINNED_STACK: {
+        case MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             final int stackId = data.readInt();
             final Rect r = Rect.CREATOR.createFromParcel(data);
@@ -2029,7 +2029,8 @@
             data.enforceInterface(IActivityManager.descriptor);
             IUidObserver observer = IUidObserver.Stub.asInterface(
                     data.readStrongBinder());
-            registerUidObserver(observer);
+            int which = data.readInt();
+            registerUidObserver(observer, which);
             return true;
         }
 
@@ -2698,13 +2699,22 @@
             reply.writeNoException();
             return true;
         }
-        case REMOVE_STACK: {
+        case REMOVE_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             final int stackId = data.readInt();
             removeStack(stackId);
             reply.writeNoException();
             return true;
         }
+        case GET_APP_START_MODE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final int uid = data.readInt();
+            final String pkg = data.readString();
+            int res = getAppStartMode(uid, pkg);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -3579,7 +3589,7 @@
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(stackId);
         r.writeToParcel(data, 0);
-        mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK, data, reply, 0);
+        mRemote.transact(MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION, data, reply, 0);
         reply.readException();
         final boolean res = reply.readInt() != 0;
         data.recycle();
@@ -5326,11 +5336,12 @@
         reply.recycle();
     }
 
-    public void registerUidObserver(IUidObserver observer) throws RemoteException {
+    public void registerUidObserver(IUidObserver observer, int which) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+        data.writeInt(which);
         mRemote.transact(REGISTER_UID_OBSERVER_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
@@ -6292,11 +6303,26 @@
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(stackId);
-        mRemote.transact(REMOVE_STACK, data, reply, 0);
+        mRemote.transact(REMOVE_STACK_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
         reply.recycle();
     }
 
+    @Override
+    public int getAppStartMode(int uid, String packageName) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(uid);
+        data.writeString(packageName);
+        mRemote.transact(GET_APP_START_MODE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 77a9795..f0453e9 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -237,8 +237,10 @@
     public static final int OP_TURN_SCREEN_ON = 61;
     /** @hide Get device accounts. */
     public static final int OP_GET_ACCOUNTS = 62;
+    /** @hide Control whether an application is allowed to run in the background. */
+    public static final int OP_RUN_IN_BACKGROUND = 63;
     /** @hide */
-    public static final int _NUM_OP = 63;
+    public static final int _NUM_OP = 64;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -409,6 +411,7 @@
             OP_WRITE_EXTERNAL_STORAGE,
             OP_TURN_SCREEN_ON,
             OP_GET_ACCOUNTS,
+            OP_RUN_IN_BACKGROUND,
     };
 
     /**
@@ -478,7 +481,8 @@
             OPSTR_READ_EXTERNAL_STORAGE,
             OPSTR_WRITE_EXTERNAL_STORAGE,
             null,
-            OPSTR_GET_ACCOUNTS
+            OPSTR_GET_ACCOUNTS,
+            null,
     };
 
     /**
@@ -549,6 +553,7 @@
             "WRITE_EXTERNAL_STORAGE",
             "TURN_ON_SCREEN",
             "GET_ACCOUNTS",
+            "RUN_IN_BACKGROUND",
     };
 
     /**
@@ -618,7 +623,8 @@
             Manifest.permission.READ_EXTERNAL_STORAGE,
             Manifest.permission.WRITE_EXTERNAL_STORAGE,
             null, // no permission for turning the screen on
-            Manifest.permission.GET_ACCOUNTS
+            Manifest.permission.GET_ACCOUNTS,
+            null, // no permission for running in background
     };
 
     /**
@@ -690,6 +696,7 @@
             null, // WRITE_EXTERNAL_STORAGE
             null, // TURN_ON_SCREEN
             null, // GET_ACCOUNTS
+            null, // RUN_IN_BACKGROUND
     };
 
     /**
@@ -760,6 +767,7 @@
             false, // WRITE_EXTERNAL_STORAGE
             false, // TURN_ON_SCREEN
             false, // GET_ACCOUNTS
+            false, // RUN_IN_BACKGROUND
     };
 
     /**
@@ -829,6 +837,7 @@
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,  // OP_TURN_ON_SCREEN
             AppOpsManager.MODE_ALLOWED,
+            AppOpsManager.MODE_ALLOWED,  // OP_RUN_IN_BACKGROUND
     };
 
     /**
@@ -901,7 +910,8 @@
             false,
             false,
             false,
-            false
+            false,
+            false,
     };
 
     /**
@@ -1329,7 +1339,7 @@
             IAppOpsCallback cb = mModeWatchers.get(callback);
             if (cb == null) {
                 cb = new IAppOpsCallback.Stub() {
-                    public void opChanged(int op, String packageName) {
+                    public void opChanged(int op, int uid, String packageName) {
                         if (callback instanceof OnOpChangedInternalListener) {
                             ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
                         }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index b69a480..3d0fc92 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -400,7 +400,7 @@
     public void registerProcessObserver(IProcessObserver observer) throws RemoteException;
     public void unregisterProcessObserver(IProcessObserver observer) throws RemoteException;
 
-    public void registerUidObserver(IUidObserver observer) throws RemoteException;
+    public void registerUidObserver(IUidObserver observer, int which) throws RemoteException;
     public void unregisterUidObserver(IUidObserver observer) throws RemoteException;
 
     public boolean isIntentSenderTargetedToPackage(IIntentSender sender) throws RemoteException;
@@ -542,6 +542,8 @@
 
     public void removeStack(int stackId) throws RemoteException;
 
+    public int getAppStartMode(int uid, String packageName) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -899,6 +901,7 @@
     int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345;
     int MOVE_TASK_TO_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346;
     int SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347;
-    int REMOVE_STACK = IBinder.FIRST_CALL_TRANSACTION + 348;
-    int MOVE_TOP_ACTIVITY_TO_PINNED_STACK = IBinder.FIRST_CALL_TRANSACTION + 349;
+    int REMOVE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 348;
+    int MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 349;
+    int GET_APP_START_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 350;
 }
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 308cb94..fa8d0c9 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -18,6 +18,24 @@
 
 /** {@hide} */
 oneway interface IUidObserver {
+    /**
+     * General report of a state change of an uid.
+     */
     void onUidStateChanged(int uid, int procState);
+
+    /**
+     * Report that there are no longer any processes running for a uid.
+     */
     void onUidGone(int uid);
+
+    /**
+     * Report that a uid is now active (no longer idle).
+     */
+    void onUidActive(int uid);
+
+    /**
+     * Report that a uid is idle -- it has either been running in the background for
+     * a sufficient period of time, or all of its processes have gone away.
+     */
+    void onUidIdle(int uid);
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 07b4d39..043c503 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -610,15 +610,39 @@
          * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
         public final int priorityMessageSenders;
 
+        public static final int SUPPRESSED_EFFECTS_UNSET = -1;
+        public static final int SUPPRESSED_EFFECT_LIGHTS = 1 << 0;
+        public static final int SUPPRESSED_EFFECT_PEEK = 1 << 1;
+
+        private static final int[] ALL_SUPPRESSED_EFFECTS = {
+                SUPPRESSED_EFFECT_LIGHTS,
+                SUPPRESSED_EFFECT_PEEK,
+        };
+
+        /**
+         * Visual effects to suppress for a notification that is filtered by Do Not Disturb mode.
+         * Bitmask of SUPPRESSED_EFFECT_* constants.
+         */
+        public final int suppressedVisualEffects;
+
+
+        @Deprecated
         public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders) {
+            this(priorityCategories, priorityCallSenders, priorityMessageSenders,
+                    SUPPRESSED_EFFECTS_UNSET);
+        }
+
+        public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders,
+                int suppressedVisualEffects) {
             this.priorityCategories = priorityCategories;
             this.priorityCallSenders = priorityCallSenders;
             this.priorityMessageSenders = priorityMessageSenders;
+            this.suppressedVisualEffects = suppressedVisualEffects;
         }
 
         /** @hide */
         public Policy(Parcel source) {
-            this(source.readInt(), source.readInt(), source.readInt());
+            this(source.readInt(), source.readInt(), source.readInt(), source.readInt());
         }
 
         @Override
@@ -626,6 +650,7 @@
             dest.writeInt(priorityCategories);
             dest.writeInt(priorityCallSenders);
             dest.writeInt(priorityMessageSenders);
+            dest.writeInt(suppressedVisualEffects);
         }
 
         @Override
@@ -635,7 +660,8 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(priorityCategories, priorityCallSenders, priorityMessageSenders);
+            return Objects.hash(priorityCategories, priorityCallSenders, priorityMessageSenders,
+                    suppressedVisualEffects);
         }
 
         @Override
@@ -645,7 +671,8 @@
             final Policy other = (Policy) o;
             return other.priorityCategories == priorityCategories
                     && other.priorityCallSenders == priorityCallSenders
-                    && other.priorityMessageSenders == priorityMessageSenders;
+                    && other.priorityMessageSenders == priorityMessageSenders
+                    && other.suppressedVisualEffects == suppressedVisualEffects;
         }
 
         @Override
@@ -654,9 +681,29 @@
                     + "priorityCategories=" + priorityCategoriesToString(priorityCategories)
                     + ",priorityCallSenders=" + prioritySendersToString(priorityCallSenders)
                     + ",priorityMessageSenders=" + prioritySendersToString(priorityMessageSenders)
+                    + ",suppressedVisualEffects="
+                    + suppressedEffectsToString(suppressedVisualEffects)
                     + "]";
         }
 
+        public static String suppressedEffectsToString(int effects) {
+            if (effects <= 0) return "";
+            final StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < ALL_SUPPRESSED_EFFECTS.length; i++) {
+                final int effect = ALL_SUPPRESSED_EFFECTS[i];
+                if ((effects & effect) != 0) {
+                    if (sb.length() > 0) sb.append(',');
+                    sb.append(effectToString(effect));
+                }
+                effects &= ~effect;
+            }
+            if (effects != 0) {
+                if (sb.length() > 0) sb.append(',');
+                sb.append("UNKNOWN_").append(effects);
+            }
+            return sb.toString();
+        }
+
         public static String priorityCategoriesToString(int priorityCategories) {
             if (priorityCategories == 0) return "";
             final StringBuilder sb = new StringBuilder();
@@ -675,6 +722,15 @@
             return sb.toString();
         }
 
+        private static String effectToString(int effect) {
+            switch (effect) {
+                case SUPPRESSED_EFFECT_LIGHTS: return "SUPPRESSED_EFFECT_LIGHTS";
+                case SUPPRESSED_EFFECT_PEEK: return "SUPPRESSED_EFFECT_PEEK";
+                case SUPPRESSED_EFFECTS_UNSET: return "SUPPRESSED_EFFECTS_UNSET";
+                default: return "UNKNOWN_" + effect;
+            }
+        }
+
         private static String priorityCategoryToString(int priorityCategory) {
             switch (priorityCategory) {
                 case PRIORITY_CATEGORY_REMINDERS: return "PRIORITY_CATEGORY_REMINDERS";
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index a3b3022..aca0763 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -23,10 +23,8 @@
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.Button;
 import android.widget.TimePicker;
 import android.widget.TimePicker.OnTimeChangedListener;
-import android.widget.TimePicker.ValidationCallback;
 
 import com.android.internal.R;
 
@@ -64,7 +62,7 @@
          * @param hourOfDay the hour that was set
          * @param minute the minute that was set
          */
-        public void onTimeSet(TimePicker view, int hourOfDay, int minute);
+        void onTimeSet(TimePicker view, int hourOfDay, int minute);
     }
 
     /**
@@ -115,7 +113,6 @@
 
         final TypedValue outValue = new TypedValue();
         context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true);
-        final int layoutResId = outValue.resourceId;
 
         final LayoutInflater inflater = LayoutInflater.from(themeContext);
         final View view = inflater.inflate(R.layout.time_picker_dialog, null);
@@ -129,7 +126,6 @@
         mTimePicker.setCurrentHour(mInitialHourOfDay);
         mTimePicker.setCurrentMinute(mInitialMinute);
         mTimePicker.setOnTimeChangedListener(this);
-        mTimePicker.setValidationCallback(mValidationCallback);
     }
 
     @Override
@@ -181,14 +177,4 @@
         mTimePicker.setCurrentHour(hour);
         mTimePicker.setCurrentMinute(minute);
     }
-
-    private final ValidationCallback mValidationCallback = new ValidationCallback() {
-        @Override
-        public void onValidationChanged(boolean valid) {
-            final Button positive = getButton(BUTTON_POSITIVE);
-            if (positive != null) {
-                positive.setEnabled(valid);
-            }
-        }
-    };
 }
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index efed2e0..f7848f9 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -21,9 +21,11 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Point;
+import android.graphics.Region;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.IBinder;
 import android.os.Looper;
@@ -1020,6 +1022,12 @@
                 public boolean onKeyEvent(KeyEvent event) {
                     return false;
                 }
+
+                @Override
+                public void onMagnificationChanged(@NonNull Region region,
+                        float scale, float centerX, float centerY) {
+                    /* do nothing */
+                }
             });
         }
     }
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 25d9aa9..09a15de 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -685,6 +685,48 @@
     }
 
     /**
+     * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
+     * audio to the HF unless explicitly told to.
+     * This method should be used in cases where the SCO channel is shared between multiple profiles
+     * and must be delegated by a source knowledgeable
+     * Note: This is an internal function and shouldn't be exposed
+     *
+     * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
+     *
+     * @hide
+     */
+    public void setAudioRouteAllowed(boolean allowed) {
+        if (VDBG) log("setAudioRouteAllowed");
+        if (mService != null && isEnabled()) {
+            try {
+                mService.setAudioRouteAllowed(allowed);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
+     * Note: This is an internal function and shouldn't be exposed
+     *
+     * @hide
+     */
+    public boolean getAudioRouteAllowed() {
+        if (VDBG) log("getAudioRouteAllowed");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getAudioRouteAllowed();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
      * Check if Bluetooth SCO audio is connected.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 0e23fad..0bb4088 100755
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -50,6 +50,8 @@
     boolean isAudioOn();
     boolean connectAudio();
     boolean disconnectAudio();
+    void setAudioRouteAllowed(boolean allowed);
+    boolean getAudioRouteAllowed();
     boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device);
     boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device);
     void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 68b77fe..30fe531 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -18,6 +18,7 @@
 
 import android.content.pm.ApplicationInfo;
 import android.os.ResultReceiver;
+import android.os.ShellCommand;
 import android.provider.MediaStore;
 import android.util.ArraySet;
 
@@ -57,11 +58,13 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.io.Serializable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -3054,21 +3057,21 @@
 
     /**
      * Thermal state when the device is normal. This state is sent in the
-     * {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}.
+     * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
      * {@hide}
      */
     public static final int EXTRA_THERMAL_STATE_NORMAL = 0;
 
     /**
      * Thermal state where the device is approaching its maximum threshold. This state is sent in
-     * the {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}.
+     * the {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
      * {@hide}
      */
     public static final int EXTRA_THERMAL_STATE_WARNING = 1;
 
     /**
      * Thermal state where the device has reached its maximum threshold. This state is sent in the
-     * {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}.
+     * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
      * {@hide}
      */
     public static final int EXTRA_THERMAL_STATE_EXCEEDED = 2;
@@ -5083,6 +5086,437 @@
         return intent;
     }
 
+    /** @hide */
+    public interface CommandOptionHandler {
+        boolean handleOption(String opt, ShellCommand cmd);
+    }
+
+    /** @hide */
+    public static Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler)
+            throws URISyntaxException {
+        Intent intent = new Intent();
+        Intent baseIntent = intent;
+        boolean hasIntentInfo = false;
+
+        Uri data = null;
+        String type = null;
+
+        String opt;
+        while ((opt=cmd.getNextOption()) != null) {
+            switch (opt) {
+                case "-a":
+                    intent.setAction(cmd.getNextArgRequired());
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                    break;
+                case "-d":
+                    data = Uri.parse(cmd.getNextArgRequired());
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                    break;
+                case "-t":
+                    type = cmd.getNextArgRequired();
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                    break;
+                case "-c":
+                    intent.addCategory(cmd.getNextArgRequired());
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                    break;
+                case "-e":
+                case "--es": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, value);
+                }
+                break;
+                case "--esn": {
+                    String key = cmd.getNextArgRequired();
+                    intent.putExtra(key, (String) null);
+                }
+                break;
+                case "--ei": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, Integer.decode(value));
+                }
+                break;
+                case "--eu": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, Uri.parse(value));
+                }
+                break;
+                case "--ecn": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    ComponentName cn = ComponentName.unflattenFromString(value);
+                    if (cn == null)
+                        throw new IllegalArgumentException("Bad component name: " + value);
+                    intent.putExtra(key, cn);
+                }
+                break;
+                case "--eia": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    int[] list = new int[strings.length];
+                    for (int i = 0; i < strings.length; i++) {
+                        list[i] = Integer.decode(strings[i]);
+                    }
+                    intent.putExtra(key, list);
+                }
+                break;
+                case "--eial": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    ArrayList<Integer> list = new ArrayList<>(strings.length);
+                    for (int i = 0; i < strings.length; i++) {
+                        list.add(Integer.decode(strings[i]));
+                    }
+                    intent.putExtra(key, list);
+                }
+                break;
+                case "--el": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, Long.valueOf(value));
+                }
+                break;
+                case "--ela": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    long[] list = new long[strings.length];
+                    for (int i = 0; i < strings.length; i++) {
+                        list[i] = Long.valueOf(strings[i]);
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--elal": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    ArrayList<Long> list = new ArrayList<>(strings.length);
+                    for (int i = 0; i < strings.length; i++) {
+                        list.add(Long.valueOf(strings[i]));
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--ef": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, Float.valueOf(value));
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--efa": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    float[] list = new float[strings.length];
+                    for (int i = 0; i < strings.length; i++) {
+                        list[i] = Float.valueOf(strings[i]);
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--efal": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    ArrayList<Float> list = new ArrayList<>(strings.length);
+                    for (int i = 0; i < strings.length; i++) {
+                        list.add(Float.valueOf(strings[i]));
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--esa": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    // Split on commas unless they are preceeded by an escape.
+                    // The escape character must be escaped for the string and
+                    // again for the regex, thus four escape characters become one.
+                    String[] strings = value.split("(?<!\\\\),");
+                    intent.putExtra(key, strings);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--esal": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    // Split on commas unless they are preceeded by an escape.
+                    // The escape character must be escaped for the string and
+                    // again for the regex, thus four escape characters become one.
+                    String[] strings = value.split("(?<!\\\\),");
+                    ArrayList<String> list = new ArrayList<>(strings.length);
+                    for (int i = 0; i < strings.length; i++) {
+                        list.add(strings[i]);
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--ez": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired().toLowerCase();
+                    // Boolean.valueOf() results in false for anything that is not "true", which is
+                    // error-prone in shell commands
+                    boolean arg;
+                    if ("true".equals(value) || "t".equals(value)) {
+                        arg = true;
+                    } else if ("false".equals(value) || "f".equals(value)) {
+                        arg = false;
+                    } else {
+                        try {
+                            arg = Integer.decode(value) != 0;
+                        } catch (NumberFormatException ex) {
+                            throw new IllegalArgumentException("Invalid boolean value: " + value);
+                        }
+                    }
+
+                    intent.putExtra(key, arg);
+                }
+                break;
+                case "-n": {
+                    String str = cmd.getNextArgRequired();
+                    ComponentName cn = ComponentName.unflattenFromString(str);
+                    if (cn == null)
+                        throw new IllegalArgumentException("Bad component name: " + str);
+                    intent.setComponent(cn);
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                }
+                break;
+                case "-p": {
+                    String str = cmd.getNextArgRequired();
+                    intent.setPackage(str);
+                    if (intent == baseIntent) {
+                        hasIntentInfo = true;
+                    }
+                }
+                break;
+                case "-f":
+                    String str = cmd.getNextArgRequired();
+                    intent.setFlags(Integer.decode(str).intValue());
+                    break;
+                case "--grant-read-uri-permission":
+                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                    break;
+                case "--grant-write-uri-permission":
+                    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                    break;
+                case "--grant-persistable-uri-permission":
+                    intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+                    break;
+                case "--grant-prefix-uri-permission":
+                    intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+                    break;
+                case "--exclude-stopped-packages":
+                    intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+                    break;
+                case "--include-stopped-packages":
+                    intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+                    break;
+                case "--debug-log-resolution":
+                    intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+                    break;
+                case "--activity-brought-to-front":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+                    break;
+                case "--activity-clear-top":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                    break;
+                case "--activity-clear-when-task-reset":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+                    break;
+                case "--activity-exclude-from-recents":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                    break;
+                case "--activity-launched-from-history":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
+                    break;
+                case "--activity-multiple-task":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+                    break;
+                case "--activity-no-animation":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+                    break;
+                case "--activity-no-history":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+                    break;
+                case "--activity-no-user-action":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+                    break;
+                case "--activity-previous-is-top":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+                    break;
+                case "--activity-reorder-to-front":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+                    break;
+                case "--activity-reset-task-if-needed":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                    break;
+                case "--activity-single-top":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+                    break;
+                case "--activity-clear-task":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                    break;
+                case "--activity-task-on-home":
+                    intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+                    break;
+                case "--receiver-registered-only":
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    break;
+                case "--receiver-replace-pending":
+                    intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+                    break;
+                case "--selector":
+                    intent.setDataAndType(data, type);
+                    intent = new Intent();
+                    break;
+                default:
+                    if (optionHandler != null && optionHandler.handleOption(opt, cmd)) {
+                        // Okay, caller handled this option.
+                    } else {
+                        throw new IllegalArgumentException("Unknown option: " + opt);
+                    }
+                    break;
+            }
+        }
+        intent.setDataAndType(data, type);
+
+        final boolean hasSelector = intent != baseIntent;
+        if (hasSelector) {
+            // A selector was specified; fix up.
+            baseIntent.setSelector(intent);
+            intent = baseIntent;
+        }
+
+        String arg = cmd.getNextArg();
+        baseIntent = null;
+        if (arg == null) {
+            if (hasSelector) {
+                // If a selector has been specified, and no arguments
+                // have been supplied for the main Intent, then we can
+                // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't
+                // need to have a component name specified yet, the
+                // selector will take care of that.
+                baseIntent = new Intent(Intent.ACTION_MAIN);
+                baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            }
+        } else if (arg.indexOf(':') >= 0) {
+            // The argument is a URI.  Fully parse it, and use that result
+            // to fill in any data not specified so far.
+            baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME
+                    | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE);
+        } else if (arg.indexOf('/') >= 0) {
+            // The argument is a component name.  Build an Intent to launch
+            // it.
+            baseIntent = new Intent(Intent.ACTION_MAIN);
+            baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            baseIntent.setComponent(ComponentName.unflattenFromString(arg));
+        } else {
+            // Assume the argument is a package name.
+            baseIntent = new Intent(Intent.ACTION_MAIN);
+            baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            baseIntent.setPackage(arg);
+        }
+        if (baseIntent != null) {
+            Bundle extras = intent.getExtras();
+            intent.replaceExtras((Bundle)null);
+            Bundle uriExtras = baseIntent.getExtras();
+            baseIntent.replaceExtras((Bundle)null);
+            if (intent.getAction() != null && baseIntent.getCategories() != null) {
+                HashSet<String> cats = new HashSet<String>(baseIntent.getCategories());
+                for (String c : cats) {
+                    baseIntent.removeCategory(c);
+                }
+            }
+            intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR);
+            if (extras == null) {
+                extras = uriExtras;
+            } else if (uriExtras != null) {
+                uriExtras.putAll(extras);
+                extras = uriExtras;
+            }
+            intent.replaceExtras(extras);
+            hasIntentInfo = true;
+        }
+
+        if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied");
+        return intent;
+    }
+
+    /** @hide */
+    public static void printIntentArgsHelp(PrintWriter pw, String prefix) {
+        final String[] lines = new String[] {
+                "<INTENT> specifications include these flags and arguments:",
+                "    [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]",
+                "    [-c <CATEGORY> [-c <CATEGORY>] ...]",
+                "    [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]",
+                "    [--esn <EXTRA_KEY> ...]",
+                "    [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]",
+                "    [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]",
+                "    [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]",
+                "    [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]",
+                "    [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]",
+                "    [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]",
+                "    [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
+                "        (mutiple extras passed as Integer[])",
+                "    [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
+                "        (mutiple extras passed as List<Integer>)",
+                "    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
+                "        (mutiple extras passed as Long[])",
+                "    [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
+                "        (mutiple extras passed as List<Long>)",
+                "    [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
+                "        (mutiple extras passed as Float[])",
+                "    [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
+                "        (mutiple extras passed as List<Float>)",
+                "    [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
+                "        (mutiple extras passed as String[]; to embed a comma into a string,",
+                "         escape it using \"\\,\")",
+                "    [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
+                "        (mutiple extras passed as List<String>; to embed a comma into a string,",
+                "         escape it using \"\\,\")",
+                "    [--grant-read-uri-permission] [--grant-write-uri-permission]",
+                "    [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]",
+                "    [--debug-log-resolution] [--exclude-stopped-packages]",
+                "    [--include-stopped-packages]",
+                "    [--activity-brought-to-front] [--activity-clear-top]",
+                "    [--activity-clear-when-task-reset] [--activity-exclude-from-recents]",
+                "    [--activity-launched-from-history] [--activity-multiple-task]",
+                "    [--activity-no-animation] [--activity-no-history]",
+                "    [--activity-no-user-action] [--activity-previous-is-top]",
+                "    [--activity-reorder-to-front] [--activity-reset-task-if-needed]",
+                "    [--activity-single-top] [--activity-clear-task]",
+                "    [--activity-task-on-home]",
+                "    [--receiver-registered-only] [--receiver-replace-pending]",
+                "    [--selector]",
+                "    [<URI> | <PACKAGE> | <COMPONENT>]"
+        };
+        for (String line : lines) {
+            pw.print(prefix);
+            pw.println(line);
+        }
+    }
+
     /**
      * Retrieve the general action to be performed, such as
      * {@link #ACTION_VIEW}.  The action describes the general way the rest of
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 9c880d3..eda4136 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -479,6 +479,14 @@
     public static final int PRIVATE_FLAG_ENCRYPTION_AWARE = 1 << 6;
 
     /**
+     * Value for {@link #privateFlags}: set to {@code true} if the application
+     * is AutoPlay.
+     *
+     * {@hide}
+     */
+    public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7;
+
+    /**
      * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
      * {@hide}
      */
@@ -1049,6 +1057,13 @@
     /**
      * @hide
      */
+    public boolean isAutoPlayApp() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_AUTOPLAY) != 0;
+    }
+
+    /**
+     * @hide
+     */
     @Override protected ApplicationInfo getApplicationInfo() {
         return this;
     }
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 0606e35..7b3dde4 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -48,7 +48,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Trace;
-import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.LocaleList;
@@ -68,7 +67,6 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.lang.ref.WeakReference;
 import java.util.Locale;
 
 /**
@@ -120,9 +118,6 @@
     private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>>
             sPreloadedColorStateLists = new LongSparseArray<>();
 
-    private static final String CACHE_NOT_THEMED = "";
-    private static final String CACHE_NULL_THEME = "null_theme";
-
     // Pool of TypedArrays targeted to this Resources object.
     final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
 
@@ -130,10 +125,11 @@
     static Resources mSystem = null;
 
     private static boolean sPreloaded;
-    private static int sPreloadedDensity;
+
+    /** Lock object used to protect access to caches and configuration. */
+    private final Object mAccessLock = new Object();
 
     // These are protected by mAccessLock.
-    private final Object mAccessLock = new Object();
     private final Configuration mTmpConfig = new Configuration();
     private final DrawableCache mDrawableCache = new DrawableCache(this);
     private final DrawableCache mColorDrawableCache = new DrawableCache(this);
@@ -147,7 +143,12 @@
     /** Used to inflate drawable objects from XML. */
     private DrawableInflater mDrawableInflater;
 
+    /** Lock object used to protect access to {@link #mTmpValue}. */
+    private final Object mTmpValueLock = new Object();
+
+    /** Single-item pool used to minimize TypedValue allocations. */
     private TypedValue mTmpValue = new TypedValue();
+
     private boolean mPreloading;
 
     private int mLastCachedXmlBlockIndex = -1;
@@ -249,6 +250,10 @@
         public NotFoundException(String name) {
             super(name);
         }
+
+        public NotFoundException(String name, Exception cause) {
+            super(name, cause);
+        }
     }
 
     /**
@@ -621,18 +626,15 @@
      * @see #getDimensionPixelSize
      */
     public float getDimension(@DimenRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_DIMENSION) {
                 return TypedValue.complexToDimension(value.data, mMetrics);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -656,19 +658,15 @@
      * @see #getDimensionPixelSize
      */
     public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_DIMENSION) {
-                return TypedValue.complexToDimensionPixelOffset(
-                        value.data, mMetrics);
+                return TypedValue.complexToDimensionPixelOffset(value.data, mMetrics);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -693,19 +691,15 @@
      * @see #getDimensionPixelOffset
      */
     public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_DIMENSION) {
-                return TypedValue.complexToDimensionPixelSize(
-                        value.data, mMetrics);
+                return TypedValue.complexToDimensionPixelSize(value.data, mMetrics);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -727,18 +721,15 @@
      * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
      */
     public float getFraction(@FractionRes int id, int base, int pbase) {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_FRACTION) {
                 return TypedValue.complexToFraction(value.data, base, pbase);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
     
@@ -801,24 +792,14 @@
      *         not exist.
      */
     @Nullable
-    public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
-                mTmpValue = null;
-            }
-            getValue(id, value, true);
+    public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
+            throws NotFoundException {
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
+            return loadDrawable(value, id, theme);
+        } finally {
+            releaseTempTypedValue(value);
         }
-        final Drawable res = loadDrawable(value, id, theme);
-        synchronized (mAccessLock) {
-            if (mTmpValue == null) {
-                mTmpValue = value;
-            }
-        }
-        return res;
     }
 
     /**
@@ -849,7 +830,8 @@
      */
     @Deprecated
     @Nullable
-    public Drawable getDrawableForDensity(@DrawableRes int id, int density) throws NotFoundException {
+    public Drawable getDrawableForDensity(@DrawableRes int id, int density)
+            throws NotFoundException {
         return getDrawableForDensity(id, density, null);
     }
 
@@ -869,14 +851,8 @@
      */
     @Nullable
     public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
-                mTmpValue = null;
-            }
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             getValueForDensity(id, density, value, true);
 
             /*
@@ -893,15 +869,11 @@
                     value.density = (value.density * mMetrics.densityDpi) / density;
                 }
             }
-        }
 
-        final Drawable res = loadDrawable(value, id, theme);
-        synchronized (mAccessLock) {
-            if (mTmpValue == null) {
-                mTmpValue = value;
-            }
+            return loadDrawable(value, id, theme);
+        } finally {
+            releaseTempTypedValue(value);
         }
-        return res;
     }
 
     /**
@@ -963,33 +935,21 @@
      */
     @ColorInt
     public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type >= TypedValue.TYPE_FIRST_INT
                     && value.type <= TypedValue.TYPE_LAST_INT) {
-                mTmpValue = value;
                 return value.data;
             } else if (value.type != TypedValue.TYPE_STRING) {
-                throw new NotFoundException(
-                        "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                                + Integer.toHexString(value.type) + " is not valid");
+                throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                        + " type #0x" + Integer.toHexString(value.type) + " is not valid");
             }
-            mTmpValue = null;
-        }
 
-        final ColorStateList csl = loadColorStateList(value, id, theme);
-        synchronized (mAccessLock) {
-            if (mTmpValue == null) {
-                mTmpValue = value;
-            }
+            final ColorStateList csl = loadColorStateList(value, id, theme);
+            return csl.getDefaultColor();
+        } finally {
+            releaseTempTypedValue(value);
         }
-
-        return csl.getDefaultColor();
     }
 
     /**
@@ -1043,25 +1003,12 @@
     @Nullable
     public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
             throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
-                mTmpValue = null;
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
+            return loadColorStateList(value, id, theme);
+        } finally {
+            releaseTempTypedValue(value);
         }
-
-        final ColorStateList res = loadColorStateList(value, id, theme);
-        synchronized (mAccessLock) {
-            if (mTmpValue == null) {
-                mTmpValue = value;
-            }
-        }
-
-        return res;
     }
 
     /**
@@ -1078,19 +1025,16 @@
      * @return Returns the boolean value contained in the resource.
      */
     public boolean getBoolean(@BoolRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type >= TypedValue.TYPE_FIRST_INT
-                && value.type <= TypedValue.TYPE_LAST_INT) {
+                    && value.type <= TypedValue.TYPE_LAST_INT) {
                 return value.data != 0;
             }
-            throw new NotFoundException(
-                "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -1106,19 +1050,16 @@
      * @return Returns the integer value contained in the resource.
      */
     public int getInteger(@IntegerRes int id) throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type >= TypedValue.TYPE_FIRST_INT
-                && value.type <= TypedValue.TYPE_LAST_INT) {
+                    && value.type <= TypedValue.TYPE_LAST_INT) {
                 return value.data;
             }
-            throw new NotFoundException(
-                "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -1136,17 +1077,15 @@
      * @hide Pending API council approval.
      */
     public float getFloat(int id) {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_FLOAT) {
                 return value.getFloat();
             }
-            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
 
@@ -1238,22 +1177,60 @@
      * 
      */
     public InputStream openRawResource(@RawRes int id) throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
+        final TypedValue value = obtainTempTypedValue();
+        try {
+            return openRawResource(id, value);
+        } finally {
+            releaseTempTypedValue(value);
+        }
+    }
+
+    /**
+     * Returns a TypedValue populated with data for the specified resource ID
+     * that's suitable for temporary use. The obtained TypedValue should be
+     * released using {@link #releaseTempTypedValue(TypedValue)}.
+     *
+     * @param id the resource ID for which data should be obtained
+     * @return a populated typed value suitable for temporary use
+     */
+    private TypedValue obtainTempTypedValue(@AnyRes int id) {
+        final TypedValue value = obtainTempTypedValue();
+        getValue(id, value, true);
+        return value;
+    }
+
+    /**
+     * Returns a TypedValue suitable for temporary use. The obtained TypedValue
+     * should be released using {@link #releaseTempTypedValue(TypedValue)}.
+     *
+     * @return a typed value suitable for temporary use
+     */
+    private TypedValue obtainTempTypedValue() {
+        TypedValue tmpValue = null;
+        synchronized (mTmpValueLock) {
+            if (mTmpValue != null) {
+                tmpValue = mTmpValue;
                 mTmpValue = null;
             }
         }
-        InputStream res = openRawResource(id, value);
-        synchronized (mAccessLock) {
+        if (tmpValue == null) {
+            return new TypedValue();
+        }
+        return tmpValue;
+    }
+
+    /**
+     * Returns a TypedValue to the pool. After calling this method, the
+     * specified TypedValue should no longer be accessed.
+     *
+     * @param value the typed value to return to the pool
+     */
+    private void releaseTempTypedValue(TypedValue value) {
+        synchronized (mTmpValueLock) {
             if (mTmpValue == null) {
                 mTmpValue = value;
             }
         }
-        return res;
     }
 
     /**
@@ -1307,32 +1284,14 @@
      */
     public AssetFileDescriptor openRawResourceFd(@RawRes int id)
             throws NotFoundException {
-        TypedValue value;
-        synchronized (mAccessLock) {
-            value = mTmpValue;
-            if (value == null) {
-                value = new TypedValue();
-            } else {
-                mTmpValue = null;
-            }
-            getValue(id, value, true);
-        }
+        final TypedValue value = obtainTempTypedValue(id);
         try {
-            return mAssets.openNonAssetFd(
-                value.assetCookie, value.string.toString());
+            return mAssets.openNonAssetFd(value.assetCookie, value.string.toString());
         } catch (Exception e) {
-            NotFoundException rnf = new NotFoundException(
-                "File " + value.string.toString()
-                + " from drawable resource ID #0x"
-                + Integer.toHexString(id));
-            rnf.initCause(e);
-            throw rnf;
+            throw new NotFoundException("File " + value.string.toString() + " from drawable "
+                    + "resource ID #0x" + Integer.toHexString(id), e);
         } finally {
-            synchronized (mAccessLock) {
-                if (mTmpValue == null) {
-                    mTmpValue = value;
-                }
-            }
+            releaseTempTypedValue(value);
         }
     }
 
@@ -2015,8 +1974,8 @@
                     Build.VERSION.RESOURCES_SDK_INT);
 
             if (DEBUG_CONFIG) {
-                Slog.i(TAG, "**** Updating config of " + this + ": final config is " + mConfiguration
-                        + " final compat is " + mCompatibilityInfo);
+                Slog.i(TAG, "**** Updating config of " + this + ": final config is "
+                        + mConfiguration + " final compat is " + mCompatibilityInfo);
             }
 
             mDrawableCache.onConfigurationChange(configChanges);
@@ -2402,8 +2361,7 @@
             }
             sPreloaded = true;
             mPreloading = true;
-            sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE;
-            mConfiguration.densityDpi = sPreloadedDensity;
+            mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
             updateConfiguration(null, null);
         }
     }
@@ -2740,19 +2698,16 @@
 
     /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
             throws NotFoundException {
-        synchronized (mAccessLock) {
-            TypedValue value = mTmpValue;
-            if (value == null) {
-                mTmpValue = value = new TypedValue();
-            }
-            getValue(id, value, true);
+        final TypedValue value = obtainTempTypedValue(id);
+        try {
             if (value.type == TypedValue.TYPE_STRING) {
                 return loadXmlResourceParser(value.string.toString(), id,
                         value.assetCookie, type);
             }
-            throw new NotFoundException(
-                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
-                    + Integer.toHexString(value.type) + " is not valid");
+            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+        } finally {
+            releaseTempTypedValue(value);
         }
     }
     
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index f55883a..7a1a6a2 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -35,4 +35,6 @@
     long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason);
     long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason);
     void exitIdle(String reason);
+    void downloadServiceActive(IBinder token);
+    void downloadServiceInactive();
 }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 4ac361d0..54bfca3 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -632,9 +632,6 @@
             if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
                 argsForZygote.add("--enable-checkjni");
             }
-            if ((debugFlags & Zygote.DEBUG_ENABLE_JIT) != 0) {
-                argsForZygote.add("--enable-jit");
-            }
             if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
                 argsForZygote.add("--generate-debug-info");
             }
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index cad482b..6f12b62 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -48,19 +48,35 @@
     private FastPrintWriter mErrPrintWriter;
     private InputStream mInputStream;
 
-    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
-            String[] args, ResultReceiver resultReceiver) {
+    public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, int firstArgPos) {
         mTarget = target;
         mIn = in;
         mOut = out;
         mErr = err;
         mArgs = args;
-        mResultReceiver = resultReceiver;
-        mCmd = args != null && args.length > 0 ? args[0] : null;
-        mArgPos = 1;
+        mResultReceiver = null;
+        mCmd = null;
+        mArgPos = firstArgPos;
         mCurArgData = null;
         mOutPrintWriter = null;
         mErrPrintWriter = null;
+    }
+
+    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) {
+        String cmd;
+        int start;
+        if (args != null && args.length > 0) {
+            cmd = args[0];
+            start = 1;
+        } else {
+            cmd = null;
+            start = 0;
+        }
+        init(target, in, out, err, args, start);
+        mCmd = cmd;
+        mResultReceiver = resultReceiver;
 
         if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
         int res = -1;
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 95da438..f946ca7 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -256,6 +256,23 @@
         }
     }
 
+    /** @hide */
+    public static int parseUserArg(String arg) {
+        int userId;
+        if ("all".equals(arg)) {
+            userId = UserHandle.USER_ALL;
+        } else if ("current".equals(arg) || "cur".equals(arg)) {
+            userId = UserHandle.USER_CURRENT;
+        } else {
+            try {
+                userId = Integer.parseInt(arg);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Bad user number: " + arg);
+            }
+        }
+        return userId;
+    }
+
     /**
      * Returns the user id of the current process
      * @return user id of the current process
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index d19c7c9..2e43ffc 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -21,8 +21,12 @@
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
 import android.os.RemoteException;
 
+import java.io.FileDescriptor;
+
 /**
  * WARNING! Update IMountService.h and IMountService.cpp if you change this
  * file. In particular, the ordering of the methods below must match the
@@ -1312,6 +1316,25 @@
                 }
                 return _result;
             }
+
+            @Override
+            public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                ParcelFileDescriptor _result = null;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(name);
+                    mRemote.transact(Stub.TRANSACTION_mountAppFuse, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.<ParcelFileDescriptor>readParcelable(
+                            ClassLoader.getSystemClassLoader());
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
         }
 
         private static final String DESCRIPTOR = "IMountService";
@@ -1439,6 +1462,8 @@
         static final int TRANSACTION_isPerUserEncryptionEnabled = IBinder.FIRST_CALL_TRANSACTION + 67;
         static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 68;
 
+        static final int TRANSACTION_mountAppFuse = IBinder.FIRST_CALL_TRANSACTION + 69;
+
         /**
          * Cast an IBinder object into an IMountService interface, generating a
          * proxy if needed.
@@ -2056,6 +2081,14 @@
                     reply.writeInt(result ? 1 : 0);
                     return true;
                 }
+                case TRANSACTION_mountAppFuse: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String name = data.readString();
+                    ParcelFileDescriptor fd = mountAppFuse(name);
+                    reply.writeNoException();
+                    reply.writeParcelable(fd, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                    return true;
+                }
             }
             return super.onTransact(code, data, reply, flags);
         }
@@ -2379,4 +2412,6 @@
             throws RemoteException;
 
     public boolean isPerUserEncryptionEnabled() throws RemoteException;
+
+    public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 27df46d..2d9090b 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -30,6 +30,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
@@ -293,7 +294,7 @@
     /**
      * Constructs a StorageManager object through which an application can
      * can communicate with the systems mount service.
-     * 
+     *
      * @param tgtLooper The {@link android.os.Looper} which events will be received on.
      *
      * <p>Applications can get instance of this class by calling
@@ -409,7 +410,7 @@
      * file matches a package ID that is owned by the calling program's UID.
      * That is, shared UID applications can attempt to mount any other
      * application's OBB that shares its UID.
-     * 
+     *
      * @param rawPath the path to the OBB file
      * @param key secret used to encrypt the OBB; may be <code>null</code> if no
      *            encryption was used on the OBB.
@@ -447,7 +448,7 @@
      * That is, shared UID applications can obtain access to any other
      * application's OBB that shares its UID.
      * <p>
-     * 
+     *
      * @param rawPath path to the OBB file
      * @param force whether to kill any programs using this in order to unmount
      *            it
@@ -471,7 +472,7 @@
 
     /**
      * Check whether an Opaque Binary Blob (OBB) is mounted or not.
-     * 
+     *
      * @param rawPath path to OBB image
      * @return true if OBB is mounted; false if not mounted or on error
      */
@@ -491,7 +492,7 @@
      * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
      * give you the path to where you can obtain access to the internals of the
      * OBB.
-     * 
+     *
      * @param rawPath path to OBB image
      * @return absolute path to mounted OBB image data or <code>null</code> if
      *         not mounted or exception encountered trying to read status
@@ -1049,6 +1050,15 @@
         return path;
     }
 
+    /** {@hide} */
+    public ParcelFileDescriptor mountAppFuse(String name) {
+        try {
+            return mMountService.mountAppFuse(name);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     /// Consts to match the password types in cryptfs.h
     /** @hide */
     public static final int CRYPT_TYPE_PASSWORD = 0;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7e7b5fc..ee97e8e 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -108,6 +108,11 @@
      * This does not change the interruption filter, only the effects. **/
     public static final int HINT_HOST_DISABLE_EFFECTS = 1;
 
+    public static final int SUPPRESSED_EFFECT_LIGHTS =
+            NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+    public static final int SUPPRESSED_EFFECT_PEEK =
+            NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+
     /**
      * The full trim of the StatusBarNotification including all its features.
      *
@@ -822,6 +827,7 @@
         private boolean mIsAmbient;
         private boolean mMatchesInterruptionFilter;
         private int mVisibilityOverride;
+        private int mSuppressedVisualEffects;
 
         public Ranking() {}
 
@@ -861,6 +867,14 @@
             return mVisibilityOverride;
         }
 
+        /**
+         * Returns the type(s) of visual effects that should be suppressed for this notification.
+         * See {@link #SUPPRESSED_EFFECT_LIGHTS}, {@link #SUPPRESSED_EFFECT_PEEK}}.
+         */
+        public int getSuppressedVisualEffects() {
+            return mSuppressedVisualEffects;
+        }
+
 
         /**
          * Returns whether the notification matches the user's interruption
@@ -874,12 +888,14 @@
         }
 
         private void populate(String key, int rank, boolean isAmbient,
-                boolean matchesInterruptionFilter, int visibilityOverride) {
+                boolean matchesInterruptionFilter, int visibilityOverride,
+                int suppressedVisualEffects) {
             mKey = key;
             mRank = rank;
             mIsAmbient = isAmbient;
             mMatchesInterruptionFilter = matchesInterruptionFilter;
             mVisibilityOverride = visibilityOverride;
+            mSuppressedVisualEffects = suppressedVisualEffects;
         }
     }
 
@@ -896,6 +912,7 @@
         private ArrayMap<String,Integer> mRanks;
         private ArraySet<Object> mIntercepted;
         private ArrayMap<String, Integer> mVisibilityOverrides;
+        private ArrayMap<String, Integer> mSuppressedVisualEffects;
 
         private RankingMap(NotificationRankingUpdate rankingUpdate) {
             mRankingUpdate = rankingUpdate;
@@ -921,7 +938,7 @@
         public boolean getRanking(String key, Ranking outRanking) {
             int rank = getRank(key);
             outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key),
-                    getVisibilityOverride(key));
+                    getVisibilityOverride(key), getSuppressedVisualEffects(key));
             return rank >= 0;
         }
 
@@ -959,11 +976,24 @@
                     buildVisibilityOverridesLocked();
                 }
             }
-            Integer overide = mVisibilityOverrides.get(key);
-            if (overide == null) {
+            Integer override = mVisibilityOverrides.get(key);
+            if (override == null) {
                 return Ranking.VISIBILITY_NO_OVERRIDE;
             }
-            return overide.intValue();
+            return override.intValue();
+        }
+
+        private int getSuppressedVisualEffects(String key) {
+            synchronized (this) {
+                if (mSuppressedVisualEffects == null) {
+                    buildSuppressedVisualEffectsLocked();
+                }
+            }
+            Integer suppressed = mSuppressedVisualEffects.get(key);
+            if (suppressed == null) {
+                return 0;
+            }
+            return suppressed.intValue();
         }
 
         // Locked by 'this'
@@ -992,6 +1022,15 @@
             }
         }
 
+        // Locked by 'this'
+        private void buildSuppressedVisualEffectsLocked() {
+            Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects();
+            mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size());
+            for (String key: suppressedBundle.keySet()) {
+                mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key));
+            }
+        }
+
         // ----------- Parcelable
 
         @Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 6fba900..1282fb1 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -28,13 +28,15 @@
     private final String[] mInterceptedKeys;
     private final int mFirstAmbientIndex;
     private final Bundle mVisibilityOverrides;
+    private final Bundle mSuppressedVisualEffects;
 
     public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
-            Bundle visibilityOverrides, int firstAmbientIndex) {
+            Bundle visibilityOverrides, int firstAmbientIndex, Bundle suppressedVisualEffects) {
         mKeys = keys;
         mFirstAmbientIndex = firstAmbientIndex;
         mInterceptedKeys = interceptedKeys;
         mVisibilityOverrides = visibilityOverrides;
+        mSuppressedVisualEffects = suppressedVisualEffects;
     }
 
     public NotificationRankingUpdate(Parcel in) {
@@ -42,6 +44,7 @@
         mFirstAmbientIndex = in.readInt();
         mInterceptedKeys = in.readStringArray();
         mVisibilityOverrides = in.readBundle();
+        mSuppressedVisualEffects = in.readBundle();
     }
 
     @Override
@@ -55,6 +58,7 @@
         out.writeInt(mFirstAmbientIndex);
         out.writeStringArray(mInterceptedKeys);
         out.writeBundle(mVisibilityOverrides);
+        out.writeBundle(mSuppressedVisualEffects);
     }
 
     public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -83,4 +87,8 @@
     public Bundle getVisibilityOverrides() {
         return mVisibilityOverrides;
     }
+
+    public Bundle getSuppressedVisualEffects() {
+        return mSuppressedVisualEffects;
+    }
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 82f1b28..b3399d0 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -77,6 +77,8 @@
     private static final boolean DEFAULT_ALLOW_REMINDERS = true;
     private static final boolean DEFAULT_ALLOW_EVENTS = true;
     private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
+    private static final boolean DEFAULT_ALLOW_PEEK = true;
+    private static final boolean DEFAULT_ALLOW_LIGHTS = true;
 
     private static final int XML_VERSION = 2;
     private static final String ZEN_TAG = "zen";
@@ -91,6 +93,8 @@
     private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom";
     private static final String ALLOW_ATT_REMINDERS = "reminders";
     private static final String ALLOW_ATT_EVENTS = "events";
+    private static final String ALLOW_ATT_PEEK = "peek";
+    private static final String ALLOW_ATT_LIGHTS = "lights";
 
     private static final String CONDITION_TAG = "condition";
     private static final String CONDITION_ATT_COMPONENT = "component";
@@ -122,6 +126,8 @@
     public int allowCallsFrom = DEFAULT_SOURCE;
     public int allowMessagesFrom = DEFAULT_SOURCE;
     public int user = UserHandle.USER_SYSTEM;
+    public boolean allowPeek = DEFAULT_ALLOW_PEEK;
+    public boolean allowLights = DEFAULT_ALLOW_LIGHTS;
 
     public ZenRule manualRule;
     public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
@@ -148,6 +154,8 @@
                 automaticRules.put(ids[i], rules[i]);
             }
         }
+        allowPeek = source.readInt() == 1;
+        allowLights = source.readInt() == 1;
     }
 
     @Override
@@ -175,22 +183,26 @@
         } else {
             dest.writeInt(0);
         }
+        dest.writeInt(allowPeek ? 1 : 0);
+        dest.writeInt(allowLights ? 1 : 0);
     }
 
     @Override
     public String toString() {
         return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
-            .append("user=").append(user)
-            .append(",allowCalls=").append(allowCalls)
-            .append(",allowRepeatCallers=").append(allowRepeatCallers)
-            .append(",allowMessages=").append(allowMessages)
-            .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
-            .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
-            .append(",allowReminders=").append(allowReminders)
-            .append(",allowEvents=").append(allowEvents)
-            .append(",automaticRules=").append(automaticRules)
-            .append(",manualRule=").append(manualRule)
-            .append(']').toString();
+                .append("user=").append(user)
+                .append(",allowCalls=").append(allowCalls)
+                .append(",allowRepeatCallers=").append(allowRepeatCallers)
+                .append(",allowMessages=").append(allowMessages)
+                .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
+                .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
+                .append(",allowReminders=").append(allowReminders)
+                .append(",allowEvents=").append(allowEvents)
+                .append(",allowPeek=").append(allowPeek)
+                .append(",allowLights=").append(allowLights)
+                .append(",automaticRules=").append(automaticRules)
+                .append(",manualRule=").append(manualRule)
+                .append(']').toString();
     }
 
     private Diff diff(ZenModeConfig to) {
@@ -222,6 +234,12 @@
         if (allowEvents != to.allowEvents) {
             d.addLine("allowEvents", allowEvents, to.allowEvents);
         }
+        if (allowPeek != to.allowPeek) {
+            d.addLine("allowPeek", allowPeek, to.allowPeek);
+        }
+        if (allowLights != to.allowLights) {
+            d.addLine("allowLights", allowLights, to.allowLights);
+        }
         final ArraySet<String> allRules = new ArraySet<>();
         addKeys(allRules, automaticRules);
         addKeys(allRules, to.automaticRules);
@@ -319,6 +337,8 @@
                 && other.allowMessagesFrom == allowMessagesFrom
                 && other.allowReminders == allowReminders
                 && other.allowEvents == allowEvents
+                && other.allowPeek == allowPeek
+                && other.allowLights == allowLights
                 && other.user == user
                 && Objects.equals(other.automaticRules, automaticRules)
                 && Objects.equals(other.manualRule, manualRule);
@@ -327,7 +347,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
-                allowMessagesFrom, allowReminders, allowEvents, user, automaticRules, manualRule);
+                allowMessagesFrom, allowReminders, allowEvents, allowPeek, allowLights,
+                user, automaticRules, manualRule);
     }
 
     private static String toDayList(int[] days) {
@@ -412,6 +433,8 @@
                         rt.allowCallsFrom = DEFAULT_SOURCE;
                         rt.allowMessagesFrom = DEFAULT_SOURCE;
                     }
+                    rt.allowPeek = safeBoolean(parser, ALLOW_ATT_PEEK, DEFAULT_ALLOW_PEEK);
+                    rt.allowLights = safeBoolean(parser, ALLOW_ATT_LIGHTS, DEFAULT_ALLOW_LIGHTS);
                 } else if (MANUAL_TAG.equals(tag)) {
                     rt.manualRule = readRuleXml(parser);
                 } else if (AUTOMATIC_TAG.equals(tag)) {
@@ -440,6 +463,8 @@
         out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents));
         out.attribute(null, ALLOW_ATT_CALLS_FROM, Integer.toString(allowCallsFrom));
         out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
+        out.attribute(null, ALLOW_ATT_PEEK, Boolean.toString(allowPeek));
+        out.attribute(null, ALLOW_ATT_LIGHTS, Boolean.toString(allowLights));
         out.endTag(null, ALLOW_TAG);
 
         if (manualRule != null) {
@@ -611,9 +636,17 @@
         if (allowRepeatCallers) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
         }
+        int suppressedVisualEffects = 0;
+        if (!allowPeek) {
+            suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_PEEK;
+        }
+        if (!allowLights) {
+            suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
+        }
         priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
         priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
-        return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders);
+        return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
+                suppressedVisualEffects);
     }
 
     private static int sourceToPrioritySenders(int source, int def) {
@@ -645,6 +678,10 @@
         allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
         allowMessagesFrom = prioritySendersToSource(policy.priorityMessageSenders,
                 allowMessagesFrom);
+        if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+            allowPeek = (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_PEEK) == 0;
+            allowLights = (policy.suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_LIGHTS) == 0;
+        }
     }
 
     public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java
index 0ee6714..49242bb 100644
--- a/core/java/android/view/MagnificationSpec.java
+++ b/core/java/android/view/MagnificationSpec.java
@@ -28,10 +28,21 @@
 public class MagnificationSpec implements Parcelable {
     private static final int MAX_POOL_SIZE = 20;
     private static final SynchronizedPool<MagnificationSpec> sPool =
-            new SynchronizedPool<MagnificationSpec>(MAX_POOL_SIZE);
+            new SynchronizedPool<>(MAX_POOL_SIZE);
 
+    /** The magnification scaling factor. */
     public float scale = 1.0f;
+
+    /**
+     * The X coordinate, in unscaled screen-relative pixels, around which
+     * magnification is focused.
+     */
     public float offsetX;
+
+    /**
+     * The Y coordinate, in unscaled screen-relative pixels, around which
+     * magnification is focused.
+     */
     public float offsetY;
 
     private MagnificationSpec() {
@@ -75,6 +86,12 @@
        offsetY = 0.0f;
     }
 
+    public void setTo(MagnificationSpec other) {
+        scale = other.scale;
+        offsetX = other.offsetX;
+        offsetY = other.offsetY;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -89,6 +106,28 @@
     }
 
     @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other == null || getClass() != other.getClass()) {
+            return false;
+        }
+
+        final MagnificationSpec s = (MagnificationSpec) other;
+        return scale == s.scale && offsetX == s.offsetX && offsetY == s.offsetY;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (scale != +0.0f ? Float.floatToIntBits(scale) : 0);
+        result = 31 * result + (offsetX != +0.0f ? Float.floatToIntBits(offsetX) : 0);
+        result = 31 * result + (offsetY != +0.0f ? Float.floatToIntBits(offsetY) : 0);
+        return result;
+    }
+
+    @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("<scale:");
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fdea1ba..cd9dd97 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+
 import android.Manifest;
 import android.animation.LayoutTransition;
 import android.app.ActivityManagerNative;
@@ -37,7 +39,6 @@
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
-import android.hardware.input.InputManager;
 import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Build;
@@ -74,7 +75,6 @@
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
-import android.view.WindowCallbacks;
 import android.widget.Scroller;
 
 import com.android.internal.R;
@@ -176,6 +176,11 @@
 
     int mViewVisibility;
     boolean mAppVisible = true;
+    // For recents to freeform transition we need to keep drawing after the app receives information
+    // that it became invisible. This will ignore that information and depend on the decor view
+    // visibility to control drawing. The decor view visibility will get adjusted when the app get
+    // stopped and that's when the app will stop drawing further frames.
+    private boolean mForceDecorViewVisibility = false;
     int mOrigWindowType = -1;
 
     /** Whether the window had focus during the most recent traversal. */
@@ -561,6 +566,8 @@
                         & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                     mInputChannel = new InputChannel();
                 }
+                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
+                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                 try {
                     mOrigWindowType = mWindowAttributes.type;
                     mAttachInfo.mRecomputeGlobalAttributes = true;
@@ -1063,7 +1070,7 @@
     }
 
     int getHostVisibility() {
-        return mAppVisible ? mView.getVisibility() : View.GONE;
+        return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;
     }
 
     /**
@@ -1568,7 +1575,7 @@
         boolean insetsPending = false;
         int relayoutResult = 0;
 
-        boolean isViewVisible = viewVisibility == View.VISIBLE;
+        final boolean isViewVisible = viewVisibility == View.VISIBLE;
         if (mFirst || windowShouldResize || insetsChanged ||
                 viewVisibilityChanged || params != null) {
 
@@ -2471,8 +2478,7 @@
                 if (callbacks != null) {
                     for (SurfaceHolder.Callback c : callbacks) {
                         if (c instanceof SurfaceHolder.Callback2) {
-                            ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
-                                    mSurfaceHolder);
+                            ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(mSurfaceHolder);
                         }
                     }
                 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index edf4297..1521f2e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -118,8 +118,7 @@
      */
     public void removeViewImmediate(View view);
 
-    public static class LayoutParams extends ViewGroup.LayoutParams
-            implements Parcelable {
+    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
         /**
          * X position for this window.  With the default gravity it is ignored.
          * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or
@@ -1149,13 +1148,21 @@
 
         /**
          * Flag indicating that the x, y, width, and height members should be
-         * ignored (and thus their previous value preserved). For example 
+         * ignored (and thus their previous value preserved). For example
          * because they are being managed externally through repositionChild.
          *
          * {@hide}
          */
         public static final int PRIVATE_FLAG_PRESERVE_GEOMETRY = 0x00002000;
 
+        /**
+         * Flag that will make window ignore app visibility and instead depend purely on the decor
+         * view visibility for determining window visibility. This is used by recents to keep
+         * drawing after it launches an app.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
+
 
         /**
          * Control flags that are private to the platform.
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 3882bca..89b1eb9 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManagerInternal;
@@ -55,9 +56,10 @@
          * Called when the bounds of the screen content that is magnified changed.
          * Note that not the entire screen is magnified.
          *
-         * @param bounds The bounds.
+         * @param magnifiedBounds the currently magnified region
+         * @param availableBounds the region available for magnification
          */
-        public void onMagnifedBoundsChanged(Region bounds);
+        public void onMagnifiedBoundsChanged(Region magnifiedBounds, Region availableBounds);
 
         /**
          * Called when an application requests a rectangle on the screen to allow
@@ -142,7 +144,7 @@
      *
      * @param callbacks The callbacks to invoke.
      */
-    public abstract void setMagnificationCallbacks(MagnificationCallbacks callbacks);
+    public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
 
     /**
      * Set by the accessibility layer to specify the magnification and panning to
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 34a8439..a53df88 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -21,7 +21,6 @@
 import com.android.internal.view.menu.MenuPopupHelper;
 import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.view.menu.ShowableListMenu;
-import com.android.internal.view.menu.SubMenuBuilder;
 
 import android.annotation.MenuRes;
 import android.content.Context;
@@ -33,35 +32,23 @@
 import android.view.View.OnTouchListener;
 
 /**
- * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}.
- * The popup will appear below the anchor view if there is room, or above it if there is not.
- * If the IME is visible the popup will not overlap it until it is touched. Touching outside
- * of the popup will dismiss it.
+ * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a
+ * {@link View}. The popup will appear below the anchor view if there is room,
+ * or above it if there is not. If the IME is visible the popup will not
+ * overlap it until it is touched. Touching outside of the popup will dismiss
+ * it.
  */
-public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
+public class PopupMenu {
     private final Context mContext;
     private final MenuBuilder mMenu;
     private final View mAnchor;
     private final MenuPopupHelper mPopup;
-    private final boolean mShowCascadingMenus;
 
     private OnMenuItemClickListener mMenuItemClickListener;
     private OnDismissListener mDismissListener;
     private OnTouchListener mDragListener;
 
     /**
-     * Callback interface used to notify the application that the menu has closed.
-     */
-    public interface OnDismissListener {
-        /**
-         * Called when the associated menu has been dismissed.
-         *
-         * @param menu The PopupMenu that was dismissed.
-         */
-        public void onDismiss(PopupMenu menu);
-    }
-
-    /**
      * Constructor to create a new popup menu with an anchor view.
      *
      * @param context Context the popup menu is running in, through which it
@@ -108,14 +95,40 @@
     public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
             int popupStyleRes) {
         mContext = context;
-        mShowCascadingMenus = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableCascadingSubmenus);
-        mMenu = new MenuBuilder(context);
-        mMenu.setCallback(this);
         mAnchor = anchor;
+
+        mMenu = new MenuBuilder(context);
+        mMenu.setCallback(new MenuBuilder.Callback() {
+            @Override
+            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+                if (mMenuItemClickListener != null) {
+                    return mMenuItemClickListener.onMenuItemClick(item);
+                }
+                return false;
+            }
+
+            @Override
+            public void onMenuModeChange(MenuBuilder menu) {
+            }
+        });
+
         mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
         mPopup.setGravity(gravity);
-        mPopup.setCallback(this);
+        mPopup.setCallback(new MenuPresenter.Callback() {
+            @Override
+            public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+                if (mDismissListener != null) {
+                    mDismissListener.onDismiss(PopupMenu.this);
+                }
+            }
+
+            @Override
+            public boolean onOpenSubMenu(MenuBuilder subMenu) {
+                // The menu presenter will handle opening the submenu itself.
+                // Nothing to do here.
+                return false;
+            }
+        });
     }
 
     /**
@@ -125,7 +138,6 @@
      * the next time the popup is shown.
      *
      * @param gravity the gravity used to align the popup window
-     *
      * @see #getGravity()
      */
     public void setGravity(int gravity) {
@@ -134,7 +146,6 @@
 
     /**
      * @return the gravity used to align the popup window to its anchor view
-     *
      * @see #setGravity(int)
      */
     public int getGravity() {
@@ -146,8 +157,8 @@
      * to implement drag-to-open behavior.
      * <p>
      * When the listener is set on a view, touching that view and dragging
-     * outside of its bounds will open the popup window. Lifting will select the
-     * currently touched list item.
+     * outside of its bounds will open the popup window. Lifting will select
+     * the currently touched list item.
      * <p>
      * Example usage:
      * <pre>
@@ -184,9 +195,10 @@
     }
 
     /**
-     * @return the {@link Menu} associated with this popup. Populate the returned Menu with
-     * items before calling {@link #show()}.
+     * Returns the {@link Menu} associated with this popup. Populate the
+     * returned Menu with items before calling {@link #show()}.
      *
+     * @return the {@link Menu} associated with this popup
      * @see #show()
      * @see #getMenuInflater()
      */
@@ -195,9 +207,8 @@
     }
 
     /**
-     * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the
-     * menu returned by {@link #getMenu()}.
-     *
+     * @return a {@link MenuInflater} that can be used to inflate menu items
+     *         from XML into the menu returned by {@link #getMenu()}
      * @see #getMenu()
      */
     public MenuInflater getMenuInflater() {
@@ -205,8 +216,9 @@
     }
 
     /**
-     * Inflate a menu resource into this PopupMenu. This is equivalent to calling
-     * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
+     * Inflate a menu resource into this PopupMenu. This is equivalent to
+     * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
+     *
      * @param menuRes Menu resource to inflate
      */
     public void inflate(@MenuRes int menuRes) {
@@ -215,6 +227,7 @@
 
     /**
      * Show the menu popup anchored to the view specified during construction.
+     *
      * @see #dismiss()
      */
     public void show() {
@@ -223,6 +236,7 @@
 
     /**
      * Dismiss the menu popup.
+     *
      * @see #show()
      */
     public void dismiss() {
@@ -230,68 +244,49 @@
     }
 
     /**
-     * Set a listener that will be notified when the user selects an item from the menu.
+     * Sets a listener that will be notified when the user selects an item from
+     * the menu.
      *
-     * @param listener Listener to notify
+     * @param listener the listener to notify
      */
     public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
         mMenuItemClickListener = listener;
     }
 
     /**
-     * Set a listener that will be notified when this menu is dismissed.
+     * Sets a listener that will be notified when this menu is dismissed.
      *
-     * @param listener Listener to notify
+     * @param listener the listener to notify
      */
     public void setOnDismissListener(OnDismissListener listener) {
         mDismissListener = listener;
     }
 
     /**
-     * @hide
-     */
-    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
-        if (mMenuItemClickListener != null) {
-            return mMenuItemClickListener.onMenuItemClick(item);
-        }
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-        if (mDismissListener != null) {
-            mDismissListener.onDismiss(this);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean onOpenSubMenu(MenuBuilder subMenu) {
-        // The menu presenter will handle opening the submenu itself. Nothing to do here.
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    public void onMenuModeChange(MenuBuilder menu) {
-    }
-
-    /**
-     * Interface responsible for receiving menu item click events if the items themselves
-     * do not have individual item click listeners.
+     * Interface responsible for receiving menu item click events if the items
+     * themselves do not have individual item click listeners.
      */
     public interface OnMenuItemClickListener {
         /**
-         * This method will be invoked when a menu item is clicked if the item itself did
-         * not already handle the event.
+         * This method will be invoked when a menu item is clicked if the item
+         * itself did not already handle the event.
          *
-         * @param item {@link MenuItem} that was clicked
-         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+         * @param item the menu item that was clicked
+         * @return {@code true} if the event was handled, {@code false}
+         *         otherwise
          */
-        public boolean onMenuItemClick(MenuItem item);
+        boolean onMenuItemClick(MenuItem item);
+    }
+
+    /**
+     * Callback interface used to notify the application that the menu has closed.
+     */
+    public interface OnDismissListener {
+        /**
+         * Called when the associated menu has been dismissed.
+         *
+         * @param menu the popup menu that was dismissed
+         */
+        void onDismiss(PopupMenu menu);
     }
 }
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 8e5af79..a24d37f 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -100,7 +100,7 @@
      * @see #getHour()
      */
     public void setHour(int hour) {
-        mDelegate.setCurrentHour(hour);
+        mDelegate.setHour(hour);
     }
 
     /**
@@ -110,7 +110,7 @@
      * @see #setHour(int)
      */
     public int getHour() {
-        return mDelegate.getCurrentHour();
+        return mDelegate.getHour();
     }
 
     /**
@@ -120,7 +120,7 @@
      * @see #getMinute()
      */
     public void setMinute(int minute) {
-        mDelegate.setCurrentMinute(minute);
+        mDelegate.setMinute(minute);
     }
 
     /**
@@ -130,7 +130,7 @@
      * @see #setMinute(int)
      */
     public int getMinute() {
-        return mDelegate.getCurrentMinute();
+        return mDelegate.getMinute();
     }
 
     /**
@@ -150,7 +150,7 @@
     @NonNull
     @Deprecated
     public Integer getCurrentHour() {
-        return mDelegate.getCurrentHour();
+        return mDelegate.getHour();
     }
 
     /**
@@ -160,7 +160,7 @@
      */
     @Deprecated
     public void setCurrentMinute(@NonNull Integer currentMinute) {
-        mDelegate.setCurrentMinute(currentMinute);
+        mDelegate.setMinute(currentMinute);
     }
 
     /**
@@ -170,7 +170,7 @@
     @NonNull
     @Deprecated
     public Integer getCurrentMinute() {
-        return mDelegate.getCurrentMinute();
+        return mDelegate.getMinute();
     }
 
     /**
@@ -186,7 +186,7 @@
             return;
         }
 
-        mDelegate.setIs24HourView(is24HourView);
+        mDelegate.setIs24Hour(is24HourView);
     }
 
     /**
@@ -195,7 +195,7 @@
      * @see #setIs24HourView(Boolean)
      */
     public boolean is24HourView() {
-        return mDelegate.is24HourView();
+        return mDelegate.is24Hour();
     }
 
     /**
@@ -207,16 +207,6 @@
         mDelegate.setOnTimeChangedListener(onTimeChangedListener);
     }
 
-    /**
-     * Sets the callback that indicates the current time is valid.
-     *
-     * @param callback the callback, may be null
-     * @hide
-     */
-    public void setValidationCallback(@Nullable ValidationCallback callback) {
-        mDelegate.setValidationCallback(callback);
-    }
-
     @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
@@ -234,12 +224,6 @@
     }
 
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        mDelegate.onConfigurationChanged(newConfig);
-    }
-
-    @Override
     protected Parcelable onSaveInstanceState() {
         Parcelable superState = super.onSaveInstanceState();
         return mDelegate.onSaveInstanceState(superState);
@@ -269,25 +253,22 @@
      * for the real behavior.
      */
     interface TimePickerDelegate {
-        void setCurrentHour(int currentHour);
-        int getCurrentHour();
+        void setHour(int hour);
+        int getHour();
 
-        void setCurrentMinute(int currentMinute);
-        int getCurrentMinute();
+        void setMinute(int minute);
+        int getMinute();
 
-        void setIs24HourView(boolean is24HourView);
-        boolean is24HourView();
+        void setIs24Hour(boolean is24Hour);
+        boolean is24Hour();
 
         void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
-        void setValidationCallback(ValidationCallback callback);
 
         void setEnabled(boolean enabled);
         boolean isEnabled();
 
         int getBaseline();
 
-        void onConfigurationChanged(Configuration newConfig);
-
         Parcelable onSaveInstanceState(Parcelable superState);
         void onRestoreInstanceState(Parcelable state);
 
@@ -295,16 +276,6 @@
         void onPopulateAccessibilityEvent(AccessibilityEvent event);
     }
 
-    /**
-     * A callback interface for updating input validity when the TimePicker
-     * when included into a Dialog.
-     *
-     * @hide
-     */
-    public static interface ValidationCallback {
-        void onValidationChanged(boolean valid);
-    }
-
     static String[] getAmPmStrings(Context context) {
         final Locale locale = context.getResources().getConfiguration().locale;
         final LocaleData d = LocaleData.get(locale);
@@ -319,43 +290,16 @@
      * An abstract class which can be used as a start for TimePicker implementations
      */
     abstract static class AbstractTimePickerDelegate implements TimePickerDelegate {
-        // The delegator
-        protected TimePicker mDelegator;
+        protected final TimePicker mDelegator;
+        protected final Context mContext;
+        protected final Locale mLocale;
 
-        // The context
-        protected Context mContext;
-
-        // The current locale
-        protected Locale mCurrentLocale;
-
-        // Callbacks
         protected OnTimeChangedListener mOnTimeChangedListener;
-        protected ValidationCallback mValidationCallback;
 
-        public AbstractTimePickerDelegate(TimePicker delegator, Context context) {
+        public AbstractTimePickerDelegate(@NonNull TimePicker delegator, @NonNull Context context) {
             mDelegator = delegator;
             mContext = context;
-
-            // initialization based on locale
-            setCurrentLocale(Locale.getDefault());
-        }
-
-        public void setCurrentLocale(Locale locale) {
-            if (locale.equals(mCurrentLocale)) {
-                return;
-            }
-            mCurrentLocale = locale;
-        }
-
-        @Override
-        public void setValidationCallback(ValidationCallback callback) {
-            mValidationCallback = callback;
-        }
-
-        protected void onValidationChanged(boolean valid) {
-            if (mValidationCallback != null) {
-                mValidationCallback.onValidationChanged(valid);
-            }
+            mLocale = context.getResources().getConfiguration().locale;
         }
     }
 }
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 4dc5fd3e..b523b84 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Parcel;
@@ -89,7 +88,7 @@
     private boolean mAllowAutoAdvance;
     private int mInitialHourOfDay;
     private int mInitialMinute;
-    private boolean mIs24HourView;
+    private boolean mIs24Hour;
     private boolean mIsAmPmAtStart;
 
     // Accessibility strings.
@@ -199,20 +198,13 @@
 
         mAllowAutoAdvance = true;
 
-        // Updates mHourFormat variables used below.
-        updateHourFormat(mCurrentLocale, mIs24HourView);
-
-        // Update hour text field.
-        final int minHour = mHourFormatStartsAtZero ? 0 : 1;
-        final int maxHour = (mIs24HourView ? 23 : 11) + minHour;
-        mHourView.setRange(minHour, maxHour);
-        mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
+        updateHourFormat();
 
         // Initialize with current time.
-        mTempCalendar = Calendar.getInstance(mCurrentLocale);
+        mTempCalendar = Calendar.getInstance(mLocale);
         final int currentHour = mTempCalendar.get(Calendar.HOUR_OF_DAY);
         final int currentMinute = mTempCalendar.get(Calendar.MINUTE);
-        initialize(currentHour, currentMinute, mIs24HourView, HOUR_INDEX);
+        initialize(currentHour, currentMinute, mIs24Hour, HOUR_INDEX);
     }
 
     /**
@@ -233,15 +225,14 @@
     }
 
     /**
-     * Determines how the hour should be formatted and updates member variables
-     * related to hour formatting.
-     *
-     * @param locale the locale in which the view is displayed
-     * @param is24Hour whether the view is in 24-hour (hour-of-day) mode
+     * Updates hour formatting based on the current locale and 24-hour mode.
+     * <p>
+     * Determines how the hour should be formatted, sets member variables for
+     * leading zero and starting hour, and sets the hour view's presentation.
      */
-    private void updateHourFormat(Locale locale, boolean is24Hour) {
+    private void updateHourFormat() {
         final String bestDateTimePattern = DateFormat.getBestDateTimePattern(
-                locale, is24Hour ? "Hm" : "hm");
+                mLocale, mIs24Hour ? "Hm" : "hm");
         final int lengthPattern = bestDateTimePattern.length();
         boolean showLeadingZero = false;
         char hourFormat = '\0';
@@ -259,6 +250,12 @@
 
         mHourFormatShowLeadingZero = showLeadingZero;
         mHourFormatStartsAtZero = hourFormat == 'K' || hourFormat == 'H';
+
+        // Update hour text field.
+        final int minHour = mHourFormatStartsAtZero ? 0 : 1;
+        final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
+        mHourView.setRange(minHour, maxHour);
+        mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
     }
 
     private static final CharSequence obtainVerbatim(String text) {
@@ -333,7 +330,7 @@
     private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
         mInitialHourOfDay = hourOfDay;
         mInitialMinute = minute;
-        mIs24HourView = is24HourView;
+        mIs24Hour = is24HourView;
         updateUI(index);
     }
 
@@ -352,17 +349,16 @@
     }
 
     private void updateRadialPicker(int index) {
-        mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView);
+        mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24Hour);
         setCurrentItemShowing(index, false, true);
     }
 
     private void updateHeaderAmPm() {
-
-        if (mIs24HourView) {
+        if (mIs24Hour) {
             mAmPmLayout.setVisibility(View.GONE);
         } else {
             // Ensure that AM/PM layout is in the correct position.
-            final String dateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, "hm");
+            final String dateTimePattern = DateFormat.getBestDateTimePattern(mLocale, "hm");
             final boolean isAmPmAtStart = dateTimePattern.startsWith("a");
             setAmPmAtStart(isAmPmAtStart);
 
@@ -395,35 +391,32 @@
      * Set the current hour.
      */
     @Override
-    public void setCurrentHour(int currentHour) {
-        if (mInitialHourOfDay == currentHour) {
-            return;
+    public void setHour(int hour) {
+        if (mInitialHourOfDay != hour) {
+            mInitialHourOfDay = hour;
+            updateHeaderHour(hour, true);
+            updateHeaderAmPm();
+            mRadialTimePickerView.setCurrentHour(hour);
+            mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
+            mDelegator.invalidate();
+            onTimeChanged();
         }
-        mInitialHourOfDay = currentHour;
-        updateHeaderHour(currentHour, true);
-        updateHeaderAmPm();
-        mRadialTimePickerView.setCurrentHour(currentHour);
-        mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
-        mDelegator.invalidate();
-        onTimeChanged();
     }
 
     /**
-     * @return The current hour in the range (0-23).
+     * @return the current hour in the range (0-23)
      */
     @Override
-    public int getCurrentHour() {
-        int currentHour = mRadialTimePickerView.getCurrentHour();
-        if (mIs24HourView) {
+    public int getHour() {
+        final int currentHour = mRadialTimePickerView.getCurrentHour();
+        if (mIs24Hour) {
             return currentHour;
+        }
+
+        if (mRadialTimePickerView.getAmOrPm() == PM) {
+            return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
         } else {
-            switch(mRadialTimePickerView.getAmOrPm()) {
-                case PM:
-                    return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
-                case AM:
-                default:
-                    return currentHour % HOURS_IN_HALF_DAY;
-            }
+            return currentHour % HOURS_IN_HALF_DAY;
         }
     }
 
@@ -431,48 +424,49 @@
      * Set the current minute (0-59).
      */
     @Override
-    public void setCurrentMinute(int currentMinute) {
-        if (mInitialMinute == currentMinute) {
-            return;
+    public void setMinute(int minute) {
+        if (mInitialMinute != minute) {
+            mInitialMinute = minute;
+            updateHeaderMinute(minute, true);
+            mRadialTimePickerView.setCurrentMinute(minute);
+            mDelegator.invalidate();
+            onTimeChanged();
         }
-        mInitialMinute = currentMinute;
-        updateHeaderMinute(currentMinute, true);
-        mRadialTimePickerView.setCurrentMinute(currentMinute);
-        mDelegator.invalidate();
-        onTimeChanged();
     }
 
     /**
      * @return The current minute.
      */
     @Override
-    public int getCurrentMinute() {
+    public int getMinute() {
         return mRadialTimePickerView.getCurrentMinute();
     }
 
     /**
-     * Set whether in 24 hour or AM/PM mode.
+     * Sets whether time is displayed in 24-hour mode or 12-hour mode with
+     * AM/PM indicators.
      *
-     * @param is24HourView True = 24 hour mode. False = AM/PM.
+     * @param is24Hour {@code true} to display time in 24-hour mode or
+     *        {@code false} for 12-hour mode with AM/PM
      */
-    @Override
-    public void setIs24HourView(boolean is24HourView) {
-        if (is24HourView == mIs24HourView) {
-            return;
+    public void setIs24Hour(boolean is24Hour) {
+        if (mIs24Hour != is24Hour) {
+            mIs24Hour = is24Hour;
+            mInitialHourOfDay = getHour();
+
+            updateHourFormat();
+            updateUI(mRadialTimePickerView.getCurrentItemShowing());
         }
-
-        mIs24HourView = is24HourView;
-        mInitialHourOfDay = getCurrentHour();
-
-        updateUI(mRadialTimePickerView.getCurrentItemShowing());
     }
 
     /**
-     * @return true if this is in 24 hour view else false.
+     * @return {@code true} if time is displayed in 24-hour mode, or
+     *         {@code false} if time is displayed in 12-hour mode with AM/PM
+     *         indicators
      */
     @Override
-    public boolean is24HourView() {
-        return mIs24HourView;
+    public boolean is24Hour() {
+        return mIs24Hour;
     }
 
     @Override
@@ -502,14 +496,9 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        updateUI(mRadialTimePickerView.getCurrentItemShowing());
-    }
-
-    @Override
     public Parcelable onSaveInstanceState(Parcelable superState) {
-        return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
-                is24HourView(), getCurrentItemShowing());
+        return new SavedState(superState, getHour(), getMinute(),
+                is24Hour(), getCurrentItemShowing());
     }
 
     @Override
@@ -520,12 +509,6 @@
     }
 
     @Override
-    public void setCurrentLocale(Locale locale) {
-        super.setCurrentLocale(locale);
-        mTempCalendar = Calendar.getInstance(locale);
-    }
-
-    @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         onPopulateAccessibilityEvent(event);
         return true;
@@ -534,13 +517,13 @@
     @Override
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         int flags = DateUtils.FORMAT_SHOW_TIME;
-        if (mIs24HourView) {
+        if (mIs24Hour) {
             flags |= DateUtils.FORMAT_24HOUR;
         } else {
             flags |= DateUtils.FORMAT_12HOUR;
         }
-        mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
-        mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
+        mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour());
+        mTempCalendar.set(Calendar.MINUTE, getMinute());
         String selectedDate = DateUtils.formatDateTime(mContext,
                 mTempCalendar.getTimeInMillis(), flags);
         event.getText().add(selectedDate);
@@ -559,8 +542,7 @@
     private void onTimeChanged() {
         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
         if (mOnTimeChangedListener != null) {
-            mOnTimeChangedListener.onTimeChanged(mDelegator,
-                    getCurrentHour(), getCurrentMinute());
+            mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
         }
     }
 
@@ -666,25 +648,29 @@
         }
 
         if (mOnTimeChangedListener != null) {
-            mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), getCurrentMinute());
+            mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
         }
     }
 
     /**
      * Converts hour-of-day (0-23) time into a localized hour number.
+     * <p>
+     * The localized value may be in the range (0-23), (1-24), (0-11), or
+     * (1-12) depending on the locale. This method does not handle leading
+     * zeroes.
      *
      * @param hourOfDay the hour-of-day (0-23)
      * @return a localized hour number
      */
     private int getLocalizedHour(int hourOfDay) {
-        if (!mIs24HourView) {
+        if (!mIs24Hour) {
             // Convert to hour-of-am-pm.
             hourOfDay %= 12;
         }
 
         if (!mHourFormatStartsAtZero && hourOfDay == 0) {
             // Convert to clock-hour (either of-day or of-am-pm).
-            hourOfDay = mIs24HourView ? 24 : 12;
+            hourOfDay = mIs24Hour ? 24 : 12;
         }
 
         return hourOfDay;
@@ -716,8 +702,8 @@
      * separator as the character which is just after the hour marker in the returned pattern.
      */
     private void updateHeaderSeparator() {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
-                (mIs24HourView) ? "Hm" : "hm");
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
+                (mIs24Hour) ? "Hm" : "hm");
         final String separatorText;
         // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
         final char[] hourFormats = {'H', 'h', 'K', 'k'};
@@ -819,14 +805,14 @@
     private final Runnable mCommitHour = new Runnable() {
         @Override
         public void run() {
-            setCurrentHour(mHourView.getValue());
+            setHour(mHourView.getValue());
         }
     };
 
     private final Runnable mCommitMinute = new Runnable() {
         @Override
         public void run() {
-            setCurrentMinute(mMinuteView.getValue());
+            setMinute(mMinuteView.getValue());
         }
     };
 
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 8741cc3..2ed230b 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -17,7 +17,6 @@
 package android.widget;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -33,7 +32,6 @@
 import com.android.internal.R;
 
 import java.util.Calendar;
-import java.util.Locale;
 
 import libcore.icu.LocaleData;
 
@@ -92,7 +90,7 @@
         mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
             public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
                 updateInputState();
-                if (!is24HourView()) {
+                if (!is24Hour()) {
                     if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) ||
                             (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
                         mIsAm = !mIsAm;
@@ -124,14 +122,14 @@
                 int maxValue = mMinuteSpinner.getMaxValue();
                 if (oldVal == maxValue && newVal == minValue) {
                     int newHour = mHourSpinner.getValue() + 1;
-                    if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
+                    if (!is24Hour() && newHour == HOURS_IN_HALF_DAY) {
                         mIsAm = !mIsAm;
                         updateAmPmControl();
                     }
                     mHourSpinner.setValue(newHour);
                 } else if (oldVal == minValue && newVal == maxValue) {
                     int newHour = mHourSpinner.getValue() - 1;
-                    if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
+                    if (!is24Hour() && newHour == HOURS_IN_HALF_DAY - 1) {
                         mIsAm = !mIsAm;
                         updateAmPmControl();
                     }
@@ -204,8 +202,8 @@
         updateAmPmControl();
 
         // set to current time
-        setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
-        setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
+        setHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
+        setMinute(mTempCalendar.get(Calendar.MINUTE));
 
         if (!isEnabled()) {
             setEnabled(false);
@@ -221,7 +219,7 @@
     }
 
     private void getHourFormatData() {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 (mIs24HourView) ? "Hm" : "hm");
         final int lengthPattern = bestDateTimePattern.length();
         mHourWithTwoDigit = false;
@@ -241,7 +239,7 @@
     }
 
     private boolean isAmPmAtStart() {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 "hm" /* skeleton */);
 
         return bestDateTimePattern.startsWith("a");
@@ -257,7 +255,7 @@
      */
     private void setDividerText() {
         final String skeleton = (mIs24HourView) ? "Hm" : "hm";
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 skeleton);
         final String separatorText;
         int hourIndex = bestDateTimePattern.lastIndexOf('H');
@@ -279,16 +277,16 @@
     }
 
     @Override
-    public void setCurrentHour(int currentHour) {
-        setCurrentHour(currentHour, true);
+    public void setHour(int hour) {
+        setCurrentHour(hour, true);
     }
 
     private void setCurrentHour(int currentHour, boolean notifyTimeChanged) {
         // why was Integer used in the first place?
-        if (currentHour == getCurrentHour()) {
+        if (currentHour == getHour()) {
             return;
         }
-        if (!is24HourView()) {
+        if (!is24Hour()) {
             // convert [0,23] ordinal to wall clock display
             if (currentHour >= HOURS_IN_HALF_DAY) {
                 mIsAm = false;
@@ -310,9 +308,9 @@
     }
 
     @Override
-    public int getCurrentHour() {
+    public int getHour() {
         int currentHour = mHourSpinner.getValue();
-        if (is24HourView()) {
+        if (is24Hour()) {
             return currentHour;
         } else if (mIsAm) {
             return currentHour % HOURS_IN_HALF_DAY;
@@ -322,28 +320,27 @@
     }
 
     @Override
-    public void setCurrentMinute(int currentMinute) {
-        if (currentMinute == getCurrentMinute()) {
+    public void setMinute(int minute) {
+        if (minute == getMinute()) {
             return;
         }
-        mMinuteSpinner.setValue(currentMinute);
+        mMinuteSpinner.setValue(minute);
         onTimeChanged();
     }
 
     @Override
-    public int getCurrentMinute() {
+    public int getMinute() {
         return mMinuteSpinner.getValue();
     }
 
-    @Override
-    public void setIs24HourView(boolean is24HourView) {
-        if (mIs24HourView == is24HourView) {
+    public void setIs24Hour(boolean is24Hour) {
+        if (mIs24HourView == is24Hour) {
             return;
         }
         // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
-        int currentHour = getCurrentHour();
+        int currentHour = getHour();
         // Order is important here.
-        mIs24HourView = is24HourView;
+        mIs24HourView = is24Hour;
         getHourFormatData();
         updateHourControl();
         // set value after spinner range is updated
@@ -353,7 +350,7 @@
     }
 
     @Override
-    public boolean is24HourView() {
+    public boolean is24Hour() {
         return mIs24HourView;
     }
 
@@ -388,20 +385,15 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        setCurrentLocale(newConfig.locale);
-    }
-
-    @Override
     public Parcelable onSaveInstanceState(Parcelable superState) {
-        return new SavedState(superState, getCurrentHour(), getCurrentMinute());
+        return new SavedState(superState, getHour(), getMinute());
     }
 
     @Override
     public void onRestoreInstanceState(Parcelable state) {
         SavedState ss = (SavedState) state;
-        setCurrentHour(ss.getHour());
-        setCurrentMinute(ss.getMinute());
+        setHour(ss.getHour());
+        setMinute(ss.getMinute());
     }
 
     @Override
@@ -418,8 +410,8 @@
         } else {
             flags |= DateUtils.FORMAT_12HOUR;
         }
-        mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
-        mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
+        mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour());
+        mTempCalendar.set(Calendar.MINUTE, getMinute());
         String selectedDateUtterance = DateUtils.formatDateTime(mContext,
                 mTempCalendar.getTimeInMillis(), flags);
         event.getText().add(selectedDateUtterance);
@@ -447,7 +439,7 @@
     }
 
     private void updateAmPmControl() {
-        if (is24HourView()) {
+        if (is24Hour()) {
             if (mAmPmSpinner != null) {
                 mAmPmSpinner.setVisibility(View.GONE);
             } else {
@@ -466,27 +458,16 @@
         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
     }
 
-    /**
-     * Sets the current locale.
-     *
-     * @param locale The current locale.
-     */
-    @Override
-    public void setCurrentLocale(Locale locale) {
-        super.setCurrentLocale(locale);
-        mTempCalendar = Calendar.getInstance(locale);
-    }
-
     private void onTimeChanged() {
         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
         if (mOnTimeChangedListener != null) {
-            mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(),
-                    getCurrentMinute());
+            mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(),
+                    getMinute());
         }
     }
 
     private void updateHourControl() {
-        if (is24HourView()) {
+        if (is24Hour()) {
             // 'k' means 1-24 hour
             if (mHourFormat == 'k') {
                 mHourSpinner.setMinValue(1);
@@ -509,7 +490,7 @@
     }
 
     private void updateMinuteControl() {
-        if (is24HourView()) {
+        if (is24Hour()) {
             mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
         } else {
             mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index afbc609..5fdc920 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -19,5 +19,5 @@
 // This interface is also used by native code, so must
 // be kept in sync with frameworks/native/include/binder/IAppOpsCallback.h
 oneway interface IAppOpsCallback {
-    void opChanged(int op, String packageName);
+    void opChanged(int op, int uid, String packageName);
 }
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 2c5e50c..08c7935 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -30,6 +30,9 @@
     public static final int QS_LOCK_TILE = 257;
     public static final int QS_USER_TILE = 258;
     public static final int QS_BATTERY_TILE = 259;
+    public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 260;
+    public static final int ACTION_ZEN_ALLOW_PEEK = 261;
+    public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
 
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index e26b27d..c067da7 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -17,13 +17,19 @@
 
 package com.android.internal.os;
 
+import android.os.ShellCommand;
+
 import java.io.PrintStream;
 
 public abstract class BaseCommand {
 
-    protected String[] mArgs;
-    private int mNextArg;
-    private String mCurArgData;
+    final protected ShellCommand mArgs = new ShellCommand() {
+        @Override public int onCommand(String cmd) {
+            return 0;
+        }
+        @Override public void onHelp() {
+        }
+    };
 
     // These are magic strings understood by the Eclipse plugin.
     public static final String FATAL_ERROR_CODE = "Error type 1";
@@ -39,9 +45,7 @@
             return;
         }
 
-        mArgs = args;
-        mNextArg = 0;
-        mCurArgData = null;
+        mArgs.init(null, null, null, null, args, 0);
 
         try {
             onRun();
@@ -87,32 +91,7 @@
      * starts with '-'.  If the next argument is not an option, null is returned.
      */
     public String nextOption() {
-        if (mCurArgData != null) {
-            String prev = mArgs[mNextArg - 1];
-            throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
-        }
-        if (mNextArg >= mArgs.length) {
-            return null;
-        }
-        String arg = mArgs[mNextArg];
-        if (!arg.startsWith("-")) {
-            return null;
-        }
-        mNextArg++;
-        if (arg.equals("--")) {
-            return null;
-        }
-        if (arg.length() > 1 && arg.charAt(1) != '-') {
-            if (arg.length() > 2) {
-                mCurArgData = arg.substring(2);
-                return arg.substring(0, 2);
-            } else {
-                mCurArgData = null;
-                return arg;
-            }
-        }
-        mCurArgData = null;
-        return arg;
+        return mArgs.getNextOption();
     }
 
     /**
@@ -120,15 +99,7 @@
      * no arguments left, return null.
      */
     public String nextArg() {
-        if (mCurArgData != null) {
-            String arg = mCurArgData;
-            mCurArgData = null;
-            return arg;
-        } else if (mNextArg < mArgs.length) {
-            return mArgs[mNextArg++];
-        } else {
-            return null;
-        }
+        return mArgs.getNextArg();
     }
 
     /**
@@ -136,11 +107,6 @@
      * no arguments left, throws an IllegalArgumentException to report this to the user.
      */
     public String nextArgRequired() {
-        String arg = nextArg();
-        if (arg == null) {
-            String prev = mArgs[mNextArg - 1];
-            throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
-        }
-        return arg;
+        return mArgs.getNextArgRequired();
     }
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 197004c..8186378 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -39,10 +39,8 @@
     public static final int DEBUG_ENABLE_SAFEMODE   = 1 << 3;
     /** Enable logging of third-party JNI activity. */
     public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4;
-    /** enable the JIT compiler */
-    public static final int DEBUG_ENABLE_JIT         = 1 << 5;
     /** Force generation of native debugging information. */
-    public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 6;
+    public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5;
 
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = 0;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 3e86fac..a40f9a8 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -322,7 +322,7 @@
 
         /**
          * From --enable-debugger, --enable-checkjni, --enable-assert,
-         * --enable-safemode, --enable-jit, --generate-debug-info and --enable-jni-logging.
+         * --enable-safemode, --generate-debug-info and --enable-jni-logging.
          */
         int debugFlags;
 
@@ -432,8 +432,6 @@
                     debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
                 } else if (arg.equals("--enable-checkjni")) {
                     debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
-                } else if (arg.equals("--enable-jit")) {
-                    debugFlags |= Zygote.DEBUG_ENABLE_JIT;
                 } else if (arg.equals("--generate-debug-info")) {
                     debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
                 } else if (arg.equals("--enable-jni-logging")) {
diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
new file mode 100644
index 0000000..1eeb363
--- /dev/null
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.policy;
+
+import com.android.internal.widget.NonClientDecorView;
+
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Looper;
+import android.view.Choreographer;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+
+/**
+ * The thread which draws a fill in background while the app is resizing in areas where the app
+ * content draw is lagging behind the resize operation.
+ * It starts with the creation and it ends once someone calls destroy().
+ * Any size changes can be passed by a call to setTargetRect will passed to the thread and
+ * executed via the Choreographer.
+ * @hide
+ */
+public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback {
+
+    private NonClientDecorView mNonClientDecorView;
+
+    // This is containing the last requested size by a resize command. Note that this size might
+    // or might not have been applied to the output already.
+    private final Rect mTargetRect = new Rect();
+
+    // The render nodes for the multi threaded renderer.
+    private ThreadedRenderer mRenderer;
+    private RenderNode mFrameAndBackdropNode;
+
+    private final Rect mOldTargetRect = new Rect();
+    private final Rect mNewTargetRect = new Rect();
+    private Choreographer mChoreographer;
+
+    // Cached size values from the last render for the case that the view hierarchy is gone
+    // during a configuration change.
+    private int mLastContentWidth;
+    private int mLastContentHeight;
+    private int mLastCaptionHeight;
+    private int mLastXOffset;
+    private int mLastYOffset;
+
+    // Whether to report when next frame is drawn or not.
+    private boolean mReportNextDraw;
+
+    public BackdropFrameRenderer(NonClientDecorView nonClientDecorView,
+            ThreadedRenderer renderer,
+            Rect initialBounds) {
+        mNonClientDecorView = nonClientDecorView;
+        setName("ResizeFrame");
+        mRenderer = renderer;
+
+        // Create a render node for the content and frame backdrop
+        // which can be resized independently from the content.
+        mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
+
+        mRenderer.addRenderNode(mFrameAndBackdropNode, true);
+
+        // Set the initial bounds and draw once so that we do not get a broken frame.
+        mTargetRect.set(initialBounds);
+        synchronized (this) {
+            changeWindowSizeLocked(initialBounds);
+        }
+
+        // Kick off our draw thread.
+        start();
+    }
+
+    /**
+     * Call this function asynchronously when the window size has been changed. The change will
+     * be picked up once per frame and the frame will be re-rendered accordingly.
+     * @param newTargetBounds The new target bounds.
+     */
+    public void setTargetRect(Rect newTargetBounds) {
+        synchronized (this) {
+            mTargetRect.set(newTargetBounds);
+            // Notify of a bounds change.
+            pingRenderLocked();
+        }
+    }
+
+    /**
+     * The window got replaced due to a configuration change.
+     */
+    public void onConfigurationChange() {
+        synchronized (this) {
+            if (mRenderer != null) {
+                // Enforce a window redraw.
+                mOldTargetRect.set(0, 0, 0, 0);
+                pingRenderLocked();
+            }
+        }
+    }
+
+    /**
+     * All resources of the renderer will be released. This function can be called from the
+     * the UI thread as well as the renderer thread.
+     */
+    public void releaseRenderer() {
+        synchronized (this) {
+            if (mRenderer != null) {
+                // Invalidate the current content bounds.
+                mRenderer.setContentDrawBounds(0, 0, 0, 0);
+
+                // Remove the render node again
+                // (see comment above - better to do that only once).
+                mRenderer.removeRenderNode(mFrameAndBackdropNode);
+
+                mRenderer = null;
+
+                // Exit the renderer loop.
+                pingRenderLocked();
+            }
+        }
+    }
+
+    @Override
+    public void run() {
+        try {
+            Looper.prepare();
+            synchronized (this) {
+                mChoreographer = Choreographer.getInstance();
+
+                // Draw at least once.
+                mChoreographer.postFrameCallback(this);
+            }
+            Looper.loop();
+        } finally {
+            releaseRenderer();
+        }
+        synchronized (this) {
+            // Make sure no more messages are being sent.
+            mChoreographer = null;
+        }
+    }
+
+    /**
+     * The implementation of the FrameCallback.
+     * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
+     * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
+     */
+    @Override
+    public void doFrame(long frameTimeNanos) {
+        synchronized (this) {
+            if (mRenderer == null) {
+                reportDrawIfNeeded();
+                // Tell the looper to stop. We are done.
+                Looper.myLooper().quit();
+                return;
+            }
+            mNewTargetRect.set(mTargetRect);
+            if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
+                mOldTargetRect.set(mNewTargetRect);
+                changeWindowSizeLocked(mNewTargetRect);
+            }
+        }
+    }
+
+    /**
+     * The content is about to be drawn and we got the location of where it will be shown.
+     * If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
+     * if the previous call was ignored since the size was unknown.
+     * @param xOffset The x offset where the content is drawn to.
+     * @param yOffset The y offset where the content is drawn to.
+     * @param xSize The width size of the content. This should not be 0.
+     * @param ySize The height of the content.
+     * @return true if a frame should be requested after the content is drawn; false otherwise.
+     */
+    public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
+        synchronized (this) {
+            final boolean firstCall = mLastContentWidth == 0;
+            // The current content buffer is drawn here.
+            mLastContentWidth = xSize;
+            mLastContentHeight = ySize - mLastCaptionHeight;
+            mLastXOffset = xOffset;
+            mLastYOffset = yOffset;
+
+            mRenderer.setContentDrawBounds(
+                    mLastXOffset,
+                    mLastYOffset,
+                    mLastXOffset + mLastContentWidth,
+                    mLastYOffset + mLastCaptionHeight + mLastContentHeight);
+            // If this was the first call and changeWindowSizeLocked got already called prior
+            // to us, we should re-issue a changeWindowSizeLocked now.
+            return firstCall && (mLastCaptionHeight != 0 || !mNonClientDecorView.mShowDecor);
+        }
+    }
+
+    public void onRequestDraw(boolean reportNextDraw) {
+        synchronized (this) {
+            mReportNextDraw = reportNextDraw;
+            mOldTargetRect.set(0, 0, 0, 0);
+            pingRenderLocked();
+        }
+    }
+
+    /**
+     * Resizing the frame to fit the new window size.
+     * @param newBounds The window bounds which needs to be drawn.
+     */
+    private void changeWindowSizeLocked(Rect newBounds) {
+        // While a configuration change is taking place the view hierarchy might become
+        // inaccessible. For that case we remember the previous metrics to avoid flashes.
+        // Note that even when there is no visible caption, the caption child will exist.
+        View caption = mNonClientDecorView.getChildAt(0);
+        if (caption != null) {
+            final int captionHeight = caption.getHeight();
+            // The caption height will probably never dynamically change while we are resizing.
+            // Once set to something other then 0 it should be kept that way.
+            if (captionHeight != 0) {
+                // Remember the height of the caption.
+                mLastCaptionHeight = captionHeight;
+            }
+        }
+        // Make sure that the other thread has already prepared the render draw calls for the
+        // content. If any size is 0, we have to wait for it to be drawn first.
+        if ((mLastCaptionHeight == 0 && mNonClientDecorView.mShowDecor) ||
+                mLastContentWidth == 0 || mLastContentHeight == 0) {
+            return;
+        }
+        // Since the surface is spanning the entire screen, we have to add the start offset of
+        // the bounds to get to the surface location.
+        final int left = mLastXOffset + newBounds.left;
+        final int top = mLastYOffset + newBounds.top;
+        final int width = newBounds.width();
+        final int height = newBounds.height();
+
+        mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
+
+        // Draw the caption and content backdrops in to our render node.
+        DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
+        mNonClientDecorView.mCaptionBackgroundDrawable.setBounds(
+                0, 0, left + width, top + mLastCaptionHeight);
+        mNonClientDecorView.mCaptionBackgroundDrawable.draw(canvas);
+
+        // The backdrop: clear everything with the background. Clipping is done elsewhere.
+        mNonClientDecorView.mResizingBackgroundDrawable.setBounds(
+                0, mLastCaptionHeight, left + width, top + height);
+        mNonClientDecorView.mResizingBackgroundDrawable.draw(canvas);
+        mFrameAndBackdropNode.end(canvas);
+
+        // We need to render the node explicitly
+        mRenderer.drawRenderNode(mFrameAndBackdropNode);
+
+        reportDrawIfNeeded();
+    }
+
+    /**
+     * Notify view root that a frame has been drawn by us, if it has requested so.
+     */
+    private void reportDrawIfNeeded() {
+        if (mReportNextDraw) {
+            if (mNonClientDecorView.isAttachedToWindow()) {
+                mNonClientDecorView.getViewRootImpl().reportDrawFinish();
+            }
+            mReportNextDraw = false;
+        }
+    }
+
+    /**
+     * Sends a message to the renderer to wake up and perform the next action which can be
+     * either the next rendering or the self destruction if mRenderer is null.
+     * Note: This call must be synchronized.
+     */
+    private void pingRenderLocked() {
+        if (mChoreographer != null) {
+            mChoreographer.postFrameCallback(this);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
new file mode 100644
index 0000000..49ceced
--- /dev/null
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -0,0 +1,1788 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.policy;
+
+import com.android.internal.R;
+import com.android.internal.view.FloatingActionMode;
+import com.android.internal.view.RootViewSurfaceTaker;
+import com.android.internal.view.StandaloneActionMode;
+import com.android.internal.view.menu.ContextMenuBuilder;
+import com.android.internal.view.menu.MenuDialogHelper;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.widget.ActionBarContextView;
+import com.android.internal.widget.BackgroundFallback;
+import com.android.internal.widget.FloatingToolbar;
+import com.android.internal.widget.NonClientDecorView;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ActionMode;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.InputQueue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.PopupWindow;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.getMode;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
+class DecorView extends FrameLayout implements RootViewSurfaceTaker {
+    private static final String TAG = "DecorView";
+
+    private static final boolean SWEEP_OPEN_MENU = false;
+
+    int mDefaultOpacity = PixelFormat.OPAQUE;
+
+    /** The feature ID of the panel, or -1 if this is the application's DecorView */
+    private final int mFeatureId;
+
+    private final Rect mDrawingBounds = new Rect();
+
+    private final Rect mBackgroundPadding = new Rect();
+
+    private final Rect mFramePadding = new Rect();
+
+    private final Rect mFrameOffsets = new Rect();
+
+    // True if a non client area decor exists.
+    private boolean mHasNonClientDecor = false;
+
+    private boolean mChanging;
+
+    private Drawable mMenuBackground;
+    private boolean mWatchingForMenu;
+    private int mDownY;
+
+    ActionMode mPrimaryActionMode;
+    private ActionMode mFloatingActionMode;
+    private ActionBarContextView mPrimaryActionModeView;
+    private PopupWindow mPrimaryActionModePopup;
+    private Runnable mShowPrimaryActionModePopup;
+    private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
+    private View mFloatingActionModeOriginatingView;
+    private FloatingToolbar mFloatingToolbar;
+    private ObjectAnimator mFadeAnim;
+
+    // View added at runtime to draw under the status bar area
+    private View mStatusGuard;
+    // View added at runtime to draw under the navigation bar area
+    private View mNavigationGuard;
+
+    private final ColorViewState mStatusColorViewState = new ColorViewState(
+            SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
+            Gravity.TOP, Gravity.LEFT,
+            Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
+            com.android.internal.R.id.statusBarBackground,
+            FLAG_FULLSCREEN);
+    private final ColorViewState mNavigationColorViewState = new ColorViewState(
+            SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
+            Gravity.BOTTOM, Gravity.RIGHT,
+            Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
+            com.android.internal.R.id.navigationBarBackground,
+            0 /* hideWindowFlag */);
+
+    private final Interpolator mShowInterpolator;
+    private final Interpolator mHideInterpolator;
+    private final int mBarEnterExitDuration;
+
+    private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
+
+    private int mLastTopInset = 0;
+    private int mLastBottomInset = 0;
+    private int mLastRightInset = 0;
+    private boolean mLastHasTopStableInset = false;
+    private boolean mLastHasBottomStableInset = false;
+    private boolean mLastHasRightStableInset = false;
+    private int mLastWindowFlags = 0;
+
+    private int mRootScrollY = 0;
+
+    private PhoneWindow mWindow;
+
+    ViewGroup mContentRoot;
+
+    private Rect mTempRect;
+    private Rect mOutsets = new Rect();
+
+    // This is the non client decor view for the window, containing the caption and window control
+    // buttons. The visibility of this decor depends on the workspace and the window type.
+    // If the window type does not require such a view, this member might be null.
+    NonClientDecorView mNonClientDecorView;
+
+    // The non client decor needs to adapt to the used workspace. Since querying and changing the
+    // workspace is expensive, this is the workspace value the window is currently set up for.
+    int mWorkspaceId;
+
+    DecorView(Context context, int featureId, PhoneWindow window) {
+        super(context);
+        mFeatureId = featureId;
+
+        mShowInterpolator = AnimationUtils.loadInterpolator(context,
+                android.R.interpolator.linear_out_slow_in);
+        mHideInterpolator = AnimationUtils.loadInterpolator(context,
+                android.R.interpolator.fast_out_linear_in);
+
+        mBarEnterExitDuration = context.getResources().getInteger(
+                R.integer.dock_enter_exit_duration);
+
+        setWindow(window);
+    }
+
+    public void setBackgroundFallback(int resId) {
+        mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
+        setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
+    }
+
+    @Override
+    public void onDraw(Canvas c) {
+        super.onDraw(c);
+        mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        final int keyCode = event.getKeyCode();
+        final int action = event.getAction();
+        final boolean isDown = action == KeyEvent.ACTION_DOWN;
+
+        if (isDown && (event.getRepeatCount() == 0)) {
+            // First handle chording of panel key: if a panel key is held
+            // but not released, try to execute a shortcut in it.
+            if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
+                boolean handled = dispatchKeyShortcutEvent(event);
+                if (handled) {
+                    return true;
+                }
+            }
+
+            // If a panel is open, perform a shortcut on it without the
+            // chorded panel key
+            if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
+                if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
+                    return true;
+                }
+            }
+        }
+
+        if (!mWindow.isDestroyed()) {
+            final Window.Callback cb = mWindow.getCallback();
+            final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
+                    : super.dispatchKeyEvent(event);
+            if (handled) {
+                return true;
+            }
+        }
+
+        return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
+                : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
+    }
+
+    @Override
+    public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
+        // If the panel is already prepared, then perform the shortcut using it.
+        boolean handled;
+        if (mWindow.mPreparedPanel != null) {
+            handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev,
+                    Menu.FLAG_PERFORM_NO_CLOSE);
+            if (handled) {
+                if (mWindow.mPreparedPanel != null) {
+                    mWindow.mPreparedPanel.isHandled = true;
+                }
+                return true;
+            }
+        }
+
+        // Shortcut not handled by the panel.  Dispatch to the view hierarchy.
+        final Window.Callback cb = mWindow.getCallback();
+        handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+                ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
+        if (handled) {
+            return true;
+        }
+
+        // If the panel is not prepared, then we may be trying to handle a shortcut key
+        // combination such as Control+C.  Temporarily prepare the panel then mark it
+        // unprepared again when finished to ensure that the panel will again be prepared
+        // the next time it is shown for real.
+        PhoneWindow.PanelFeatureState st =
+                mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+        if (st != null && mWindow.mPreparedPanel == null) {
+            mWindow.preparePanel(st, ev);
+            handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev,
+                    Menu.FLAG_PERFORM_NO_CLOSE);
+            st.isPrepared = false;
+            if (handled) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        final Window.Callback cb = mWindow.getCallback();
+        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
+    }
+
+    @Override
+    public boolean dispatchTrackballEvent(MotionEvent ev) {
+        final Window.Callback cb = mWindow.getCallback();
+        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+                ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
+    }
+
+    @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+        final Window.Callback cb = mWindow.getCallback();
+        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+                ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
+    }
+
+    public boolean superDispatchKeyEvent(KeyEvent event) {
+        // Give priority to closing action modes if applicable.
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            final int action = event.getAction();
+            // Back cancels action modes first.
+            if (mPrimaryActionMode != null) {
+                if (action == KeyEvent.ACTION_UP) {
+                    mPrimaryActionMode.finish();
+                }
+                return true;
+            }
+        }
+
+        return super.dispatchKeyEvent(event);
+    }
+
+    public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
+        return super.dispatchKeyShortcutEvent(event);
+    }
+
+    public boolean superDispatchTouchEvent(MotionEvent event) {
+        return super.dispatchTouchEvent(event);
+    }
+
+    public boolean superDispatchTrackballEvent(MotionEvent event) {
+        return super.dispatchTrackballEvent(event);
+    }
+
+    public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+        return super.dispatchGenericMotionEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return onInterceptTouchEvent(event);
+    }
+
+    private boolean isOutOfInnerBounds(int x, int y) {
+        return x < 0 || y < 0 || x > getWidth() || y > getHeight();
+    }
+
+    private boolean isOutOfBounds(int x, int y) {
+        return x < -5 || y < -5 || x > (getWidth() + 5)
+                || y > (getHeight() + 5);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        int action = event.getAction();
+        if (mHasNonClientDecor && mNonClientDecorView.mVisible) {
+            // Don't dispatch ACTION_DOWN to the non client decor if the window is
+            // resizable and the event was (starting) outside the window.
+            // Window resizing events should be handled by WindowManager.
+            // TODO: Investigate how to handle the outside touch in window manager
+            //       without generating these events.
+            //       Currently we receive these because we need to enlarge the window's
+            //       touch region so that the monitor channel receives the events
+            //       in the outside touch area.
+            if (action == MotionEvent.ACTION_DOWN) {
+                final int x = (int) event.getX();
+                final int y = (int) event.getY();
+                if (isOutOfInnerBounds(x, y)) {
+                    return true;
+                }
+            }
+        }
+
+        if (mFeatureId >= 0) {
+            if (action == MotionEvent.ACTION_DOWN) {
+                int x = (int)event.getX();
+                int y = (int)event.getY();
+                if (isOutOfBounds(x, y)) {
+                    mWindow.closePanel(mFeatureId);
+                    return true;
+                }
+            }
+        }
+
+        if (!SWEEP_OPEN_MENU) {
+            return false;
+        }
+
+        if (mFeatureId >= 0) {
+            if (action == MotionEvent.ACTION_DOWN) {
+                Log.i(TAG, "Watchiing!");
+                mWatchingForMenu = true;
+                mDownY = (int) event.getY();
+                return false;
+            }
+
+            if (!mWatchingForMenu) {
+                return false;
+            }
+
+            int y = (int)event.getY();
+            if (action == MotionEvent.ACTION_MOVE) {
+                if (y > (mDownY+30)) {
+                    Log.i(TAG, "Closing!");
+                    mWindow.closePanel(mFeatureId);
+                    mWatchingForMenu = false;
+                    return true;
+                }
+            } else if (action == MotionEvent.ACTION_UP) {
+                mWatchingForMenu = false;
+            }
+
+            return false;
+        }
+
+        //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
+        //        + " (in " + getHeight() + ")");
+
+        if (action == MotionEvent.ACTION_DOWN) {
+            int y = (int)event.getY();
+            if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
+                Log.i(TAG, "Watching!");
+                mWatchingForMenu = true;
+            }
+            return false;
+        }
+
+        if (!mWatchingForMenu) {
+            return false;
+        }
+
+        int y = (int)event.getY();
+        if (action == MotionEvent.ACTION_MOVE) {
+            if (y < (getHeight()-30)) {
+                Log.i(TAG, "Opening!");
+                mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent(
+                        KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
+                mWatchingForMenu = false;
+                return true;
+            }
+        } else if (action == MotionEvent.ACTION_UP) {
+            mWatchingForMenu = false;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void sendAccessibilityEvent(int eventType) {
+        if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+            return;
+        }
+
+        // if we are showing a feature that should be announced and one child
+        // make this child the event source since this is the feature itself
+        // otherwise the callback will take over and announce its client
+        if ((mFeatureId == Window.FEATURE_OPTIONS_PANEL ||
+                mFeatureId == Window.FEATURE_CONTEXT_MENU ||
+                mFeatureId == Window.FEATURE_PROGRESS ||
+                mFeatureId == Window.FEATURE_INDETERMINATE_PROGRESS)
+                && getChildCount() == 1) {
+            getChildAt(0).sendAccessibilityEvent(eventType);
+        } else {
+            super.sendAccessibilityEvent(eventType);
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+        final Window.Callback cb = mWindow.getCallback();
+        if (cb != null && !mWindow.isDestroyed()) {
+            if (cb.dispatchPopulateAccessibilityEvent(event)) {
+                return true;
+            }
+        }
+        return super.dispatchPopulateAccessibilityEventInternal(event);
+    }
+
+    @Override
+    protected boolean setFrame(int l, int t, int r, int b) {
+        boolean changed = super.setFrame(l, t, r, b);
+        if (changed) {
+            final Rect drawingBounds = mDrawingBounds;
+            getDrawingRect(drawingBounds);
+
+            Drawable fg = getForeground();
+            if (fg != null) {
+                final Rect frameOffsets = mFrameOffsets;
+                drawingBounds.left += frameOffsets.left;
+                drawingBounds.top += frameOffsets.top;
+                drawingBounds.right -= frameOffsets.right;
+                drawingBounds.bottom -= frameOffsets.bottom;
+                fg.setBounds(drawingBounds);
+                final Rect framePadding = mFramePadding;
+                drawingBounds.left += framePadding.left - frameOffsets.left;
+                drawingBounds.top += framePadding.top - frameOffsets.top;
+                drawingBounds.right -= framePadding.right - frameOffsets.right;
+                drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
+            }
+
+            Drawable bg = getBackground();
+            if (bg != null) {
+                bg.setBounds(drawingBounds);
+            }
+
+            if (SWEEP_OPEN_MENU) {
+                if (mMenuBackground == null && mFeatureId < 0
+                        && mWindow.getAttributes().height
+                        == WindowManager.LayoutParams.MATCH_PARENT) {
+                    mMenuBackground = getContext().getDrawable(
+                            R.drawable.menu_background);
+                }
+                if (mMenuBackground != null) {
+                    mMenuBackground.setBounds(drawingBounds.left,
+                            drawingBounds.bottom-6, drawingBounds.right,
+                            drawingBounds.bottom+20);
+                }
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+        final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+
+        final int widthMode = getMode(widthMeasureSpec);
+        final int heightMode = getMode(heightMeasureSpec);
+
+        boolean fixedWidth = false;
+        if (widthMode == AT_MOST) {
+            final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
+                    : mWindow.mFixedWidthMajor;
+            if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
+                final int w;
+                if (tvw.type == TypedValue.TYPE_DIMENSION) {
+                    w = (int) tvw.getDimension(metrics);
+                } else if (tvw.type == TypedValue.TYPE_FRACTION) {
+                    w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
+                } else {
+                    w = 0;
+                }
+
+                if (w > 0) {
+                    final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                            Math.min(w, widthSize), EXACTLY);
+                    fixedWidth = true;
+                }
+            }
+        }
+
+        if (heightMode == AT_MOST) {
+            final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
+                    : mWindow.mFixedHeightMinor;
+            if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
+                final int h;
+                if (tvh.type == TypedValue.TYPE_DIMENSION) {
+                    h = (int) tvh.getDimension(metrics);
+                } else if (tvh.type == TypedValue.TYPE_FRACTION) {
+                    h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
+                } else {
+                    h = 0;
+                }
+                if (h > 0) {
+                    final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                            Math.min(h, heightSize), EXACTLY);
+                }
+            }
+        }
+
+        getOutsets(mOutsets);
+        if (mOutsets.top > 0 || mOutsets.bottom > 0) {
+            int mode = MeasureSpec.getMode(heightMeasureSpec);
+            if (mode != MeasureSpec.UNSPECIFIED) {
+                int height = MeasureSpec.getSize(heightMeasureSpec);
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        height + mOutsets.top + mOutsets.bottom, mode);
+            }
+        }
+        if (mOutsets.left > 0 || mOutsets.right > 0) {
+            int mode = MeasureSpec.getMode(widthMeasureSpec);
+            if (mode != MeasureSpec.UNSPECIFIED) {
+                int width = MeasureSpec.getSize(widthMeasureSpec);
+                widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        width + mOutsets.left + mOutsets.right, mode);
+            }
+        }
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int width = getMeasuredWidth();
+        boolean measure = false;
+
+        widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
+
+        if (!fixedWidth && widthMode == AT_MOST) {
+            final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
+            if (tv.type != TypedValue.TYPE_NULL) {
+                final int min;
+                if (tv.type == TypedValue.TYPE_DIMENSION) {
+                    min = (int)tv.getDimension(metrics);
+                } else if (tv.type == TypedValue.TYPE_FRACTION) {
+                    min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
+                } else {
+                    min = 0;
+                }
+
+                if (width < min) {
+                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
+                    measure = true;
+                }
+            }
+        }
+
+        // TODO: Support height?
+
+        if (measure) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        getOutsets(mOutsets);
+        if (mOutsets.left > 0) {
+            offsetLeftAndRight(-mOutsets.left);
+        }
+        if (mOutsets.top > 0) {
+            offsetTopAndBottom(-mOutsets.top);
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        if (mMenuBackground != null) {
+            mMenuBackground.draw(canvas);
+        }
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView) {
+        // Reuse the context menu builder
+        if (mWindow.mContextMenu == null) {
+            mWindow.mContextMenu = new ContextMenuBuilder(getContext());
+            mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
+        } else {
+            mWindow.mContextMenu.clearAll();
+        }
+
+        final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView,
+                originalView.getWindowToken());
+        if (helper != null) {
+            helper.setPresenterCallback(mWindow.mContextMenuCallback);
+        } else if (mWindow.mContextMenuHelper != null) {
+            // No menu to show, but if we have a menu currently showing it just became blank.
+            // Close it.
+            mWindow.mContextMenuHelper.dismiss();
+        }
+        mWindow.mContextMenuHelper = helper;
+        return helper != null;
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView, float x, float y) {
+        // Reuse the context menu builder
+        if (mWindow.mContextMenu == null) {
+            mWindow.mContextMenu = new ContextMenuBuilder(getContext());
+            mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
+        } else {
+            mWindow.mContextMenu.clearAll();
+        }
+
+        final MenuPopupHelper helper = mWindow.mContextMenu.showPopup(
+                getContext(), originalView, x, y);
+        if (helper != null) {
+            helper.setCallback(mWindow.mContextMenuCallback);
+        } else if (mWindow.mContextMenuPopupHelper != null) {
+            // No menu to show, but if we have a menu currently showing it just became blank.
+            // Close it.
+            mWindow.mContextMenuPopupHelper.dismiss();
+        }
+        mWindow.mContextMenuPopupHelper = helper;
+        return helper != null;
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(View originalView,
+            ActionMode.Callback callback) {
+        return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
+    }
+
+    @Override
+    public ActionMode startActionModeForChild(
+            View child, ActionMode.Callback callback, int type) {
+        return startActionMode(child, callback, type);
+    }
+
+    @Override
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        return startActionMode(callback, ActionMode.TYPE_PRIMARY);
+    }
+
+    @Override
+    public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+        return startActionMode(this, callback, type);
+    }
+
+    private ActionMode startActionMode(
+            View originatingView, ActionMode.Callback callback, int type) {
+        ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
+        ActionMode mode = null;
+        if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+            try {
+                mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
+            } catch (AbstractMethodError ame) {
+                // Older apps might not implement the typed version of this method.
+                if (type == ActionMode.TYPE_PRIMARY) {
+                    try {
+                        mode = mWindow.getCallback().onWindowStartingActionMode(
+                                wrappedCallback);
+                    } catch (AbstractMethodError ame2) {
+                        // Older apps might not implement this callback method at all.
+                    }
+                }
+            }
+        }
+        if (mode != null) {
+            if (mode.getType() == ActionMode.TYPE_PRIMARY) {
+                cleanupPrimaryActionMode();
+                mPrimaryActionMode = mode;
+            } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
+                if (mFloatingActionMode != null) {
+                    mFloatingActionMode.finish();
+                }
+                mFloatingActionMode = mode;
+            }
+        } else {
+            mode = createActionMode(type, wrappedCallback, originatingView);
+            if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
+                setHandledActionMode(mode);
+            } else {
+                mode = null;
+            }
+        }
+        if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+            try {
+                mWindow.getCallback().onActionModeStarted(mode);
+            } catch (AbstractMethodError ame) {
+                // Older apps might not implement this callback method.
+            }
+        }
+        return mode;
+    }
+
+    private void cleanupPrimaryActionMode() {
+        if (mPrimaryActionMode != null) {
+            mPrimaryActionMode.finish();
+            mPrimaryActionMode = null;
+        }
+        if (mPrimaryActionModeView != null) {
+            mPrimaryActionModeView.killMode();
+        }
+    }
+
+    private void cleanupFloatingActionModeViews() {
+        if (mFloatingToolbar != null) {
+            mFloatingToolbar.dismiss();
+            mFloatingToolbar = null;
+        }
+        if (mFloatingActionModeOriginatingView != null) {
+            if (mFloatingToolbarPreDrawListener != null) {
+                mFloatingActionModeOriginatingView.getViewTreeObserver()
+                    .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
+                mFloatingToolbarPreDrawListener = null;
+            }
+            mFloatingActionModeOriginatingView = null;
+        }
+    }
+
+    public void startChanging() {
+        mChanging = true;
+    }
+
+    public void finishChanging() {
+        mChanging = false;
+        drawableChanged();
+    }
+
+    public void setWindowBackground(Drawable drawable) {
+        if (getBackground() != drawable) {
+            setBackgroundDrawable(drawable);
+            if (drawable != null) {
+                drawable.getPadding(mBackgroundPadding);
+            } else {
+                mBackgroundPadding.setEmpty();
+            }
+            drawableChanged();
+        }
+    }
+
+    public void setWindowFrame(Drawable drawable) {
+        if (getForeground() != drawable) {
+            setForeground(drawable);
+            if (drawable != null) {
+                drawable.getPadding(mFramePadding);
+            } else {
+                mFramePadding.setEmpty();
+            }
+            drawableChanged();
+        }
+    }
+
+    @Override
+    public void onWindowSystemUiVisibilityChanged(int visible) {
+        updateColorViews(null /* insets */, true /* animate */);
+    }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        mFrameOffsets.set(insets.getSystemWindowInsets());
+        insets = updateColorViews(insets, true /* animate */);
+        insets = updateStatusGuard(insets);
+        updateNavigationGuard(insets);
+        if (getForeground() != null) {
+            drawableChanged();
+        }
+        return insets;
+    }
+
+    @Override
+    public boolean isTransitionGroup() {
+        return false;
+    }
+
+    WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
+        WindowManager.LayoutParams attrs = mWindow.getAttributes();
+        int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
+
+        if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
+            boolean disallowAnimate = !isLaidOut();
+            disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
+                    & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+            mLastWindowFlags = attrs.flags;
+
+            if (insets != null) {
+                mLastTopInset = Math.min(insets.getStableInsetTop(),
+                        insets.getSystemWindowInsetTop());
+                mLastBottomInset = Math.min(insets.getStableInsetBottom(),
+                        insets.getSystemWindowInsetBottom());
+                mLastRightInset = Math.min(insets.getStableInsetRight(),
+                        insets.getSystemWindowInsetRight());
+
+                // Don't animate if the presence of stable insets has changed, because that
+                // indicates that the window was either just added and received them for the
+                // first time, or the window size or position has changed.
+                boolean hasTopStableInset = insets.getStableInsetTop() != 0;
+                disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
+                mLastHasTopStableInset = hasTopStableInset;
+
+                boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
+                disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
+                mLastHasBottomStableInset = hasBottomStableInset;
+
+                boolean hasRightStableInset = insets.getStableInsetRight() != 0;
+                disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
+                mLastHasRightStableInset = hasRightStableInset;
+            }
+
+            boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0;
+            int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset;
+            updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
+                    mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
+                    0 /* rightInset */, animate && !disallowAnimate);
+
+            boolean statusBarNeedsRightInset = navBarToRightEdge
+                    && mNavigationColorViewState.present;
+            int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
+            updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor,
+                    mLastTopInset, false /* matchVertical */, statusBarRightInset,
+                    animate && !disallowAnimate);
+        }
+
+        // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
+        // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
+        // explicitly asked for it.
+
+        boolean consumingNavBar =
+                (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+                        && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+                        && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+
+        int consumedRight = consumingNavBar ? mLastRightInset : 0;
+        int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
+
+        if (mContentRoot != null
+                && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
+            MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
+            if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
+                lp.rightMargin = consumedRight;
+                lp.bottomMargin = consumedBottom;
+                mContentRoot.setLayoutParams(lp);
+
+                if (insets == null) {
+                    // The insets have changed, but we're not currently in the process
+                    // of dispatching them.
+                    requestApplyInsets();
+                }
+            }
+            if (insets != null) {
+                insets = insets.replaceSystemWindowInsets(
+                        insets.getSystemWindowInsetLeft(),
+                        insets.getSystemWindowInsetTop(),
+                        insets.getSystemWindowInsetRight() - consumedRight,
+                        insets.getSystemWindowInsetBottom() - consumedBottom);
+            }
+        }
+
+        if (insets != null) {
+            insets = insets.consumeStableInsets();
+        }
+        return insets;
+    }
+
+    /**
+     * Update a color view
+     *
+     * @param state the color view to update.
+     * @param sysUiVis the current systemUiVisibility to apply.
+     * @param color the current color to apply.
+     * @param size the current size in the non-parent-matching dimension.
+     * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
+     *                    horizontal edge,
+     * @param rightMargin rightMargin for the color view.
+     * @param animate if true, the change will be animated.
+     */
+    private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
+            int size, boolean verticalBar, int rightMargin, boolean animate) {
+        state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
+                && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
+                && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+        boolean show = state.present
+                && (color & Color.BLACK) != 0
+                && (mWindow.getAttributes().flags & state.translucentFlag) == 0;
+
+        boolean visibilityChanged = false;
+        View view = state.view;
+
+        int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
+        int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
+        int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
+
+        if (view == null) {
+            if (show) {
+                state.view = view = new View(mContext);
+                view.setBackgroundColor(color);
+                view.setTransitionName(state.transitionName);
+                view.setId(state.id);
+                visibilityChanged = true;
+                view.setVisibility(INVISIBLE);
+                state.targetVisibility = VISIBLE;
+
+                LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
+                        resolvedGravity);
+                lp.rightMargin = rightMargin;
+                addView(view, lp);
+                updateColorViewTranslations();
+            }
+        } else {
+            int vis = show ? VISIBLE : INVISIBLE;
+            visibilityChanged = state.targetVisibility != vis;
+            state.targetVisibility = vis;
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (lp.height != resolvedHeight || lp.width != resolvedWidth
+                    || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
+                lp.height = resolvedHeight;
+                lp.width = resolvedWidth;
+                lp.gravity = resolvedGravity;
+                lp.rightMargin = rightMargin;
+                view.setLayoutParams(lp);
+            }
+            if (show) {
+                view.setBackgroundColor(color);
+            }
+        }
+        if (visibilityChanged) {
+            view.animate().cancel();
+            if (animate) {
+                if (show) {
+                    if (view.getVisibility() != VISIBLE) {
+                        view.setVisibility(VISIBLE);
+                        view.setAlpha(0.0f);
+                    }
+                    view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
+                            setDuration(mBarEnterExitDuration);
+                } else {
+                    view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
+                            .setDuration(mBarEnterExitDuration)
+                            .withEndAction(new Runnable() {
+                                @Override
+                                public void run() {
+                                    state.view.setAlpha(1.0f);
+                                    state.view.setVisibility(INVISIBLE);
+                                }
+                            });
+                }
+            } else {
+                view.setAlpha(1.0f);
+                view.setVisibility(show ? VISIBLE : INVISIBLE);
+            }
+        }
+    }
+
+    private void updateColorViewTranslations() {
+        // Put the color views back in place when they get moved off the screen
+        // due to the the ViewRootImpl panning.
+        int rootScrollY = mRootScrollY;
+        if (mStatusColorViewState.view != null) {
+            mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
+        }
+        if (mNavigationColorViewState.view != null) {
+            mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
+        }
+    }
+
+    private WindowInsets updateStatusGuard(WindowInsets insets) {
+        boolean showStatusGuard = false;
+        // Show the status guard when the non-overlay contextual action bar is showing
+        if (mPrimaryActionModeView != null) {
+            if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
+                // Insets are magic!
+                final MarginLayoutParams mlp = (MarginLayoutParams)
+                        mPrimaryActionModeView.getLayoutParams();
+                boolean mlpChanged = false;
+                if (mPrimaryActionModeView.isShown()) {
+                    if (mTempRect == null) {
+                        mTempRect = new Rect();
+                    }
+                    final Rect rect = mTempRect;
+
+                    // If the parent doesn't consume the insets, manually
+                    // apply the default system window insets.
+                    mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
+                    final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
+                    if (mlp.topMargin != newMargin) {
+                        mlpChanged = true;
+                        mlp.topMargin = insets.getSystemWindowInsetTop();
+
+                        if (mStatusGuard == null) {
+                            mStatusGuard = new View(mContext);
+                            mStatusGuard.setBackgroundColor(mContext.getColor(
+                                    R.color.input_method_navigation_guard));
+                            addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
+                                    new LayoutParams(LayoutParams.MATCH_PARENT,
+                                            mlp.topMargin, Gravity.START | Gravity.TOP));
+                        } else {
+                            final LayoutParams lp = (LayoutParams)
+                                    mStatusGuard.getLayoutParams();
+                            if (lp.height != mlp.topMargin) {
+                                lp.height = mlp.topMargin;
+                                mStatusGuard.setLayoutParams(lp);
+                            }
+                        }
+                    }
+
+                    // The action mode's theme may differ from the app, so
+                    // always show the status guard above it if we have one.
+                    showStatusGuard = mStatusGuard != null;
+
+                    // We only need to consume the insets if the action
+                    // mode is overlaid on the app content (e.g. it's
+                    // sitting in a FrameLayout, see
+                    // screen_simple_overlay_action_mode.xml).
+                    final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate()
+                            & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0;
+                    insets = insets.consumeSystemWindowInsets(
+                            false, nonOverlay && showStatusGuard /* top */, false, false);
+                } else {
+                    // reset top margin
+                    if (mlp.topMargin != 0) {
+                        mlpChanged = true;
+                        mlp.topMargin = 0;
+                    }
+                }
+                if (mlpChanged) {
+                    mPrimaryActionModeView.setLayoutParams(mlp);
+                }
+            }
+        }
+        if (mStatusGuard != null) {
+            mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
+        }
+        return insets;
+    }
+
+    private void updateNavigationGuard(WindowInsets insets) {
+        // IMEs lay out below the nav bar, but the content view must not (for back compat)
+        if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
+            // prevent the content view from including the nav bar height
+            if (mWindow.mContentParent != null) {
+                if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
+                    MarginLayoutParams mlp =
+                            (MarginLayoutParams) mWindow.mContentParent.getLayoutParams();
+                    mlp.bottomMargin = insets.getSystemWindowInsetBottom();
+                    mWindow.mContentParent.setLayoutParams(mlp);
+                }
+            }
+            // position the navigation guard view, creating it if necessary
+            if (mNavigationGuard == null) {
+                mNavigationGuard = new View(mContext);
+                mNavigationGuard.setBackgroundColor(mContext.getColor(
+                        R.color.input_method_navigation_guard));
+                addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
+                        new LayoutParams(LayoutParams.MATCH_PARENT,
+                                insets.getSystemWindowInsetBottom(),
+                                Gravity.START | Gravity.BOTTOM));
+            } else {
+                LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
+                lp.height = insets.getSystemWindowInsetBottom();
+                mNavigationGuard.setLayoutParams(lp);
+            }
+        }
+    }
+
+    private void drawableChanged() {
+        if (mChanging) {
+            return;
+        }
+
+        setPadding(mFramePadding.left + mBackgroundPadding.left,
+                mFramePadding.top + mBackgroundPadding.top,
+                mFramePadding.right + mBackgroundPadding.right,
+                mFramePadding.bottom + mBackgroundPadding.bottom);
+        requestLayout();
+        invalidate();
+
+        int opacity = PixelFormat.OPAQUE;
+        if (windowHasShadow()) {
+            // If the window has a shadow, it must be translucent.
+            opacity = PixelFormat.TRANSLUCENT;
+        } else{
+            // Note: If there is no background, we will assume opaque. The
+            // common case seems to be that an application sets there to be
+            // no background so it can draw everything itself. For that,
+            // we would like to assume OPAQUE and let the app force it to
+            // the slower TRANSLUCENT mode if that is really what it wants.
+            Drawable bg = getBackground();
+            Drawable fg = getForeground();
+            if (bg != null) {
+                if (fg == null) {
+                    opacity = bg.getOpacity();
+                } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
+                        && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
+                    // If the frame padding is zero, then we can be opaque
+                    // if either the frame -or- the background is opaque.
+                    int fop = fg.getOpacity();
+                    int bop = bg.getOpacity();
+                    if (false)
+                        Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
+                    if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
+                        opacity = PixelFormat.OPAQUE;
+                    } else if (fop == PixelFormat.UNKNOWN) {
+                        opacity = bop;
+                    } else if (bop == PixelFormat.UNKNOWN) {
+                        opacity = fop;
+                    } else {
+                        opacity = Drawable.resolveOpacity(fop, bop);
+                    }
+                } else {
+                    // For now we have to assume translucent if there is a
+                    // frame with padding... there is no way to tell if the
+                    // frame and background together will draw all pixels.
+                    if (false)
+                        Log.v(TAG, "Padding: " + mFramePadding);
+                    opacity = PixelFormat.TRANSLUCENT;
+                }
+            }
+            if (false)
+                Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
+        }
+
+        if (false)
+            Log.v(TAG, "Selected default opacity: " + opacity);
+
+        mDefaultOpacity = opacity;
+        if (mFeatureId < 0) {
+            mWindow.setDefaultWindowFormat(opacity);
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+
+        // If the user is chording a menu shortcut, release the chord since
+        // this window lost focus
+        if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) && !hasWindowFocus
+                && mWindow.mPanelChordingKey != 0) {
+            mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+        }
+
+        final Window.Callback cb = mWindow.getCallback();
+        if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
+            cb.onWindowFocusChanged(hasWindowFocus);
+        }
+
+        if (mPrimaryActionMode != null) {
+            mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus);
+        }
+        if (mFloatingActionMode != null) {
+            mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        final Window.Callback cb = mWindow.getCallback();
+        if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
+            cb.onAttachedToWindow();
+        }
+
+        if (mFeatureId == -1) {
+            /*
+             * The main window has been attached, try to restore any panels
+             * that may have been open before. This is called in cases where
+             * an activity is being killed for configuration change and the
+             * menu was open. When the activity is recreated, the menu
+             * should be shown again.
+             */
+            mWindow.openPanelsAfterRestore();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        final Window.Callback cb = mWindow.getCallback();
+        if (cb != null && mFeatureId < 0) {
+            cb.onDetachedFromWindow();
+        }
+
+        if (mWindow.mDecorContentParent != null) {
+            mWindow.mDecorContentParent.dismissPopups();
+        }
+
+        if (mPrimaryActionModePopup != null) {
+            removeCallbacks(mShowPrimaryActionModePopup);
+            if (mPrimaryActionModePopup.isShowing()) {
+                mPrimaryActionModePopup.dismiss();
+            }
+            mPrimaryActionModePopup = null;
+        }
+        if (mFloatingToolbar != null) {
+            mFloatingToolbar.dismiss();
+            mFloatingToolbar = null;
+        }
+
+        PhoneWindow.PanelFeatureState st = mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+        if (st != null && st.menu != null && mFeatureId < 0) {
+            st.menu.close();
+        }
+    }
+
+    @Override
+    public void onCloseSystemDialogs(String reason) {
+        if (mFeatureId >= 0) {
+            mWindow.closeAllPanels();
+        }
+    }
+
+    public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
+        return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
+    }
+
+    public InputQueue.Callback willYouTakeTheInputQueue() {
+        return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null;
+    }
+
+    public void setSurfaceType(int type) {
+        mWindow.setType(type);
+    }
+
+    public void setSurfaceFormat(int format) {
+        mWindow.setFormat(format);
+    }
+
+    public void setSurfaceKeepScreenOn(boolean keepOn) {
+        if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    @Override
+    public void onRootViewScrollYChanged(int rootScrollY) {
+        mRootScrollY = rootScrollY;
+        updateColorViewTranslations();
+    }
+
+    private ActionMode createActionMode(
+            int type, ActionMode.Callback2 callback, View originatingView) {
+        switch (type) {
+            case ActionMode.TYPE_PRIMARY:
+            default:
+                return createStandaloneActionMode(callback);
+            case ActionMode.TYPE_FLOATING:
+                return createFloatingActionMode(originatingView, callback);
+        }
+    }
+
+    private void setHandledActionMode(ActionMode mode) {
+        if (mode.getType() == ActionMode.TYPE_PRIMARY) {
+            setHandledPrimaryActionMode(mode);
+        } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
+            setHandledFloatingActionMode(mode);
+        }
+    }
+
+    private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
+        endOnGoingFadeAnimation();
+        cleanupPrimaryActionMode();
+        if (mPrimaryActionModeView == null) {
+            if (mWindow.isFloating()) {
+                // Use the action bar theme.
+                final TypedValue outValue = new TypedValue();
+                final Resources.Theme baseTheme = mContext.getTheme();
+                baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+                final Context actionBarContext;
+                if (outValue.resourceId != 0) {
+                    final Resources.Theme actionBarTheme = mContext.getResources().newTheme();
+                    actionBarTheme.setTo(baseTheme);
+                    actionBarTheme.applyStyle(outValue.resourceId, true);
+
+                    actionBarContext = new ContextThemeWrapper(mContext, 0);
+                    actionBarContext.getTheme().setTo(actionBarTheme);
+                } else {
+                    actionBarContext = mContext;
+                }
+
+                mPrimaryActionModeView = new ActionBarContextView(actionBarContext);
+                mPrimaryActionModePopup = new PopupWindow(actionBarContext, null,
+                        R.attr.actionModePopupWindowStyle);
+                mPrimaryActionModePopup.setWindowLayoutType(
+                        WindowManager.LayoutParams.TYPE_APPLICATION);
+                mPrimaryActionModePopup.setContentView(mPrimaryActionModeView);
+                mPrimaryActionModePopup.setWidth(MATCH_PARENT);
+
+                actionBarContext.getTheme().resolveAttribute(
+                        R.attr.actionBarSize, outValue, true);
+                final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
+                        actionBarContext.getResources().getDisplayMetrics());
+                mPrimaryActionModeView.setContentHeight(height);
+                mPrimaryActionModePopup.setHeight(WRAP_CONTENT);
+                mShowPrimaryActionModePopup = new Runnable() {
+                    public void run() {
+                        mPrimaryActionModePopup.showAtLocation(
+                                mPrimaryActionModeView.getApplicationWindowToken(),
+                                Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
+                        endOnGoingFadeAnimation();
+                        mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
+                                0f, 1f);
+                        mFadeAnim.addListener(new Animator.AnimatorListener() {
+                            @Override
+                            public void onAnimationStart(Animator animation) {
+                                mPrimaryActionModeView.setVisibility(VISIBLE);
+                            }
+
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                mPrimaryActionModeView.setAlpha(1f);
+                                mFadeAnim = null;
+                            }
+
+                            @Override
+                            public void onAnimationCancel(Animator animation) {
+
+                            }
+
+                            @Override
+                            public void onAnimationRepeat(Animator animation) {
+
+                            }
+                        });
+                        mFadeAnim.start();
+                    }
+                };
+            } else {
+                ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub);
+                if (stub != null) {
+                    mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
+                }
+            }
+        }
+        if (mPrimaryActionModeView != null) {
+            mPrimaryActionModeView.killMode();
+            ActionMode mode = new StandaloneActionMode(
+                    mPrimaryActionModeView.getContext(), mPrimaryActionModeView,
+                    callback, mPrimaryActionModePopup == null);
+            return mode;
+        }
+        return null;
+    }
+
+    private void endOnGoingFadeAnimation() {
+        if (mFadeAnim != null) {
+            mFadeAnim.end();
+        }
+    }
+
+    private void setHandledPrimaryActionMode(ActionMode mode) {
+        endOnGoingFadeAnimation();
+        mPrimaryActionMode = mode;
+        mPrimaryActionMode.invalidate();
+        mPrimaryActionModeView.initForMode(mPrimaryActionMode);
+        if (mPrimaryActionModePopup != null) {
+            post(mShowPrimaryActionModePopup);
+        } else {
+            mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f);
+            mFadeAnim.addListener(new Animator.AnimatorListener() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mPrimaryActionModeView.setVisibility(View.VISIBLE);
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mPrimaryActionModeView.setAlpha(1f);
+                    mFadeAnim = null;
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+
+                }
+
+                @Override
+                public void onAnimationRepeat(Animator animation) {
+
+                }
+            });
+            mFadeAnim.start();
+        }
+        mPrimaryActionModeView.sendAccessibilityEvent(
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    private ActionMode createFloatingActionMode(
+            View originatingView, ActionMode.Callback2 callback) {
+        if (mFloatingActionMode != null) {
+            mFloatingActionMode.finish();
+        }
+        cleanupFloatingActionModeViews();
+        final FloatingActionMode mode =
+                new FloatingActionMode(mContext, callback, originatingView);
+        mFloatingActionModeOriginatingView = originatingView;
+        mFloatingToolbarPreDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    mode.updateViewLocationInWindow();
+                    return true;
+                }
+            };
+        return mode;
+    }
+
+    private void setHandledFloatingActionMode(ActionMode mode) {
+        mFloatingActionMode = mode;
+        mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
+        ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
+        mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
+        mFloatingActionModeOriginatingView.getViewTreeObserver()
+            .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
+    }
+
+    /**
+     * Informs the decor if a non client decor is attached and visible.
+     * @param attachedAndVisible true when the decor is visible.
+     * Note that this will even be called if there is no non client decor.
+     **/
+    void enableNonClientDecor(boolean attachedAndVisible) {
+        if (mHasNonClientDecor != attachedAndVisible) {
+            mHasNonClientDecor = attachedAndVisible;
+            if (getForeground() != null) {
+                drawableChanged();
+            }
+        }
+    }
+
+    /**
+     * Returns true if the window has a non client decor.
+     * @return If there is a non client decor - even if it is not visible.
+     **/
+    private boolean windowHasNonClientDecor() {
+        return mHasNonClientDecor;
+    }
+
+    /**
+     * Returns true if the Window is free floating and has a shadow (although at some times
+     * it might not be displaying it, e.g. during a resize). Note that non overlapping windows
+     * do not have a shadow since it could not be seen anyways (a small screen / tablet
+     * "tiles" the windows side by side but does not overlap them).
+     * @return Returns true when the window has a shadow created by the non client decor.
+     **/
+    private boolean windowHasShadow() {
+        return windowHasNonClientDecor() && ActivityManager.StackId.hasWindowShadow(mWorkspaceId);
+    }
+
+    void setWindow(PhoneWindow phoneWindow) {
+        mWindow = phoneWindow;
+        Context context = getContext();
+        if (context instanceof DecorContext) {
+            DecorContext decorContext = (DecorContext) context;
+            decorContext.setPhoneWindow(mWindow);
+        }
+    }
+
+    void onConfigurationChanged() {
+        if (mNonClientDecorView != null) {
+            int workspaceId = getWorkspaceId();
+            if (mWorkspaceId != workspaceId) {
+                mWorkspaceId = workspaceId;
+                // We might have to change the kind of surface before we do anything else.
+                mNonClientDecorView.onConfigurationChanged(
+                        ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
+                        ActivityManager.StackId.hasWindowShadow(mWorkspaceId));
+                enableNonClientDecor(ActivityManager.StackId.hasWindowDecor(workspaceId));
+            }
+        }
+    }
+
+    View onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
+        mNonClientDecorView = createNonClientDecorView(inflater);
+        final View root = inflater.inflate(layoutResource, null);
+        if (mNonClientDecorView != null) {
+            if (mNonClientDecorView.getParent() == null) {
+                addView(mNonClientDecorView,
+                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+            }
+            mNonClientDecorView.addView(root,
+                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+        } else {
+            addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+        }
+        mContentRoot = (ViewGroup) root;
+        return root;
+    }
+
+    // Free floating overlapping windows require a non client decor with a caption and shadow..
+    private NonClientDecorView createNonClientDecorView(LayoutInflater inflater) {
+        NonClientDecorView nonClientDecorView = null;
+        for (int i = getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) {
+            View view = getChildAt(i);
+            if (view instanceof NonClientDecorView) {
+                // The decor was most likely saved from a relaunch - so reuse it.
+                nonClientDecorView = (NonClientDecorView) view;
+                removeViewAt(i);
+            }
+        }
+        final WindowManager.LayoutParams attrs = mWindow.getAttributes();
+        boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
+                attrs.type == TYPE_APPLICATION;
+        mWorkspaceId = getWorkspaceId();
+        // Only a non floating application window on one of the allowed workspaces can get a non
+        // client decor.
+        if (!mWindow.isFloating()
+                && isApplication
+                && ActivityManager.StackId.isStaticStack(mWorkspaceId)) {
+            // Dependent on the brightness of the used title we either use the
+            // dark or the light button frame.
+            if (nonClientDecorView == null) {
+                Context context = getContext();
+                TypedValue value = new TypedValue();
+                context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
+                inflater = inflater.from(context);
+                if (Color.luminance(value.data) < 0.5) {
+                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
+                            R.layout.non_client_decor_dark, null);
+                } else {
+                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
+                            R.layout.non_client_decor_light, null);
+                }
+            }
+            nonClientDecorView.setPhoneWindow(mWindow,
+                    ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
+                    ActivityManager.StackId.hasWindowShadow(mWorkspaceId),
+                    getResizingBackgroundDrawable(),
+                    getContext().getDrawable(R.drawable.non_client_decor_title_focused));
+        }
+        // Tell the decor if it has a visible non client decor.
+        enableNonClientDecor(
+                nonClientDecorView != null && ActivityManager.StackId.hasWindowDecor(mWorkspaceId));
+
+        return nonClientDecorView;
+    }
+
+    /**
+     * Returns the color used to fill areas the app has not rendered content to yet when the user
+     * is resizing the window of an activity in multi-window mode.
+     * */
+    private Drawable getResizingBackgroundDrawable() {
+        final Context context = getContext();
+
+        if (mWindow.mBackgroundResource != 0) {
+            final Drawable drawable = context.getDrawable(mWindow.mBackgroundResource);
+            if (drawable != null) {
+                return drawable;
+            }
+        }
+
+        if (mWindow.mBackgroundFallbackResource != 0) {
+            final Drawable fallbackDrawable =
+                    context.getDrawable(mWindow.mBackgroundFallbackResource);
+            if (fallbackDrawable != null) {
+                return fallbackDrawable;
+            }
+        }
+
+        // We shouldn't really get here as the background fallback should be always available since
+        // it is defaulted by the system.
+        Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + mWindow);
+        return null;
+    }
+
+    /**
+     * Returns the Id of the workspace which contains this window.
+     * Note that if no workspace can be determined - which usually means that it was not
+     * created for an activity - the fullscreen workspace ID will be returned.
+     * @return Returns the workspace stack id which contains this window.
+     **/
+    private int getWorkspaceId() {
+        int workspaceId = INVALID_STACK_ID;
+        final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback();
+        if (callback != null) {
+            try {
+                workspaceId = callback.getWindowStackId();
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
+            }
+        }
+        if (workspaceId == INVALID_STACK_ID) {
+            return FULLSCREEN_WORKSPACE_STACK_ID;
+        }
+        return workspaceId;
+    }
+
+    void clearContentView() {
+        if (mNonClientDecorView != null) {
+            if (mNonClientDecorView.getChildCount() > 1) {
+                mNonClientDecorView.removeViewAt(1);
+            }
+        } else {
+            // This window doesn't have non client decor, so we need to just remove the
+            // children of the decor view.
+            removeAllViews();
+        }
+    }
+
+    private static class ColorViewState {
+        View view = null;
+        int targetVisibility = View.INVISIBLE;
+        boolean present = false;
+
+        final int id;
+        final int systemUiHideFlag;
+        final int translucentFlag;
+        final int verticalGravity;
+        final int horizontalGravity;
+        final String transitionName;
+        final int hideWindowFlag;
+
+        ColorViewState(int systemUiHideFlag,
+                int translucentFlag, int verticalGravity, int horizontalGravity,
+                String transitionName, int id, int hideWindowFlag) {
+            this.id = id;
+            this.systemUiHideFlag = systemUiHideFlag;
+            this.translucentFlag = translucentFlag;
+            this.verticalGravity = verticalGravity;
+            this.horizontalGravity = horizontalGravity;
+            this.transitionName = transitionName;
+            this.hideWindowFlag = hideWindowFlag;
+        }
+    }
+
+    /**
+     * Clears out internal references when the action mode is destroyed.
+     */
+    private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
+        private final ActionMode.Callback mWrapped;
+
+        public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
+            mWrapped = wrapped;
+        }
+
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            return mWrapped.onCreateActionMode(mode, menu);
+        }
+
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            requestFitSystemWindows();
+            return mWrapped.onPrepareActionMode(mode, menu);
+        }
+
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            return mWrapped.onActionItemClicked(mode, item);
+        }
+
+        public void onDestroyActionMode(ActionMode mode) {
+            mWrapped.onDestroyActionMode(mode);
+            final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion
+                    >= Build.VERSION_CODES.M;
+            final boolean isPrimary;
+            final boolean isFloating;
+            if (isMncApp) {
+                isPrimary = mode == mPrimaryActionMode;
+                isFloating = mode == mFloatingActionMode;
+                if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) {
+                    Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
+                            + mode + " was not the current primary action mode! Expected "
+                            + mPrimaryActionMode);
+                }
+                if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) {
+                    Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
+                            + mode + " was not the current floating action mode! Expected "
+                            + mFloatingActionMode);
+                }
+            } else {
+                isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY;
+                isFloating = mode.getType() == ActionMode.TYPE_FLOATING;
+            }
+            if (isPrimary) {
+                if (mPrimaryActionModePopup != null) {
+                    removeCallbacks(mShowPrimaryActionModePopup);
+                }
+                if (mPrimaryActionModeView != null) {
+                    endOnGoingFadeAnimation();
+                    mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
+                            1f, 0f);
+                    mFadeAnim.addListener(new Animator.AnimatorListener() {
+                                @Override
+                                public void onAnimationStart(Animator animation) {
+
+                                }
+
+                                @Override
+                                public void onAnimationEnd(Animator animation) {
+                                    mPrimaryActionModeView.setVisibility(GONE);
+                                    if (mPrimaryActionModePopup != null) {
+                                        mPrimaryActionModePopup.dismiss();
+                                    }
+                                    mPrimaryActionModeView.removeAllViews();
+                                    mFadeAnim = null;
+                                }
+
+                                @Override
+                                public void onAnimationCancel(Animator animation) {
+
+                                }
+
+                                @Override
+                                public void onAnimationRepeat(Animator animation) {
+
+                                }
+                            });
+                    mFadeAnim.start();
+                }
+
+                mPrimaryActionMode = null;
+            } else if (isFloating) {
+                cleanupFloatingActionModeViews();
+                mFloatingActionMode = null;
+            }
+            if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+                try {
+                    mWindow.getCallback().onActionModeFinished(mode);
+                } catch (AbstractMethodError ame) {
+                    // Older apps might not implement this callback method.
+                }
+            }
+            requestFitSystemWindows();
+        }
+
+        @Override
+        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+            if (mWrapped instanceof ActionMode.Callback2) {
+                ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
+            } else {
+                super.onGetContentRect(mode, view, outRect);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 83f810f..6e7e5cf 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -16,24 +16,14 @@
 
 package com.android.internal.policy;
 
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.view.View.MeasureSpec.AT_MOST;
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.getMode;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManager.LayoutParams.*;
 
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManagerNative;
 import android.app.SearchManager;
-import android.os.Build;
 import android.os.UserHandle;
 
-import android.view.ActionMode;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.IRotationWatcher.Stub;
@@ -55,15 +45,9 @@
 import android.view.ViewManager;
 import android.view.ViewParent;
 import android.view.ViewRootImpl;
-import android.view.ViewStub;
-import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.Window;
-import android.view.WindowInsets;
 import android.view.WindowManager;
 import com.android.internal.R;
-import com.android.internal.view.FloatingActionMode;
-import com.android.internal.view.RootViewSurfaceTaker;
-import com.android.internal.view.StandaloneActionMode;
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.android.internal.view.menu.IconMenuPresenter;
 import com.android.internal.view.menu.ListMenuPresenter;
@@ -72,11 +56,7 @@
 import com.android.internal.view.menu.MenuPopupHelper;
 import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.view.menu.MenuView;
-import com.android.internal.widget.ActionBarContextView;
-import com.android.internal.widget.BackgroundFallback;
 import com.android.internal.widget.DecorContentParent;
-import com.android.internal.widget.FloatingToolbar;
-import com.android.internal.widget.NonClientDecorView;
 import com.android.internal.widget.SwipeDismissLayout;
 
 import android.app.ActivityManager;
@@ -87,10 +67,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.media.session.MediaController;
@@ -108,22 +85,17 @@
 import android.transition.TransitionManager;
 import android.transition.TransitionSet;
 import android.util.AndroidRuntimeException;
-import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.TypedValue;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.ImageView;
-import android.widget.PopupWindow;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -142,8 +114,6 @@
 
     private final static String TAG = "PhoneWindow";
 
-    private final static boolean SWEEP_OPEN_MENU = false;
-
     private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
 
     private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
@@ -174,32 +144,21 @@
     // view is requested, so we need to force the recreating without introducing an infinite loop.
     private boolean mForceDecorInstall = false;
 
-    // This is the non client decor view for the window, containing the caption and window control
-    // buttons. The visibility of this decor depends on the workspace and the window type.
-    // If the window type does not require such a view, this member might be null.
-    NonClientDecorView mNonClientDecorView;
-
-    // The non client decor needs to adapt to the used workspace. Since querying and changing the
-    // workspace is expensive, this is the workspace value the window is currently set up for.
-    int mWorkspaceId;
-
     // This is the view in which the window contents are placed. It is either
     // mDecor itself, or a child of mDecor where the contents go.
-    private ViewGroup mContentParent;
-
-    private ViewGroup mContentRoot;
+    ViewGroup mContentParent;
 
     Callback2 mTakeSurfaceCallback;
 
     InputQueue.Callback mTakeInputQueueCallback;
 
-    private boolean mIsFloating;
+    boolean mIsFloating;
 
     private LayoutInflater mLayoutInflater;
 
     private TextView mTitleView;
 
-    private DecorContentParent mDecorContentParent;
+    DecorContentParent mDecorContentParent;
     private ActionMenuPresenterCallback mActionMenuPresenterCallback;
     private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
 
@@ -231,13 +190,13 @@
      * multiple panels). Shortcuts will go to this panel. It gets set in
      * {@link #preparePanel} and cleared in {@link #closePanel}.
      */
-    private PanelFeatureState mPreparedPanel;
+    PanelFeatureState mPreparedPanel;
 
     /**
      * The keycode that is currently held down (as a modifier) for chording. If
      * this is 0, there is no key held down.
      */
-    private int mPanelChordingKey;
+    int mPanelChordingKey;
 
     private ImageView mLeftIconView;
 
@@ -247,12 +206,12 @@
 
     private ProgressBar mHorizontalProgressBar;
 
-    private int mBackgroundResource = 0;
-    private int mBackgroundFallbackResource = 0;
+    int mBackgroundResource = 0;
+    int mBackgroundFallbackResource = 0;
 
     private Drawable mBackgroundDrawable;
 
-    private boolean mLoadEleveation = true;
+    private boolean mLoadElevation = true;
     private float mElevation;
 
     /** Whether window content should be clipped to the background outline. */
@@ -261,8 +220,8 @@
     private int mFrameResource = 0;
 
     private int mTextColor = 0;
-    private int mStatusBarColor = 0;
-    private int mNavigationBarColor = 0;
+    int mStatusBarColor = 0;
+    int mNavigationBarColor = 0;
     private boolean mForcedStatusBarColor = false;
     private boolean mForcedNavigationBarColor = false;
 
@@ -272,9 +231,9 @@
 
     private boolean mAlwaysReadCloseOnTouchAttr = false;
 
-    private ContextMenuBuilder mContextMenu;
-    private MenuDialogHelper mContextMenuHelper;
-    private MenuPopupHelper mContextMenuPopupHelper;
+    ContextMenuBuilder mContextMenu;
+    MenuDialogHelper mContextMenuHelper;
+    MenuPopupHelper mContextMenuPopupHelper;
     private boolean mClosingActionMenu;
 
     private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
@@ -312,9 +271,6 @@
     private long mBackgroundFadeDurationMillis = -1;
     private Boolean mSharedElementsUseOverlay;
 
-    private Rect mTempRect;
-    private Rect mOutsets = new Rect();
-
     private boolean mIsStartingWindow;
     private int mTheme = -1;
 
@@ -335,7 +291,7 @@
         if (preservedWindow != null) {
             mDecor = (DecorView) preservedWindow.getDecorView();
             mElevation = preservedWindow.getElevation();
-            mLoadEleveation = false;
+            mLoadElevation = false;
             mForceDecorInstall = true;
             // If we're preserving window, carry over the app token from the preserved
             // window, as we'll be skipping the addView in handleResumeActivity(), and
@@ -508,15 +464,10 @@
         }
     }
 
+    @Override
     public void clearContentView() {
-        if (mNonClientDecorView != null) {
-            if (mNonClientDecorView.getChildCount() > 1) {
-                mNonClientDecorView.removeViewAt(1);
-            }
-        } else {
-            // This window doesn't have non client decor, so we need to just remove the children
-            // of the decor view.
-            mDecor.removeAllViews();
+        if (mDecor != null) {
+            mDecor.clearContentView();
         }
     }
 
@@ -730,15 +681,8 @@
                 }
             }
         }
-        if (mNonClientDecorView != null) {
-            int workspaceId = getWorkspaceId();
-            if (mWorkspaceId != workspaceId) {
-                mWorkspaceId = workspaceId;
-                // We might have to change the kind of surface before we do anything else.
-                mNonClientDecorView.phoneWindowUpdated(StackId.hasWindowDecor(mWorkspaceId),
-                        StackId.hasWindowShadow(mWorkspaceId));
-                mDecor.enableNonClientDecor(StackId.hasWindowDecor(workspaceId));
-            }
+        if (mDecor != null) {
+            mDecor.onConfigurationChanged();
         }
     }
 
@@ -1170,7 +1114,7 @@
         return performPanelShortcut(getPanelState(featureId, false), keyCode, event, flags);
     }
 
-    private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
+    boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
             int flags) {
         if (event.isSystem() || (st == null)) {
             return false;
@@ -2261,7 +2205,7 @@
      * called sometime after {@link #restorePanelState} when it is safe to add
      * to the window manager.
      */
-    private void openPanelsAfterRestore() {
+    void openPanelsAfterRestore() {
         PanelFeatureState[] panels = mPanels;
 
         if (panels == null) {
@@ -2332,1530 +2276,6 @@
         }
     }
 
-    private static final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
-
-        /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
-
-        /** The feature ID of the panel, or -1 if this is the application's DecorView */
-        private final int mFeatureId;
-
-        private final Rect mDrawingBounds = new Rect();
-
-        private final Rect mBackgroundPadding = new Rect();
-
-        private final Rect mFramePadding = new Rect();
-
-        private final Rect mFrameOffsets = new Rect();
-
-        // True if a non client area decor exists.
-        private boolean mHasNonClientDecor = false;
-
-        private boolean mChanging;
-
-        private Drawable mMenuBackground;
-        private boolean mWatchingForMenu;
-        private int mDownY;
-
-        private ActionMode mPrimaryActionMode;
-        private ActionMode mFloatingActionMode;
-        private ActionBarContextView mPrimaryActionModeView;
-        private PopupWindow mPrimaryActionModePopup;
-        private Runnable mShowPrimaryActionModePopup;
-        private OnPreDrawListener mFloatingToolbarPreDrawListener;
-        private View mFloatingActionModeOriginatingView;
-        private FloatingToolbar mFloatingToolbar;
-        private ObjectAnimator mFadeAnim;
-
-        // View added at runtime to draw under the status bar area
-        private View mStatusGuard;
-        // View added at runtime to draw under the navigation bar area
-        private View mNavigationGuard;
-
-        private final ColorViewState mStatusColorViewState = new ColorViewState(
-                SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
-                Gravity.TOP,
-                Gravity.LEFT,
-                STATUS_BAR_BACKGROUND_TRANSITION_NAME,
-                com.android.internal.R.id.statusBarBackground,
-                FLAG_FULLSCREEN);
-        private final ColorViewState mNavigationColorViewState = new ColorViewState(
-                SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
-                Gravity.BOTTOM,
-                Gravity.RIGHT,
-                NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
-                com.android.internal.R.id.navigationBarBackground,
-                0 /* hideWindowFlag */);
-
-        private final Interpolator mShowInterpolator;
-        private final Interpolator mHideInterpolator;
-        private final int mBarEnterExitDuration;
-
-        private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
-
-        private int mLastTopInset = 0;
-        private int mLastBottomInset = 0;
-        private int mLastRightInset = 0;
-        private boolean mLastHasTopStableInset = false;
-        private boolean mLastHasBottomStableInset = false;
-        private boolean mLastHasRightStableInset = false;
-        private int mLastWindowFlags = 0;
-
-        private int mRootScrollY = 0;
-
-        private PhoneWindow mWindow;
-
-        private DecorView(Context context, int featureId, PhoneWindow window) {
-            super(context);
-            mFeatureId = featureId;
-
-            mShowInterpolator = AnimationUtils.loadInterpolator(context,
-                    android.R.interpolator.linear_out_slow_in);
-            mHideInterpolator = AnimationUtils.loadInterpolator(context,
-                    android.R.interpolator.fast_out_linear_in);
-
-            mBarEnterExitDuration = context.getResources().getInteger(
-                    R.integer.dock_enter_exit_duration);
-
-            setWindow(window);
-        }
-
-        public void setBackgroundFallback(int resId) {
-            mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
-            setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
-        }
-
-        @Override
-        public void onDraw(Canvas c) {
-            super.onDraw(c);
-            mBackgroundFallback.draw(mWindow.mContentRoot, c, mWindow.mContentParent);
-        }
-
-        @Override
-        public boolean dispatchKeyEvent(KeyEvent event) {
-            final int keyCode = event.getKeyCode();
-            final int action = event.getAction();
-            final boolean isDown = action == KeyEvent.ACTION_DOWN;
-
-            if (isDown && (event.getRepeatCount() == 0)) {
-                // First handle chording of panel key: if a panel key is held
-                // but not released, try to execute a shortcut in it.
-                if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
-                    boolean handled = dispatchKeyShortcutEvent(event);
-                    if (handled) {
-                        return true;
-                    }
-                }
-
-                // If a panel is open, perform a shortcut on it without the
-                // chorded panel key
-                if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
-                    if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
-                        return true;
-                    }
-                }
-            }
-
-            if (!mWindow.isDestroyed()) {
-                final Callback cb = mWindow.getCallback();
-                final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
-                        : super.dispatchKeyEvent(event);
-                if (handled) {
-                    return true;
-                }
-            }
-
-            return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
-                    : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
-        }
-
-        @Override
-        public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
-            // If the panel is already prepared, then perform the shortcut using it.
-            boolean handled;
-            if (mWindow.mPreparedPanel != null) {
-                handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev,
-                        Menu.FLAG_PERFORM_NO_CLOSE);
-                if (handled) {
-                    if (mWindow.mPreparedPanel != null) {
-                        mWindow.mPreparedPanel.isHandled = true;
-                    }
-                    return true;
-                }
-            }
-
-            // Shortcut not handled by the panel.  Dispatch to the view hierarchy.
-            final Callback cb = mWindow.getCallback();
-            handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
-            if (handled) {
-                return true;
-            }
-
-            // If the panel is not prepared, then we may be trying to handle a shortcut key
-            // combination such as Control+C.  Temporarily prepare the panel then mark it
-            // unprepared again when finished to ensure that the panel will again be prepared
-            // the next time it is shown for real.
-            PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false);
-            if (st != null && mWindow.mPreparedPanel == null) {
-                mWindow.preparePanel(st, ev);
-                handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev,
-                        Menu.FLAG_PERFORM_NO_CLOSE);
-                st.isPrepared = false;
-                if (handled) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public boolean dispatchTouchEvent(MotionEvent ev) {
-            final Callback cb = mWindow.getCallback();
-            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
-        }
-
-        @Override
-        public boolean dispatchTrackballEvent(MotionEvent ev) {
-            final Callback cb = mWindow.getCallback();
-            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
-        }
-
-        @Override
-        public boolean dispatchGenericMotionEvent(MotionEvent ev) {
-            final Callback cb = mWindow.getCallback();
-            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
-        }
-
-        public boolean superDispatchKeyEvent(KeyEvent event) {
-            // Give priority to closing action modes if applicable.
-            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-                final int action = event.getAction();
-                // Back cancels action modes first.
-                if (mPrimaryActionMode != null) {
-                    if (action == KeyEvent.ACTION_UP) {
-                        mPrimaryActionMode.finish();
-                    }
-                    return true;
-                }
-            }
-
-            return super.dispatchKeyEvent(event);
-        }
-
-        public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
-            return super.dispatchKeyShortcutEvent(event);
-        }
-
-        public boolean superDispatchTouchEvent(MotionEvent event) {
-            return super.dispatchTouchEvent(event);
-        }
-
-        public boolean superDispatchTrackballEvent(MotionEvent event) {
-            return super.dispatchTrackballEvent(event);
-        }
-
-        public boolean superDispatchGenericMotionEvent(MotionEvent event) {
-            return super.dispatchGenericMotionEvent(event);
-        }
-
-        @Override
-        public boolean onTouchEvent(MotionEvent event) {
-            return onInterceptTouchEvent(event);
-        }
-
-        private boolean isOutOfInnerBounds(int x, int y) {
-            return x < 0 || y < 0 || x > getWidth() || y > getHeight();
-        }
-
-        private boolean isOutOfBounds(int x, int y) {
-            return x < -5 || y < -5 || x > (getWidth() + 5)
-                    || y > (getHeight() + 5);
-        }
-
-        @Override
-        public boolean onInterceptTouchEvent(MotionEvent event) {
-            int action = event.getAction();
-            if (mHasNonClientDecor && mWindow.mNonClientDecorView.mVisible) {
-                // Don't dispatch ACTION_DOWN to the non client decor if the window is
-                // resizable and the event was (starting) outside the window.
-                // Window resizing events should be handled by WindowManager.
-                // TODO: Investigate how to handle the outside touch in window manager
-                //       without generating these events.
-                //       Currently we receive these because we need to enlarge the window's
-                //       touch region so that the monitor channel receives the events
-                //       in the outside touch area.
-                if (action == MotionEvent.ACTION_DOWN) {
-                    final int x = (int) event.getX();
-                    final int y = (int) event.getY();
-                    if (isOutOfInnerBounds(x, y)) {
-                        return true;
-                    }
-                }
-            }
-
-            if (mFeatureId >= 0) {
-                if (action == MotionEvent.ACTION_DOWN) {
-                    int x = (int)event.getX();
-                    int y = (int)event.getY();
-                    if (isOutOfBounds(x, y)) {
-                        mWindow.closePanel(mFeatureId);
-                        return true;
-                    }
-                }
-            }
-
-            if (!SWEEP_OPEN_MENU) {
-                return false;
-            }
-
-            if (mFeatureId >= 0) {
-                if (action == MotionEvent.ACTION_DOWN) {
-                    Log.i(TAG, "Watchiing!");
-                    mWatchingForMenu = true;
-                    mDownY = (int) event.getY();
-                    return false;
-                }
-
-                if (!mWatchingForMenu) {
-                    return false;
-                }
-
-                int y = (int)event.getY();
-                if (action == MotionEvent.ACTION_MOVE) {
-                    if (y > (mDownY+30)) {
-                        Log.i(TAG, "Closing!");
-                        mWindow.closePanel(mFeatureId);
-                        mWatchingForMenu = false;
-                        return true;
-                    }
-                } else if (action == MotionEvent.ACTION_UP) {
-                    mWatchingForMenu = false;
-                }
-
-                return false;
-            }
-
-            //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
-            //        + " (in " + getHeight() + ")");
-
-            if (action == MotionEvent.ACTION_DOWN) {
-                int y = (int)event.getY();
-                if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
-                    Log.i(TAG, "Watching!");
-                    mWatchingForMenu = true;
-                }
-                return false;
-            }
-
-            if (!mWatchingForMenu) {
-                return false;
-            }
-
-            int y = (int)event.getY();
-            if (action == MotionEvent.ACTION_MOVE) {
-                if (y < (getHeight()-30)) {
-                    Log.i(TAG, "Opening!");
-                    mWindow.openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent(
-                            KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
-                    mWatchingForMenu = false;
-                    return true;
-                }
-            } else if (action == MotionEvent.ACTION_UP) {
-                mWatchingForMenu = false;
-            }
-
-            return false;
-        }
-
-        @Override
-        public void sendAccessibilityEvent(int eventType) {
-            if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
-                return;
-            }
-
-            // if we are showing a feature that should be announced and one child
-            // make this child the event source since this is the feature itself
-            // otherwise the callback will take over and announce its client
-            if ((mFeatureId == FEATURE_OPTIONS_PANEL ||
-                    mFeatureId == FEATURE_CONTEXT_MENU ||
-                    mFeatureId == FEATURE_PROGRESS ||
-                    mFeatureId == FEATURE_INDETERMINATE_PROGRESS)
-                    && getChildCount() == 1) {
-                getChildAt(0).sendAccessibilityEvent(eventType);
-            } else {
-                super.sendAccessibilityEvent(eventType);
-            }
-        }
-
-        @Override
-        public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && !mWindow.isDestroyed()) {
-                if (cb.dispatchPopulateAccessibilityEvent(event)) {
-                    return true;
-                }
-            }
-            return super.dispatchPopulateAccessibilityEventInternal(event);
-        }
-
-        @Override
-        protected boolean setFrame(int l, int t, int r, int b) {
-            boolean changed = super.setFrame(l, t, r, b);
-            if (changed) {
-                final Rect drawingBounds = mDrawingBounds;
-                getDrawingRect(drawingBounds);
-
-                Drawable fg = getForeground();
-                if (fg != null) {
-                    final Rect frameOffsets = mFrameOffsets;
-                    drawingBounds.left += frameOffsets.left;
-                    drawingBounds.top += frameOffsets.top;
-                    drawingBounds.right -= frameOffsets.right;
-                    drawingBounds.bottom -= frameOffsets.bottom;
-                    fg.setBounds(drawingBounds);
-                    final Rect framePadding = mFramePadding;
-                    drawingBounds.left += framePadding.left - frameOffsets.left;
-                    drawingBounds.top += framePadding.top - frameOffsets.top;
-                    drawingBounds.right -= framePadding.right - frameOffsets.right;
-                    drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
-                }
-
-                Drawable bg = getBackground();
-                if (bg != null) {
-                    bg.setBounds(drawingBounds);
-                }
-
-                if (SWEEP_OPEN_MENU) {
-                    if (mMenuBackground == null && mFeatureId < 0
-                            && mWindow.getAttributes().height
-                            == WindowManager.LayoutParams.MATCH_PARENT) {
-                        mMenuBackground = getContext().getDrawable(
-                                R.drawable.menu_background);
-                    }
-                    if (mMenuBackground != null) {
-                        mMenuBackground.setBounds(drawingBounds.left,
-                                drawingBounds.bottom-6, drawingBounds.right,
-                                drawingBounds.bottom+20);
-                    }
-                }
-            }
-            return changed;
-        }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
-            final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
-
-            final int widthMode = getMode(widthMeasureSpec);
-            final int heightMode = getMode(heightMeasureSpec);
-
-            boolean fixedWidth = false;
-            if (widthMode == AT_MOST) {
-                final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
-                        : mWindow.mFixedWidthMajor;
-                if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
-                    final int w;
-                    if (tvw.type == TypedValue.TYPE_DIMENSION) {
-                        w = (int) tvw.getDimension(metrics);
-                    } else if (tvw.type == TypedValue.TYPE_FRACTION) {
-                        w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
-                    } else {
-                        w = 0;
-                    }
-
-                    if (w > 0) {
-                        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                                Math.min(w, widthSize), EXACTLY);
-                        fixedWidth = true;
-                    }
-                }
-            }
-
-            if (heightMode == AT_MOST) {
-                final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
-                        : mWindow.mFixedHeightMinor;
-                if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
-                    final int h;
-                    if (tvh.type == TypedValue.TYPE_DIMENSION) {
-                        h = (int) tvh.getDimension(metrics);
-                    } else if (tvh.type == TypedValue.TYPE_FRACTION) {
-                        h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
-                    } else {
-                        h = 0;
-                    }
-                    if (h > 0) {
-                        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-                        heightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                                Math.min(h, heightSize), EXACTLY);
-                    }
-                }
-            }
-
-            getOutsets(mWindow.mOutsets);
-            if (mWindow.mOutsets.top > 0 || mWindow.mOutsets.bottom > 0) {
-                int mode = MeasureSpec.getMode(heightMeasureSpec);
-                if (mode != MeasureSpec.UNSPECIFIED) {
-                    int height = MeasureSpec.getSize(heightMeasureSpec);
-                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                            height + mWindow.mOutsets.top + mWindow.mOutsets.bottom, mode);
-                }
-            }
-            if (mWindow.mOutsets.left > 0 || mWindow.mOutsets.right > 0) {
-                int mode = MeasureSpec.getMode(widthMeasureSpec);
-                if (mode != MeasureSpec.UNSPECIFIED) {
-                    int width = MeasureSpec.getSize(widthMeasureSpec);
-                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                            width + mWindow.mOutsets.left + mWindow.mOutsets.right, mode);
-                }
-            }
-
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-            int width = getMeasuredWidth();
-            boolean measure = false;
-
-            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
-
-            if (!fixedWidth && widthMode == AT_MOST) {
-                final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
-                if (tv.type != TypedValue.TYPE_NULL) {
-                    final int min;
-                    if (tv.type == TypedValue.TYPE_DIMENSION) {
-                        min = (int)tv.getDimension(metrics);
-                    } else if (tv.type == TypedValue.TYPE_FRACTION) {
-                        min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
-                    } else {
-                        min = 0;
-                    }
-
-                    if (width < min) {
-                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
-                        measure = true;
-                    }
-                }
-            }
-
-            // TODO: Support height?
-
-            if (measure) {
-                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            }
-        }
-
-        @Override
-        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-            super.onLayout(changed, left, top, right, bottom);
-            getOutsets(mWindow.mOutsets);
-            if (mWindow.mOutsets.left > 0) {
-                offsetLeftAndRight(-mWindow.mOutsets.left);
-            }
-            if (mWindow.mOutsets.top > 0) {
-                offsetTopAndBottom(-mWindow.mOutsets.top);
-            }
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            super.draw(canvas);
-
-            if (mMenuBackground != null) {
-                mMenuBackground.draw(canvas);
-            }
-        }
-
-        @Override
-        public boolean showContextMenuForChild(View originalView) {
-            // Reuse the context menu builder
-            if (mWindow.mContextMenu == null) {
-                mWindow.mContextMenu = new ContextMenuBuilder(getContext());
-                mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
-            } else {
-                mWindow.mContextMenu.clearAll();
-            }
-
-            final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView,
-                    originalView.getWindowToken());
-            if (helper != null) {
-                helper.setPresenterCallback(mWindow.mContextMenuCallback);
-            } else if (mWindow.mContextMenuHelper != null) {
-                // No menu to show, but if we have a menu currently showing it just became blank.
-                // Close it.
-                mWindow.mContextMenuHelper.dismiss();
-            }
-            mWindow.mContextMenuHelper = helper;
-            return helper != null;
-        }
-
-        @Override
-        public boolean showContextMenuForChild(View originalView, float x, float y) {
-            // Reuse the context menu builder
-            if (mWindow.mContextMenu == null) {
-                mWindow.mContextMenu = new ContextMenuBuilder(getContext());
-                mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
-            } else {
-                mWindow.mContextMenu.clearAll();
-            }
-
-            final MenuPopupHelper helper = mWindow.mContextMenu.showPopup(
-                    getContext(), originalView, x, y);
-            if (helper != null) {
-                helper.setCallback(mWindow.mContextMenuCallback);
-            } else if (mWindow.mContextMenuPopupHelper != null) {
-                // No menu to show, but if we have a menu currently showing it just became blank.
-                // Close it.
-                mWindow.mContextMenuPopupHelper.dismiss();
-            }
-            mWindow.mContextMenuPopupHelper = helper;
-            return helper != null;
-        }
-
-        @Override
-        public ActionMode startActionModeForChild(View originalView,
-                ActionMode.Callback callback) {
-            return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
-        }
-
-        @Override
-        public ActionMode startActionModeForChild(
-                View child, ActionMode.Callback callback, int type) {
-            return startActionMode(child, callback, type);
-        }
-
-        @Override
-        public ActionMode startActionMode(ActionMode.Callback callback) {
-            return startActionMode(callback, ActionMode.TYPE_PRIMARY);
-        }
-
-        @Override
-        public ActionMode startActionMode(ActionMode.Callback callback, int type) {
-            return startActionMode(this, callback, type);
-        }
-
-        private ActionMode startActionMode(
-                View originatingView, ActionMode.Callback callback, int type) {
-            ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
-            ActionMode mode = null;
-            if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
-                try {
-                    mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
-                } catch (AbstractMethodError ame) {
-                    // Older apps might not implement the typed version of this method.
-                    if (type == ActionMode.TYPE_PRIMARY) {
-                        try {
-                            mode = mWindow.getCallback().onWindowStartingActionMode(
-                                    wrappedCallback);
-                        } catch (AbstractMethodError ame2) {
-                            // Older apps might not implement this callback method at all.
-                        }
-                    }
-                }
-            }
-            if (mode != null) {
-                if (mode.getType() == ActionMode.TYPE_PRIMARY) {
-                    cleanupPrimaryActionMode();
-                    mPrimaryActionMode = mode;
-                } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
-                    if (mFloatingActionMode != null) {
-                        mFloatingActionMode.finish();
-                    }
-                    mFloatingActionMode = mode;
-                }
-            } else {
-                mode = createActionMode(type, wrappedCallback, originatingView);
-                if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
-                    setHandledActionMode(mode);
-                } else {
-                    mode = null;
-                }
-            }
-            if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) {
-                try {
-                    mWindow.getCallback().onActionModeStarted(mode);
-                } catch (AbstractMethodError ame) {
-                    // Older apps might not implement this callback method.
-                }
-            }
-            return mode;
-        }
-
-        private void cleanupPrimaryActionMode() {
-            if (mPrimaryActionMode != null) {
-                mPrimaryActionMode.finish();
-                mPrimaryActionMode = null;
-            }
-            if (mPrimaryActionModeView != null) {
-                mPrimaryActionModeView.killMode();
-            }
-        }
-
-        private void cleanupFloatingActionModeViews() {
-            if (mFloatingToolbar != null) {
-                mFloatingToolbar.dismiss();
-                mFloatingToolbar = null;
-            }
-            if (mFloatingActionModeOriginatingView != null) {
-                if (mFloatingToolbarPreDrawListener != null) {
-                    mFloatingActionModeOriginatingView.getViewTreeObserver()
-                        .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
-                    mFloatingToolbarPreDrawListener = null;
-                }
-                mFloatingActionModeOriginatingView = null;
-            }
-        }
-
-        public void startChanging() {
-            mChanging = true;
-        }
-
-        public void finishChanging() {
-            mChanging = false;
-            drawableChanged();
-        }
-
-        public void setWindowBackground(Drawable drawable) {
-            if (getBackground() != drawable) {
-                setBackgroundDrawable(drawable);
-                if (drawable != null) {
-                    drawable.getPadding(mBackgroundPadding);
-                } else {
-                    mBackgroundPadding.setEmpty();
-                }
-                drawableChanged();
-            }
-        }
-
-        public void setWindowFrame(Drawable drawable) {
-            if (getForeground() != drawable) {
-                setForeground(drawable);
-                if (drawable != null) {
-                    drawable.getPadding(mFramePadding);
-                } else {
-                    mFramePadding.setEmpty();
-                }
-                drawableChanged();
-            }
-        }
-
-        @Override
-        public void onWindowSystemUiVisibilityChanged(int visible) {
-            updateColorViews(null /* insets */, true /* animate */);
-        }
-
-        @Override
-        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-            mFrameOffsets.set(insets.getSystemWindowInsets());
-            insets = updateColorViews(insets, true /* animate */);
-            insets = updateStatusGuard(insets);
-            updateNavigationGuard(insets);
-            if (getForeground() != null) {
-                drawableChanged();
-            }
-            return insets;
-        }
-
-        @Override
-        public boolean isTransitionGroup() {
-            return false;
-        }
-
-        private WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
-            WindowManager.LayoutParams attrs = mWindow.getAttributes();
-            int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
-
-            if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
-                boolean disallowAnimate = !isLaidOut();
-                disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
-                        & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
-                mLastWindowFlags = attrs.flags;
-
-                if (insets != null) {
-                    mLastTopInset = Math.min(insets.getStableInsetTop(),
-                            insets.getSystemWindowInsetTop());
-                    mLastBottomInset = Math.min(insets.getStableInsetBottom(),
-                            insets.getSystemWindowInsetBottom());
-                    mLastRightInset = Math.min(insets.getStableInsetRight(),
-                            insets.getSystemWindowInsetRight());
-
-                    // Don't animate if the presence of stable insets has changed, because that
-                    // indicates that the window was either just added and received them for the
-                    // first time, or the window size or position has changed.
-                    boolean hasTopStableInset = insets.getStableInsetTop() != 0;
-                    disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
-                    mLastHasTopStableInset = hasTopStableInset;
-
-                    boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
-                    disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
-                    mLastHasBottomStableInset = hasBottomStableInset;
-
-                    boolean hasRightStableInset = insets.getStableInsetRight() != 0;
-                    disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
-                    mLastHasRightStableInset = hasRightStableInset;
-                }
-
-                boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0;
-                int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset;
-                updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
-                        mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
-                        0 /* rightInset */, animate && !disallowAnimate);
-
-                boolean statusBarNeedsRightInset = navBarToRightEdge
-                        && mNavigationColorViewState.present;
-                int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
-                updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor,
-                        mLastTopInset, false /* matchVertical */, statusBarRightInset,
-                        animate && !disallowAnimate);
-            }
-
-            // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
-            // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
-            // explicitly asked for it.
-
-            boolean consumingNavBar =
-                    (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
-                            && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
-                            && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
-
-            int consumedRight = consumingNavBar ? mLastRightInset : 0;
-            int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
-
-            if (mWindow.mContentRoot != null
-                    && mWindow.mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
-                MarginLayoutParams lp = (MarginLayoutParams) mWindow.mContentRoot.getLayoutParams();
-                if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
-                    lp.rightMargin = consumedRight;
-                    lp.bottomMargin = consumedBottom;
-                    mWindow.mContentRoot.setLayoutParams(lp);
-
-                    if (insets == null) {
-                        // The insets have changed, but we're not currently in the process
-                        // of dispatching them.
-                        requestApplyInsets();
-                    }
-                }
-                if (insets != null) {
-                    insets = insets.replaceSystemWindowInsets(
-                            insets.getSystemWindowInsetLeft(),
-                            insets.getSystemWindowInsetTop(),
-                            insets.getSystemWindowInsetRight() - consumedRight,
-                            insets.getSystemWindowInsetBottom() - consumedBottom);
-                }
-            }
-
-            if (insets != null) {
-                insets = insets.consumeStableInsets();
-            }
-            return insets;
-        }
-
-        /**
-         * Update a color view
-         *
-         * @param state the color view to update.
-         * @param sysUiVis the current systemUiVisibility to apply.
-         * @param color the current color to apply.
-         * @param size the current size in the non-parent-matching dimension.
-         * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
-         *                    horizontal edge,
-         * @param rightMargin rightMargin for the color view.
-         * @param animate if true, the change will be animated.
-         */
-        private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
-                int size, boolean verticalBar, int rightMargin, boolean animate) {
-            state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
-                    && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
-                    && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
-            boolean show = state.present
-                    && (color & Color.BLACK) != 0
-                    && (mWindow.getAttributes().flags & state.translucentFlag) == 0;
-
-            boolean visibilityChanged = false;
-            View view = state.view;
-
-            int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
-            int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
-            int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
-
-            if (view == null) {
-                if (show) {
-                    state.view = view = new View(mContext);
-                    view.setBackgroundColor(color);
-                    view.setTransitionName(state.transitionName);
-                    view.setId(state.id);
-                    visibilityChanged = true;
-                    view.setVisibility(INVISIBLE);
-                    state.targetVisibility = VISIBLE;
-
-                    LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
-                            resolvedGravity);
-                    lp.rightMargin = rightMargin;
-                    addView(view, lp);
-                    updateColorViewTranslations();
-                }
-            } else {
-                int vis = show ? VISIBLE : INVISIBLE;
-                visibilityChanged = state.targetVisibility != vis;
-                state.targetVisibility = vis;
-                LayoutParams lp = (LayoutParams) view.getLayoutParams();
-                if (lp.height != resolvedHeight || lp.width != resolvedWidth
-                        || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
-                    lp.height = resolvedHeight;
-                    lp.width = resolvedWidth;
-                    lp.gravity = resolvedGravity;
-                    lp.rightMargin = rightMargin;
-                    view.setLayoutParams(lp);
-                }
-                if (show) {
-                    view.setBackgroundColor(color);
-                }
-            }
-            if (visibilityChanged) {
-                view.animate().cancel();
-                if (animate) {
-                    if (show) {
-                        if (view.getVisibility() != VISIBLE) {
-                            view.setVisibility(VISIBLE);
-                            view.setAlpha(0.0f);
-                        }
-                        view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
-                                setDuration(mBarEnterExitDuration);
-                    } else {
-                        view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
-                                .setDuration(mBarEnterExitDuration)
-                                .withEndAction(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        state.view.setAlpha(1.0f);
-                                        state.view.setVisibility(INVISIBLE);
-                                    }
-                                });
-                    }
-                } else {
-                    view.setAlpha(1.0f);
-                    view.setVisibility(show ? VISIBLE : INVISIBLE);
-                }
-            }
-        }
-
-        private void updateColorViewTranslations() {
-            // Put the color views back in place when they get moved off the screen
-            // due to the the ViewRootImpl panning.
-            int rootScrollY = mRootScrollY;
-            if (mStatusColorViewState.view != null) {
-                mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
-            }
-            if (mNavigationColorViewState.view != null) {
-                mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
-            }
-        }
-
-        private WindowInsets updateStatusGuard(WindowInsets insets) {
-            boolean showStatusGuard = false;
-            // Show the status guard when the non-overlay contextual action bar is showing
-            if (mPrimaryActionModeView != null) {
-                if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
-                    // Insets are magic!
-                    final MarginLayoutParams mlp = (MarginLayoutParams)
-                            mPrimaryActionModeView.getLayoutParams();
-                    boolean mlpChanged = false;
-                    if (mPrimaryActionModeView.isShown()) {
-                        if (mWindow.mTempRect == null) {
-                            mWindow.mTempRect = new Rect();
-                        }
-                        final Rect rect = mWindow.mTempRect;
-
-                        // If the parent doesn't consume the insets, manually
-                        // apply the default system window insets.
-                        mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
-                        final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
-                        if (mlp.topMargin != newMargin) {
-                            mlpChanged = true;
-                            mlp.topMargin = insets.getSystemWindowInsetTop();
-
-                            if (mStatusGuard == null) {
-                                mStatusGuard = new View(mContext);
-                                mStatusGuard.setBackgroundColor(mContext.getColor(
-                                        R.color.input_method_navigation_guard));
-                                addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
-                                        new LayoutParams(LayoutParams.MATCH_PARENT,
-                                                mlp.topMargin, Gravity.START | Gravity.TOP));
-                            } else {
-                                final LayoutParams lp = (LayoutParams)
-                                        mStatusGuard.getLayoutParams();
-                                if (lp.height != mlp.topMargin) {
-                                    lp.height = mlp.topMargin;
-                                    mStatusGuard.setLayoutParams(lp);
-                                }
-                            }
-                        }
-
-                        // The action mode's theme may differ from the app, so
-                        // always show the status guard above it if we have one.
-                        showStatusGuard = mStatusGuard != null;
-
-                        // We only need to consume the insets if the action
-                        // mode is overlaid on the app content (e.g. it's
-                        // sitting in a FrameLayout, see
-                        // screen_simple_overlay_action_mode.xml).
-                        final boolean nonOverlay = (mWindow.getLocalFeatures()
-                                & (1 << FEATURE_ACTION_MODE_OVERLAY)) == 0;
-                        insets = insets.consumeSystemWindowInsets(
-                                false, nonOverlay && showStatusGuard /* top */, false, false);
-                    } else {
-                        // reset top margin
-                        if (mlp.topMargin != 0) {
-                            mlpChanged = true;
-                            mlp.topMargin = 0;
-                        }
-                    }
-                    if (mlpChanged) {
-                        mPrimaryActionModeView.setLayoutParams(mlp);
-                    }
-                }
-            }
-            if (mStatusGuard != null) {
-                mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
-            }
-            return insets;
-        }
-
-        private void updateNavigationGuard(WindowInsets insets) {
-            // IMEs lay out below the nav bar, but the content view must not (for back compat)
-            if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
-                // prevent the content view from including the nav bar height
-                if (mWindow.mContentParent != null) {
-                    if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
-                        MarginLayoutParams mlp =
-                                (MarginLayoutParams) mWindow.mContentParent.getLayoutParams();
-                        mlp.bottomMargin = insets.getSystemWindowInsetBottom();
-                        mWindow.mContentParent.setLayoutParams(mlp);
-                    }
-                }
-                // position the navigation guard view, creating it if necessary
-                if (mNavigationGuard == null) {
-                    mNavigationGuard = new View(mContext);
-                    mNavigationGuard.setBackgroundColor(mContext.getColor(
-                            R.color.input_method_navigation_guard));
-                    addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
-                            new LayoutParams(LayoutParams.MATCH_PARENT,
-                                    insets.getSystemWindowInsetBottom(),
-                                    Gravity.START | Gravity.BOTTOM));
-                } else {
-                    LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
-                    lp.height = insets.getSystemWindowInsetBottom();
-                    mNavigationGuard.setLayoutParams(lp);
-                }
-            }
-        }
-
-        private void drawableChanged() {
-            if (mChanging) {
-                return;
-            }
-
-            setPadding(mFramePadding.left + mBackgroundPadding.left,
-                    mFramePadding.top + mBackgroundPadding.top,
-                    mFramePadding.right + mBackgroundPadding.right,
-                    mFramePadding.bottom + mBackgroundPadding.bottom);
-            requestLayout();
-            invalidate();
-
-            int opacity = PixelFormat.OPAQUE;
-            if (windowHasShadow()) {
-                // If the window has a shadow, it must be translucent.
-                opacity = PixelFormat.TRANSLUCENT;
-            } else{
-                // Note: If there is no background, we will assume opaque. The
-                // common case seems to be that an application sets there to be
-                // no background so it can draw everything itself. For that,
-                // we would like to assume OPAQUE and let the app force it to
-                // the slower TRANSLUCENT mode if that is really what it wants.
-                Drawable bg = getBackground();
-                Drawable fg = getForeground();
-                if (bg != null) {
-                    if (fg == null) {
-                        opacity = bg.getOpacity();
-                    } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
-                            && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
-                        // If the frame padding is zero, then we can be opaque
-                        // if either the frame -or- the background is opaque.
-                        int fop = fg.getOpacity();
-                        int bop = bg.getOpacity();
-                        if (false)
-                            Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
-                        if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
-                            opacity = PixelFormat.OPAQUE;
-                        } else if (fop == PixelFormat.UNKNOWN) {
-                            opacity = bop;
-                        } else if (bop == PixelFormat.UNKNOWN) {
-                            opacity = fop;
-                        } else {
-                            opacity = Drawable.resolveOpacity(fop, bop);
-                        }
-                    } else {
-                        // For now we have to assume translucent if there is a
-                        // frame with padding... there is no way to tell if the
-                        // frame and background together will draw all pixels.
-                        if (false)
-                            Log.v(TAG, "Padding: " + mFramePadding);
-                        opacity = PixelFormat.TRANSLUCENT;
-                    }
-                }
-                if (false)
-                    Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
-            }
-
-            if (false)
-                Log.v(TAG, "Selected default opacity: " + opacity);
-
-            mDefaultOpacity = opacity;
-            if (mFeatureId < 0) {
-                mWindow.setDefaultWindowFormat(opacity);
-            }
-        }
-
-        @Override
-        public void onWindowFocusChanged(boolean hasWindowFocus) {
-            super.onWindowFocusChanged(hasWindowFocus);
-
-            // If the user is chording a menu shortcut, release the chord since
-            // this window lost focus
-            if (mWindow.hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus
-                    && mWindow.mPanelChordingKey != 0) {
-                mWindow.closePanel(FEATURE_OPTIONS_PANEL);
-            }
-
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
-                cb.onWindowFocusChanged(hasWindowFocus);
-            }
-
-            if (mPrimaryActionMode != null) {
-                mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus);
-            }
-            if (mFloatingActionMode != null) {
-                mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
-            }
-        }
-
-        @Override
-        protected void onAttachedToWindow() {
-            super.onAttachedToWindow();
-
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
-                cb.onAttachedToWindow();
-            }
-
-            if (mFeatureId == -1) {
-                /*
-                 * The main window has been attached, try to restore any panels
-                 * that may have been open before. This is called in cases where
-                 * an activity is being killed for configuration change and the
-                 * menu was open. When the activity is recreated, the menu
-                 * should be shown again.
-                 */
-                mWindow.openPanelsAfterRestore();
-            }
-        }
-
-        @Override
-        protected void onDetachedFromWindow() {
-            super.onDetachedFromWindow();
-
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && mFeatureId < 0) {
-                cb.onDetachedFromWindow();
-            }
-
-            if (mWindow.mDecorContentParent != null) {
-                mWindow.mDecorContentParent.dismissPopups();
-            }
-
-            if (mPrimaryActionModePopup != null) {
-                removeCallbacks(mShowPrimaryActionModePopup);
-                if (mPrimaryActionModePopup.isShowing()) {
-                    mPrimaryActionModePopup.dismiss();
-                }
-                mPrimaryActionModePopup = null;
-            }
-            if (mFloatingToolbar != null) {
-                mFloatingToolbar.dismiss();
-                mFloatingToolbar = null;
-            }
-
-            PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false);
-            if (st != null && st.menu != null && mFeatureId < 0) {
-                st.menu.close();
-            }
-        }
-
-        @Override
-        public void onCloseSystemDialogs(String reason) {
-            if (mFeatureId >= 0) {
-                mWindow.closeAllPanels();
-            }
-        }
-
-        public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
-            return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
-        }
-
-        public InputQueue.Callback willYouTakeTheInputQueue() {
-            return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null;
-        }
-
-        public void setSurfaceType(int type) {
-            mWindow.setType(type);
-        }
-
-        public void setSurfaceFormat(int format) {
-            mWindow.setFormat(format);
-        }
-
-        public void setSurfaceKeepScreenOn(boolean keepOn) {
-            if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-            else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-        }
-
-        @Override
-        public void onRootViewScrollYChanged(int rootScrollY) {
-            mRootScrollY = rootScrollY;
-            updateColorViewTranslations();
-        }
-
-        private ActionMode createActionMode(
-                int type, ActionMode.Callback2 callback, View originatingView) {
-            switch (type) {
-                case ActionMode.TYPE_PRIMARY:
-                default:
-                    return createStandaloneActionMode(callback);
-                case ActionMode.TYPE_FLOATING:
-                    return createFloatingActionMode(originatingView, callback);
-            }
-        }
-
-        private void setHandledActionMode(ActionMode mode) {
-            if (mode.getType() == ActionMode.TYPE_PRIMARY) {
-                setHandledPrimaryActionMode(mode);
-            } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
-                setHandledFloatingActionMode(mode);
-            }
-        }
-
-        private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
-            endOnGoingFadeAnimation();
-            cleanupPrimaryActionMode();
-            if (mPrimaryActionModeView == null) {
-                if (mWindow.isFloating()) {
-                    // Use the action bar theme.
-                    final TypedValue outValue = new TypedValue();
-                    final Theme baseTheme = mContext.getTheme();
-                    baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
-
-                    final Context actionBarContext;
-                    if (outValue.resourceId != 0) {
-                        final Theme actionBarTheme = mContext.getResources().newTheme();
-                        actionBarTheme.setTo(baseTheme);
-                        actionBarTheme.applyStyle(outValue.resourceId, true);
-
-                        actionBarContext = new ContextThemeWrapper(mContext, 0);
-                        actionBarContext.getTheme().setTo(actionBarTheme);
-                    } else {
-                        actionBarContext = mContext;
-                    }
-
-                    mPrimaryActionModeView = new ActionBarContextView(actionBarContext);
-                    mPrimaryActionModePopup = new PopupWindow(actionBarContext, null,
-                            R.attr.actionModePopupWindowStyle);
-                    mPrimaryActionModePopup.setWindowLayoutType(
-                            WindowManager.LayoutParams.TYPE_APPLICATION);
-                    mPrimaryActionModePopup.setContentView(mPrimaryActionModeView);
-                    mPrimaryActionModePopup.setWidth(MATCH_PARENT);
-
-                    actionBarContext.getTheme().resolveAttribute(
-                            R.attr.actionBarSize, outValue, true);
-                    final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
-                            actionBarContext.getResources().getDisplayMetrics());
-                    mPrimaryActionModeView.setContentHeight(height);
-                    mPrimaryActionModePopup.setHeight(WRAP_CONTENT);
-                    mShowPrimaryActionModePopup = new Runnable() {
-                        public void run() {
-                            mPrimaryActionModePopup.showAtLocation(
-                                    mPrimaryActionModeView.getApplicationWindowToken(),
-                                    Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
-                            endOnGoingFadeAnimation();
-                            mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
-                                    0f, 1f);
-                            mFadeAnim.addListener(new Animator.AnimatorListener() {
-                                @Override
-                                public void onAnimationStart(Animator animation) {
-                                    mPrimaryActionModeView.setVisibility(VISIBLE);
-                                }
-
-                                @Override
-                                public void onAnimationEnd(Animator animation) {
-                                    mPrimaryActionModeView.setAlpha(1f);
-                                    mFadeAnim = null;
-                                }
-
-                                @Override
-                                public void onAnimationCancel(Animator animation) {
-
-                                }
-
-                                @Override
-                                public void onAnimationRepeat(Animator animation) {
-
-                                }
-                            });
-                            mFadeAnim.start();
-                        }
-                    };
-                } else {
-                    ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub);
-                    if (stub != null) {
-                        mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
-                    }
-                }
-            }
-            if (mPrimaryActionModeView != null) {
-                mPrimaryActionModeView.killMode();
-                ActionMode mode = new StandaloneActionMode(
-                        mPrimaryActionModeView.getContext(), mPrimaryActionModeView,
-                        callback, mPrimaryActionModePopup == null);
-                return mode;
-            }
-            return null;
-        }
-
-        private void endOnGoingFadeAnimation() {
-            if (mFadeAnim != null) {
-                mFadeAnim.end();
-            }
-        }
-
-        private void setHandledPrimaryActionMode(ActionMode mode) {
-            endOnGoingFadeAnimation();
-            mPrimaryActionMode = mode;
-            mPrimaryActionMode.invalidate();
-            mPrimaryActionModeView.initForMode(mPrimaryActionMode);
-            if (mPrimaryActionModePopup != null) {
-                post(mShowPrimaryActionModePopup);
-            } else {
-                mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f);
-                mFadeAnim.addListener(new Animator.AnimatorListener() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        mPrimaryActionModeView.setVisibility(View.VISIBLE);
-                    }
-
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mPrimaryActionModeView.setAlpha(1f);
-                        mFadeAnim = null;
-                    }
-
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-
-                    }
-
-                    @Override
-                    public void onAnimationRepeat(Animator animation) {
-
-                    }
-                });
-                mFadeAnim.start();
-            }
-            mPrimaryActionModeView.sendAccessibilityEvent(
-                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        }
-
-        private ActionMode createFloatingActionMode(
-                View originatingView, ActionMode.Callback2 callback) {
-            if (mFloatingActionMode != null) {
-                mFloatingActionMode.finish();
-            }
-            cleanupFloatingActionModeViews();
-            final FloatingActionMode mode =
-                    new FloatingActionMode(mContext, callback, originatingView);
-            mFloatingActionModeOriginatingView = originatingView;
-            mFloatingToolbarPreDrawListener =
-                new OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        mode.updateViewLocationInWindow();
-                        return true;
-                    }
-                };
-            return mode;
-        }
-
-        private void setHandledFloatingActionMode(ActionMode mode) {
-            mFloatingActionMode = mode;
-            mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
-            ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
-            mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
-            mFloatingActionModeOriginatingView.getViewTreeObserver()
-                .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
-        }
-
-        /**
-         * Informs the decor if a non client decor is attached and visible.
-         * @param attachedAndVisible true when the decor is visible.
-         * Note that this will even be called if there is no non client decor.
-         **/
-        void enableNonClientDecor(boolean attachedAndVisible) {
-            if (mHasNonClientDecor != attachedAndVisible) {
-                mHasNonClientDecor = attachedAndVisible;
-                if (getForeground() != null) {
-                    drawableChanged();
-                }
-            }
-        }
-
-        /**
-         * Returns true if the window has a non client decor.
-         * @return If there is a non client decor - even if it is not visible.
-         **/
-        private boolean windowHasNonClientDecor() {
-            return mHasNonClientDecor;
-        }
-
-        /**
-         * Returns true if the Window is free floating and has a shadow (although at some times
-         * it might not be displaying it, e.g. during a resize). Note that non overlapping windows
-         * do not have a shadow since it could not be seen anyways (a small screen / tablet
-         * "tiles" the windows side by side but does not overlap them).
-         * @return Returns true when the window has a shadow created by the non client decor.
-         **/
-        private boolean windowHasShadow() {
-            return windowHasNonClientDecor() && StackId.hasWindowShadow(mWindow.mWorkspaceId);
-        }
-
-        void setWindow(PhoneWindow phoneWindow) {
-            mWindow = phoneWindow;
-            Context context = getContext();
-            if (context instanceof DecorContext) {
-                DecorContext decorContex = (DecorContext) context;
-                decorContex.setPhoneWindow(mWindow);
-            }
-        }
-
-        /**
-         * Clears out internal references when the action mode is destroyed.
-         */
-        private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
-            private final ActionMode.Callback mWrapped;
-
-            public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
-                mWrapped = wrapped;
-            }
-
-            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-                return mWrapped.onCreateActionMode(mode, menu);
-            }
-
-            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-                requestFitSystemWindows();
-                return mWrapped.onPrepareActionMode(mode, menu);
-            }
-
-            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-                return mWrapped.onActionItemClicked(mode, item);
-            }
-
-            public void onDestroyActionMode(ActionMode mode) {
-                mWrapped.onDestroyActionMode(mode);
-                final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion
-                        >= Build.VERSION_CODES.M;
-                final boolean isPrimary;
-                final boolean isFloating;
-                if (isMncApp) {
-                    isPrimary = mode == mPrimaryActionMode;
-                    isFloating = mode == mFloatingActionMode;
-                    if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) {
-                        Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
-                                + mode + " was not the current primary action mode! Expected "
-                                + mPrimaryActionMode);
-                    }
-                    if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) {
-                        Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
-                                + mode + " was not the current floating action mode! Expected "
-                                + mFloatingActionMode);
-                    }
-                } else {
-                    isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY;
-                    isFloating = mode.getType() == ActionMode.TYPE_FLOATING;
-                }
-                if (isPrimary) {
-                    if (mPrimaryActionModePopup != null) {
-                        removeCallbacks(mShowPrimaryActionModePopup);
-                    }
-                    if (mPrimaryActionModeView != null) {
-                        endOnGoingFadeAnimation();
-                        mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
-                                1f, 0f);
-                        mFadeAnim.addListener(new Animator.AnimatorListener() {
-                                    @Override
-                                    public void onAnimationStart(Animator animation) {
-
-                                    }
-
-                                    @Override
-                                    public void onAnimationEnd(Animator animation) {
-                                        mPrimaryActionModeView.setVisibility(GONE);
-                                        if (mPrimaryActionModePopup != null) {
-                                            mPrimaryActionModePopup.dismiss();
-                                        }
-                                        mPrimaryActionModeView.removeAllViews();
-                                        mFadeAnim = null;
-                                    }
-
-                                    @Override
-                                    public void onAnimationCancel(Animator animation) {
-
-                                    }
-
-                                    @Override
-                                    public void onAnimationRepeat(Animator animation) {
-
-                                    }
-                                });
-                        mFadeAnim.start();
-                    }
-
-                    mPrimaryActionMode = null;
-                } else if (isFloating) {
-                    cleanupFloatingActionModeViews();
-                    mFloatingActionMode = null;
-                }
-                if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
-                    try {
-                        mWindow.getCallback().onActionModeFinished(mode);
-                    } catch (AbstractMethodError ame) {
-                        // Older apps might not implement this callback method.
-                    }
-                }
-                requestFitSystemWindows();
-            }
-
-            @Override
-            public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
-                if (mWrapped instanceof ActionMode.Callback2) {
-                    ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
-                } else {
-                    super.onGetContentRect(mode, view, outRect);
-                }
-            }
-        }
-    }
-
     protected DecorView generateDecor(int featureId) {
         // System process doesn't have application context and in that case we need to directly use
         // the context we have. Otherwise we want the application context, so we don't cling to the
@@ -4065,7 +2485,7 @@
                             + Integer.toHexString(mFrameResource));
                 }
             }
-            if (mLoadEleveation) {
+            if (mLoadElevation) {
                 mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
             }
             mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
@@ -4135,19 +2555,7 @@
         }
 
         mDecor.startChanging();
-
-        mNonClientDecorView = createNonClientDecorView();
-        View in = mLayoutInflater.inflate(layoutResource, null);
-        if (mNonClientDecorView != null) {
-            if (mNonClientDecorView.getParent() == null) {
-                decor.addView(mNonClientDecorView,
-                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-            }
-            mNonClientDecorView.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-        } else {
-            decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-        }
-        mContentRoot = (ViewGroup) in;
+        final View in = mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
 
         ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
         if (contentParent == null) {
@@ -4202,50 +2610,6 @@
         return contentParent;
     }
 
-    // Free floating overlapping windows require a non client decor with a caption and shadow..
-    private NonClientDecorView createNonClientDecorView() {
-        NonClientDecorView nonClientDecorView = null;
-        for (int i = mDecor.getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) {
-            View view = mDecor.getChildAt(i);
-            if (view instanceof NonClientDecorView) {
-                // The decor was most likely saved from a relaunch - so reuse it.
-                nonClientDecorView = (NonClientDecorView) view;
-                mDecor.removeViewAt(i);
-            }
-        }
-        final WindowManager.LayoutParams attrs = getAttributes();
-        boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
-                attrs.type == TYPE_APPLICATION;
-        mWorkspaceId = getWorkspaceId();
-        // Only a non floating application window on one of the allowed workspaces can get a non
-        // client decor.
-        if (!isFloating() && isApplication && StackId.isStaticStack(mWorkspaceId)) {
-            // Dependent on the brightness of the used title we either use the
-            // dark or the light button frame.
-            if (nonClientDecorView == null) {
-                Context context = mDecor.getContext();
-                TypedValue value = new TypedValue();
-                context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
-                LayoutInflater inflater = mLayoutInflater.from(context);
-                if (Color.luminance(value.data) < 0.5) {
-                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
-                            R.layout.non_client_decor_dark, null);
-                } else {
-                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
-                            R.layout.non_client_decor_light, null);
-                }
-            }
-            nonClientDecorView.setPhoneWindow(this, StackId.hasWindowDecor(mWorkspaceId),
-                    StackId.hasWindowShadow(mWorkspaceId), getResizingBackgroundDrawable(),
-                    mDecor.getContext().getDrawable(R.drawable.non_client_decor_title_focused));
-        }
-        // Tell the decor if it has a visible non client decor.
-        mDecor.enableNonClientDecor(
-                nonClientDecorView != null&& StackId.hasWindowDecor(mWorkspaceId));
-
-        return nonClientDecorView;
-    }
-
     /** @hide */
     public void alwaysReadCloseOnTouchAttr() {
         mAlwaysReadCloseOnTouchAttr = true;
@@ -4450,7 +2814,7 @@
      *            isn't in our features, this throws an exception).
      * @return The panel state.
      */
-    private PanelFeatureState getPanelState(int featureId, boolean required) {
+    PanelFeatureState getPanelState(int featureId, boolean required) {
         return getPanelState(featureId, required, null);
     }
 
@@ -4914,7 +3278,7 @@
         int curAlpha = 255;
     }
 
-    private static final class PanelFeatureState {
+    static final class PanelFeatureState {
 
         /** Feature ID for this panel. */
         int featureId;
@@ -5316,30 +3680,12 @@
         }
     }
 
-    private static class ColorViewState {
-        View view = null;
-        int targetVisibility = View.INVISIBLE;
-        boolean present = false;
+    int getLocalFeaturesPrivate() {
+        return super.getLocalFeatures();
+    }
 
-        final int id;
-        final int systemUiHideFlag;
-        final int translucentFlag;
-        final int verticalGravity;
-        final int horizontalGravity;
-        final String transitionName;
-        final int hideWindowFlag;
-
-        ColorViewState(int systemUiHideFlag,
-                int translucentFlag, int verticalGravity, int horizontalGravity,
-                String transitionName, int id, int hideWindowFlag) {
-            this.id = id;
-            this.systemUiHideFlag = systemUiHideFlag;
-            this.translucentFlag = translucentFlag;
-            this.verticalGravity = verticalGravity;
-            this.horizontalGravity = horizontalGravity;
-            this.transitionName = transitionName;
-            this.hideWindowFlag = hideWindowFlag;
-        }
+    protected void setDefaultWindowFormat(int format) {
+        super.setDefaultWindowFormat(format);
     }
 
     void sendCloseSystemWindows() {
@@ -5391,28 +3737,6 @@
         mIsStartingWindow = isStartingWindow;
     }
 
-    /**
-     * Returns the Id of the workspace which contains this window.
-     * Note that if no workspace can be determined - which usually means that it was not
-     * created for an activity - the fullscreen workspace ID will be returned.
-     * @return Returns the workspace stack id which contains this window.
-     **/
-    private int getWorkspaceId() {
-        int workspaceId = INVALID_STACK_ID;
-        WindowControllerCallback callback = getWindowControllerCallback();
-        if (callback != null) {
-            try {
-                workspaceId = callback.getWindowStackId();
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
-            }
-        }
-        if (workspaceId == INVALID_STACK_ID) {
-            return FULLSCREEN_WORKSPACE_STACK_ID;
-        }
-        return workspaceId;
-    }
-
     @Override
     public void setTheme(int resid) {
         mTheme = resid;
@@ -5423,31 +3747,4 @@
             }
         }
     }
-
-    /**
-     * Returns the color used to fill areas the app has not rendered content to yet when the user
-     * is resizing the window of an activity in multi-window mode.
-     * */
-    private Drawable getResizingBackgroundDrawable() {
-        final Context context = mDecor.getContext();
-
-        if (mBackgroundResource != 0) {
-            final Drawable drawable = context.getDrawable(mBackgroundResource);
-            if (drawable != null) {
-                return drawable;
-            }
-        }
-
-        if (mBackgroundFallbackResource != 0) {
-            final Drawable fallbackDrawable = context.getDrawable(mBackgroundFallbackResource);
-            if (fallbackDrawable != null) {
-                return fallbackDrawable;
-            }
-        }
-
-        // We shouldn't really get here as the background fallback should be always available since
-        // it is defaulted by the system.
-        Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + this);
-        return null;
-    }
 }
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index de6e228..48420da 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -21,13 +21,9 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.util.AttributeSet;
-import android.view.Choreographer;
-import android.view.DisplayListCanvas;
 import android.view.MotionEvent;
-import android.view.RenderNode;
 import android.view.ThreadedRenderer;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -39,6 +35,7 @@
 import android.util.TypedValue;
 
 import com.android.internal.R;
+import com.android.internal.policy.BackdropFrameRenderer;
 import com.android.internal.policy.PhoneWindow;
 
 /**
@@ -75,7 +72,7 @@
     private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
     private PhoneWindow mOwner = null;
     private boolean mWindowHasShadow = false;
-    private boolean mShowDecor = false;
+    public boolean mShowDecor = false;
     // True when this object is listening for window size changes.
     private boolean mAttachedCallbacksToRootViewImpl = false;
 
@@ -96,11 +93,10 @@
     // to max until the first layout command has been executed.
     private boolean mAllowUpdateElevation = false;
 
-    // The resize frame renderer.
-    private ResizeFrameThread mFrameRendererThread = null;
+    private BackdropFrameRenderer mBackdropFrameRenderer = null;
 
-    private Drawable mResizingBackgroundDrawable;
-    private Drawable mCaptionBackgroundDrawable;
+    public Drawable mResizingBackgroundDrawable;
+    public Drawable mCaptionBackgroundDrawable;
 
     public NonClientDecorView(Context context) {
         super(context);
@@ -122,10 +118,10 @@
             // Note that our ViewRootImpl object will not change.
             getViewRootImpl().addWindowCallbacks(this);
             mAttachedCallbacksToRootViewImpl = true;
-        } else if (mFrameRendererThread != null) {
+        } else if (mBackdropFrameRenderer != null) {
             // We are resizing and this call happened due to a configuration change. Tell the
             // renderer about it.
-            mFrameRendererThread.onConfigurationChange();
+            mBackdropFrameRenderer.onConfigurationChange();
         }
     }
 
@@ -208,7 +204,7 @@
      * @param showDecor True if the decor should be shown.
      * @param windowHasShadow True when the window should show a shadow.
      **/
-    public void phoneWindowUpdated(boolean showDecor, boolean windowHasShadow) {
+    public void onConfigurationChanged(boolean showDecor, boolean windowHasShadow) {
         mShowDecor = showDecor;
         updateCaptionVisibility();
         if (windowHasShadow != mWindowHasShadow) {
@@ -298,7 +294,7 @@
         float elevation = 0;
         // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow
         // is bound to the content size and not the target size.
-        if (mWindowHasShadow && mFrameRendererThread == null) {
+        if (mWindowHasShadow && mBackdropFrameRenderer == null) {
             boolean fill = isFillingScreen();
             elevation = fill ? 0 :
                     (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
@@ -347,13 +343,13 @@
             releaseResources();
             return;
         }
-        if (mFrameRendererThread != null) {
+        if (mBackdropFrameRenderer != null) {
             return;
         }
         final ThreadedRenderer renderer =
                 (ThreadedRenderer) mOwner.getDecorView().getHardwareRenderer();
         if (renderer != null) {
-            mFrameRendererThread = new ResizeFrameThread(renderer, initialBounds);
+            mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer, initialBounds);
             // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time.
             // If we want to get the shadow shown while resizing, we would need to elevate a new
             // element which owns the caption and has the elevation.
@@ -363,16 +359,16 @@
 
     @Override
     public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
-        if (mFrameRendererThread == null) {
+        if (mBackdropFrameRenderer == null) {
             return false;
         }
-        return mFrameRendererThread.onContentDrawn(xOffset, yOffset, xSize, ySize);
+        return mBackdropFrameRenderer.onContentDrawn(xOffset, yOffset, xSize, ySize);
     }
 
     @Override
     public void onRequestDraw(boolean reportNextDraw) {
-        if (mFrameRendererThread != null) {
-            mFrameRendererThread.onRequestDraw(reportNextDraw);
+        if (mBackdropFrameRenderer != null) {
+            mBackdropFrameRenderer.onRequestDraw(reportNextDraw);
         } else if (reportNextDraw) {
             // If render thread is gone, just report immediately.
             if (isAttachedToWindow()) {
@@ -388,8 +384,8 @@
 
     @Override
     public void onWindowSizeIsChanging(Rect newBounds) {
-        if (mFrameRendererThread != null) {
-            mFrameRendererThread.setTargetRect(newBounds);
+        if (mBackdropFrameRenderer != null) {
+            mBackdropFrameRenderer.setTargetRect(newBounds);
         }
     }
 
@@ -397,9 +393,9 @@
      * Release the renderer thread which is usually done when the user stops resizing.
      */
     private void releaseThreadedRenderer() {
-        if (mFrameRendererThread != null) {
-            mFrameRendererThread.releaseRenderer();
-            mFrameRendererThread = null;
+        if (mBackdropFrameRenderer != null) {
+            mBackdropFrameRenderer.releaseRenderer();
+            mBackdropFrameRenderer = null;
             // Bring the shadow back.
             updateElevation();
         }
@@ -413,256 +409,4 @@
         releaseThreadedRenderer();
     }
 
-    /**
-     * The thread which draws the chrome while we are resizing.
-     * It starts with the creation and it ends once someone calls destroy().
-     * Any size changes can be passed by a call to setTargetRect will passed to the thread and
-     * executed via the Choreographer.
-     * TODO(b/24810450): Separate functionality from non-client-decor so that it can be used
-     * independently.
-     */
-    private class ResizeFrameThread extends Thread implements Choreographer.FrameCallback {
-        // This is containing the last requested size by a resize command. Note that this size might
-        // or might not have been applied to the output already.
-        private final Rect mTargetRect = new Rect();
-
-        // The render nodes for the multi threaded renderer.
-        private ThreadedRenderer mRenderer;
-        private RenderNode mFrameAndBackdropNode;
-
-        private final Rect mOldTargetRect = new Rect();
-        private final Rect mNewTargetRect = new Rect();
-        private Choreographer mChoreographer;
-
-        // Cached size values from the last render for the case that the view hierarchy is gone
-        // during a configuration change.
-        private int mLastContentWidth;
-        private int mLastContentHeight;
-        private int mLastCaptionHeight;
-        private int mLastXOffset;
-        private int mLastYOffset;
-
-        // Whether to report when next frame is drawn or not.
-        private boolean mReportNextDraw;
-
-        ResizeFrameThread(ThreadedRenderer renderer, Rect initialBounds) {
-            setName("ResizeFrame");
-            mRenderer = renderer;
-
-            // Create a render node for the content and frame backdrop
-            // which can be resized independently from the content.
-            mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
-
-            mRenderer.addRenderNode(mFrameAndBackdropNode, true);
-
-            // Set the initial bounds and draw once so that we do not get a broken frame.
-            mTargetRect.set(initialBounds);
-            synchronized (this) {
-                changeWindowSizeLocked(initialBounds);
-            }
-
-            // Kick off our draw thread.
-            start();
-        }
-
-        /**
-         * Call this function asynchronously when the window size has been changed. The change will
-         * be picked up once per frame and the frame will be re-rendered accordingly.
-         * @param newTargetBounds The new target bounds.
-         */
-        public void setTargetRect(Rect newTargetBounds) {
-            synchronized (this) {
-                mTargetRect.set(newTargetBounds);
-                // Notify of a bounds change.
-                pingRenderLocked();
-            }
-        }
-
-        /**
-         * The window got replaced due to a configuration change.
-         */
-        public void onConfigurationChange() {
-            synchronized (this) {
-                if (mRenderer != null) {
-                    // Enforce a window redraw.
-                    mOldTargetRect.set(0, 0, 0, 0);
-                    pingRenderLocked();
-                }
-            }
-        }
-
-        /**
-         * All resources of the renderer will be released. This function can be called from the
-         * the UI thread as well as the renderer thread.
-         */
-        public void releaseRenderer() {
-            synchronized (this) {
-                if (mRenderer != null) {
-                    // Invalidate the current content bounds.
-                    mRenderer.setContentDrawBounds(0, 0, 0, 0);
-
-                    // Remove the render node again
-                    // (see comment above - better to do that only once).
-                    mRenderer.removeRenderNode(mFrameAndBackdropNode);
-
-                    mRenderer = null;
-
-                    // Exit the renderer loop.
-                    pingRenderLocked();
-                }
-            }
-        }
-
-        @Override
-        public void run() {
-            try {
-                Looper.prepare();
-                synchronized (this) {
-                    mChoreographer = Choreographer.getInstance();
-
-                    // Draw at least once.
-                    mChoreographer.postFrameCallback(this);
-                }
-                Looper.loop();
-            } finally {
-                releaseRenderer();
-            }
-            synchronized (this) {
-                // Make sure no more messages are being sent.
-                mChoreographer = null;
-            }
-        }
-
-        /**
-         * The implementation of the FrameCallback.
-         * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
-         * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
-         */
-        @Override
-        public void doFrame(long frameTimeNanos) {
-            synchronized (this) {
-                if (mRenderer == null) {
-                    reportDrawIfNeeded();
-                    // Tell the looper to stop. We are done.
-                    Looper.myLooper().quit();
-                    return;
-                }
-                mNewTargetRect.set(mTargetRect);
-                if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
-                    mOldTargetRect.set(mNewTargetRect);
-                    changeWindowSizeLocked(mNewTargetRect);
-                }
-            }
-        }
-
-        /**
-         * The content is about to be drawn and we got the location of where it will be shown.
-         * If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
-         * if the previous call was ignored since the size was unknown.
-         * @param xOffset The x offset where the content is drawn to.
-         * @param yOffset The y offset where the content is drawn to.
-         * @param xSize The width size of the content. This should not be 0.
-         * @param ySize The height of the content.
-         * @return true if a frame should be requested after the content is drawn; false otherwise.
-         */
-        public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
-            synchronized (this) {
-                final boolean firstCall = mLastContentWidth == 0;
-                // The current content buffer is drawn here.
-                mLastContentWidth = xSize;
-                mLastContentHeight = ySize - mLastCaptionHeight;
-                mLastXOffset = xOffset;
-                mLastYOffset = yOffset;
-
-                mRenderer.setContentDrawBounds(
-                        mLastXOffset,
-                        mLastYOffset,
-                        mLastXOffset + mLastContentWidth,
-                        mLastYOffset + mLastCaptionHeight + mLastContentHeight);
-                // If this was the first call and changeWindowSizeLocked got already called prior
-                // to us, we should re-issue a changeWindowSizeLocked now.
-                return firstCall && (mLastCaptionHeight != 0 || !mShowDecor);
-            }
-        }
-
-        public void onRequestDraw(boolean reportNextDraw) {
-            synchronized (this) {
-                mReportNextDraw = reportNextDraw;
-                mOldTargetRect.set(0, 0, 0, 0);
-                pingRenderLocked();
-            }
-        }
-
-        /**
-         * Resizing the frame to fit the new window size.
-         * @param newBounds The window bounds which needs to be drawn.
-         */
-        private void changeWindowSizeLocked(Rect newBounds) {
-            // While a configuration change is taking place the view hierarchy might become
-            // inaccessible. For that case we remember the previous metrics to avoid flashes.
-            // Note that even when there is no visible caption, the caption child will exist.
-            View caption = getChildAt(0);
-            if (caption != null) {
-                final int captionHeight = caption.getHeight();
-                // The caption height will probably never dynamically change while we are resizing.
-                // Once set to something other then 0 it should be kept that way.
-                if (captionHeight != 0) {
-                    // Remember the height of the caption.
-                    mLastCaptionHeight = captionHeight;
-                }
-            }
-            // Make sure that the other thread has already prepared the render draw calls for the
-            // content. If any size is 0, we have to wait for it to be drawn first.
-            if ((mLastCaptionHeight == 0 && mShowDecor) ||
-                    mLastContentWidth == 0 || mLastContentHeight == 0) {
-                return;
-            }
-            // Since the surface is spanning the entire screen, we have to add the start offset of
-            // the bounds to get to the surface location.
-            final int left = mLastXOffset + newBounds.left;
-            final int top = mLastYOffset + newBounds.top;
-            final int width = newBounds.width();
-            final int height = newBounds.height();
-
-            mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
-
-            // Draw the caption and content backdrops in to our render node.
-            DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
-            mCaptionBackgroundDrawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
-            mCaptionBackgroundDrawable.draw(canvas);
-
-            // The backdrop: clear everything with the background. Clipping is done elsewhere.
-            mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
-            mResizingBackgroundDrawable.draw(canvas);
-            mFrameAndBackdropNode.end(canvas);
-
-            // We need to render the node explicitly
-            mRenderer.drawRenderNode(mFrameAndBackdropNode);
-
-            reportDrawIfNeeded();
-        }
-
-        /**
-         * Notify view root that a frame has been drawn by us, if it has requested so.
-         */
-        private void reportDrawIfNeeded() {
-            if (mReportNextDraw) {
-                if (isAttachedToWindow()) {
-                    getViewRootImpl().reportDrawFinish();
-                }
-                mReportNextDraw = false;
-            }
-        }
-
-        /**
-         * Sends a message to the renderer to wake up and perform the next action which can be
-         * either the next rendering or the self destruction if mRenderer is null.
-         * Note: This call must be synchronized.
-         */
-        private void pingRenderLocked() {
-            if (mChoreographer != null) {
-                mChoreographer.postFrameCallback(this);
-            }
-        }
-    }
 }
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 703a9bd..a805b6d 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -1006,6 +1006,7 @@
         // is disposed.
         int dupFd = dup(blob.fd());
         if (dupFd < 0) {
+            ALOGE("Error allocating dup fd. Error:%d", errno);
             blob.release();
             SkSafeUnref(ctable);
             doThrowRE(env, "Could not allocate dup blob fd.");
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 50cf302..90fc22b 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3242,6 +3242,14 @@
              </p>
          -->
         <attr name="canRequestFilterKeyEvents" format="boolean" />
+        <!-- Attribute whether the accessibility service wants to be able to control
+             display magnification.
+             <p>
+             Required to allow setting the {@link android.accessibilityservice
+             #AccessibilityServiceInfo#FLAG_CAN_CONTROL_MAGNIFICATION} flag.
+             </p>
+         -->
+        <attr name="canControlMagnification" format="boolean" />
         <!-- Short description of the accessibility serivce purpose or behavior.-->
         <attr name="description" />
     </declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 54e43c8..b6b2e20 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2682,6 +2682,7 @@
     <public type="attr" name="forceDeviceEncrypted" />
     <public type="attr" name="encryptionAware" />
     <public type="attr" name="preferenceFragmentStyle" />
+    <public type="attr" name="canControlMagnification" />
 
     <public type="style" name="Theme.Material.DayNight" />
     <public type="style" name="Theme.Material.DayNight.DarkActionBar" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index faa76f2..00c0fe8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -613,6 +613,12 @@
     <string name="capability_desc_canRequestFilterKeyEvents">Includes personal data such as credit
         card numbers and passwords.</string>
 
+    <!-- Title for the capability of an accessibility service to control display magnification. -->
+    <string name="capability_title_canControlMagnification">Control display magnification</string>
+    <!-- Description for the capability of an accessibility service to control display magnification. -->
+    <string name="capability_desc_canControlMagnification">Control the display\'s zoom level and
+        positioning.</string>
+
     <!--  Permissions -->
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6820c25..e8f6b46 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -564,6 +564,8 @@
   <java-symbol type="string" name="capability_desc_canRequestFilterKeyEvents" />
   <java-symbol type="string" name="capability_title_canRequestTouchExploration" />
   <java-symbol type="string" name="capability_title_canRetrieveWindowContent" />
+  <java-symbol type="string" name="capability_desc_canControlMagnification" />
+  <java-symbol type="string" name="capability_title_canControlMagnification" />
   <java-symbol type="string" name="cfTemplateForwarded" />
   <java-symbol type="string" name="cfTemplateForwardedTime" />
   <java-symbol type="string" name="cfTemplateNotForwarded" />
diff --git a/docs/html/guide/topics/manifest/uses-feature-element.jd b/docs/html/guide/topics/manifest/uses-feature-element.jd
index e22dc4a..21e3057 100644
--- a/docs/html/guide/topics/manifest/uses-feature-element.jd
+++ b/docs/html/guide/topics/manifest/uses-feature-element.jd
@@ -563,8 +563,8 @@
        <td rowspan="6">Camera</td>
        <td><code>android.hardware.camera</code></td>
        <td>The application uses the device's back-facing (main) camera.</td>
-       <td>Importantly, devices with only a front-facing camera will not list this
-           feature, so the <code>android.hardware.camera.any</code> feature should be
+       <td>Devices with only a front-facing camera do not list this feature, so the
+           <code>android.hardware.camera.any</code> feature should be
            used instead if a camera facing any direction is acceptable for the
            application.</td>
     </tr>
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 4acad67..8565372 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -206,7 +206,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
 LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
-LOCAL_CFLAGS := $(hwui_cflags)
+LOCAL_CFLAGS := \
+        $(hwui_cflags) \
+        -DHWUI_NULL_GPU
 
 LOCAL_SRC_FILES += \
     unit_tests/CanvasStateTests.cpp \
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 1aa291f..d2d3285 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -35,11 +35,11 @@
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
     OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
-    startRepaintLayer(buffer);
+    startRepaintLayer(buffer, Rect(width, height));
     return buffer;
 }
 
-void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer) {
+void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) {
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
     mRenderTarget.offscreenBuffer = offscreenBuffer;
@@ -55,12 +55,10 @@
     LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
             "framebuffer incomplete!");
 
-    // Clear the FBO
-    mRenderState.scissor().setEnabled(false);
-    glClear(GL_COLOR_BUFFER_BIT);
-
     // Change the viewport & ortho projection
     setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
+
+    clearColorBuffer(repaintRect);
 }
 
 void BakedOpRenderer::endLayer() {
@@ -74,16 +72,13 @@
     mRenderTarget.frameBufferId = -1;
 }
 
-void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) {
+void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {
     mRenderState.bindFramebuffer(0);
     setViewport(width, height);
     mCaches.clearGarbage();
 
     if (!mOpaque) {
-        // TODO: partial invalidate!
-        mRenderState.scissor().setEnabled(false);
-        glClear(GL_COLOR_BUFFER_BIT);
-        mHasDrawn = true;
+        clearColorBuffer(repaintRect);
     }
 }
 
@@ -113,6 +108,20 @@
     mRenderState.blend().syncEnabled();
 }
 
+void BakedOpRenderer::clearColorBuffer(const Rect& rect) {
+    if (Rect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight).contains(rect)) {
+        // Full viewport is being cleared - disable scissor
+        mRenderState.scissor().setEnabled(false);
+    } else {
+        // Requested rect is subset of viewport - scissor to it to avoid over-clearing
+        mRenderState.scissor().setEnabled(true);
+        mRenderState.scissor().set(rect.left, mRenderTarget.viewportHeight - rect.bottom,
+                rect.getWidth(), rect.getHeight());
+    }
+    glClear(GL_COLOR_BUFFER_BIT);
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
+}
+
 Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
     Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
     if (!texture) {
@@ -136,7 +145,7 @@
         mRenderTarget.offscreenBuffer->region.orSelf(dirty);
     }
     mRenderState.render(glop, mRenderTarget.orthoMatrix);
-    mHasDrawn = true;
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -217,7 +226,7 @@
     paint.setAntiAlias(true); // want to use AlphaVertex
 
     // The caller has made sure casterAlpha > 0.
-    uint8_t ambientShadowAlpha = 128u; //TODO: mAmbientShadowAlpha;
+    uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
     if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
         ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
     }
@@ -227,7 +236,7 @@
                 paint, VertexBufferRenderFlags::ShadowInterp);
     }
 
-    uint8_t spotShadowAlpha = 128u; //TODO: mSpotShadowAlpha;
+    uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
     if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
         spotShadowAlpha = Properties::overrideSpotShadowStrength;
     }
@@ -240,12 +249,10 @@
 
 void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
     TessellationCache::vertexBuffer_pair_t buffers;
-    Vector3 lightCenter = { 300, 300, 300 }; // TODO!
-    float lightRadius = 150; // TODO!
-
     renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
             op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
-            &op.shadowMatrixXY, &op.shadowMatrixZ, lightCenter, lightRadius,
+            &op.shadowMatrixXY, &op.shadowMatrixZ,
+            op.lightCenter, renderer.getLightInfo().lightRadius,
             buffers);
 
     renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index d6d9cb1..29f9a6f 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -39,27 +39,39 @@
  */
 class BakedOpRenderer {
 public:
-    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque)
+    /**
+     * Position agnostic shadow lighting info. Used with all shadow ops in scene.
+     */
+    struct LightInfo {
+        float lightRadius = 0;
+        uint8_t ambientShadowAlpha = 0;
+        uint8_t spotShadowAlpha = 0;
+    };
+
+    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, const LightInfo& lightInfo)
             : mRenderState(renderState)
             , mCaches(caches)
-            , mOpaque(opaque) {
+            , mOpaque(opaque)
+            , mLightInfo(lightInfo) {
     }
 
     RenderState& renderState() { return mRenderState; }
     Caches& caches() { return mCaches; }
 
-    void startFrame(uint32_t width, uint32_t height);
+    void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
     void endFrame();
     OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
-    void startRepaintLayer(OffscreenBuffer* offscreenBuffer);
+    void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
     void endLayer();
 
     Texture* getTexture(const SkBitmap* bitmap);
+    const LightInfo& getLightInfo() { return mLightInfo; }
 
     void renderGlop(const BakedOpState& state, const Glop& glop);
     bool didDraw() { return mHasDrawn; }
 private:
     void setViewport(uint32_t width, uint32_t height);
+    void clearColorBuffer(const Rect& clearRect);
 
     RenderState& mRenderState;
     Caches& mCaches;
@@ -75,6 +87,8 @@
         uint32_t viewportHeight = 0;
         Matrix4 orthoMatrix;
     } mRenderTarget;
+
+    const LightInfo mLightInfo;
 };
 
 /**
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index a327614..94a11f1 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -79,7 +79,6 @@
 }
 
 void Caches::initExtensions() {
-    mExtensions.load();
     if (mExtensions.hasDebugMarker()) {
         eventMark = glInsertEventMarkerEXT;
 
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 03b1706..39b7ecb 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -40,7 +40,6 @@
 }
 
 void DeviceInfo::load() {
-    mExtensions.load();
     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
 }
 
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index e257715..02caaa4 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -35,7 +35,7 @@
 #endif
 
 
-void Extensions::load() {
+Extensions::Extensions() {
     auto extensions = StringUtils::split((const char*) glGetString(GL_EXTENSIONS));
     mHasNPot = extensions.has("GL_OES_texture_npot");
     mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch");
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 8ccfabd..67cc747 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -31,7 +31,7 @@
 
 class Extensions {
 public:
-    void load();
+    Extensions();
 
     inline bool hasNPot() const { return mHasNPot; }
     inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 80efaed..b04f16f 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -18,6 +18,7 @@
 
 #include "LayerUpdateQueue.h"
 #include "RenderNode.h"
+#include "renderstate/OffscreenBufferPool.h"
 #include "utils/FatVector.h"
 #include "utils/PaintUtils.h"
 
@@ -33,8 +34,8 @@
 
 public:
     BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
-        : mBatchId(batchId)
-        , mMerging(merging) {
+            : mBatchId(batchId)
+            , mMerging(merging) {
         mBounds = op->computedState.clippedBounds;
         mOps.push_back(op);
     }
@@ -207,9 +208,10 @@
 };
 
 OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
-        const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+        const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
         : width(width)
         , height(height)
+        , repaintRect(repaintRect)
         , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
         , beginLayerOp(beginLayerOp)
         , renderNode(renderNode) {}
@@ -309,15 +311,19 @@
 
 OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
         uint32_t viewportWidth, uint32_t viewportHeight,
-        const std::vector< sp<RenderNode> >& nodes)
+        const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter)
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
-    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
-    mLayerStack.push_back(0);
 
+    mLayerReorderers.reserve(layers.entries().size());
+    mLayerStack.reserve(layers.entries().size());
+
+    // Prepare to defer Fbo0
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight, Rect(clip));
+    mLayerStack.push_back(0);
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
             clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
-            Vector3());
+            lightCenter);
 
     // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
     // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
@@ -325,7 +331,8 @@
         RenderNode* layerNode = layers.entries()[i].renderNode;
         const Rect& layerDamage = layers.entries()[i].damage;
 
-        saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode);
+        saveForLayer(layerNode->getWidth(), layerNode->getHeight(),
+                layerDamage, nullptr, layerNode);
         mCanvasState.writableSnapshot()->setClip(
                 layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom);
 
@@ -345,14 +352,17 @@
     }
 }
 
-OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
+OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+        const Vector3& lightCenter)
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
-    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+    // Prepare to defer Fbo0
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight,
+            Rect(viewportWidth, viewportHeight));
     mLayerStack.push_back(0);
-
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
-            0, 0, viewportWidth, viewportHeight, Vector3());
+            0, 0, viewportWidth, viewportHeight, lightCenter);
+
     deferImpl(displayList);
 }
 
@@ -508,7 +518,8 @@
     }
 
     ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
-            mCanvasState.getLocalClipBounds());
+            mCanvasState.getLocalClipBounds(),
+            mCanvasState.currentSnapshot()->getRelativeLightCenter());
     BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
             mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
     if (CC_LIKELY(bakedOpState)) {
@@ -589,17 +600,36 @@
     currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
 }
 
-void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, const Rect& repaintRect,
         const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
 
+    auto previous = mCanvasState.currentSnapshot();
     mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
     mCanvasState.writableSnapshot()->transform->loadIdentity();
     mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
     mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
 
+    Vector3 lightCenter = previous->getRelativeLightCenter();
+    if (renderNode) {
+        Matrix4& inverse = renderNode->getLayer()->inverseTransformInWindow;
+        inverse.mapPoint3d(lightCenter);
+    } else {
+        // Combine all transforms used to present saveLayer content:
+        // parent content transform * canvas transform * bounds offset
+        Matrix4 contentTransform(*previous->transform);
+        contentTransform.multiply(beginLayerOp->localMatrix);
+        contentTransform.translate(beginLayerOp->unmappedBounds.left, beginLayerOp->unmappedBounds.top);
+
+        // inverse the total transform, to map light center into layer-relative space
+        Matrix4 inverse;
+        inverse.loadInverse(contentTransform);
+        inverse.mapPoint3d(lightCenter);
+    }
+    mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
+
     // create a new layer, and push its index on the stack
     mLayerStack.push_back(mLayerReorderers.size());
-    mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode);
+    mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode);
 }
 
 void OpReorderer::restoreForLayer() {
@@ -612,7 +642,7 @@
 void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
     const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
     const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
-    saveForLayer(layerWidth, layerHeight, &op, nullptr);
+    saveForLayer(layerWidth, layerHeight, Rect(layerWidth, layerHeight), &op, nullptr);
 }
 
 void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 2c30f0d..09d5cbc 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -67,13 +67,13 @@
     class LayerReorderer {
     public:
         // Create LayerReorderer for Fbo0
-        LayerReorderer(uint32_t width, uint32_t height)
-                : LayerReorderer(width, height, nullptr, nullptr) {};
+        LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect)
+                : LayerReorderer(width, height, repaintRect, nullptr, nullptr) {};
 
         // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
         // saveLayer, renderNode is present for a HW layer.
         LayerReorderer(uint32_t width, uint32_t height,
-                const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+                const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
 
         // iterate back toward target to see if anything drawn since should overlap the new op
         // if no target, merging ops still iterate to find similar batch to insert after
@@ -101,6 +101,7 @@
 
         const uint32_t width;
         const uint32_t height;
+        const Rect repaintRect;
         OffscreenBuffer* offscreenBuffer;
         const BeginLayerOp* beginLayerOp;
         const RenderNode* renderNode;
@@ -116,14 +117,15 @@
 
         // Maps batch ids to the most recent *non-merging* batch of that id
         OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
-
     };
+
 public:
     OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
             uint32_t viewportWidth, uint32_t viewportHeight,
-            const std::vector< sp<RenderNode> >& nodes);
+            const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter);
 
-    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+            const Vector3& lightCenter);
 
     virtual ~OpReorderer() {}
 
@@ -153,7 +155,7 @@
             LayerReorderer& layer = mLayerReorderers[i];
             if (layer.renderNode) {
                 // cached HW layer - can't skip layer if empty
-                renderer.startRepaintLayer(layer.offscreenBuffer);
+                renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect);
                 layer.replayBakedOpsImpl((void*)&renderer, receivers);
                 renderer.endLayer();
             } else if (!layer.empty()) { // save layer - skip entire layer if empty
@@ -164,7 +166,7 @@
         }
 
         const LayerReorderer& fbo0 = mLayerReorderers[0];
-        renderer.startFrame(fbo0.width, fbo0.height);
+        renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
         fbo0.replayBakedOpsImpl((void*)&renderer, receivers);
         renderer.endFrame();
     }
@@ -188,7 +190,7 @@
         Positive
     };
     void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
-            const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+            const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
     void restoreForLayer();
 
     LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index bb7a0a7c..ef05367 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,10 +17,11 @@
 #ifndef ANDROID_HWUI_RECORDED_OP_H
 #define ANDROID_HWUI_RECORDED_OP_H
 
-#include "utils/LinearAllocator.h"
-#include "Rect.h"
 #include "Matrix.h"
+#include "Rect.h"
 #include "RenderNode.h"
+#include "utils/LinearAllocator.h"
+#include "Vector.h"
 
 #include "SkXfermode.h"
 
@@ -116,15 +117,17 @@
  * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time,
  * and are resolved dynamically, and transform isn't needed.
  *
- * State construction handles these properties specially.
+ * State construction handles these properties specially, ignoring matrix/bounds.
  */
 struct ShadowOp : RecordedOp {
-    ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath, const Rect& clipRect)
+    ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath,
+            const Rect& clipRect, const Vector3& lightCenter)
             : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
             , shadowMatrixXY(casterOp.localMatrix)
             , shadowMatrixZ(casterOp.localMatrix)
             , casterAlpha(casterAlpha)
-            , casterPath(casterPath) {
+            , casterPath(casterPath)
+            , lightCenter(lightCenter) {
         const RenderNode& node = *casterOp.renderNode;
         node.applyViewPropertyTransforms(shadowMatrixXY, false);
         node.applyViewPropertyTransforms(shadowMatrixZ, true);
@@ -133,6 +136,7 @@
     Matrix4 shadowMatrixZ;
     const float casterAlpha;
     const SkPath* casterPath;
+    const Vector3 lightCenter;
 };
 
 struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index e988555..6ab253c 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -156,7 +156,7 @@
 
     snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
     snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
-    snapshot.resetTransform(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+    snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
 
     Rect clip = layerBounds;
     clip.translate(-untransformedBounds.left, -untransformedBounds.top);
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index e177f9a..2713f46 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -326,15 +326,11 @@
         return;
     }
 
-    if (transformUpdateNeeded) {
+    if (transformUpdateNeeded && mLayer) {
         // update the transform in window of the layer to reset its origin wrt light source position
         Matrix4 windowTransform;
         info.damageAccumulator->computeCurrentTransform(&windowTransform);
-#if HWUI_NEW_OPS
-        // TODO: update layer transform (perhaps as part of enqueueLayerWithDamage)
-#else
         mLayer->setWindowTransform(windowTransform);
-#endif
     }
 
 #if HWUI_NEW_OPS
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 0a58f4b..2f535bb 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -130,6 +130,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void Snapshot::resetTransform(float x, float y, float z) {
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("not supported - light center managed differently");
+#else
     // before resetting, map current light pos with inverse of current transform
     Vector3 center = mRelativeLightCenter;
     mat4 inverse;
@@ -139,6 +142,7 @@
 
     transform = &mTransformRoot;
     transform->loadTranslate(x, y, z);
+#endif
 }
 
 void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 7b8d0e5..b24858e 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -48,7 +48,7 @@
 void BM_OpReorderer_defer::Run(int iters) {
     StartBenchmarkTiming();
     for (int i = 0; i < iters; i++) {
-        OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+        OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
         MicroBench::DoNotOptimize(&reorderer);
     }
     StopBenchmarkTiming();
@@ -59,11 +59,13 @@
     TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
         RenderState& renderState = thread.renderState();
         Caches& caches = Caches::getInstance();
+        BakedOpRenderer::LightInfo lightInfo = { 50.0f, 128, 128 };
+
         StartBenchmarkTiming();
         for (int i = 0; i < iters; i++) {
-            OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+            OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
 
-            BakedOpRenderer renderer(caches, renderState, true);
+            BakedOpRenderer renderer(caches, renderState, true, lightInfo);
             reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
             MicroBench::DoNotOptimize(&renderer);
         }
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
index f0fd82d..fac6c35 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.h
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -34,6 +34,11 @@
  * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
  * encompasses enough information to draw it back on screen (minus paint properties, which are held
  * by LayerOp).
+ *
+ * Has two distinct sizes - viewportWidth/viewportHeight describe content area,
+ * texture.width/.height are actual allocated texture size. Texture will tend to be larger than the
+ * viewport bounds, since textures are always allocated with width / height as a multiple of 64, for
+ * the purpose of improving reuse.
  */
 class OffscreenBuffer {
 public:
@@ -44,11 +49,17 @@
     // must be called prior to rendering, to construct/update vertex buffer
     void updateMeshFromRegion();
 
+    // Set by RenderNode for HW layers, TODO for clipped saveLayers
+    void setWindowTransform(const Matrix4& transform) {
+        inverseTransformInWindow.loadInverse(transform);
+    }
+
     static uint32_t computeIdealDimension(uint32_t dimension);
 
     uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
 
     RenderState& renderState;
+
     uint32_t viewportWidth;
     uint32_t viewportHeight;
     Texture texture;
@@ -56,6 +67,10 @@
     // Portion of layer that has been drawn to. Used to minimize drawing area when
     // drawing back to screen / parent FBO.
     Region region;
+
+    Matrix4 inverseTransformInWindow;
+
+    // vbo / size of mesh
     GLsizei elementCount = 0;
     GLuint vbo = 0;
 };
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f094b2d..89cadea 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -31,7 +31,6 @@
 #include "utils/TimeUtils.h"
 
 #if HWUI_NEW_OPS
-#include "BakedOpRenderer.h"
 #include "OpReorderer.h"
 #endif
 
@@ -150,13 +149,23 @@
 // TODO: don't pass viewport size, it's automatic via EGL
 void CanvasContext::setup(int width, int height, float lightRadius,
         uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+#if HWUI_NEW_OPS
+    mLightInfo.lightRadius = lightRadius;
+    mLightInfo.ambientShadowAlpha = ambientShadowAlpha;
+    mLightInfo.spotShadowAlpha = spotShadowAlpha;
+#else
     if (!mCanvas) return;
     mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha);
+#endif
 }
 
 void CanvasContext::setLightCenter(const Vector3& lightCenter) {
+#if HWUI_NEW_OPS
+    mLightCenter = lightCenter;
+#else
     if (!mCanvas) return;
     mCanvas->setLightCenter(lightCenter);
+#endif
 }
 
 void CanvasContext::setOpaque(bool opaque) {
@@ -340,9 +349,11 @@
     mEglManager.damageFrame(frame, dirty);
 
 #if HWUI_NEW_OPS
-    OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes);
+    OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
+            mRenderNodes, mLightCenter);
     mLayerUpdateQueue.clear();
-    BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
+    BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(),
+            mOpaque, mLightInfo);
     // TODO: profiler().draw(mCanvas);
     reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
 
@@ -576,8 +587,12 @@
     // purposes when the frame is actually drawn
     node->setPropertyFieldsDirty(RenderNode::GENERIC);
 
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("unsupported");
+#else
     mCanvas->markLayersAsBuildLayers();
     mCanvas->flushLayerUpdates();
+#endif
 
     node->incStrong(nullptr);
     mPrefetechedLayers.insert(node);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index d656014..c3cfc94 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -27,6 +27,10 @@
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
 
+#if HWUI_NEW_OPS
+#include "BakedOpRenderer.h"
+#endif
+
 #include <cutils/compiler.h>
 #include <EGL/egl.h>
 #include <SkBitmap.h>
@@ -165,6 +169,11 @@
 
     bool mOpaque;
     OpenGLRenderer* mCanvas = nullptr;
+#if HWUI_NEW_OPS
+    BakedOpRenderer::LightInfo mLightInfo;
+    Vector3 mLightCenter = { 0, 0, 0 };
+#endif
+
     bool mHaveNewSurface = false;
     DamageAccumulator mDamageAccumulator;
     LayerUpdateQueue mLayerUpdateQueue;
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index 07080a2..a8c9bba 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -29,6 +29,14 @@
 namespace uirenderer {
 
 LayerUpdateQueue sEmptyLayerUpdateQueue;
+Vector3 sLightCenter = {100, 100, 100};
+
+static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
+    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+    std::vector<sp<RenderNode>> vec;
+    vec.emplace_back(node);
+    return vec;
+}
 
 /**
  * Virtual class implemented by each test to redirect static operation / state transitions to
@@ -48,13 +56,13 @@
         ADD_FAILURE() << "Layer creation not expected in this test";
         return nullptr;
     }
-    virtual void startRepaintLayer(OffscreenBuffer*) {
+    virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) {
         ADD_FAILURE() << "Layer repaint not expected in this test";
     }
     virtual void endLayer() {
         ADD_FAILURE() << "Layer updates not expected in this test";
     }
-    virtual void startFrame(uint32_t width, uint32_t height) {}
+    virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
     virtual void endFrame() {}
 
     // define virtual defaults for direct
@@ -87,7 +95,7 @@
 TEST(OpReorderer, simple) {
     class SimpleTestRenderer : public TestRendererBase {
     public:
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(0, mIndex++);
             EXPECT_EQ(100u, width);
             EXPECT_EQ(200u, height);
@@ -108,7 +116,7 @@
         canvas.drawRect(0, 0, 100, 200, SkPaint());
         canvas.drawBitmap(bitmap, 10, 10, nullptr);
     });
-    OpReorderer reorderer(100, 200, *dl);
+    OpReorderer reorderer(100, 200, *dl, sLightCenter);
 
     SimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -122,7 +130,7 @@
         canvas.drawRect(0, 0, 400, 400, SkPaint());
         canvas.restore();
     });
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     FailRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -154,7 +162,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     SimpleBatchingTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -198,13 +206,8 @@
         canvas.restore();
     });
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue,
-            SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
 
     RenderNodeTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -225,13 +228,10 @@
         SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
         canvas.drawBitmap(bitmap, 0, 0, nullptr);
     });
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
 
     OpReorderer reorderer(sEmptyLayerUpdateQueue,
             SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
-            200, 200, nodes);
+            200, 200, createSyncedNodeList(node), sLightCenter);
 
     ClippedTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -273,7 +273,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     SaveLayerSimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -305,7 +305,7 @@
             int index = mIndex++;
             EXPECT_TRUE(index == 2 || index == 6);
         }
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(7, mIndex++);
         }
         void endFrame() override {
@@ -344,7 +344,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(800, 800, *dl);
+    OpReorderer reorderer(800, 800, *dl, sLightCenter);
 
     SaveLayerNestedTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -363,19 +363,21 @@
         canvas.restore();
         canvas.restore();
     });
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     FailRenderer renderer;
     // should see no ops, even within the layer, since the layer should be rejected
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-TEST(OpReorderer, hwLayerSimple) {
+RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
     class HwLayerSimpleTestRenderer : public TestRendererBase {
     public:
-        void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
             EXPECT_EQ(0, mIndex++);
-            EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
+            EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+            EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+            EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
             EXPECT_EQ(1, mIndex++);
@@ -389,7 +391,7 @@
         void endLayer() override {
             EXPECT_EQ(2, mIndex++);
         }
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(3, mIndex++);
         }
         void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
@@ -405,29 +407,29 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     }, TestUtils::getHwLayerSetupCallback());
-    OffscreenBuffer** bufferHandle = node->getLayerHandle();
-    *bufferHandle = (OffscreenBuffer*) 0x0124;
+    OffscreenBuffer** layerHandle = node->getLayerHandle();
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+    // create RenderNode's layer here in same way prepareTree would
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    *layerHandle = &layer;
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
+    auto syncedNodeList = createSyncedNodeList(node);
 
     // only enqueue partial damage
-    LayerUpdateQueue layerUpdateQueue;
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
 
-    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
-
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedNodeList, sLightCenter);
     HwLayerSimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(6, renderer.getIndex());
 
     // clean up layer pointer, so we can safely destruct RenderNode
-    *bufferHandle = nullptr;
+    *layerHandle = nullptr;
 }
 
-TEST(OpReorderer, hwLayerComplex) {
+RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) {
     /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
      * - startRepaintLayer(child), rect(grey), endLayer
      * - startTemporaryLayer, drawLayer(child), endLayer
@@ -440,14 +442,16 @@
             EXPECT_EQ(3, mIndex++); // savelayer first
             return (OffscreenBuffer*)0xabcd;
         }
-        void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
             int index = mIndex++;
             if (index == 0) {
                 // starting inner layer
-                EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
+                EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+                EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
             } else if (index == 6) {
                 // starting outer layer
-                EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
+                EXPECT_EQ(200u, offscreenBuffer->viewportWidth);
+                EXPECT_EQ(200u, offscreenBuffer->viewportHeight);
             } else { ADD_FAILURE(); }
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -464,17 +468,20 @@
             int index = mIndex++;
             EXPECT_TRUE(index == 2 || index == 5 || index == 9);
         }
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(10, mIndex++);
         }
         void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            OffscreenBuffer* layer = *op.layerHandle;
             int index = mIndex++;
             if (index == 4) {
-                EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
+                EXPECT_EQ(100u, layer->viewportWidth);
+                EXPECT_EQ(100u, layer->viewportHeight);
             } else if (index == 8) {
                 EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
             } else if (index == 11) {
-                EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
+                EXPECT_EQ(200u, layer->viewportWidth);
+                EXPECT_EQ(200u, layer->viewportHeight);
             } else { ADD_FAILURE(); }
         }
         void endFrame() override {
@@ -488,7 +495,8 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     }, TestUtils::getHwLayerSetupCallback());
-    *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
+    OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    *(child->getLayerHandle()) = &childLayer;
 
     RenderNode* childPtr = child.get();
     auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
@@ -501,18 +509,17 @@
         canvas.drawRenderNode(childPtr);
         canvas.restore();
     }, TestUtils::getHwLayerSetupCallback());
-    *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
+    OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
+    *(parent->getLayerHandle()) = &parentLayer;
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+    auto syncedList = createSyncedNodeList(parent);
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    LayerUpdateQueue layerUpdateQueue;
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
     layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
 
-    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedList, sLightCenter);
 
     HwLayerComplexTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -561,58 +568,184 @@
         drawOrderedRect(&canvas, 8);
         drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
     });
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+            createSyncedNodeList(parent), sLightCenter);
 
     ZReorderTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
 };
 
+// creates a 100x100 shadow casting node with provided translationZ
+static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
+    return TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    }, [translationZ] (RenderProperties& properties) {
+        properties.setTranslationZ(translationZ);
+        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
+        return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
+    });
+}
+
 TEST(OpReorderer, shadow) {
     class ShadowTestRenderer : public TestRendererBase {
     public:
         void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
             EXPECT_EQ(0, mIndex++);
+            EXPECT_FLOAT_EQ(1.0f, op.casterAlpha);
+            EXPECT_TRUE(op.casterPath->isRect(nullptr));
+            EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowMatrixXY);
+
+            Matrix4 expectedZ;
+            expectedZ.loadTranslate(0, 0, 5);
+            EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowMatrixZ);
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
             EXPECT_EQ(1, mIndex++);
         }
     };
 
-    sp<RenderNode> caster = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
-            [](RecordingCanvas& canvas) {
-        SkPaint paint;
-        paint.setColor(SK_ColorWHITE);
-        canvas.drawRect(0, 0, 100, 100, paint);
-    }, [] (RenderProperties& properties) {
-        properties.setTranslationZ(5.0f);
-        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 5, 1.0f);
-        return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
-    });
     sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
-            [&caster] (RecordingCanvas& canvas) {
+            [] (RecordingCanvas& canvas) {
         canvas.insertReorderBarrier(true);
-        canvas.drawRenderNode(caster.get());
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
     });
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
 
     ShadowTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
 }
 
-static void testProperty(
-        TestUtils::PropSetupCallback propSetupCallback,
+TEST(OpReorderer, shadowSaveLayer) {
+    class ShadowSaveLayerTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(0, mIndex++);
+            return nullptr;
+        }
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+            EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endLayer() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+    };
+
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+            [] (RecordingCanvas& canvas) {
+        // save/restore outside of reorderBarrier, so they don't get moved out of place
+        canvas.translate(20, 10);
+        int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.insertReorderBarrier(false);
+        canvas.restoreToCount(count);
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
+
+    ShadowSaveLayerTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) {
+    class ShadowHwLayerTestRenderer : public TestRendererBase {
+    public:
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+            EXPECT_EQ(0, mIndex++);
+        }
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+            EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endLayer() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+    };
+
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(50, 60, 150, 160,
+            [] (RecordingCanvas& canvas) {
+        canvas.insertReorderBarrier(true);
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(20, 10);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.restore();
+    }, TestUtils::getHwLayerSetupCallback());
+    OffscreenBuffer** layerHandle = parent->getLayerHandle();
+
+    // create RenderNode's layer here in same way prepareTree would, setting windowTransform
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    Matrix4 windowTransform;
+    windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin
+    layer.setWindowTransform(windowTransform);
+    *layerHandle = &layer;
+
+    auto syncedList = createSyncedNodeList(parent);
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+    layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedList, (Vector3) { 100, 100, 100 });
+
+    ShadowHwLayerTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+
+    // clean up layer pointer, so we can safely destruct RenderNode
+    *layerHandle = nullptr;
+}
+
+TEST(OpReorderer, shadowLayering) {
+    class ShadowLayeringTestRenderer : public TestRendererBase {
+    public:
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 0 || index == 1);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 2 || index == 3);
+        }
+    };
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+            [] (RecordingCanvas& canvas) {
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
+
+    ShadowLayeringTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+
+static void testProperty(TestUtils::PropSetupCallback propSetupCallback,
         std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
     class PropertyTestRenderer : public TestRendererBase {
     public:
@@ -630,13 +763,9 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     }, propSetupCallback);
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue,
-            SkRect::MakeWH(100, 100), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
+            createSyncedNodeList(node), sLightCenter);
 
     PropertyTestRenderer renderer(opValidateCallback);
     reorderer.replayBakedOps<TestDispatcher>(renderer);
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index efa28ae..38bafd5 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -45,6 +45,20 @@
             && MathUtils::areEqual(a.right, b.right) \
             && MathUtils::areEqual(a.bottom, b.bottom));
 
+/**
+ * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
+ * (for e.g. accessing its RenderState)
+ */
+#define RENDERTHREAD_TEST(test_case_name, test_name) \
+    class test_case_name##_##test_name##_RenderThreadTest { \
+    public: \
+        static void doTheThing(renderthread::RenderThread& renderThread); \
+    }; \
+    TEST(test_case_name, test_name) { \
+        TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+    }; \
+    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread)
+
 class TestUtils {
 public:
     class SignalingDtor {
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 1355635..3164930 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -166,7 +166,7 @@
         updateAppOpsPlayAudio();
         // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
         mAppOpsCallback = new IAppOpsCallback.Stub() {
-            public void opChanged(int op, String packageName) {
+            public void opChanged(int op, int uid, String packageName) {
                 synchronized (mLock) {
                     if (op == AppOpsManager.OP_PLAY_AUDIO) {
                         updateAppOpsPlayAudio();
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 043b80e..6197c70 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -931,9 +931,7 @@
      *
      * @param rating The {@link TvContentRating} to check.
      * @return {@code true} if this object contains {@code rating}, {@code false} otherwise.
-     * @hide
      */
-    @SystemApi
     public final boolean contains(@NonNull TvContentRating rating) {
         Preconditions.checkNotNull(rating);
         if (!rating.getMainRating().equals(mRating)) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index 6f1a89b..369ab7d 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -32,6 +32,8 @@
 import android.test.MoreAsserts;
 import android.test.ServiceTestCase;
 import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
 import com.android.documentsui.model.DocumentInfo;
@@ -52,6 +54,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+@MediumTest
 public class CopyTest extends ServiceTestCase<CopyService> {
 
     public CopyTest() {
@@ -89,9 +92,6 @@
         super.tearDown();
     }
 
-    /**
-     * Test copying a single file.
-     */
     public void testCopyFile() throws Exception {
         String srcPath = "/test0.txt";
         Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
@@ -131,9 +131,6 @@
         MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
     }
 
-    /**
-     * Test copying multiple files.
-     */
     public void testCopyMultipleFiles() throws Exception {
         String testContent[] = {
                 "The five boxing wizards jump quickly",
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 9060516..ba91c83 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -31,11 +31,13 @@
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
 import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.documentsui.model.RootInfo;
 
+@LargeTest
 public class FilesActivityUiTest extends InstrumentationTestCase {
 
     private static final int TIMEOUT = 5000;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
index 746e2117..b250e5d 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
@@ -25,6 +25,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.test.AndroidTestCase;
 import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.view.ViewGroup;
 
 import com.android.documentsui.DirectoryResult;
@@ -34,6 +35,7 @@
 
 import java.util.List;
 
+@SmallTest
 public class DirectoryFragmentModelTest extends AndroidTestCase {
 
     private static final int ITEM_COUNT = 5;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index d1ce564..b3d45ae 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -18,6 +18,7 @@
 
 import android.support.v7.widget.RecyclerView;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.SparseBooleanArray;
 import android.view.View;
 import android.view.ViewGroup;
@@ -32,6 +33,7 @@
 import java.util.List;
 import java.util.Set;
 
+@SmallTest
 public class MultiSelectManagerTest extends AndroidTestCase {
 
     private static final List<String> items;
@@ -163,7 +165,6 @@
         assertRangeSelection(14, 17);
     }
 
-
     public void testSingleTapUp_ShiftReversesSelectionDirection() {
         longPress(7);
         shiftTap(17);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
index c4b6ce5..c856b22 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
@@ -20,11 +20,13 @@
 import android.graphics.Rect;
 import android.support.v7.widget.RecyclerView.OnScrollListener;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.SparseBooleanArray;
 import android.view.View;
 
 import com.android.documentsui.dirlist.MultiSelectManager.GridModel;
 
+@SmallTest
 public class MultiSelectManager_GridModelTest extends AndroidTestCase {
 
     private static final int VIEW_PADDING_PX = 5;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
index 64da750..72fc108 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
@@ -17,10 +17,11 @@
 package com.android.documentsui.dirlist;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 
-
+@SmallTest
 public class MultiSelectManager_SelectionTest extends AndroidTestCase{
 
     private Selection selection;
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 0d4265a..1c96906 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -18,7 +18,7 @@
 
 import android.content.ContentResolver;
 import android.database.Cursor;
-import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
 import android.mtp.MtpObjectInfo;
 import android.net.Uri;
 import android.os.Bundle;
@@ -26,6 +26,7 @@
 import android.provider.DocumentsContract;
 import android.util.Log;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Date;
@@ -44,12 +45,14 @@
 
     private final MtpManager mMtpManager;
     private final ContentResolver mResolver;
+    private final MtpDatabase mDatabase;
     private final TaskList mTaskList = new TaskList();
     private boolean mHasBackgroundThread = false;
 
-    DocumentLoader(MtpManager mtpManager, ContentResolver resolver) {
+    DocumentLoader(MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) {
         mMtpManager = mtpManager;
         mResolver = resolver;
+        mDatabase = database;
     }
 
     private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles)
@@ -65,13 +68,17 @@
             throws IOException {
         LoaderTask task = mTaskList.findTask(parent);
         if (task == null) {
+            if (parent.mDocumentId == null) {
+                throw new FileNotFoundException("Parent not found.");
+            }
+
             int parentHandle = parent.mObjectHandle;
             // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
             // getObjectHandles if we would like to obtain children under the root.
             if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
                 parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
             }
-            task = new LoaderTask(parent, mMtpManager.getObjectHandles(
+            task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles(
                     parent.mDeviceId, parent.mStorageId, parentHandle));
             task.fillDocuments(loadDocuments(
                     mMtpManager,
@@ -83,11 +90,10 @@
         }
 
         mTaskList.addFirst(task);
-        if (!task.completed() && !mHasBackgroundThread) {
+        if (task.getState() == LoaderTask.STATE_LOADING && !mHasBackgroundThread) {
             mHasBackgroundThread = true;
             new BackgroundLoaderThread().start();
         }
-
         return task.createCursor(mResolver, columnNames);
     }
 
@@ -120,26 +126,20 @@
                     deviceId = task.mIdentifier.mDeviceId;
                     handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES);
                 }
-                MtpObjectInfo[] objectInfos;
+
                 try {
-                    objectInfos = loadDocuments(mMtpManager, deviceId, handles);
-                } catch (IOException exception) {
-                    objectInfos = null;
-                    Log.d(MtpDocumentsProvider.TAG, exception.getMessage());
-                }
-                synchronized (DocumentLoader.this) {
-                    if (objectInfos != null) {
-                        task.fillDocuments(objectInfos);
-                        final boolean shouldNotify =
-                                task.mLastNotified.getTime() <
-                                new Date().getTime() - NOTIFY_PERIOD_MS ||
-                                task.completed();
-                        if (shouldNotify) {
-                            task.notify(mResolver);
-                        }
-                    } else {
-                        mTaskList.remove(task);
+                    final MtpObjectInfo[] objectInfos =
+                            loadDocuments(mMtpManager, deviceId, handles);
+                    task.fillDocuments(objectInfos);
+                    final boolean shouldNotify =
+                            task.mLastNotified.getTime() <
+                            new Date().getTime() - NOTIFY_PERIOD_MS ||
+                            task.getState() != LoaderTask.STATE_LOADING;
+                    if (shouldNotify) {
+                        task.notify(mResolver);
                     }
+                } catch (IOException exception) {
+                    task.setError(exception);
                 }
             }
         }
@@ -156,7 +156,7 @@
 
         LoaderTask findRunningTask() {
             for (int i = 0; i < size(); i++) {
-                if (!get(i).completed())
+                if (get(i).getState() == LoaderTask.STATE_LOADING)
                     return get(i);
             }
             return null;
@@ -165,7 +165,7 @@
         void clearCompletedTasks() {
             int i = 0;
             while (i < size()) {
-                if (get(i).completed()) {
+                if (get(i).getState() == LoaderTask.STATE_COMPLETED) {
                     remove(i);
                 } else {
                     i++;
@@ -186,36 +186,51 @@
     }
 
     private static class LoaderTask {
+        static final int STATE_LOADING = 0;
+        static final int STATE_COMPLETED = 1;
+        static final int STATE_ERROR = 2;
+
+        final MtpDatabase mDatabase;
         final Identifier mIdentifier;
         final int[] mObjectHandles;
-        final MtpObjectInfo[] mObjectInfos;
         Date mLastNotified;
         int mNumLoaded;
+        Exception mError;
 
-        LoaderTask(Identifier identifier, int[] objectHandles) {
+        LoaderTask(MtpDatabase database, Identifier identifier, int[] objectHandles) {
+            mDatabase = database;
             mIdentifier = identifier;
             mObjectHandles = objectHandles;
-            mObjectInfos = new MtpObjectInfo[mObjectHandles.length];
             mNumLoaded = 0;
             mLastNotified = new Date();
         }
 
-        Cursor createCursor(ContentResolver resolver, String[] columnNames) {
-            final MatrixCursor cursor = new MatrixCursor(columnNames);
-            final Identifier rootIdentifier = new Identifier(
-                    mIdentifier.mDeviceId, mIdentifier.mStorageId);
-            for (int i = 0; i < mNumLoaded; i++) {
-                CursorHelper.addToCursor(mObjectInfos[i], rootIdentifier, cursor.newRow());
-            }
+        Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException {
             final Bundle extras = new Bundle();
-            extras.putBoolean(DocumentsContract.EXTRA_LOADING, !completed());
+            switch (getState()) {
+                case STATE_LOADING:
+                    extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
+                    break;
+                case STATE_ERROR:
+                    throw new IOException(mError);
+            }
+
+            final Cursor cursor = mDatabase.queryChildDocuments(
+                    columnNames, mIdentifier.mDocumentId, /* use old ID format */ true);
             cursor.setNotificationUri(resolver, createUri());
             cursor.respond(extras);
+
             return cursor;
         }
 
-        boolean completed() {
-            return mNumLoaded == mObjectInfos.length;
+        int getState() {
+            if (mError != null) {
+                return STATE_ERROR;
+            } else if (mNumLoaded == mObjectHandles.length) {
+                return STATE_COMPLETED;
+            } else {
+                return STATE_LOADING;
+            }
         }
 
         int[] getUnloadedObjectHandles(int count) {
@@ -230,9 +245,32 @@
             mLastNotified = new Date();
         }
 
-        void fillDocuments(MtpObjectInfo[] objectInfos) {
-            for (int i = 0; i < objectInfos.length; i++) {
-                mObjectInfos[mNumLoaded++] = objectInfos[i];
+        void fillDocuments(MtpObjectInfo[] objectInfoList) {
+            if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
+                return;
+            }
+            if (mNumLoaded == 0) {
+                mDatabase.startAddingChildDocuments(mIdentifier.mDocumentId);
+            }
+            try {
+                mDatabase.putChildDocuments(
+                        mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
+                mNumLoaded += objectInfoList.length;
+            } catch (SQLiteException exp) {
+                mError = exp;
+                mNumLoaded = 0;
+            }
+            if (getState() != STATE_LOADING) {
+                mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+            }
+        }
+
+        void setError(Exception message) {
+            final int lastState = getState();
+            mError = message;
+            mNumLoaded = 0;
+            if (lastState == STATE_LOADING) {
+                mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
             }
         }
 
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
index ae29f52..4238721e 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
@@ -23,6 +23,7 @@
     final int mDeviceId;
     final int mStorageId;
     final int mObjectHandle;
+    final String mDocumentId;
 
     static Identifier createFromRootId(String rootId) {
         final String[] components = rootId.split("_");
@@ -45,9 +46,14 @@
     }
 
     Identifier(int deviceId, int storageId, int objectHandle) {
+        this(deviceId, storageId, objectHandle, null);
+    }
+
+    Identifier(int deviceId, int storageId, int objectHandle, String documentId) {
         mDeviceId = deviceId;
         mStorageId = storageId;
         mObjectHandle = objectHandle;
+        mDocumentId = documentId;
     }
 
     // TODO: Make the ID persistent.
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 3151ccb..3dc69cc 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -23,6 +23,7 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.mtp.MtpObjectInfo;
+import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
 
@@ -79,8 +80,8 @@
     private final Map<String, Integer> mMappingMode = new HashMap<>();
 
     @VisibleForTesting
-    MtpDatabase(Context context) {
-        mDatabase = new MtpDatabaseInternal(context);
+    MtpDatabase(Context context, int flags) {
+        mDatabase = new MtpDatabaseInternal(context, flags);
     }
 
     /**
@@ -111,7 +112,29 @@
      */
     @VisibleForTesting
     Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
-        return mDatabase.queryChildDocuments(columnNames, parentDocumentId);
+        return queryChildDocuments(columnNames, parentDocumentId, false);
+    }
+
+    @VisibleForTesting
+    Cursor queryChildDocuments(String[] columnNames, String parentDocumentId, boolean useOldId) {
+        final String[] newColumnNames = new String[columnNames.length];
+
+        // TODO: Temporary replace document ID with old format.
+        for (int i = 0; i < columnNames.length; i++) {
+            if (useOldId && DocumentsContract.Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) {
+                newColumnNames[i] = COLUMN_DEVICE_ID + " || '_' || " + COLUMN_STORAGE_ID +
+                        " || '_' || IFNULL(" + COLUMN_OBJECT_HANDLE + ",0) AS " +
+                        DocumentsContract.Document.COLUMN_DOCUMENT_ID;
+            } else {
+                newColumnNames[i] = columnNames[i];
+            }
+        }
+
+        return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId);
+    }
+
+    Identifier createIdentifier(String parentDocumentId) {
+        return mDatabase.createIdentifier(parentDocumentId);
     }
 
     /**
@@ -193,9 +216,13 @@
             int i = 0;
             for (final MtpRoot root : roots) {
                 // Use the same value for the root ID and the corresponding document ID.
-                values.put(
-                        Root.COLUMN_ROOT_ID,
-                        valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID));
+                final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
+                // If it fails to insert/update documents, the document ID will be set with -1.
+                // In this case we don't insert/update root extra information neither.
+                if (documentId == null) {
+                    continue;
+                }
+                values.put(Root.COLUMN_ROOT_ID, documentId);
                 values.put(
                         Root.COLUMN_FLAGS,
                         Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index 977b12e..97c1d29 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -26,6 +26,9 @@
     static final int DATABASE_VERSION = 1;
     static final String DATABASE_NAME = null;
 
+    static final int FLAG_DATABASE_IN_MEMORY = 1;
+    static final int FLAG_DATABASE_IN_FILE = 0;
+
     /**
      * Table representing documents including root documents.
      */
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
index 7328f05..9c5d6b6 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
@@ -23,6 +23,7 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.provider.DocumentsContract.Document;
@@ -35,8 +36,11 @@
  */
 class MtpDatabaseInternal {
     private static class OpenHelper extends SQLiteOpenHelper {
-        public OpenHelper(Context context) {
-            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        public OpenHelper(Context context, int flags) {
+            super(context,
+                  flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
+                  null,
+                  DATABASE_VERSION);
         }
 
         @Override
@@ -54,8 +58,8 @@
 
     private final SQLiteDatabase mDatabase;
 
-    MtpDatabaseInternal(Context context) {
-        final OpenHelper helper = new OpenHelper(context);
+    MtpDatabaseInternal(Context context, int flags) {
+        final OpenHelper helper = new OpenHelper(context, flags);
         mDatabase = helper.getWritableDatabase();
     }
 
@@ -122,6 +126,64 @@
     }
 
     /**
+     * Gets identifier from document ID.
+     * @param documentId Document ID.
+     * @return Identifier.
+     */
+    Identifier createIdentifier(String documentId) {
+        // Currently documentId is old format.
+        final Identifier oldIdentifier = Identifier.createFromDocumentId(documentId);
+        final String selection;
+        final String[] args;
+        if (oldIdentifier.mObjectHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
+            selection = COLUMN_DEVICE_ID + "= ? AND " +
+                    COLUMN_ROW_STATE + " IN (?, ?) AND " +
+                    COLUMN_STORAGE_ID + "= ? AND " +
+                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+            args = strings(
+                    oldIdentifier.mDeviceId,
+                    ROW_STATE_VALID,
+                    ROW_STATE_INVALIDATED,
+                    oldIdentifier.mStorageId);
+        } else {
+            selection = COLUMN_DEVICE_ID + "= ? AND " +
+                    COLUMN_ROW_STATE + " IN (?, ?) AND " +
+                    COLUMN_STORAGE_ID + "= ? AND " +
+                    COLUMN_OBJECT_HANDLE + " = ?";
+            args = strings(
+                    oldIdentifier.mDeviceId,
+                    ROW_STATE_VALID,
+                    ROW_STATE_INVALIDATED,
+                    oldIdentifier.mStorageId,
+                    oldIdentifier.mObjectHandle);
+        }
+
+        final Cursor cursor = mDatabase.query(
+                TABLE_DOCUMENTS,
+                strings(Document.COLUMN_DOCUMENT_ID),
+                selection,
+                args,
+                null,
+                null,
+                null,
+                "1");
+        try {
+            if (cursor.getCount() == 0) {
+                return oldIdentifier;
+            } else {
+                cursor.moveToNext();
+                return new Identifier(
+                        oldIdentifier.mDeviceId,
+                        oldIdentifier.mStorageId,
+                        oldIdentifier.mObjectHandle,
+                        cursor.getString(0));
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
      * Starts adding new documents.
      * The methods decides mapping mode depends on if all documents under the given parent have MTP
      * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
@@ -164,7 +226,7 @@
      * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
      * rows. If the methods adds rows to database, it updates valueList with correct document ID.
      *
-     * @param valuesList Values that are stored in the database.
+     * @param valuesList Values for documents to be stored in the database.
      * @param selection SQL where closure to select rows that shares the same parent.
      * @param arg Argument for selection SQL.
      * @param heuristic Whether the mapping mode is heuristic.
@@ -191,23 +253,32 @@
                         null,
                         null,
                         "1");
-                final long rowId;
-                if (candidateCursor.getCount() == 0) {
-                    rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
-                    added = true;
-                } else if (!heuristic) {
-                    candidateCursor.moveToNext();
-                    final String documentId = candidateCursor.getString(0);
-                    rowId = mDatabase.update(
-                            TABLE_DOCUMENTS, values, SELECTION_DOCUMENT_ID, strings(documentId));
-                } else {
-                    values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
-                    rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                try {
+                    final long rowId;
+                    if (candidateCursor.getCount() == 0) {
+                        rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                        if (rowId == -1) {
+                            throw new SQLiteException("Failed to put a document into database.");
+                        }
+                        added = true;
+                    } else if (!heuristic) {
+                        candidateCursor.moveToNext();
+                        final String documentId = candidateCursor.getString(0);
+                        rowId = mDatabase.update(
+                                TABLE_DOCUMENTS,
+                                values,
+                                SELECTION_DOCUMENT_ID,
+                                strings(documentId));
+                    } else {
+                        values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
+                        rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                    }
+                    // Document ID is a primary integer key of the table. So the returned row
+                    // IDs should be same with the document ID.
+                    values.put(Document.COLUMN_DOCUMENT_ID, rowId);
+                } finally {
+                    candidateCursor.close();
                 }
-                // Document ID is a primary integer key of the table. So the returned row
-                // IDs should be same with the document ID.
-                values.put(Document.COLUMN_DOCUMENT_ID, rowId);
-                candidateCursor.close();
             }
 
             mDatabase.setTransactionSuccessful();
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 0931445..f0f8161 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -78,7 +78,7 @@
         mMtpManager = new MtpManager(getContext());
         mResolver = getContext().getContentResolver();
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
-        mDatabase = new MtpDatabase(getContext());
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
         mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
         return true;
     }
@@ -155,7 +155,7 @@
         if (projection == null) {
             projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
         }
-        final Identifier parentIdentifier = Identifier.createFromDocumentId(parentDocumentId);
+        final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
         try {
             return getDocumentLoader(parentIdentifier).queryChildDocuments(
                     projection, parentIdentifier);
@@ -255,7 +255,7 @@
 
     void openDevice(int deviceId) throws IOException {
         mMtpManager.openDevice(deviceId);
-        mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver));
+        mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
         mRootScanner.scanNow();
     }
 
@@ -318,9 +318,9 @@
         public final PipeManager mPipeManager;
         public final DocumentLoader mDocumentLoader;
 
-        public DeviceToolkit(MtpManager manager, ContentResolver resolver) {
+        public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
             mPipeManager = new PipeManager();
-            mDocumentLoader = new DocumentLoader(manager, resolver);
+            mDocumentLoader = new DocumentLoader(manager, resolver, database);
         }
     }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index 415f89e..d9ed4ab 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -2,6 +2,7 @@
 
 import android.content.ContentResolver;
 import android.content.res.Resources;
+import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.os.Process;
 import android.provider.DocumentsContract;
@@ -93,16 +94,17 @@
                     }
                     boolean changed = false;
                     for (int deviceId : deviceIds) {
+                        mDatabase.startAddingRootDocuments(deviceId);
                         try {
-                            mDatabase.startAddingRootDocuments(deviceId);
                             changed = mDatabase.putRootDocuments(
                                     deviceId, mResources, mManager.getRoots(deviceId)) || changed;
-                            changed = mDatabase.stopAddingRootDocuments(deviceId) || changed;
-                        } catch (IOException exp) {
+                        } catch (IOException|SQLiteException exp) {
                             // The error may happen on the device. We would like to continue getting
                             // roots for other devices.
                             Log.e(MtpDocumentsProvider.TAG, exp.getMessage());
                             continue;
+                        } finally {
+                            changed = mDatabase.stopAddingRootDocuments(deviceId) || changed;
                         }
                     }
                     if (changed) {
diff --git a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
index 28ad3f4..e1307e9 100644
--- a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
@@ -18,7 +18,4 @@
     <instrumentation android:name="com.android.mtp.TestResultInstrumentation"
         android:targetPackage="com.android.mtp"
         android:label="Tests for MtpDocumentsProvider with the UI for output." />
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
-        android:targetPackage="com.android.mtp"
-        android:label="Tests for MtpDocumentsProvider." />
 </manifest>
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index a012d7f..a80eb51 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -22,26 +22,37 @@
 import android.net.Uri;
 import android.provider.DocumentsContract;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import java.io.IOException;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 
-@SmallTest
+@MediumTest
 public class DocumentLoaderTest extends AndroidTestCase {
+    private MtpDatabase mDatabase;
     private BlockableTestMtpManager mManager;
     private TestContentResolver mResolver;
     private DocumentLoader mLoader;
-    final private Identifier mParentIdentifier = new Identifier(0, 0, 0);
+    final private Identifier mParentIdentifier = new Identifier(0, 0, 0, "1");
 
     @Override
     public void setUp() {
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, new TestResources(), new MtpRoot[] {
+                new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+        });
+        mDatabase.stopAddingRootDocuments(0);
         mManager = new BlockableTestMtpManager(getContext());
         mResolver = new TestContentResolver();
-        mLoader = new DocumentLoader(mManager, mResolver);
+        mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
+    }
+
+    @Override
+    public void tearDown() {
+        mDatabase.close();
     }
 
     public void testBasic() throws Exception {
@@ -88,6 +99,7 @@
             childDocuments[i] = objectHandle;
             manager.setObjectInfo(0, new MtpObjectInfo.Builder()
                     .setObjectHandle(objectHandle)
+                    .setName(Integer.toString(i))
                     .build());
         }
         manager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, childDocuments);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index ce4cf14..25dd1c8 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -41,18 +41,29 @@
     };
 
     private final TestResources resources = new TestResources();
+    MtpDatabase mDatabase;
+
+    @Override
+    public void setUp() {
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+    }
+
+    @Override
+    public void tearDown() {
+        mDatabase.close();
+        mDatabase = null;
+    }
 
     public void testPutRootDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
                 new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
                 new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(COLUMN_NAMES);
+            final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES);
             assertEquals(3, cursor.getCount());
 
             cursor.moveToNext();
@@ -80,7 +91,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(new String [] {
+            final Cursor cursor = mDatabase.queryRoots(new String [] {
                     Root.COLUMN_ROOT_ID,
                     Root.COLUMN_FLAGS,
                     Root.COLUMN_ICON,
@@ -136,15 +147,14 @@
     }
 
     public void testPutChildDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
-        database.startAddingChildDocuments("parentId");
-        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId");
+        mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
         });
 
-        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES, "parentId");
+        final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "parentId");
         assertEquals(3, cursor.getCount());
 
         cursor.moveToNext();
@@ -202,7 +212,6 @@
     }
 
     public void testRestoreIdForRootDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -212,14 +221,15 @@
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
                 new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -233,7 +243,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -244,10 +254,10 @@
             cursor.close();
         }
 
-        database.clearMapping();
+        mDatabase.clearMapping();
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -261,7 +271,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -272,14 +282,14 @@
             cursor.close();
         }
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
                 new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(3, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -297,7 +307,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(3, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -311,10 +321,10 @@
             cursor.close();
         }
 
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -328,7 +338,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -341,22 +351,21 @@
     }
 
     public void testRestoreIdForChildDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        database.startAddingChildDocuments("parentId");
-        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId");
+        mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
             assertEquals(3, cursor.getCount());
 
             cursor.moveToNext();
@@ -377,14 +386,14 @@
             cursor.close();
         }
 
-        database.startAddingChildDocuments("parentId");
-        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId");
+        mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
         });
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
             assertEquals(4, cursor.getCount());
 
             cursor.moveToPosition(3);
@@ -395,10 +404,10 @@
             cursor.close();
         }
 
-        database.stopAddingChildDocuments("parentId");
+        mDatabase.stopAddingChildDocuments("parentId");
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
             assertEquals(2, cursor.getCount());
 
             cursor.moveToNext();
@@ -415,7 +424,6 @@
     }
 
     public void testRestoreIdForDifferentDevices() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -425,17 +433,17 @@
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
-        database.startAddingRootDocuments(0);
-        database.startAddingRootDocuments(1);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.startAddingRootDocuments(1);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
         });
-        database.putRootDocuments(1, resources, new MtpRoot[] {
+        mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
                 new MtpRoot(1, 100, "Device", "Storage", 0, 0, "")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -449,7 +457,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -460,21 +468,21 @@
             cursor.close();
         }
 
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingRootDocuments(0);
-        database.startAddingRootDocuments(1);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.startAddingRootDocuments(1);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
         });
-        database.putRootDocuments(1, resources, new MtpRoot[] {
+        mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
                 new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
         });
-        database.stopAddingRootDocuments(0);
-        database.stopAddingRootDocuments(1);
+        mDatabase.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(1);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -488,7 +496,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -501,34 +509,33 @@
     }
 
     public void testRestoreIdForDifferentParents() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
         };
 
-        database.startAddingChildDocuments("parentId1");
-        database.startAddingChildDocuments("parentId2");
-        database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId1");
+        mDatabase.startAddingChildDocuments("parentId2");
+        mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+        mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                 createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingChildDocuments("parentId1");
-        database.startAddingChildDocuments("parentId2");
-        database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId1");
+        mDatabase.startAddingChildDocuments("parentId2");
+        mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+        mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                 createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.stopAddingChildDocuments("parentId1");
+        mDatabase.stopAddingChildDocuments("parentId1");
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId1");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -536,7 +543,7 @@
             cursor.close();
         }
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId2");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId2");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 2, cursor.getInt(0));
@@ -546,7 +553,6 @@
     }
 
     public void testClearMtpIdentifierBeforeResolveRootDocuments() {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -557,26 +563,26 @@
                 Root.COLUMN_AVAILABLE_BYTES
         };
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
         });
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -585,7 +591,7 @@
             cursor.close();
         }
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -595,7 +601,6 @@
     }
 
     public void testPutSameNameRootsAfterClearing() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -606,21 +611,21 @@
                 Root.COLUMN_AVAILABLE_BYTES
         };
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
                 new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
         });
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 2, cursor.getInt(0));
@@ -633,7 +638,7 @@
             cursor.close();
         }
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 2, cursor.getInt(0));
@@ -646,27 +651,26 @@
     }
 
     public void testReplaceExistingRoots() {
-        // The client code should be able to replace exisitng rows with new information.
-        final MtpDatabase database = new MtpDatabase(getContext());
+        // The client code should be able to replace existing rows with new information.
         // Add one.
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
         // Replace it.
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
         });
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
         {
             final String[] columns = new String[] {
                     DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                     MtpDatabaseConstants.COLUMN_STORAGE_ID,
                     DocumentsContract.Document.COLUMN_DISPLAY_NAME
             };
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -679,7 +683,7 @@
                     Root.COLUMN_ROOT_ID,
                     Root.COLUMN_AVAILABLE_BYTES
             };
-            final Cursor cursor = database.queryRoots(columns);
+            final Cursor cursor = mDatabase.queryRoots(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -690,20 +694,19 @@
 
     public void _testFailToReplaceExisitingUnmappedRoots() {
         // The client code should not be able to replace rows before resolving 'unmapped' rows.
-        final MtpDatabase database = new MtpDatabase(getContext());
         // Add one.
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
         // Add one.
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
         });
         // Add one more before resolving unmapped documents.
         try {
-            database.putRootDocuments(0, resources, new MtpRoot[] {
+            mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                     new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
             });
             fail();
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index bc7f28c..82e08cd 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -43,7 +43,7 @@
         mResolver = new TestContentResolver();
         mMtpManager = new TestMtpManager(getContext());
         mProvider = new MtpDocumentsProvider();
-        mDatabase = new MtpDatabase(getContext());
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
     }
 
@@ -200,6 +200,7 @@
         mProvider.openDevice(0);
         mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
                 .setObjectHandle(2)
+                .setStorageId(1)
                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
                 .setName("image.jpg")
                 .setDateModified(1422716400000L)
@@ -210,6 +211,7 @@
         assertEquals(1, cursor.getCount());
 
         cursor.moveToNext();
+
         assertEquals("0_1_2", cursor.getString(0));
         assertEquals("image/jpeg", cursor.getString(1));
         assertEquals("image.jpg", cursor.getString(2));
@@ -227,6 +229,7 @@
         mProvider.openDevice(0);
         mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
                 .setObjectHandle(2)
+                .setStorageId(1)
                 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
                 .setName("directory")
                 .setDateModified(1422716400000L)
@@ -277,6 +280,12 @@
         mProvider.openDevice(0);
         mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
 
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, mResources, new MtpRoot[] {
+                new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+        });
+        mDatabase.stopAddingRootDocuments(0);
+
         mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
                 .setObjectHandle(1)
                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
index 53018cc..7c947f5 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
@@ -19,15 +19,14 @@
 import android.mtp.MtpObjectInfo;
 import android.os.ParcelFileDescriptor;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import java.io.IOException;
-import java.util.Date;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
-@SmallTest
+@MediumTest
 public class PipeManagerTest extends AndroidTestCase {
     private static final byte[] HELLO_BYTES = new byte[] { 'h', 'e', 'l', 'l', 'o' };
 
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
index 3d92cc2..3833799 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
@@ -96,7 +96,7 @@
         if (mRoots.containsKey(deviceId)) {
             return mRoots.get(deviceId);
         } else {
-            throw new IOException("getRoots error");
+            throw new IOException("getRoots error: " + Integer.toString(deviceId));
         }
     }
 
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
index a243375..0fb0f34 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
@@ -12,8 +12,20 @@
 
     @Override
     public void onCreate(Bundle arguments) {
+        if (arguments == null) {
+            arguments = new Bundle();
+        }
+        final boolean includeRealDeviceTest =
+                Boolean.parseBoolean(arguments.getString("realDeviceTest", "false"));
+        if (!includeRealDeviceTest) {
+            arguments.putString("notAnnotation", "com.android.mtp.RealDeviceTest");
+        }
         super.onCreate(arguments);
-        addTestListener(this);
+        if (includeRealDeviceTest) {
+            // Show the test result by using activity because we need to disconnect USB cable
+            // from adb host while testing with real MTP device.
+            addTestListener(this);
+        }
     }
 
     @Override
diff --git a/packages/PrintSpooler/res/drawable/ic_add.xml b/packages/PrintSpooler/res/drawable/ic_add.xml
new file mode 100644
index 0000000..1442b1b
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_add.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
+        android:fillColor="#FFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/drawable/ic_search.xml b/packages/PrintSpooler/res/drawable/ic_search.xml
deleted file mode 100644
index 991fa38b..0000000
--- a/packages/PrintSpooler/res/drawable/ic_search.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:autoMirrored="true">
-
-    <item
-        android:state_checked="true">
-        <bitmap
-            android:src="@*android:drawable/ic_menu_search"
-            android:tint="?android:attr/colorControlActivated">
-        </bitmap>
-    </item>
-
-    <item
-        android:state_pressed="true">
-        <bitmap
-            android:src="@*android:drawable/ic_menu_search"
-            android:tint="?android:attr/colorControlActivated">
-        </bitmap>
-    </item>
-
-    <item>
-        <bitmap
-            android:src="@*android:drawable/ic_menu_search"
-            android:tint="?android:attr/colorControlNormal">
-        </bitmap>
-    </item>
-
-</selector>
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml
new file mode 100644
index 0000000..11fef2d
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:textAppearance="?android:attr/textAppearanceMedium"
+      android:textIsSelectable="false"
+      android:textColor="?android:attr/textColorPrimary"
+      android:paddingStart="20dip"
+      android:paddingEnd="8dip"
+      android:minHeight="56dip"
+      android:orientation="horizontal"
+      android:text="@string/destination_default_text"
+      android:gravity="start|center_vertical" />
diff --git a/packages/PrintSpooler/res/menu/select_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml
index 8da5769..15cc139 100644
--- a/packages/PrintSpooler/res/menu/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml
@@ -19,7 +19,7 @@
     <item
         android:id="@+id/action_search"
         android:title="@string/search"
-        android:icon="@*android:drawable/ic_search"
+        android:icon="@*android:drawable/ic_search_api_material"
         android:actionViewClass="android.widget.SearchView"
         android:showAsAction="ifRoom|collapseActionView"
         android:alphabeticShortcut="f"
@@ -29,7 +29,7 @@
     <item
         android:id="@+id/action_add_printer"
         android:title="@string/print_add_printer"
-        android:icon="@*android:drawable/create_contact"
+        android:icon="@drawable/ic_add"
         android:showAsAction="ifRoom"
         android:alphabeticShortcut="a">
     </item>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 70abdf4..6d81788 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -49,6 +49,9 @@
     <!-- Label of the page selection widget. [CHAR LIMIT=20] -->
     <string name="label_pages">Pages</string>
 
+    <!-- Label of the destination widget. [CHAR LIMIT=20] -->
+    <string name="destination_default_text">Select a printer</string>
+
     <!-- Template for the all pages option in the page selection widget. [CHAR LIMIT=20] -->
     <string name="template_all_pages">All <xliff:g id="page_count" example="100">%1$s</xliff:g></string>
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index f409fd4..e758835 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -64,6 +64,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
@@ -125,6 +126,8 @@
 
     private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
 
+    private static final String HAS_PRINTED_PREF = "has_printed";
+
     private static final int ORIENTATION_PORTRAIT = 0;
     private static final int ORIENTATION_LANDSCAPE = 1;
 
@@ -187,6 +190,7 @@
 
     private Spinner mDestinationSpinner;
     private DestinationAdapter mDestinationSpinnerAdapter;
+    private boolean mShowDestinationPrompt;
 
     private Spinner mMediaSizeSpinner;
     private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
@@ -1093,6 +1097,7 @@
 
         updateOptionsUi();
         addCurrentPrinterToHistory();
+        setUserPrinted();
 
         PageRange[] selectedPages = computeSelectedPages();
         if (!Arrays.equals(mSelectedPages, selectedPages)) {
@@ -1195,6 +1200,29 @@
         // Print button
         mPrintButton = (ImageView) findViewById(R.id.print_button);
         mPrintButton.setOnClickListener(clickListener);
+
+        // Special prompt instead of destination spinner for the first time the user printed
+        if (!hasUserEverPrinted()) {
+            mShowDestinationPrompt = true;
+
+            mSummaryCopies.setEnabled(false);
+            mSummaryPaperSize.setEnabled(false);
+
+            mDestinationSpinner.setOnTouchListener(new View.OnTouchListener() {
+                @Override
+                public boolean onTouch(View v, MotionEvent event) {
+                    mShowDestinationPrompt = false;
+                    mSummaryCopies.setEnabled(true);
+                    mSummaryPaperSize.setEnabled(true);
+                    updateOptionsUi();
+
+                    mDestinationSpinner.setOnTouchListener(null);
+                    mDestinationSpinnerAdapter.notifyDataSetChanged();
+
+                    return false;
+                }
+            });
+        }
     }
 
     /**
@@ -1332,6 +1360,22 @@
                 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
     }
 
+    /**
+     * Disable all options UI elements, beside the {@link #mDestinationSpinner}
+     */
+    private void disableOptionsUi() {
+        mCopiesEditText.setEnabled(false);
+        mCopiesEditText.setFocusable(false);
+        mMediaSizeSpinner.setEnabled(false);
+        mColorModeSpinner.setEnabled(false);
+        mDuplexModeSpinner.setEnabled(false);
+        mOrientationSpinner.setEnabled(false);
+        mRangeOptionsSpinner.setEnabled(false);
+        mPageRangeEditText.setEnabled(false);
+        mPrintButton.setVisibility(View.GONE);
+        mMoreOptionsButton.setEnabled(false);
+    }
+
     void updateOptionsUi() {
         // Always update the summary.
         updateSummary();
@@ -1346,32 +1390,14 @@
             if (mState != STATE_PRINTER_UNAVAILABLE) {
                 mDestinationSpinner.setEnabled(false);
             }
-            mCopiesEditText.setEnabled(false);
-            mCopiesEditText.setFocusable(false);
-            mMediaSizeSpinner.setEnabled(false);
-            mColorModeSpinner.setEnabled(false);
-            mDuplexModeSpinner.setEnabled(false);
-            mOrientationSpinner.setEnabled(false);
-            mRangeOptionsSpinner.setEnabled(false);
-            mPageRangeEditText.setEnabled(false);
-            mPrintButton.setVisibility(View.GONE);
-            mMoreOptionsButton.setEnabled(false);
+            disableOptionsUi();
             return;
         }
 
         // If no current printer, or it has no capabilities, or it is not
         // available, we disable all print options except the destination.
         if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
-            mCopiesEditText.setEnabled(false);
-            mCopiesEditText.setFocusable(false);
-            mMediaSizeSpinner.setEnabled(false);
-            mColorModeSpinner.setEnabled(false);
-            mDuplexModeSpinner.setEnabled(false);
-            mOrientationSpinner.setEnabled(false);
-            mRangeOptionsSpinner.setEnabled(false);
-            mPageRangeEditText.setEnabled(false);
-            mPrintButton.setVisibility(View.GONE);
-            mMoreOptionsButton.setEnabled(false);
+            disableOptionsUi();
             return;
         }
 
@@ -1679,6 +1705,10 @@
             mCopiesEditText.setText(MIN_COPIES_STRING);
             mCopiesEditText.requestFocus();
         }
+
+        if (mShowDestinationPrompt) {
+            disableOptionsUi();
+        }
     }
 
     private void updateSummary() {
@@ -1980,6 +2010,32 @@
         }
     }
 
+
+    /**
+     * Check if the user has ever printed a document
+     *
+     * @return true iff the user has ever printed a document
+     */
+    private boolean hasUserEverPrinted() {
+        SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
+
+        return preferences.getBoolean(HAS_PRINTED_PREF, false);
+    }
+
+    /**
+     * Remember that the user printed a document
+     */
+    private void setUserPrinted() {
+        SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
+
+        if (!preferences.getBoolean(HAS_PRINTED_PREF, false)) {
+            SharedPreferences.Editor edit = preferences.edit();
+
+            edit.putBoolean(HAS_PRINTED_PREF, true);
+            edit.apply();
+        }
+    }
+
     private final class DestinationAdapter extends BaseAdapter
             implements PrinterRegistry.OnPrintersChangeListener {
         private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
@@ -1988,6 +2044,11 @@
 
         private boolean mHistoricalPrintersLoaded;
 
+        /**
+         * Has the {@link #mDestinationSpinner} ever used a view from printer_dropdown_prompt
+         */
+        private boolean hadPromptView;
+
         public DestinationAdapter() {
             mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
             if (mHistoricalPrintersLoaded) {
@@ -2098,9 +2159,20 @@
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = getLayoutInflater().inflate(
-                        R.layout.printer_dropdown_item, parent, false);
+            if (mShowDestinationPrompt) {
+                if (convertView == null) {
+                    convertView = getLayoutInflater().inflate(
+                            R.layout.printer_dropdown_prompt, parent, false);
+                    hadPromptView = true;
+                }
+
+                return convertView;
+            } else {
+                // We don't know if we got an recyled printer_dropdown_prompt, hence do not use it
+                if (hadPromptView || convertView == null) {
+                    convertView = getLayoutInflater().inflate(
+                            R.layout.printer_dropdown_item, parent, false);
+                }
             }
 
             CharSequence title = null;
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 9e48849..a37196e 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -348,4 +348,7 @@
     <!-- Header for items under the work user [CHAR LIMIT=30] -->
     <string name="category_work">Work</string>
 
+    <!-- Full package name of OEM preferred device feedback reporter. Leave this blank, overlaid in Settings/TvSettings [DO NOT TRANSLATE] -->
+    <string name="oem_preferred_feedback_reporter" translatable="false" />
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
new file mode 100644
index 0000000..ff1c866
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DeviceInfoUtils {
+    private static final String TAG = "DeviceInfoUtils";
+
+    private static final String FILENAME_PROC_VERSION = "/proc/version";
+    private static final String FILENAME_MSV = "/sys/board_properties/soc/msv";
+
+    /**
+     * Reads a line from the specified file.
+     * @param filename the file to read from
+     * @return the first line, if any.
+     * @throws IOException if the file couldn't be read
+     */
+    private static String readLine(String filename) throws IOException {
+        BufferedReader reader = new BufferedReader(new FileReader(filename), 256);
+        try {
+            return reader.readLine();
+        } finally {
+            reader.close();
+        }
+    }
+
+    public static String getFormattedKernelVersion() {
+        try {
+            return formatKernelVersion(readLine(FILENAME_PROC_VERSION));
+        } catch (IOException e) {
+            Log.e(TAG, "IO Exception when getting kernel version for Device Info screen",
+                    e);
+
+            return "Unavailable";
+        }
+    }
+
+    public static String formatKernelVersion(String rawKernelVersion) {
+        // Example (see tests for more):
+        // Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com) \
+        //     (gcc version 4.6.x-xxx 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT \
+        //     Thu Jun 28 11:02:39 PDT 2012
+
+        final String PROC_VERSION_REGEX =
+                "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */
+                "\\((\\S+?)\\) " +        /* group 2: "x@y.com" (kernel builder) */
+                "(?:\\(gcc.+? \\)) " +    /* ignore: GCC version information */
+                "(#\\d+) " +              /* group 3: "#1" */
+                "(?:.*?)?" +              /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
+                "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 4: "Thu Jun 28 11:02:39 PDT 2012" */
+
+        Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion);
+        if (!m.matches()) {
+            Log.e(TAG, "Regex did not match on /proc/version: " + rawKernelVersion);
+            return "Unavailable";
+        } else if (m.groupCount() < 4) {
+            Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount()
+                    + " groups");
+            return "Unavailable";
+        }
+        return m.group(1) + "\n" +                 // 3.0.31-g6fb96c9
+                m.group(2) + " " + m.group(3) + "\n" + // x@y.com #1
+                m.group(4);                            // Thu Jun 28 11:02:39 PDT 2012
+    }
+
+    /**
+     * Returns " (ENGINEERING)" if the msv file has a zero value, else returns "".
+     * @return a string to append to the model number description.
+     */
+    public static String getMsvSuffix() {
+        // Production devices should have a non-zero value. If we can't read it, assume it's a
+        // production device so that we don't accidentally show that it's an ENGINEERING device.
+        try {
+            String msv = readLine(FILENAME_MSV);
+            // Parse as a hex number. If it evaluates to a zero, then it's an engineering build.
+            if (Long.parseLong(msv, 16) == 0) {
+                return " (ENGINEERING)";
+            }
+        } catch (IOException|NumberFormatException e) {
+            // Fail quietly, as the file may not exist on some devices, or may be unreadable
+        }
+        return "";
+    }
+
+    public static String getFeedbackReporterPackage(Context context) {
+        final String feedbackReporter =
+                context.getResources().getString(R.string.oem_preferred_feedback_reporter);
+        if (TextUtils.isEmpty(feedbackReporter)) {
+            // Reporter not configured. Return.
+            return feedbackReporter;
+        }
+        // Additional checks to ensure the reporter is on system image, and reporter is
+        // configured to listen to the intent. Otherwise, dont show the "send feedback" option.
+        final Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+
+        PackageManager pm = context.getPackageManager();
+        List<ResolveInfo> resolvedPackages =
+                pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
+        for (ResolveInfo info : resolvedPackages) {
+            if (info.activityInfo != null) {
+                if (!TextUtils.isEmpty(info.activityInfo.packageName)) {
+                    try {
+                        ApplicationInfo ai =
+                                pm.getApplicationInfo(info.activityInfo.packageName, 0);
+                        if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                            // Package is on the system image
+                            if (TextUtils.equals(
+                                    info.activityInfo.packageName, feedbackReporter)) {
+                                return feedbackReporter;
+                            }
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                        // No need to do anything here.
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public static String getSecurityPatch() {
+        String patch = Build.VERSION.SECURITY_PATCH;
+        if (!"".equals(patch)) {
+            try {
+                SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd");
+                Date patchDate = template.parse(patch);
+                String format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dMMMMyyyy");
+                patch = DateFormat.format(format, patchDate).toString();
+            } catch (ParseException e) {
+                // broken parse; fall through and use the raw string
+            }
+            return patch;
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d5f9557e..00b10a1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1067,6 +1067,9 @@
     <!-- Name of status bar -->
     <string name="status_bar">Status bar</string>
 
+    <!-- Name of overview -->
+    <string name="overview">Overview</string>
+
     <!-- Name of demo mode (mode with preset icons for screenshots) -->
     <string name="demo_mode">Demo mode</string>
 
@@ -1156,6 +1159,11 @@
     <!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] -->
     <string name="qs_paging" translatable="false">Use the new Quick Settings</string>
 
+    <!-- Toggles fast-toggling recents via the recents button -->
+    <string name="overview_fast_toggle_via_button">Enable fast toggle</string>
+    <!-- Description for the toggle for fast-toggling recents via the recents button -->
+    <string name="overview_fast_toggle_via_button_desc">Enable paging via the Overview button</string>
+
     <!-- Category in the System UI Tuner settings, where new/experimental
          settings are -->
     <string name="experimental">Experimental</string>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 5980108..c36cab8 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -87,6 +87,17 @@
 
     </PreferenceScreen>
 
+
+    <PreferenceScreen
+        android:title="@string/overview" >
+
+        <com.android.systemui.tuner.TunerSwitch
+            android:key="overview_fast_toggle"
+            android:title="@string/overview_fast_toggle_via_button"
+            android:summary="@string/overview_fast_toggle_via_button_desc" />
+
+    </PreferenceScreen>
+
     <SwitchPreference
         android:key="battery_pct"
         android:title="@string/show_battery_percentage"
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 3657cf2..9d98772 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -30,7 +30,8 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @StringDef({
-        Key.SEARCH_APP_WIDGET_ID,
+        Key.OVERVIEW_SEARCH_APP_WIDGET_ID,
+        Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
         Key.DEBUG_MODE_ENABLED,
         Key.HOTSPOT_TILE_LAST_USED,
         Key.COLOR_INVERSION_TILE_LAST_USED,
@@ -43,8 +44,8 @@
         Key.DND_FAVORITE_ZEN,
     })
     public @interface Key {
-        String SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
-        String SEARCH_APP_WIDGET_PACKAGE = "searchAppWidgetPackage";
+        String OVERVIEW_SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
+        String OVERVIEW_SEARCH_APP_WIDGET_PACKAGE = "searchAppWidgetPackage";
         String DEBUG_MODE_ENABLED = "debugModeEnabled";
         String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
         String COLOR_INVERSION_TILE_LAST_USED = "ColorInversionTileLastUsed";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index a429447..c08fb05 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -29,34 +29,4 @@
         public static final int DismissSourceHeaderButton = 2;
     }
 
-    // TODO: Move into RecentsDebugFlags
-    public static class DebugFlags {
-
-        public static class App {
-            // Enables debug drawing for the transition thumbnail
-            public static final boolean EnableTransitionThumbnailDebugMode = false;
-            // Enables the filtering of tasks according to their grouping
-            public static final boolean EnableTaskFiltering = false;
-            // Enables dismiss-all
-            public static final boolean EnableDismissAll = false;
-            // Enables fast-toggling by just tapping on the recents button
-            public static final boolean EnableFastToggleRecents = false;
-            // Enables the thumbnail alpha on the front-most task
-            public static final boolean EnableThumbnailAlphaOnFrontmost = false;
-            // This disables the search bar integration
-            public static final boolean DisableSearchBar = true;
-            // This disables the bitmap and icon caches
-            public static final boolean DisableBackgroundCache = false;
-            // Enables the simulated task affiliations
-            public static final boolean EnableSimulatedTaskGroups = false;
-            // Defines the number of mock task affiliations per group
-            public static final int TaskAffiliationsGroupCount = 12;
-            // Enables us to create mock recents tasks
-            public static final boolean EnableSystemServicesProxy = false;
-            // Defines the number of mock recents packages to create
-            public static final int SystemServicesProxyMockPackageCount = 3;
-            // Defines the number of mock recents tasks to create
-            public static final int SystemServicesProxyMockTaskCount = 100;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java
new file mode 100644
index 0000000..8ae8c53
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Event sent when the exit animation is started.
+ *
+ * This is sent so parts of UI can synchronize on this event and adjust their appearance. An example
+ * of that is hiding the tasks when the launched application window becomes visible.
+ */
+public class ExitRecentsWindowFirstAnimationFrameEvent extends EventBus.Event {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 95f1eb2..3806b46 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -62,6 +62,7 @@
     private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
 
     private static SystemServicesProxy sSystemServicesProxy;
+    private static RecentsDebugFlags sDebugFlags;
     private static RecentsTaskLoader sTaskLoader;
     private static RecentsConfiguration sConfiguration;
 
@@ -148,8 +149,13 @@
         return sConfiguration;
     }
 
+    public static RecentsDebugFlags getDebugFlags() {
+        return sDebugFlags;
+    }
+
     @Override
     public void start() {
+        sDebugFlags = new RecentsDebugFlags(mContext);
         sSystemServicesProxy = new SystemServicesProxy(mContext);
         sTaskLoader = new RecentsTaskLoader(mContext);
         sConfiguration = new RecentsConfiguration(mContext);
@@ -166,6 +172,7 @@
 
         // Register with the event bus
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
+        EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY);
         EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
 
         // Due to the fact that RecentsActivity is per-user, we need to establish and interface for
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 3ae8827..b4bb392 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -37,6 +37,7 @@
 import android.view.View;
 import android.view.ViewStub;
 import android.view.ViewTreeObserver;
+import android.view.WindowManager;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.events.EventBus;
@@ -44,6 +45,7 @@
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
@@ -216,14 +218,14 @@
                 mEmptyView = mEmptyViewStub.inflate();
             }
             mEmptyView.setVisibility(View.VISIBLE);
-            if (!Constants.DebugFlags.App.DisableSearchBar) {
+            if (!RecentsDebugFlags.Static.DisableSearchBar) {
                 mRecentsView.setSearchBarVisibility(View.GONE);
             }
         } else {
             if (mEmptyView != null) {
                 mEmptyView.setVisibility(View.GONE);
             }
-            if (!Constants.DebugFlags.App.DisableSearchBar) {
+            if (!RecentsDebugFlags.Static.DisableSearchBar) {
                 if (mRecentsView.hasValidSearchBar()) {
                     mRecentsView.setSearchBarVisibility(View.VISIBLE);
                 } else {
@@ -337,7 +339,7 @@
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
 
         // Initialize the widget host (the host id is static and does not change)
-        if (!Constants.DebugFlags.App.DisableSearchBar) {
+        if (!RecentsDebugFlags.Static.DisableSearchBar) {
             mAppWidgetHost = new RecentsAppWidgetHost(this, RecentsAppWidgetHost.HOST_ID);
         }
         mPackageMonitor = new RecentsPackageMonitor();
@@ -352,6 +354,8 @@
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
         mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
         mScrimViews = new SystemBarScrimViews(this);
+        getWindow().getAttributes().privateFlags |=
+                WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 
         // Create the home intent runnable
         Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -361,14 +365,14 @@
         mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
 
         // Bind the search app widget when we first start up
-        if (!Constants.DebugFlags.App.DisableSearchBar) {
+        if (!RecentsDebugFlags.Static.DisableSearchBar) {
             mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
         }
 
         // Register the broadcast receiver to handle messages when the screen is turned off
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        if (!Constants.DebugFlags.App.DisableSearchBar) {
+        if (!RecentsDebugFlags.Static.DisableSearchBar) {
             filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
         }
         registerReceiver(mSystemBroadcastReceiver, filter);
@@ -430,12 +434,8 @@
     protected void onPause() {
         super.onPause();
 
-        if (Constants.DebugFlags.App.EnableFastToggleRecents) {
-            // Stop the fast-toggle dozer
-            mIterateTrigger.stopDozing();
-        }
-
-        if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+        RecentsDebugFlags flags = Recents.getDebugFlags();
+        if (flags.isFastToggleRecentsEnabled()) {
             // Stop the fast-toggle dozer
             mIterateTrigger.stopDozing();
         }
@@ -480,7 +480,7 @@
         mPackageMonitor.unregister();
 
         // Stop listening for widget package changes if there was one bound
-        if (!Constants.DebugFlags.App.DisableSearchBar) {
+        if (!RecentsDebugFlags.Static.DisableSearchBar) {
             mAppWidgetHost.stopListening();
         }
 
@@ -614,7 +614,7 @@
         ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
         ctx.postAnimationTrigger.increment();
         if (mSearchWidgetInfo != null) {
-            if (!Constants.DebugFlags.App.DisableSearchBar) {
+            if (!RecentsDebugFlags.Static.DisableSearchBar) {
                 ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                     @Override
                     public void run() {
@@ -633,8 +633,8 @@
                 // the dozer now
                 RecentsConfiguration config = Recents.getConfiguration();
                 RecentsActivityLaunchState launchState = config.getLaunchState();
-                if (Constants.DebugFlags.App.EnableFastToggleRecents &&
-                        !launchState.launchedWithAltTab) {
+                RecentsDebugFlags flags = Recents.getDebugFlags();
+                if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
                     mIterateTrigger.startDozing();
                 }
             }
@@ -648,6 +648,11 @@
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
     }
 
+    public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
+        mRecentsView.setStackViewVisibility(View.INVISIBLE);
+        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+    }
+
     public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         int launchToTaskId = launchState.launchedToTaskId;
@@ -717,6 +722,11 @@
         MetricsLogger.count(this, "overview_screen_pinned", 1);
     }
 
+    public final void onBusEvent(DebugFlagsChangedEvent event) {
+        // Just finish recents so that we can reload the flags anew on the next instantiation
+        finish();
+    }
+
     private void refreshSearchWidgetView() {
         if (mSearchWidgetInfo != null) {
             SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 01ffd2a..7f7dbce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -74,7 +74,8 @@
      * Returns the task to focus given the current launch state.
      */
     public int getInitialFocusTaskIndex(int numTasks) {
-        if (Constants.DebugFlags.App.EnableFastToggleRecents && !launchedWithAltTab) {
+        RecentsDebugFlags flags = Recents.getDebugFlags();
+        if (flags.isFastToggleRecentsEnabled() && !launchedWithAltTab) {
             // If we are fast toggling, then focus the next task depending on when you are on home
             // or coming in from another app
             if (launchedFromHome) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index a73f323..8f952be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -154,7 +154,7 @@
             int swInset = getInsetToSmallestWidth(windowBounds.right - windowBounds.left);
             int top = searchBarBounds.isEmpty() ? topInset : 0;
             taskStackBounds.set(windowBounds.left + swInset, searchBarBounds.bottom + top,
-                    windowBounds.right - swInset, windowBounds.bottom);
+                    windowBounds.right - swInset - rightInset, windowBounds.bottom);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
new file mode 100644
index 0000000..6c74a4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 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.recents;
+
+import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
+import com.android.systemui.tuner.TunerService;
+
+/**
+ * Tunable debug flags
+ */
+public class RecentsDebugFlags implements TunerService.Tunable {
+
+    private static final String KEY_FAST_TOGGLE = "overview_fast_toggle";
+
+    public static class Static {
+        // Enables debug drawing for the transition thumbnail
+        public static final boolean EnableTransitionThumbnailDebugMode = false;
+        // This disables the search bar integration
+        public static final boolean DisableSearchBar = true;
+        // This disables the bitmap and icon caches
+        public static final boolean DisableBackgroundCache = false;
+        // Enables the simulated task affiliations
+        public static final boolean EnableSimulatedTaskGroups = false;
+        // Defines the number of mock task affiliations per group
+        public static final int TaskAffiliationsGroupCount = 12;
+        // Enables us to create mock recents tasks
+        public static final boolean EnableSystemServicesProxy = false;
+        // Defines the number of mock recents packages to create
+        public static final int SystemServicesProxyMockPackageCount = 3;
+        // Defines the number of mock recents tasks to create
+        public static final int SystemServicesProxyMockTaskCount = 100;
+    }
+
+    private boolean mForceEnableFreeformWorkspace;
+    private boolean mEnableFastToggleRecents;
+
+    /**
+     * We read the prefs once when we start the activity, then update them as the tuner changes
+     * the flags.
+     */
+    public RecentsDebugFlags(Context context) {
+        // Register all our flags, this will also call onTuningChanged() for each key, which will
+        // initialize the current state of each flag
+        TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE);
+    }
+
+    /**
+     * @return whether we are enabling fast toggling.
+     */
+    public boolean isFastToggleRecentsEnabled() {
+        return mEnableFastToggleRecents;
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        switch (key) {
+            case KEY_FAST_TOGGLE:
+                mEnableFastToggleRecents = (newValue != null) &&
+                        (Integer.parseInt(newValue) != 0);
+                break;
+        }
+        EventBus.getDefault().send(new DebugFlagsChangedEvent());
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 50aa2f7..85a2eda 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -68,8 +68,8 @@
  * An implementation of the Recents component for the current user.  For secondary users, this can
  * be called remotely from the system user.
  */
-public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
-        implements ActivityOptions.OnAnimationFinishedListener {
+public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
+        ActivityOptions.OnAnimationFinishedListener {
 
     private final static String TAG = "RecentsImpl";
     private final static boolean DEBUG = false;
@@ -324,8 +324,8 @@
             if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
                 RecentsConfiguration config = Recents.getConfiguration();
                 RecentsActivityLaunchState launchState = config.getLaunchState();
-                if (Constants.DebugFlags.App.EnableFastToggleRecents &&
-                        !launchState.launchedWithAltTab) {
+                RecentsDebugFlags flags = Recents.getDebugFlags();
+                if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
                     // Notify recents to move onto the next task
                     EventBus.getDefault().post(new IterateRecentsEvent());
                 } else {
@@ -555,7 +555,7 @@
         // Update the configuration for the current state
         config.update(mContext, ssp, ssp.getWindowRect());
 
-        if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) {
+        if (!RecentsDebugFlags.Static.DisableSearchBar && tryAndBindSearchWidget) {
             // Try and pre-emptively bind the search widget on startup to ensure that we
             // have the right thumbnail bounds to animate to.
             // Note: We have to reload the widget id before we get the task stack bounds below
@@ -758,7 +758,7 @@
                 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
                 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
                         Bitmap.Config.ARGB_8888);
-                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+                if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
                     thumbnail.eraseColor(0xFFff0000);
                 } else {
                     Canvas c = new Canvas(thumbnail);
@@ -816,11 +816,11 @@
         if (!useThumbnailTransition) {
             // If there is no thumbnail transition, but is launching from home into recents, then
             // use a quick home transition and do the animation from home
-            if (!Constants.DebugFlags.App.DisableSearchBar && hasRecentTasks) {
+            if (!RecentsDebugFlags.Static.DisableSearchBar && hasRecentTasks) {
                 SystemServicesProxy ssp = Recents.getSystemServices();
                 String homeActivityPackage = ssp.getHomeActivityPackageName();
                 String searchWidgetPackage = Prefs.getString(mContext,
-                        Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
+                        Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
 
                 // Determine whether we are coming from a search owned home activity
                 boolean fromSearchHome = (homeActivityPackage != null) &&
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
new file mode 100644
index 0000000..fe3bf26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the SystemUI tuner changes a flag.
+ */
+public class DebugFlagsChangedEvent extends EventBus.Event {
+    // Simple event
+}
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 0432ac9..16d6929 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -63,7 +63,8 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.RecentsImpl;
 
 import java.io.IOException;
@@ -113,6 +114,7 @@
 
     /** Private constructor */
     public SystemServicesProxy(Context context) {
+        RecentsDebugFlags flags = Recents.getDebugFlags();
         mAccm = AccessibilityManager.getInstance(context);
         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         mIam = ActivityManagerNative.getDefault();
@@ -124,8 +126,8 @@
         mUm = UserManager.get(context);
         mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
-        mHasFreeformWorkspaceSupport = false && mPm.hasSystemFeature(
-                PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+        mHasFreeformWorkspaceSupport = false &&
+                mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
 
         // Get the dummy thumbnail width/heights
         Resources res = context.getResources();
@@ -143,7 +145,7 @@
         // Resolve the assist intent
         mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId());
 
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             // Create a dummy icon
             mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
             mDummyIcon.eraseColor(0xFF999999);
@@ -156,13 +158,13 @@
         if (mAm == null) return null;
 
         // If we are mocking, then create some recent tasks
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             ArrayList<ActivityManager.RecentTaskInfo> tasks =
                     new ArrayList<ActivityManager.RecentTaskInfo>();
-            int count = Math.min(numLatestTasks, Constants.DebugFlags.App.SystemServicesProxyMockTaskCount);
+            int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.SystemServicesProxyMockTaskCount);
             for (int i = 0; i < count; i++) {
                 // Create a dummy component name
-                int packageIndex = i % Constants.DebugFlags.App.SystemServicesProxyMockPackageCount;
+                int packageIndex = i % RecentsDebugFlags.Static.SystemServicesProxyMockPackageCount;
                 ComponentName cn = new ComponentName("com.android.test" + packageIndex,
                         "com.android.test" + i + ".Activity");
                 String description = "" + i + " - " +
@@ -381,7 +383,7 @@
         if (mAm == null) return null;
 
         // If we are mocking, then just return a dummy thumbnail
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight,
                     Bitmap.Config.ARGB_8888);
             thumbnail.eraseColor(0xff333333);
@@ -443,7 +445,7 @@
     /** Moves a task to the front with the specified activity options. */
     public void moveTaskToFront(int taskId, ActivityOptions opts) {
         if (mAm == null) return;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return;
 
         if (opts != null) {
             mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME,
@@ -456,7 +458,7 @@
     /** Removes the task */
     public void removeTask(final int taskId) {
         if (mAm == null) return;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return;
 
         // Remove the task.
         BackgroundThread.getHandler().post(new Runnable() {
@@ -475,7 +477,7 @@
      */
     public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
         if (mIpm == null) return null;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return new ActivityInfo();
 
         try {
             return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
@@ -492,7 +494,7 @@
      */
     public ActivityInfo getActivityInfo(ComponentName cn) {
         if (mPm == null) return null;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return new ActivityInfo();
 
         try {
             return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
@@ -507,7 +509,7 @@
         if (mPm == null) return null;
 
         // If we are mocking, then return a mock label
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             return "Recent Task";
         }
 
@@ -519,7 +521,7 @@
         if (mPm == null) return null;
 
         // If we are mocking, then return a mock label
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             return "Recent Task";
         }
 
@@ -549,7 +551,7 @@
         if (mPm == null) return null;
 
         // If we are mocking, then return a mock label
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
             return new ColorDrawable(0xFF666666);
         }
 
@@ -580,7 +582,7 @@
     /** Returns the package name of the home activity. */
     public String getHomeActivityPackageName() {
         if (mPm == null) return null;
-        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return null;
+        if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return null;
 
         ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
         ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
@@ -623,22 +625,22 @@
      * Returns the current search widget id.
      */
     public int getSearchAppWidgetId(Context context) {
-        return Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
+        return Prefs.getInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, -1);
     }
 
     /**
      * Returns the current search widget info, binding a new one if necessary.
      */
     public AppWidgetProviderInfo getOrBindSearchAppWidget(Context context, AppWidgetHost host) {
-        int searchWidgetId = Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
+        int searchWidgetId = Prefs.getInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, -1);
         AppWidgetProviderInfo searchWidgetInfo = mAwm.getAppWidgetInfo(searchWidgetId);
         AppWidgetProviderInfo resolvedSearchWidgetInfo = resolveSearchAppWidget();
 
         // Return the search widget info if it hasn't changed
         if (searchWidgetInfo != null && resolvedSearchWidgetInfo != null &&
                 searchWidgetInfo.provider.equals(resolvedSearchWidgetInfo.provider)) {
-            if (Prefs.getString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null) == null) {
-                Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
+            if (Prefs.getString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null) == null) {
+                Prefs.putString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
                         searchWidgetInfo.provider.getPackageName());
             }
             return searchWidgetInfo;
@@ -654,16 +656,16 @@
             Pair<Integer, AppWidgetProviderInfo> widgetInfo = bindSearchAppWidget(host,
                     resolvedSearchWidgetInfo);
             if (widgetInfo != null) {
-                Prefs.putInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, widgetInfo.first);
-                Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
+                Prefs.putInt(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID, widgetInfo.first);
+                Prefs.putString(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE,
                         widgetInfo.second.provider.getPackageName());
                 return widgetInfo.second;
             }
         }
 
         // If we fall through here, then there is no resolved search widget, so clear the state
-        Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_ID);
-        Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE);
+        Prefs.remove(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_ID);
+        Prefs.remove(context, Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE);
         return null;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index bba453a..fe67fd9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -30,9 +30,9 @@
 import android.util.Log;
 import android.util.LruCache;
 import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
@@ -283,9 +283,9 @@
                 res.getColor(R.color.recents_task_bar_default_background_color);
         mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
         mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
-        int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
+        int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
                 mMaxIconCacheSize;
-        int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
+        int thumbnailCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
                 mMaxThumbnailCacheSize;
 
         // Create the default assets
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 6734012..f26dcde 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -24,8 +24,8 @@
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.misc.NamedCounter;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
@@ -543,7 +543,7 @@
      * Temporary: This method will simulate affiliation groups by
      */
     public void createAffiliatedGroupings(Context context) {
-        if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
+        if (RecentsDebugFlags.Static.EnableSimulatedTaskGroups) {
             HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
             // Sort all tasks by increasing firstActiveTime of the task
             ArrayList<Task> tasks = mTaskList.getTasks();
@@ -559,7 +559,7 @@
             String prevPackage = "";
             int prevAffiliation = -1;
             Random r = new Random();
-            int groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
+            int groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount;
             for (int i = 0; i < taskCount; i++) {
                 Task t = tasks.get(i);
                 String packageName = t.key.getComponent().getPackageName();
@@ -574,7 +574,7 @@
                     addGroup(group);
                     prevAffiliation = affiliation;
                     prevPackage = packageName;
-                    groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
+                    groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount;
                 }
                 group.addTask(t);
                 taskMap.put(t.key, t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 85b8fcf..4ecb80a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -27,13 +27,14 @@
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.AppTransitionAnimationSpec;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.WindowManagerGlobal;
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.ExitRecentsWindowFirstAnimationFrameEvent;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
@@ -106,6 +107,7 @@
                     // If we are launching into another task, cancel the previous task's
                     // window transition
                     EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
 
                     if (lockToTask) {
                         // Request screen pinning after the animation runs
@@ -116,7 +118,12 @@
         } else {
             // This is only the case if the task is not on screen (scrolled offscreen for example)
             transitionFuture = null;
-            animStartedListener = null;
+            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+                @Override
+                public void onAnimationStarted() {
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                }
+            };
         }
 
         if (taskView == null) {
@@ -313,7 +320,7 @@
             b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
                     Bitmap.Config.ARGB_8888);
 
-            if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+            if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
                 b.eraseColor(0xFFff0000);
             } else {
                 Canvas c = new Canvas(b);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 51091c3..b3bd6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -201,7 +201,7 @@
                 taskStackBounds.right - widthPadding,
                 taskStackBounds.bottom);
         // Anchor the task rect to the top-center of the non-freeform stack rect
-        int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
+        int size = mStackRect.width();
         mTaskRect.set(mStackRect.left, mStackRect.top,
                 mStackRect.left + size, mStackRect.top + size);
         mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
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 a57ac9d..7250d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -114,6 +114,7 @@
     HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
     ArrayList<TaskView> mTaskViews = new ArrayList<>();
     List<TaskView> mImmutableTaskViews = new ArrayList<>();
+    List<TaskView> mTmpTaskViews = new ArrayList<>();
     LayoutInflater mInflater;
     boolean mLayersDisabled;
     boolean mTouchExplorationEnabled;
@@ -279,14 +280,9 @@
         }
 
         // Mark each task view for relayout
-        if (mViewPool != null) {
-            Iterator<TaskView> iter = mViewPool.poolViewIterator();
-            if (iter != null) {
-                while (iter.hasNext()) {
-                    TaskView tv = iter.next();
-                    tv.reset();
-                }
-            }
+        List<TaskView> poolViews = mViewPool.getViews();
+        for (TaskView tv : poolViews) {
+            tv.reset();
         }
 
         // Reset the stack state
@@ -862,10 +858,12 @@
         }
 
         // Measure each of the TaskViews
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
+        mTmpTaskViews.clear();
+        mTmpTaskViews.addAll(getTaskViews());
+        mTmpTaskViews.addAll(mViewPool.getViews());
+        int taskViewCount = mTmpTaskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
+            TaskView tv = mTmpTaskViews.get(i);
             if (tv.getBackground() != null) {
                 tv.getBackground().getPadding(mTmpRect);
             } else {
@@ -891,10 +889,12 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         // Layout each of the TaskViews
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
+        mTmpTaskViews.clear();
+        mTmpTaskViews.addAll(getTaskViews());
+        mTmpTaskViews.addAll(mViewPool.getViews());
+        int taskViewCount = mTmpTaskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
+            TaskView tv = mTmpTaskViews.get(i);
             if (tv.getBackground() != null) {
                 tv.getBackground().getPadding(mTmpRect);
             } else {
@@ -911,6 +911,9 @@
         }
 
         if (changed) {
+            if (mStackScroller.isScrollOutOfBounds()) {
+                mStackScroller.boundScroll();
+            }
             requestSynchronizeStackViewsWithModel();
             synchronizeStackViewsWithModel();
             clipTaskViews(true /* forceUpdate */);
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 cb7465d..2c8f316 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -45,6 +45,7 @@
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
@@ -324,22 +325,9 @@
 
         if (launchState.launchedFromAppWithThumbnail) {
             if (mTask.isLaunchTarget) {
-                // Animate the dim/overlay
-                if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) {
-                    // Animate the thumbnail alpha before the dim animation (to prevent updating the
-                    // hardware layer)
-                    mThumbnailView.startEnterRecentsAnimation(new Runnable() {
-                            @Override
-                            public void run() {
-                                animateDimToProgress(taskViewEnterFromAppDuration,
-                                        ctx.postAnimationTrigger.decrementOnAnimationEnd());
-                            }
-                        });
-                } else {
-                    // Immediately start the dim animation
-                    animateDimToProgress(taskViewEnterFromAppDuration,
-                            ctx.postAnimationTrigger.decrementOnAnimationEnd());
-                }
+                // Immediately start the dim animation
+                animateDimToProgress(taskViewEnterFromAppDuration,
+                        ctx.postAnimationTrigger.decrementOnAnimationEnd());
                 ctx.postAnimationTrigger.increment();
 
                 // Animate the action button in
@@ -635,7 +623,9 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        if (Constants.DebugFlags.App.EnableFastToggleRecents && mIsFocused) {
+
+        RecentsDebugFlags flags = Recents.getDebugFlags();
+        if (flags.isFastToggleRecentsEnabled() && mIsFocused) {
             Paint tmpPaint = new Paint();
             Rect tmpRect = new Rect();
             tmpRect.set(0, 0, getWidth(), getHeight());
@@ -676,7 +666,8 @@
                 clearAccessibilityFocus();
             }
         }
-        if (Constants.DebugFlags.App.EnableFastToggleRecents) {
+        RecentsDebugFlags flags = Recents.getDebugFlags();
+        if (flags.isFastToggleRecentsEnabled()) {
             invalidate();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
index 12b91af..31fbd3e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
@@ -20,6 +20,7 @@
 
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;
 
 
 /* A view pool to manage more views than we can visibly handle */
@@ -76,11 +77,10 @@
         return v;
     }
 
-    /** Returns an iterator to the list of the views in the pool. */
-    Iterator<V> poolViewIterator() {
-        if (mPool != null) {
-            return mPool.iterator();
-        }
-        return null;
+    /**
+     * Returns the list of views in the pool.
+     */
+    List<V> getViews() {
+        return mPool;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 3f0000e..d3d9bef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -62,6 +62,7 @@
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -175,6 +176,7 @@
     protected boolean mDeviceInteractive;
 
     protected boolean mVisible;
+    protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
 
     // mScreenOnFromKeyguard && mVisible.
     private boolean mVisibleToUser;
@@ -468,7 +470,6 @@
                         processForRemoteInput(sbn.getNotification());
                         String key = sbn.getKey();
                         boolean isUpdate = mNotificationData.get(key) != null;
-
                         // In case we don't allow child notifications, we ignore children of
                         // notifications that have a summary, since we're not going to show them
                         // anyway. This is true also when the summary is canceled,
@@ -1664,7 +1665,10 @@
                 return;
             }
 
-            final PendingIntent intent = sbn.getNotification().contentIntent;
+            Notification notification = sbn.getNotification();
+            final PendingIntent intent = notification.contentIntent != null
+                    ? notification.contentIntent
+                    : notification.fullScreenIntent;
             final String notificationKey = sbn.getKey();
 
             // Mark notification for one frame.
@@ -1746,8 +1750,8 @@
         }
 
         public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
-            final PendingIntent contentIntent = sbn.getNotification().contentIntent;
-            if (contentIntent != null) {
+            Notification notification = sbn.getNotification();
+            if (notification.contentIntent != null || notification.fullScreenIntent != null) {
                 row.setOnClickListener(this);
             } else {
                 row.setOnClickListener(null);
@@ -2013,9 +2017,12 @@
         Entry entry = mNotificationData.get(key);
         if (entry == null) {
             return;
+        } else if (mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
         }
 
         Notification n = notification.getNotification();
+        mNotificationData.updateRanking(ranking);
 
         boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification());
         boolean shouldInterrupt = shouldInterrupt(entry, notification);
@@ -2070,7 +2077,6 @@
             inflateViews(entry, mStackScroller);
         }
         updateHeadsUp(key, entry, shouldInterrupt, alertAgain);
-        mNotificationData.updateRanking(ranking);
         updateNotifications();
 
         // Update the veto button accordingly (and as a result, whether this row is
@@ -2160,7 +2166,6 @@
         boolean accessibilityForcesLaunch = isFullscreen
                 && mAccessibilityManager.isTouchExplorationEnabled();
         boolean justLaunchedFullScreenIntent = entry.hasJustLaunchedFullScreenIntent();
-
         boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
                 && isAllowed
                 && !accessibilityForcesLaunch
@@ -2168,7 +2173,8 @@
                 && mPowerManager.isScreenOn()
                 && (!mStatusBarKeyguardViewManager.isShowing()
                         || mStatusBarKeyguardViewManager.isOccluded())
-                && !mStatusBarKeyguardViewManager.isInputRestricted();
+                && !mStatusBarKeyguardViewManager.isInputRestricted()
+                && !mNotificationData.shouldSuppressPeek(sbn.getKey());
         try {
             interrupt = interrupt && !mDreamManager.isDreaming();
         } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 6a90d8e..4328e24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -292,6 +292,15 @@
         return NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
     }
 
+    public boolean shouldSuppressPeek(String key) {
+        if (mRankingMap != null) {
+            mRankingMap.getRanking(key, mTmpRanking);
+            return (mTmpRanking.getSuppressedVisualEffects()
+                    & NotificationListenerService.SUPPRESSED_EFFECT_PEEK) != 0;
+        }
+        return false;
+    }
+
     private void updateRankingAndSort(RankingMap ranking) {
         if (ranking != null) {
             mRankingMap = ranking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index bbef1c0..fbe9730 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -84,10 +84,10 @@
             // the close future. See b/23676310 for reference.
             return;
         }
-        if (notif.isGroupSummary()) {
-            group.summary = null;
-        } else {
+        if (notif.isGroupChild()) {
             group.children.remove(removed);
+        } else {
+            group.summary = null;
         }
         if (group.children.isEmpty()) {
             if (group.summary == null) {
@@ -107,17 +107,17 @@
             group = new NotificationGroup();
             mGroupMap.put(groupKey, group);
         }
-        if (notif.isGroupSummary()) {
+        if (notif.isGroupChild()) {
+            group.children.add(added);
+            if (group.summary != null && group.children.size() == 1 && !group.expanded) {
+                group.summary.row.updateNotificationHeader();
+            }
+        } else {
             group.summary = added;
             group.expanded = added.row.areChildrenExpanded();
             if (!group.children.isEmpty()) {
                 mListener.onGroupCreatedFromChildren(group);
             }
-        } else {
-            group.children.add(added);
-            if (group.summary != null && group.children.size() == 1 && !group.expanded) {
-                group.summary.row.updateNotificationHeader();
-            }
         }
     }
 
@@ -169,7 +169,7 @@
      * @return whether a given notification is a summary in a group which has children
      */
     public boolean isSummaryOfGroup(StatusBarNotification sbn) {
-        if (sbn.getNotification().isGroupChild()) {
+        if (!sbn.getNotification().isGroupSummary()) {
             return false;
         }
         NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 3e52515..0cddf1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -617,7 +617,6 @@
     };
     private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
             = new HashMap<>();
-    private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
     private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
     private FalsingManager mFalsingManager;
@@ -1262,6 +1261,7 @@
             Entry oldEntry) {
         if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
 
+        mNotificationData.updateRanking(ranking);
         Entry shadeEntry = createNotificationViews(notification);
         if (shadeEntry == null) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index dc9f5e8..5cfd174 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -458,7 +458,10 @@
             mReleaseOnExpandFinish = false;
         } else {
             for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
-                removeHeadsUpEntry(entry);
+                if (isHeadsUp(entry.key)) {
+                    // Maybe the heads-up was removed already
+                    removeHeadsUpEntry(entry);
+                }
             }
         }
         mEntriesToRemoveAfterExpand.clear();
@@ -596,6 +599,9 @@
                 postTime = Math.max(postTime, currentTime);
             }
             removeAutoRemovalCallbacks();
+            if (mEntriesToRemoveAfterExpand.contains(entry)) {
+                mEntriesToRemoveAfterExpand.remove(entry);
+            }
             if (!hasFullScreenIntent(entry) && !mRemoteInputActive) {
                 long finishTime = postTime + mHeadsUpNotificationDecay;
                 long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index b52687a..5f6cbf9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -21,7 +21,6 @@
 import android.util.Pools.SimplePool;
 import android.util.Slog;
 import android.view.Choreographer;
-import android.view.Display;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputFilter;
@@ -103,7 +102,7 @@
 
     private TouchExplorer mTouchExplorer;
 
-    private ScreenMagnifier mScreenMagnifier;
+    private MagnificationGestureHandler mMagnificationGestureHandler;
 
     private AutoclickController mAutoclickController;
 
@@ -363,14 +362,13 @@
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
-            mScreenMagnifier = new ScreenMagnifier(mContext, mUserId,
-                    Display.DEFAULT_DISPLAY, mAms);
-            addFirstEventHandler(mScreenMagnifier);
+            mMagnificationGestureHandler = new MagnificationGestureHandler(mContext, mAms);
+            addFirstEventHandler(mMagnificationGestureHandler);
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
-           mKeyboardInterceptor = new KeyboardInterceptor(mAms);
-           addFirstEventHandler(mKeyboardInterceptor);
+            mKeyboardInterceptor = new KeyboardInterceptor(mAms);
+            addFirstEventHandler(mKeyboardInterceptor);
         }
     }
 
@@ -398,9 +396,9 @@
             mTouchExplorer.onDestroy();
             mTouchExplorer = null;
         }
-        if (mScreenMagnifier != null) {
-            mScreenMagnifier.onDestroy();
-            mScreenMagnifier = null;
+        if (mMagnificationGestureHandler != null) {
+            mMagnificationGestureHandler.onDestroy();
+            mMagnificationGestureHandler = null;
         }
         if (mKeyboardInterceptor != null) {
             mKeyboardInterceptor.onDestroy();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 535a8ef..9f1dc0a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -23,6 +23,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
@@ -90,6 +91,7 @@
 
 import com.android.internal.R;
 import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.server.LocalServices;
 
@@ -181,6 +183,8 @@
 
     private MagnificationController mMagnificationController;
 
+    private boolean mUnregisterMagnificationOnReset;
+
     private InteractionBridge mInteractionBridge;
 
     private AlertDialog mEnableTouchExplorationDialog;
@@ -762,6 +766,28 @@
     }
 
     /**
+     * Called by the MagnificationController when the state of display
+     * magnification changes.
+     *
+     * @param region the new magnified region, may be empty if
+     *               magnification is not enabled (e.g. scale is 1)
+     * @param scale the new scale
+     * @param centerX the new screen-relative center X coordinate
+     * @param centerY the new screen-relative center Y coordinate
+     */
+    void notifyMagnificationChanged(@NonNull Region region,
+            float scale, float centerX, float centerY) {
+        synchronized (mLock) {
+            notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+
+            if (mUnregisterMagnificationOnReset && scale == 1.0f) {
+                mUnregisterMagnificationOnReset = false;
+                mMagnificationController.unregister();
+            }
+        }
+    }
+
+    /**
      * Gets a point within the accessibility focused node where we can send down
      * and up events to perform a click.
      *
@@ -942,6 +968,15 @@
         }
     }
 
+    private void notifyMagnificationChangedLocked(@NonNull Region region,
+            float scale, float centerX, float centerY) {
+        final UserState state = getCurrentUserStateLocked();
+        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+            final Service service = state.mBoundServices.get(i);
+            service.notifyMagnificationChanged(region, scale, centerX, centerY);
+        }
+    }
+
     /**
      * Removes an AccessibilityInteractionConnection.
      *
@@ -1363,6 +1398,11 @@
         }
     }
 
+    /**
+     * Called when any property of the user state has changed.
+     *
+     * @param userState the new user state
+     */
     private void onUserStateChangedLocked(UserState userState) {
         // TODO: Remove this hack
         mInitialized = true;
@@ -1374,6 +1414,7 @@
         updateTouchExplorationLocked(userState);
         updateEnhancedWebAccessibilityLocked(userState);
         updateDisplayColorAdjustmentSettingsLocked(userState);
+        updateMagnificationLocked(userState);
         scheduleUpdateInputFilter(userState);
         scheduleUpdateClientsIfNeededLocked(userState);
     }
@@ -1663,6 +1704,44 @@
         DisplayAdjustmentUtils.applyAdjustments(mContext, userState.mUserId);
     }
 
+    private void updateMagnificationLocked(UserState userState) {
+        final int userId = userState.mUserId;
+        if (userId == mCurrentUserId && mMagnificationController != null) {
+            if (userHasMagnificationServicesLocked(userState)) {
+                mMagnificationController.setUserId(userState.mUserId);
+            } else {
+                // If the user no longer has any magnification-controlling
+                // services and is not using magnification gestures, then
+                // reset the state to normal.
+                if (!userState.mIsDisplayMagnificationEnabled
+                        && mMagnificationController.resetIfNeeded(true)) {
+                    // Animations are still running, so wait until we receive a
+                    // callback verifying that we've reset magnification.
+                    mUnregisterMagnificationOnReset = true;
+                } else {
+                    mUnregisterMagnificationOnReset = false;
+                    mMagnificationController.unregister();
+                    mMagnificationController = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns whether the specified user has any services that are capable of
+     * controlling magnification.
+     */
+    private boolean userHasMagnificationServicesLocked(UserState userState) {
+        final List<Service> services = userState.mBoundServices;
+        for (int i = 0, count = services.size(); i < count; i++) {
+            final Service service = services.get(i);
+            if (mSecurityPolicy.canControlMagnification(service)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
         IBinder windowToken = mGlobalWindowTokens.get(windowId);
         if (windowToken == null) {
@@ -1938,10 +2017,14 @@
     }
 
     MagnificationController getMagnificationController() {
-        if (mMagnificationController == null) {
-            mMagnificationController = new MagnificationController(mContext, this);
+        synchronized (mLock) {
+            if (mMagnificationController == null) {
+                mMagnificationController = new MagnificationController(mContext, this);
+                mMagnificationController.register();
+                mMagnificationController.setUserId(mCurrentUserId);
+            }
+            return mMagnificationController;
         }
-        return mMagnificationController;
     }
 
     /**
@@ -2625,6 +2708,149 @@
         }
 
         @Override
+        public float getMagnificationScale() {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return 1.0f;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().getScale();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public Region getMagnifiedRegion() {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return Region.obtain();
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                final Region region = Region.obtain();
+                getMagnificationController().getMagnifiedRegion(region);
+                return region;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public float getMagnificationCenterX() {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return 0.0f;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().getCenterX();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public float getMagnificationCenterY() {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return 0.0f;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().getCenterY();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public boolean resetMagnification(boolean animate) {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return false;
+                }
+                final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
+                if (!permissionGranted) {
+                    return false;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().reset(animate);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+                boolean animate) {
+            synchronized (mLock) {
+                // We treat calls from a profile as if made by its parent as profiles
+                // share the accessibility state of the parent. The call below
+                // performs the current profile parent resolution.
+                final int resolvedUserId = mSecurityPolicy
+                        .resolveCallingUserIdEnforcingPermissionsLocked(
+                                UserHandle.USER_CURRENT);
+                if (resolvedUserId != mCurrentUserId) {
+                    return false;
+                }
+                final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
+                if (!permissionGranted) {
+                    return false;
+                }
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getMagnificationController().setScaleAndCenter(
+                        scale, centerX, centerY, animate);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void setMagnificationCallbackEnabled(boolean enabled) {
+            mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+        }
+
+        @Override
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
             synchronized (mLock) {
@@ -2819,6 +3045,30 @@
                     InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
         }
 
+        public void notifyMagnificationChanged(@NonNull Region region,
+                float scale, float centerX, float centerY) {
+            mInvocationHandler.notifyMagnificationChanged(region, scale, centerX, centerY);
+        }
+
+        /**
+         * Called by the invocation handler to notify the service that the
+         * state of magnification has changed.
+         */
+        private void notifyMagnificationChangedInternal(@NonNull Region region,
+                float scale, float centerX, float centerY) {
+            final IAccessibilityServiceClient listener;
+            synchronized (mLock) {
+                listener = mServiceInterface;
+            }
+            if (listener != null) {
+                try {
+                    listener.onMagnificationChanged(region, scale, centerX, centerY);
+                } catch (RemoteException re) {
+                    Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+                }
+            }
+        }
+
         private void notifyGestureInternal(int gestureId) {
             final IAccessibilityServiceClient listener;
             synchronized (mLock) {
@@ -2959,6 +3209,10 @@
             public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 3;
             public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4;
 
+            private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
+
+            private boolean mIsMagnificationCallbackEnabled = false;
+
             public InvocationHandler(Looper looper) {
                 super(looper, null, true);
             }
@@ -2987,11 +3241,41 @@
                         setOnKeyEventResult(false, eventState.sequence);
                     } break;
 
+                    case MSG_ON_MAGNIFICATION_CHANGED: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final Region region = (Region) args.arg1;
+                        final float scale = (float) args.arg2;
+                        final float centerX = (float) args.arg3;
+                        final float centerY = (float) args.arg4;
+                        notifyMagnificationChangedInternal(region, scale, centerX, centerY);
+                    } break;
+
                     default: {
                         throw new IllegalArgumentException("Unknown message: " + type);
                     }
                 }
             }
+
+            public void notifyMagnificationChanged(@NonNull Region region, float scale,
+                    float centerX, float centerY) {
+                if (!mIsMagnificationCallbackEnabled) {
+                    // Callback is disabled, don't bother packing args.
+                    return;
+                }
+
+                final SomeArgs args = SomeArgs.obtain();
+                args.arg1 = region;
+                args.arg2 = scale;
+                args.arg3 = centerX;
+                args.arg4 = centerY;
+
+                final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
+                msg.sendToTarget();
+            }
+
+            public void setMagnificationCallbackEnabled(boolean enabled) {
+                mIsMagnificationCallbackEnabled = enabled;
+            }
         }
 
         private final class KeyEventDispatcher {
@@ -3660,6 +3944,11 @@
                     & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
         }
 
+        public boolean canControlMagnification(Service service) {
+            return (service.mAccessibilityServiceInfo.getCapabilities()
+                    & AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
+        }
+
         private int resolveProfileParentLocked(int userId) {
             if (userId != mCurrentUserId) {
                 final long identity = Binder.clearCallingIdentity();
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 781d134..a093d92 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -17,20 +17,36 @@
 package com.android.server.accessibility;
 
 import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
 import com.android.server.LocalServices;
 
 import android.animation.ObjectAnimator;
 import android.animation.TypeEvaluator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.MathUtils;
 import android.util.Property;
 import android.util.Slog;
 import android.view.MagnificationSpec;
+import android.view.View;
 import android.view.WindowManagerInternal;
 import android.view.animation.DecelerateInterpolator;
 
+import java.util.Locale;
+
 /**
  * This class is used to control and query the state of display magnification
  * from the accessibility manager and related classes. It is responsible for
@@ -38,37 +54,71 @@
  * communication between the accessibility manager and window manager.
  */
 class MagnificationController {
-    private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+    private static final String LOG_TAG = "MagnificationController";
 
     private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
-    private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
 
-    private static final String PROPERTY_NAME_MAGNIFICATION_SPEC = "magnificationSpec";
+    private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
 
-    private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+    private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+
+    private static final float MIN_SCALE = 1.0f;
+    private static final float MAX_SCALE = 5.0f;
+
+    /**
+     * The minimum scaling factor that can be persisted to secure settings.
+     * This must be > 1.0 to ensure that magnification is actually set to an
+     * enabled state when the scaling factor is restored from settings.
+     */
+    private static final float MIN_PERSISTED_SCALE = 2.0f;
+
+    private final Object mLock = new Object();
+
+    /**
+     * The current magnification spec. If an animation is running, this
+     * reflects the end state.
+     */
     private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
 
-    private final Region mMagnifiedBounds = new Region();
+    private final Region mMagnifiedRegion = Region.obtain();
+    private final Region mAvailableRegion = Region.obtain();
+    private final Rect mMagnifiedBounds = new Rect();
+
     private final Rect mTempRect = new Rect();
+    private final Rect mTempRect1 = new Rect();
 
     private final AccessibilityManagerService mAms;
-    private final WindowManagerInternal mWindowManager;
-    private final ValueAnimator mTransformationAnimator;
+    private final ContentResolver mContentResolver;
+
+    private final ScreenStateObserver mScreenStateObserver;
+    private final WindowStateObserver mWindowStateObserver;
+
+    private final SpecAnimationBridge mSpecAnimationBridge;
+
+    private int mUserId;
 
     public MagnificationController(Context context, AccessibilityManagerService ams) {
         mAms = ams;
-        mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+        mContentResolver = context.getContentResolver();
+        mScreenStateObserver = new ScreenStateObserver(context, this);
+        mWindowStateObserver = new WindowStateObserver(context, this);
+        mSpecAnimationBridge = new SpecAnimationBridge(context);
+    }
 
-        final Property<MagnificationController, MagnificationSpec> property =
-                Property.of(MagnificationController.class, MagnificationSpec.class,
-                        PROPERTY_NAME_MAGNIFICATION_SPEC);
-        final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
-        final long animationDuration = context.getResources().getInteger(
-                R.integer.config_longAnimTime);
-        mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
-                mSentMagnificationSpec, mCurrentMagnificationSpec);
-        mTransformationAnimator.setDuration(animationDuration);
-        mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+    /**
+     * Registers magnification-related observers.
+     */
+    public void register() {
+        mScreenStateObserver.register();
+        mWindowStateObserver.register();
+    }
+
+    /**
+     * Unregisters magnification-related observers.
+     */
+    public void unregister() {
+        mScreenStateObserver.unregister();
+        mWindowStateObserver.unregister();
     }
 
     /**
@@ -80,26 +130,33 @@
     }
 
     /**
-     * Sets the magnified region.
+     * Sets the magnified and available regions.
      *
-     * @param region the region to set
-     *  @param updateSpec {@code true} to update the scale and center based on
-     *                    the region bounds, {@code false} to leave them as-is
+     * @param magnified the magnified region
+     * @param available the region available for magnification
+     * @param updateSpec {@code true} to update the scale and center based on
+     *                   the region bounds, {@code false} to leave them as-is
      */
-    public void setMagnifiedRegion(Region region, boolean updateSpec) {
-        mMagnifiedBounds.set(region);
+    public void setMagnifiedRegion(Region magnified, Region available, boolean updateSpec) {
+        synchronized (mLock) {
+            mMagnifiedRegion.set(magnified);
+            mMagnifiedRegion.getBounds(mMagnifiedBounds);
+            mAvailableRegion.set(available);
 
-        if (updateSpec) {
-            final Rect magnifiedFrame = mTempRect;
-            region.getBounds(magnifiedFrame);
-            final float scale = mSentMagnificationSpec.scale;
-            final float offsetX = mSentMagnificationSpec.offsetX;
-            final float offsetY = mSentMagnificationSpec.offsetY;
-            final float centerX = (-offsetX + magnifiedFrame.width() / 2) / scale;
-            final float centerY = (-offsetY + magnifiedFrame.height() / 2) / scale;
-            setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, false);
-        } else {
-            mAms.onMagnificationStateChanged();
+            final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec;
+            final float scale = sentSpec.scale;
+            final float offsetX = sentSpec.offsetX;
+            final float offsetY = sentSpec.offsetY;
+
+            // Compute the new center and update spec as needed.
+            final float centerX = (mMagnifiedBounds.width() / 2.0f - offsetX) / scale;
+            final float centerY = (mMagnifiedBounds.height() / 2.0f - offsetY) / scale;
+            if (updateSpec) {
+                setScaleAndCenter(scale, centerX, centerY, false);
+            } else {
+                mAms.onMagnificationStateChanged();
+                mAms.notifyMagnificationChanged(mMagnifiedRegion, scale, centerX, centerY);
+            }
         }
     }
 
@@ -113,18 +170,51 @@
      *         magnified region, or {@code false} otherwise
      */
     public boolean magnifiedRegionContains(float x, float y) {
-        return mMagnifiedBounds.contains((int) x, (int) y);
+        synchronized (mLock) {
+            return mMagnifiedRegion.contains((int) x, (int) y);
+        }
     }
 
     /**
-     * Populates the specified rect with the bounds of the magnified
-     * region.
+     * Returns whether the region available for magnification contains the
+     * specified screen-relative coordinates.
+     *
+     * @param x the screen-relative X coordinate to check
+     * @param y the screen-relative Y coordinate to check
+     * @return {@code true} if the coordinate is contained within the
+     *         region available for magnification, or {@code false} otherwise
+     */
+    private boolean availableRegionContains(float x, float y) {
+        synchronized (mLock) {
+            return mAvailableRegion.contains((int) x, (int) y);
+        }
+    }
+
+    /**
+     * Populates the specified rect with the screen-relative bounds of the
+     * magnified region. If magnification is not enabled, the returned
+     * bounds will be empty.
      *
      * @param outBounds rect to populate with the bounds of the magnified
      *                  region
      */
-    public void getMagnifiedBounds(Rect outBounds) {
-        mMagnifiedBounds.getBounds(outBounds);
+    public void getMagnifiedBounds(@NonNull Rect outBounds) {
+        synchronized (mLock) {
+            outBounds.set(mMagnifiedBounds);
+        }
+    }
+
+    /**
+     * Populates the specified region with the screen-relative magnified
+     * region. If magnification is not enabled, then the returned region
+     * will be empty.
+     *
+     * @param outRegion the region to populate
+     */
+    public void getMagnifiedRegion(@NonNull Region outRegion) {
+        synchronized (mLock) {
+            outRegion.set(mMagnifiedRegion);
+        }
     }
 
     /**
@@ -147,6 +237,19 @@
         return mCurrentMagnificationSpec.offsetX;
     }
 
+
+    /**
+     * Returns the screen-relative X coordinate of the center of the
+     * magnification viewport.
+     *
+     * @return the X coordinate
+     */
+    public float getCenterX() {
+        synchronized (mLock) {
+            return  (mMagnifiedBounds.width() / 2.0f - getOffsetX()) / getScale();
+        }
+    }
+
     /**
      * Returns the Y offset of the magnification viewport. If an animation
      * is in progress, this reflects the end state of the animation.
@@ -158,6 +261,18 @@
     }
 
     /**
+     * Returns the screen-relative Y coordinate of the center of the
+     * magnification viewport.
+     *
+     * @return the Y coordinate
+     */
+    public float getCenterY() {
+        synchronized (mLock) {
+            return (mMagnifiedBounds.height() / 2.0f - getOffsetY()) / getScale();
+        }
+    }
+
+    /**
      * Returns the scale currently used by the window manager. If an
      * animation is in progress, this reflects the current state of the
      * animation.
@@ -165,7 +280,7 @@
      * @return the scale currently used by the window manager
      */
     public float getSentScale() {
-        return mSentMagnificationSpec.scale;
+        return mSpecAnimationBridge.mSentMagnificationSpec.scale;
     }
 
     /**
@@ -176,7 +291,7 @@
      * @return the X offset currently used by the window manager
      */
     public float getSentOffsetX() {
-        return mSentMagnificationSpec.offsetX;
+        return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
     }
 
     /**
@@ -187,7 +302,7 @@
      * @return the Y offset currently used by the window manager
      */
     public float getSentOffsetY() {
-        return mSentMagnificationSpec.offsetY;
+        return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
     }
 
     /**
@@ -196,21 +311,24 @@
      *
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
      */
-    public void reset(boolean animate) {
-        if (mTransformationAnimator.isRunning()) {
-            mTransformationAnimator.cancel();
+    public boolean reset(boolean animate) {
+        synchronized (mLock) {
+            return resetLocked(animate);
         }
-        mCurrentMagnificationSpec.clear();
-        if (animate) {
-            animateMagnificationSpec(mSentMagnificationSpec,
-                    mCurrentMagnificationSpec);
-        } else {
-            setMagnificationSpec(mCurrentMagnificationSpec);
+    }
+
+    private boolean resetLocked(boolean animate) {
+        final MagnificationSpec spec = mCurrentMagnificationSpec;
+        final boolean changed = !spec.isNop();
+        if (changed) {
+            spec.clear();
         }
-        final Rect bounds = mTempRect;
-        bounds.setEmpty();
-        mAms.onMagnificationStateChanged();
+
+        mSpecAnimationBridge.updateSentSpec(spec, animate);
+        return changed;
     }
 
     /**
@@ -219,23 +337,32 @@
      * transition is immediate.
      *
      * @param scale the target scale, must be >= 1
+     * @param pivotX the screen-relative X coordinate around which to scale
+     * @param pivotY the screen-relative Y coordinate around which to scale
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
      */
-    public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
-        final Rect magnifiedFrame = mTempRect;
-        mMagnifiedBounds.getBounds(magnifiedFrame);
-        final MagnificationSpec spec = mCurrentMagnificationSpec;
-        final float oldScale = spec.scale;
-        final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale;
-        final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale;
-        final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
-        final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
-        final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
-        final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
-        final float centerX = normPivotX + offsetX;
-        final float centerY = normPivotY + offsetY;
-        setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
+    public boolean setScale(float scale, float pivotX, float pivotY, boolean animate) {
+        synchronized (mLock) {
+            // Constrain scale immediately for use in the pivot calculations.
+            scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+
+            final Rect viewport = mTempRect;
+            mMagnifiedRegion.getBounds(viewport);
+            final MagnificationSpec spec = mCurrentMagnificationSpec;
+            final float oldScale = spec.scale;
+            final float oldCenterX = (viewport.width() / 2.0f - spec.offsetX) / oldScale;
+            final float oldCenterY = (viewport.height() / 2.0f - spec.offsetY) / oldScale;
+            final float normPivotX = (pivotX - spec.offsetX) / oldScale;
+            final float normPivotY = (pivotY - spec.offsetY) / oldScale;
+            final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+            final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+            final float centerX = normPivotX + offsetX;
+            final float centerY = normPivotY + offsetY;
+            return setScaleAndCenterLocked(scale, centerX, centerY, animate);
+        }
     }
 
     /**
@@ -248,10 +375,13 @@
      *                center
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
      */
-    public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
-        setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
-                animate);
+    public boolean setCenter(float centerX, float centerY, boolean animate) {
+        synchronized (mLock) {
+            return setScaleAndCenterLocked(Float.NaN, centerX, centerY, animate);
+        }
     }
 
     /**
@@ -259,35 +389,27 @@
      * animating the transition. If animation is disabled, the transition
      * is immediate.
      *
-     * @param scale the target scale, must be >= 1
+     * @param scale the target scale, or {@link Float#NaN} to leave unchanged
      * @param centerX the screen-relative X coordinate around which to
-     *                center and scale
+     *                center and scale, or {@link Float#NaN} to leave unchanged
      * @param centerY the screen-relative Y coordinate around which to
-     *                center and scale
+     *                center and scale, or {@link Float#NaN} to leave unchanged
      * @param animate {@code true} to animate the transition, {@code false}
      *                to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     *         the spec did not change
      */
-    public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
-            boolean animate) {
-        if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
-                && Float.compare(mCurrentMagnificationSpec.offsetX, centerX) == 0
-                && Float.compare(mCurrentMagnificationSpec.offsetY, centerY) == 0) {
-            return;
+    public boolean setScaleAndCenter(float scale, float centerX, float centerY, boolean animate) {
+        synchronized (mLock) {
+            return setScaleAndCenterLocked(scale, centerX, centerY, animate);
         }
-        if (mTransformationAnimator.isRunning()) {
-            mTransformationAnimator.cancel();
-        }
-        if (DEBUG_MAGNIFICATION_CONTROLLER) {
-            Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX + " offsetY: " + centerY);
-        }
-        updateMagnificationSpec(scale, centerX, centerY);
-        if (animate) {
-            animateMagnificationSpec(mSentMagnificationSpec,
-                    mCurrentMagnificationSpec);
-        } else {
-            setMagnificationSpec(mCurrentMagnificationSpec);
-        }
-        mAms.onMagnificationStateChanged();
+    }
+
+    private boolean setScaleAndCenterLocked(
+            float scale, float centerX, float centerY, boolean animate) {
+        final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
+        mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate);
+        return changed;
     }
 
     /**
@@ -297,75 +419,504 @@
      * @param offsetY the amount in pixels to offset the Y center
      */
     public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
-        final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
-        mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
-                getMinOffsetX()), 0);
-        final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
-        mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
-                getMinOffsetY()), 0);
-        setMagnificationSpec(mCurrentMagnificationSpec);
+        synchronized (mLock) {
+            final MagnificationSpec currSpec = mCurrentMagnificationSpec;
+            final float nonNormOffsetX = currSpec.offsetX - offsetX;
+            currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+            final float nonNormOffsetY = currSpec.offsetY - offsetY;
+            currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+            mSpecAnimationBridge.updateSentSpec(currSpec, false);
+        }
     }
 
-    private void updateMagnificationSpec(float scale, float magnifiedCenterX,
-            float magnifiedCenterY) {
-        final Rect magnifiedFrame = mTempRect;
-        mMagnifiedBounds.getBounds(magnifiedFrame);
-        mCurrentMagnificationSpec.scale = scale;
-        final int viewportWidth = magnifiedFrame.width();
-        final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
-        mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
-                getMinOffsetX()), 0);
-        final int viewportHeight = magnifiedFrame.height();
-        final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
-        mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
-                getMinOffsetY()), 0);
+    /**
+     * Persists the current magnification scale to the current user's settings.
+     */
+    public void persistScale() {
+        final float scale = mCurrentMagnificationSpec.scale;
+        final int userId = mUserId;
+
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                Settings.Secure.putFloatForUser(mContentResolver,
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId);
+                return null;
+            }
+        }.execute();
     }
 
-    private float getMinOffsetX() {
-        final Rect magnifiedFrame = mTempRect;
-        mMagnifiedBounds.getBounds(magnifiedFrame);
-        final float viewportWidth = magnifiedFrame.width();
+    /**
+     * Retrieves a previously persisted magnification scale from the current
+     * user's settings.
+     *
+     * @return the previously persisted magnification scale, or the default
+     *         scale if none is available
+     */
+    public float getPersistedScale() {
+        return Settings.Secure.getFloatForUser(mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                DEFAULT_MAGNIFICATION_SCALE, mUserId);
+    }
+
+    /**
+     * Updates the current magnification spec.
+     *
+     * @param scale the magnification scale
+     * @param centerX the unscaled, screen-relative X coordinate of the center
+     *                of the viewport, or {@link Float#NaN} to leave unchanged
+     * @param centerY the unscaled, screen-relative Y coordinate of the center
+     *                of the viewport, or {@link Float#NaN} to leave unchanged
+     * @return {@code true} if the magnification spec changed or {@code false}
+     *         otherwise
+     */
+    private boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
+        if (!availableRegionContains(centerX, centerY)) {
+            return false;
+        }
+
+        boolean changed = false;
+
+        final MagnificationSpec currSpec = mCurrentMagnificationSpec;
+
+        // Handle scale.
+        if (Float.isNaN(scale)) {
+            scale = getScale();
+        }
+
+        final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+        if (Float.compare(currSpec.scale, normScale) != 0) {
+            currSpec.scale = normScale;
+            changed = true;
+        }
+
+        // Handle X offset.
+        if (Float.isNaN(centerX)) {
+            centerX = getCenterX();
+        }
+
+        final float nonNormOffsetX = mMagnifiedBounds.width() / 2.0f - centerX * scale;
+        final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+        if (Float.compare(currSpec.offsetX, offsetX) != 0) {
+            currSpec.offsetX = offsetX;
+            changed = true;
+        }
+
+        // Handle Y offset.
+        if (Float.isNaN(centerY)) {
+            centerY = getCenterY();
+        }
+
+        final float nonNormOffsetY = mMagnifiedBounds.height() / 2.0f - centerY * scale;
+        final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+        if (Float.compare(currSpec.offsetY, offsetY) != 0) {
+            currSpec.offsetY = offsetY;
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    private float getMinOffsetXLocked() {
+        final float viewportWidth = mMagnifiedBounds.width();
         return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
     }
 
-    private float getMinOffsetY() {
-        final Rect magnifiedFrame = mTempRect;
-        mMagnifiedBounds.getBounds(magnifiedFrame);
-        final float viewportHeight = magnifiedFrame.height();
+    private float getMinOffsetYLocked() {
+        final float viewportHeight = mMagnifiedBounds.height();
         return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
     }
 
-    private void animateMagnificationSpec(MagnificationSpec fromSpec,
-            MagnificationSpec toSpec) {
-        mTransformationAnimator.setObjectValues(fromSpec, toSpec);
-        mTransformationAnimator.start();
-    }
+    /**
+     * Sets the currently active user ID.
+     *
+     * @param userId the currently active user ID
+     */
+    public void setUserId(int userId) {
+        if (mUserId != userId) {
+            mUserId = userId;
 
-    public void setMagnificationSpec(MagnificationSpec spec) {
-        if (DEBUG_SET_MAGNIFICATION_SPEC) {
-            Slog.i(LOG_TAG, "Sending: " + spec);
+            synchronized (mLock) {
+                if (isMagnifying()) {
+                    reset(false);
+                }
+            }
         }
-        mSentMagnificationSpec.scale = spec.scale;
-        mSentMagnificationSpec.offsetX = spec.offsetX;
-        mSentMagnificationSpec.offsetY = spec.offsetY;
-        mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
     }
 
-    public MagnificationSpec getMagnificationSpec() {
-        return mSentMagnificationSpec;
+    private boolean isScreenMagnificationAutoUpdateEnabled() {
+        return (Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+                DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
     }
 
-    private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> {
-        private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain();
+    /**
+     * Resets magnification if magnification and auto-update are both enabled.
+     *
+     * @param animate whether the animate the transition
+     * @return {@code true} if magnification was reset to the disabled state,
+     *         {@code false} if magnification is still active
+     */
+    boolean resetIfNeeded(boolean animate) {
+        synchronized (mLock) {
+            if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) {
+                reset(animate);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) {
+        final float scale = getSentScale();
+        final float offsetX = getSentOffsetX();
+        final float offsetY = getSentOffsetY();
+        getMagnifiedBounds(outFrame);
+        outFrame.offset((int) -offsetX, (int) -offsetY);
+        outFrame.scale(1.0f / scale);
+    }
+
+    private void requestRectangleOnScreen(int left, int top, int right, int bottom) {
+        synchronized (mLock) {
+            final Rect magnifiedFrame = mTempRect;
+            getMagnifiedBounds(magnifiedFrame);
+            if (!magnifiedFrame.intersects(left, top, right, bottom)) {
+                return;
+            }
+
+            final Rect magnifFrameInScreenCoords = mTempRect1;
+            getMagnifiedFrameInContentCoordsLocked(magnifFrameInScreenCoords);
+
+            final float scrollX;
+            final float scrollY;
+            if (right - left > magnifFrameInScreenCoords.width()) {
+                final int direction = TextUtils
+                        .getLayoutDirectionFromLocale(Locale.getDefault());
+                if (direction == View.LAYOUT_DIRECTION_LTR) {
+                    scrollX = left - magnifFrameInScreenCoords.left;
+                } else {
+                    scrollX = right - magnifFrameInScreenCoords.right;
+                }
+            } else if (left < magnifFrameInScreenCoords.left) {
+                scrollX = left - magnifFrameInScreenCoords.left;
+            } else if (right > magnifFrameInScreenCoords.right) {
+                scrollX = right - magnifFrameInScreenCoords.right;
+            } else {
+                scrollX = 0;
+            }
+
+            if (bottom - top > magnifFrameInScreenCoords.height()) {
+                scrollY = top - magnifFrameInScreenCoords.top;
+            } else if (top < magnifFrameInScreenCoords.top) {
+                scrollY = top - magnifFrameInScreenCoords.top;
+            } else if (bottom > magnifFrameInScreenCoords.bottom) {
+                scrollY = bottom - magnifFrameInScreenCoords.bottom;
+            } else {
+                scrollY = 0;
+            }
+
+            final float scale = getScale();
+            offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
+        }
+    }
+
+    /**
+     * Class responsible for animating spec on the main thread and sending spec
+     * updates to the window manager.
+     */
+    private static class SpecAnimationBridge {
+        private static final int ACTION_UPDATE_SPEC = 1;
+
+        private final Handler mHandler;
+        private final WindowManagerInternal mWindowManager;
+
+        /**
+         * The magnification spec that was sent to the window manager. This should
+         * only be accessed and modified on the main (e.g. animation) thread.
+         */
+        private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+
+        /**
+         * The animator that updates the sent spec. This should only be accessed
+         * and modified on the main (e.g. animation) thread.
+         */
+        private final ValueAnimator mTransformationAnimator;
+
+        private final long mMainThreadId;
+
+        private SpecAnimationBridge(Context context) {
+            final Looper mainLooper = context.getMainLooper();
+            mMainThreadId = mainLooper.getThread().getId();
+
+            mHandler = new UpdateHandler(context);
+            mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+
+            final MagnificationSpecProperty property = new MagnificationSpecProperty();
+            final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
+            final long animationDuration = context.getResources().getInteger(
+                    R.integer.config_longAnimTime);
+            mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
+                    mSentMagnificationSpec);
+            mTransformationAnimator.setDuration(animationDuration);
+            mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+        }
+
+        public void updateSentSpec(MagnificationSpec spec, boolean animate) {
+            if (Thread.currentThread().getId() == mMainThreadId) {
+                // Already on the main thread, don't bother proxying.
+                updateSentSpecInternal(spec, animate);
+            } else {
+                mHandler.obtainMessage(ACTION_UPDATE_SPEC,
+                        animate ? 1 : 0, 0, spec).sendToTarget();
+            }
+        }
+
+        /**
+         * Updates the sent spec.
+         */
+        private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) {
+            if (mTransformationAnimator.isRunning()) {
+                mTransformationAnimator.cancel();
+            }
+
+            // If the current and sent specs don't match, update the sent spec.
+            final boolean changed = !mSentMagnificationSpec.equals(spec);
+            if (changed) {
+                if (animate) {
+                    animateMagnificationSpec(spec);
+                } else {
+                    setMagnificationSpec(spec);
+                }
+            }
+        }
+
+        private void animateMagnificationSpec(MagnificationSpec toSpec) {
+            mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec);
+            mTransformationAnimator.start();
+        }
+
+        private void setMagnificationSpec(MagnificationSpec spec) {
+            if (DEBUG_SET_MAGNIFICATION_SPEC) {
+                Slog.i(LOG_TAG, "Sending: " + spec);
+            }
+
+            mSentMagnificationSpec.setTo(spec);
+            mWindowManager.setMagnificationSpec(spec);
+        }
+
+        private class UpdateHandler extends Handler {
+            public UpdateHandler(Context context) {
+                super(context.getMainLooper());
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case ACTION_UPDATE_SPEC:
+                        final boolean animate = msg.arg1 == 1;
+                        final MagnificationSpec spec = (MagnificationSpec) msg.obj;
+                        updateSentSpecInternal(spec, animate);
+                        break;
+                }
+            }
+        }
+
+        private static class MagnificationSpecProperty
+                extends Property<SpecAnimationBridge, MagnificationSpec> {
+            public MagnificationSpecProperty() {
+                super(MagnificationSpec.class, "spec");
+            }
+
+            @Override
+            public MagnificationSpec get(SpecAnimationBridge object) {
+                return object.mSentMagnificationSpec;
+            }
+
+            @Override
+            public void set(SpecAnimationBridge object, MagnificationSpec value) {
+                object.setMagnificationSpec(value);
+            }
+        }
+
+        private static class MagnificationSpecEvaluator
+                implements TypeEvaluator<MagnificationSpec> {
+            private final MagnificationSpec mTempSpec = MagnificationSpec.obtain();
+
+            @Override
+            public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
+                    MagnificationSpec toSpec) {
+                final MagnificationSpec result = mTempSpec;
+                result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
+                result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
+                result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
+                return result;
+            }
+        }
+    }
+
+    private static class ScreenStateObserver extends BroadcastReceiver {
+        private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
+
+        private final Context mContext;
+        private final MagnificationController mController;
+        private final Handler mHandler;
+
+        public ScreenStateObserver(Context context, MagnificationController controller) {
+            mContext = context;
+            mController = controller;
+            mHandler = new StateChangeHandler(context);
+        }
+
+        public void register() {
+            mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+        }
+
+        public void unregister() {
+            mContext.unregisterReceiver(this);
+        }
 
         @Override
-        public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
-                MagnificationSpec toSpec) {
-            final MagnificationSpec result = mTempTransformationSpec;
-            result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
-            result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
-            result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
-            return result;
+        public void onReceive(Context context, Intent intent) {
+            mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
+                    intent.getAction()).sendToTarget();
+        }
+
+        private void handleOnScreenStateChange() {
+            mController.resetIfNeeded(false);
+        }
+
+        private class StateChangeHandler extends Handler {
+            public StateChangeHandler(Context context) {
+                super(context.getMainLooper());
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MESSAGE_ON_SCREEN_STATE_CHANGE:
+                        handleOnScreenStateChange();
+                        break;
+                }
+            }
+        }
+    }
+
+    /**
+     * This class handles the screen magnification when accessibility is enabled.
+     */
+    private static class WindowStateObserver
+            implements WindowManagerInternal.MagnificationCallbacks {
+        private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
+        private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+        private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
+        private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
+
+        private final Rect mTempRect = new Rect();
+        private final Rect mTempRect1 = new Rect();
+
+        private final MagnificationController mController;
+        private final WindowManagerInternal mWindowManager;
+        private final Handler mHandler;
+
+        private boolean mSpecIsDirty;
+
+        public WindowStateObserver(Context context, MagnificationController controller) {
+            mController = controller;
+            mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+            mHandler = new CallbackHandler(context);
+        }
+
+        public void register() {
+            mWindowManager.setMagnificationCallbacks(this);
+        }
+
+        public void unregister() {
+            mWindowManager.setMagnificationCallbacks(null);
+        }
+
+        @Override
+        public void onMagnifiedBoundsChanged(Region magnified, Region available) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = Region.obtain(magnified);
+            args.arg2 = Region.obtain(available);
+            mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
+        }
+
+        private void handleOnMagnifiedBoundsChanged(Region magnified, Region available) {
+            mController.setMagnifiedRegion(magnified, available, mSpecIsDirty);
+            mSpecIsDirty = false;
+        }
+
+        @Override
+        public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.argi1 = left;
+            args.argi2 = top;
+            args.argi3 = right;
+            args.argi4 = bottom;
+            mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
+        }
+
+        private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+            mController.requestRectangleOnScreen(left, top, right, bottom);
+        }
+
+        @Override
+        public void onRotationChanged(int rotation) {
+            mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
+        }
+
+        private void handleOnRotationChanged() {
+            // If there was a rotation and magnification is still enabled,
+            // we'll need to rewrite the spec to reflect the new screen
+            // configuration. Conveniently, we'll receive a callback from
+            // the window manager with updated bounds for the magnified
+            // region.
+            mSpecIsDirty = !mController.resetIfNeeded(true);
+        }
+
+        @Override
+        public void onUserContextChanged() {
+            mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
+        }
+
+        private void handleOnUserContextChanged() {
+            mController.resetIfNeeded(true);
+        }
+
+        private class CallbackHandler extends Handler {
+            public CallbackHandler(Context context) {
+                super(context.getMainLooper());
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final Region magnifiedBounds = (Region) args.arg1;
+                        final Region availableBounds = (Region) args.arg2;
+                        handleOnMagnifiedBoundsChanged(magnifiedBounds, availableBounds);
+                        magnifiedBounds.recycle();
+                        availableBounds.recycle();
+                    } break;
+                    case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final int left = args.argi1;
+                        final int top = args.argi2;
+                        final int right = args.argi3;
+                        final int bottom = args.argi4;
+                        handleOnRectangleOnScreenRequested(left, top, right, bottom);
+                        args.recycle();
+                    } break;
+                    case MESSAGE_ON_USER_CONTEXT_CHANGED: {
+                        handleOnUserContextChanged();
+                    } break;
+                    case MESSAGE_ON_ROTATION_CHANGED: {
+                        handleOnRotationChanged();
+                    } break;
+                }
+            }
         }
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
similarity index 65%
rename from services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
rename to services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 8feb167..51c8ab5 100644
--- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,18 +16,10 @@
 
 package com.android.server.accessibility;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.AsyncTask;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.Message;
-import android.provider.Settings;
-import android.text.TextUtils;
+import android.util.MathUtils;
 import android.util.Slog;
 import android.util.TypedValue;
 import android.view.GestureDetector;
@@ -39,18 +31,12 @@
 import android.view.MotionEvent.PointerProperties;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
-import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityEvent;
 
-import com.android.internal.os.SomeArgs;
-import com.android.server.LocalServices;
-
-import java.util.Locale;
-
 /**
- * This class handles the screen magnification when accessibility is enabled.
+ * This class handles magnification in response to touch events.
+ *
  * The behavior is as follows:
  *
  * 1. Triple tap toggles permanent screen magnification which is magnifying
@@ -88,10 +74,8 @@
  *
  * 6. The magnification scale will be persisted in settings and in the cloud.
  */
-public final class ScreenMagnifier implements WindowManagerInternal.MagnificationCallbacks,
-        EventStreamTransformation {
-
-    private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+class MagnificationGestureHandler implements EventStreamTransformation {
+    private static final String LOG_TAG = "MagnificationEventHandler";
 
     private static final boolean DEBUG_STATE_TRANSITIONS = false;
     private static final boolean DEBUG_DETECTING = false;
@@ -103,40 +87,19 @@
     private static final int STATE_VIEWPORT_DRAGGING = 3;
     private static final int STATE_MAGNIFIED_INTERACTION = 4;
 
-    private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+    private static final float MIN_SCALE = 2.0f;
+    private static final float MAX_SCALE = 5.0f;
 
-    private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
-    private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
-    private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
-    private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
-
-    private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
-
-    private static final int MY_PID = android.os.Process.myPid();
-
-    private final Rect mTempRect = new Rect();
-    private final Rect mTempRect1 = new Rect();
-
-    private final Context mContext;
-    private final WindowManagerInternal mWindowManager;
     private final MagnificationController mMagnificationController;
-    private final ScreenStateObserver mScreenStateObserver;
-
     private final DetectingStateHandler mDetectingStateHandler;
-    private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
+    private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler;
     private final StateViewportDraggingHandler mStateViewportDraggingHandler;
 
-    private final int mUserId;
-
-    private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
-    private final int mMultiTapTimeSlop;
-    private final int mTapDistanceSlop;
-    private final int mMultiTapDistanceSlop;
-
     private EventStreamTransformation mNext;
 
     private int mCurrentState;
     private int mPreviousState;
+
     private boolean mTranslationEnabledBeforePan;
 
     private PointerCoords[] mTempPointerCoords;
@@ -144,189 +107,44 @@
 
     private long mDelegatingStateDownTime;
 
-    private boolean mUpdateMagnificationSpecOnNextBoundsChange;
-
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message message) {
-            switch (message.what) {
-                case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
-                    Region bounds = (Region) message.obj;
-                    handleOnMagnifiedBoundsChanged(bounds);
-                    bounds.recycle();
-                } break;
-                case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
-                    SomeArgs args = (SomeArgs) message.obj;
-                    final int left = args.argi1;
-                    final int top = args.argi2;
-                    final int right = args.argi3;
-                    final int bottom = args.argi4;
-                    handleOnRectangleOnScreenRequested(left, top, right, bottom);
-                    args.recycle();
-                } break;
-                case MESSAGE_ON_USER_CONTEXT_CHANGED: {
-                    handleOnUserContextChanged();
-                } break;
-                case MESSAGE_ON_ROTATION_CHANGED: {
-                    final int rotation = message.arg1;
-                    handleOnRotationChanged(rotation);
-                } break;
-            }
-        }
-    };
-
-    public ScreenMagnifier(Context context, int userId, int displayId,
-            AccessibilityManagerService service) {
-        mContext = context;
-        mUserId = userId;
-        mWindowManager = LocalServices.getService(WindowManagerInternal.class);
-
-        mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
-                + mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
-        mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
-        mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
-
-        mDetectingStateHandler = new DetectingStateHandler();
+    public MagnificationGestureHandler(Context context, AccessibilityManagerService ams) {
+        mMagnificationController = ams.getMagnificationController();
+        mDetectingStateHandler = new DetectingStateHandler(context);
         mStateViewportDraggingHandler = new StateViewportDraggingHandler();
-        mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
-                context);
-
-        mMagnificationController = service.getMagnificationController();
-        mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController);
-
-        mWindowManager.setMagnificationCallbacks(this);
+        mMagnifiedContentInteractionStateHandler =
+                new MagnifiedContentInteractionStateHandler(context);
 
         transitionToState(STATE_DETECTING);
     }
 
     @Override
-    public void onMagnifedBoundsChanged(Region bounds) {
-        Region newBounds = Region.obtain(bounds);
-        mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, newBounds).sendToTarget();
-        if (MY_PID != Binder.getCallingPid()) {
-            bounds.recycle();
-        }
-    }
-
-    private void handleOnMagnifiedBoundsChanged(Region bounds) {
-        // If there was a rotation we have to update the center of the magnified
-        // region since the old offset X/Y may be out of its acceptable range for
-        // the new display width and height.
-        mMagnificationController.setMagnifiedRegion(
-                bounds, mUpdateMagnificationSpecOnNextBoundsChange);
-        mUpdateMagnificationSpecOnNextBoundsChange = false;
-    }
-
-    @Override
-    public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
-        SomeArgs args = SomeArgs.obtain();
-        args.argi1 = left;
-        args.argi2 = top;
-        args.argi3 = right;
-        args.argi4 = bottom;
-        mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
-    }
-
-    private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
-        Rect magnifiedFrame = mTempRect;
-        mMagnificationController.getMagnifiedBounds(magnifiedFrame);
-        if (!magnifiedFrame.intersects(left, top, right, bottom)) {
-            return;
-        }
-        Rect magnifFrameInScreenCoords = mTempRect1;
-        getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords);
-        final float scrollX;
-        final float scrollY;
-        if (right - left > magnifFrameInScreenCoords.width()) {
-            final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
-            if (direction == View.LAYOUT_DIRECTION_LTR) {
-                scrollX = left - magnifFrameInScreenCoords.left;
-            } else {
-                scrollX = right - magnifFrameInScreenCoords.right;
-            }
-        } else if (left < magnifFrameInScreenCoords.left) {
-            scrollX = left - magnifFrameInScreenCoords.left;
-        } else if (right > magnifFrameInScreenCoords.right) {
-            scrollX = right - magnifFrameInScreenCoords.right;
-        } else {
-            scrollX = 0;
-        }
-        if (bottom - top > magnifFrameInScreenCoords.height()) {
-            scrollY = top - magnifFrameInScreenCoords.top;
-        } else if (top < magnifFrameInScreenCoords.top) {
-            scrollY = top - magnifFrameInScreenCoords.top;
-        } else if (bottom > magnifFrameInScreenCoords.bottom) {
-            scrollY = bottom - magnifFrameInScreenCoords.bottom;
-        } else {
-            scrollY = 0;
-        }
-        final float scale = mMagnificationController.getScale();
-        mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
-    }
-
-    @Override
-    public void onRotationChanged(int rotation) {
-        mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
-    }
-
-    private void handleOnRotationChanged(int rotation) {
-        resetMagnificationIfNeeded();
-        if (mMagnificationController.isMagnifying()) {
-            mUpdateMagnificationSpecOnNextBoundsChange = true;
-        }
-    }
-
-    @Override
-    public void onUserContextChanged() {
-        mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
-    }
-
-    private void handleOnUserContextChanged() {
-        resetMagnificationIfNeeded();
-    }
-
-    private void getMagnifiedFrameInContentCoords(Rect rect) {
-        final float scale = mMagnificationController.getSentScale();
-        final float offsetX = mMagnificationController.getSentOffsetX();
-        final float offsetY = mMagnificationController.getSentOffsetY();
-        mMagnificationController.getMagnifiedBounds(rect);
-        rect.offset((int) -offsetX, (int) -offsetY);
-        rect.scale(1.0f / scale);
-    }
-
-    private void resetMagnificationIfNeeded() {
-        if (mMagnificationController.isMagnifying()
-                && isScreenMagnificationAutoUpdateEnabled(mContext)) {
-            mMagnificationController.reset(true);
-        }
-    }
-
-    @Override
-    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
-            int policyFlags) {
+    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
             if (mNext != null) {
                 mNext.onMotionEvent(event, rawEvent, policyFlags);
             }
             return;
         }
-        mMagnifiedContentInteractonStateHandler.onMotionEvent(event);
+        mMagnifiedContentInteractionStateHandler.onMotionEvent(event, rawEvent, policyFlags);
         switch (mCurrentState) {
             case STATE_DELEGATING: {
                 handleMotionEventStateDelegating(event, rawEvent, policyFlags);
-            } break;
+            }
+            break;
             case STATE_DETECTING: {
                 mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
-            } break;
+            }
+            break;
             case STATE_VIEWPORT_DRAGGING: {
-                mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
-            } break;
+                mStateViewportDraggingHandler.onMotionEvent(event, rawEvent, policyFlags);
+            }
+            break;
             case STATE_MAGNIFIED_INTERACTION: {
                 // mMagnifiedContentInteractonStateHandler handles events only
                 // if this is the current state since it uses ScaleGestureDetecotr
                 // and a GestureDetector which need well formed event stream.
-            } break;
+            }
+            break;
             default: {
                 throw new IllegalStateException("Unknown state: " + mCurrentState);
             }
@@ -336,7 +154,7 @@
     @Override
     public void onKeyEvent(KeyEvent event, int policyFlags) {
         if (mNext != null) {
-          mNext.onKeyEvent(event, policyFlags);
+            mNext.onKeyEvent(event, policyFlags);
         }
     }
 
@@ -366,15 +184,13 @@
     @Override
     public void onDestroy() {
         clear();
-        mScreenStateObserver.destroy();
-        mWindowManager.setMagnificationCallbacks(null);
     }
 
     private void clear() {
         mCurrentState = STATE_DETECTING;
         mDetectingStateHandler.clear();
         mStateViewportDraggingHandler.clear();
-        mMagnifiedContentInteractonStateHandler.clear();
+        mMagnifiedContentInteractionStateHandler.clear();
     }
 
     private void handleMotionEventStateDelegating(MotionEvent event,
@@ -382,12 +198,14 @@
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
                 mDelegatingStateDownTime = event.getDownTime();
-            } break;
+            }
+            break;
             case MotionEvent.ACTION_UP: {
                 if (mDetectingStateHandler.mDelayedEventQueue == null) {
                     transitionToState(STATE_DETECTING);
                 }
-            } break;
+            }
+            break;
         }
         if (mNext != null) {
             // If the event is within the magnified portion of the screen we have
@@ -402,7 +220,8 @@
                 final float scaledOffsetY = mMagnificationController.getOffsetY();
                 final int pointerCount = event.getPointerCount();
                 PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
-                PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
+                PointerProperties[] properties = getTempPointerPropertiesWithMinSize(
+                        pointerCount);
                 for (int i = 0; i < pointerCount; i++) {
                     event.getPointerCoords(i, coords[i]);
                     coords[i].x = (coords[i].x - scaledOffsetX) / scale;
@@ -441,12 +260,14 @@
     }
 
     private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
-        final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0;
+        final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length
+                : 0;
         if (oldSize < size) {
             PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
             mTempPointerProperties = new PointerProperties[size];
             if (oldTempPointerProperties != null) {
-                System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize);
+                System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0,
+                        oldSize);
             }
         }
         for (int i = oldSize; i < size; i++) {
@@ -460,16 +281,20 @@
             switch (state) {
                 case STATE_DELEGATING: {
                     Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING");
-                } break;
+                }
+                break;
                 case STATE_DETECTING: {
                     Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING");
-                } break;
+                }
+                break;
                 case STATE_VIEWPORT_DRAGGING: {
                     Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING");
-                } break;
+                }
+                break;
                 case STATE_MAGNIFIED_INTERACTION: {
                     Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION");
-                } break;
+                }
+                break;
                 default: {
                     throw new IllegalArgumentException("Unknown state: " + state);
                 }
@@ -479,20 +304,30 @@
         mCurrentState = state;
     }
 
-    private final class MagnifiedContentInteractonStateHandler
-            extends SimpleOnGestureListener implements OnScaleGestureListener {
-        private static final float MIN_SCALE = 1.3f;
-        private static final float MAX_SCALE = 5.0f;
+    private interface MotionEventHandler {
+
+        void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+
+        void clear();
+    }
+
+    /**
+     * This class determines if the user is performing a scale or pan gesture.
+     */
+    private final class MagnifiedContentInteractionStateHandler extends SimpleOnGestureListener
+            implements OnScaleGestureListener, MotionEventHandler {
 
         private final ScaleGestureDetector mScaleGestureDetector;
+
         private final GestureDetector mGestureDetector;
 
         private final float mScalingThreshold;
 
         private float mInitialScaleFactor = -1;
+
         private boolean mScaling;
 
-        public MagnifiedContentInteractonStateHandler(Context context) {
+        public MagnifiedContentInteractionStateHandler(Context context) {
             final TypedValue scaleValue = new TypedValue();
             context.getResources().getValue(
                     com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
@@ -503,7 +338,8 @@
             mGestureDetector = new GestureDetector(context, this);
         }
 
-        public void onMotionEvent(MotionEvent event) {
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             mScaleGestureDetector.onTouchEvent(event);
             mGestureDetector.onTouchEvent(event);
             if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
@@ -511,11 +347,7 @@
             }
             if (event.getActionMasked() == MotionEvent.ACTION_UP) {
                 clear();
-                final float scale = Math.min(Math.max(mMagnificationController.getScale(),
-                        MIN_SCALE), MAX_SCALE);
-                if (scale != getPersistedScale()) {
-                    persistScale(scale);
-                }
+                mMagnificationController.persistScale();
                 if (mPreviousState == STATE_VIEWPORT_DRAGGING) {
                     transitionToState(STATE_VIEWPORT_DRAGGING);
                 } else {
@@ -552,14 +384,29 @@
                 }
                 return false;
             }
-            final float newScale = mMagnificationController.getScale()
-                    * detector.getScaleFactor();
-            final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE);
-            if (DEBUG_SCALING) {
-                Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
+
+            final float initialScale = mMagnificationController.getScale();
+            final float targetScale = initialScale * detector.getScaleFactor();
+
+            // Don't allow a gesture to move the user further outside the
+            // desired bounds for gesture-controlled scaling.
+            final float scale;
+            if (targetScale > MAX_SCALE && targetScale > initialScale) {
+                // The target scale is too big and getting bigger.
+                scale = MAX_SCALE;
+            } else if (targetScale < MIN_SCALE && targetScale < initialScale) {
+                // The target scale is too small and getting smaller.
+                scale = MIN_SCALE;
+            } else {
+                // The target scale may be outside our bounds, but at least
+                // it's moving in the right direction. This avoids a "jump" if
+                // we're at odds with some other service's desired bounds.
+                scale = targetScale;
             }
-            mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(),
-                    detector.getFocusY(), false);
+
+            final float pivotX = detector.getFocusX();
+            final float pivotY = detector.getFocusY();
+            mMagnificationController.setScale(scale, pivotX, pivotY, false);
             return true;
         }
 
@@ -573,16 +420,24 @@
             clear();
         }
 
-        private void clear() {
+        @Override
+        public void clear() {
             mInitialScaleFactor = -1;
             mScaling = false;
         }
     }
 
-    private final class StateViewportDraggingHandler {
+    /**
+     * This class handles motion events when the event dispatcher has
+     * determined that the user is performing a single-finger drag of the
+     * magnification viewport.
+     */
+    private final class StateViewportDraggingHandler implements MotionEventHandler {
+
         private boolean mLastMoveOutsideMagnifiedRegion;
 
-        private void onMotionEvent(MotionEvent event, int policyFlags) {
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             final int action = event.getActionMasked();
             switch (action) {
                 case MotionEvent.ACTION_DOWN: {
@@ -591,7 +446,8 @@
                 case MotionEvent.ACTION_POINTER_DOWN: {
                     clear();
                     transitionToState(STATE_MAGNIFIED_INTERACTION);
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_MOVE: {
                     if (event.getPointerCount() != 1) {
                         throw new IllegalStateException("Should have one pointer down.");
@@ -601,35 +457,43 @@
                     if (mMagnificationController.magnifiedRegionContains(eventX, eventY)) {
                         if (mLastMoveOutsideMagnifiedRegion) {
                             mLastMoveOutsideMagnifiedRegion = false;
-                            mMagnificationController.setMagnifiedRegionCenter(eventX,
+                            mMagnificationController.setCenter(eventX,
                                     eventY, true);
                         } else {
-                            mMagnificationController.setMagnifiedRegionCenter(eventX,
+                            mMagnificationController.setCenter(eventX,
                                     eventY, false);
                         }
                     } else {
                         mLastMoveOutsideMagnifiedRegion = true;
                     }
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_UP: {
                     if (!mTranslationEnabledBeforePan) {
                         mMagnificationController.reset(true);
                     }
                     clear();
                     transitionToState(STATE_DETECTING);
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_POINTER_UP: {
-                    throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP");
+                    throw new IllegalArgumentException(
+                            "Unexpected event type: ACTION_POINTER_UP");
                 }
             }
         }
 
+        @Override
         public void clear() {
             mLastMoveOutsideMagnifiedRegion = false;
         }
     }
 
-    private final class DetectingStateHandler {
+    /**
+     * This class handles motion events when the event dispatch has not yet
+     * determined what the user is doing. It watches for various tap events.
+     */
+    private final class DetectingStateHandler implements MotionEventHandler {
 
         private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
 
@@ -637,12 +501,30 @@
 
         private static final int ACTION_TAP_COUNT = 3;
 
+        private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
+
+        private final int mMultiTapTimeSlop;
+
+        private final int mTapDistanceSlop;
+
+        private final int mMultiTapDistanceSlop;
+
         private MotionEventInfo mDelayedEventQueue;
 
         private MotionEvent mLastDownEvent;
+
         private MotionEvent mLastTapUpEvent;
+
         private int mTapCount;
 
+        public DetectingStateHandler(Context context) {
+            mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
+                    + context.getResources().getInteger(
+                    com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
+            mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+            mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+        }
+
         private final Handler mHandler = new Handler() {
             @Override
             public void handleMessage(Message message) {
@@ -652,12 +534,14 @@
                         MotionEvent event = (MotionEvent) message.obj;
                         final int policyFlags = message.arg1;
                         onActionTapAndHold(event, policyFlags);
-                    } break;
+                    }
+                    break;
                     case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
                         transitionToState(STATE_DELEGATING);
                         sendDelayedMotionEvents();
                         clear();
-                    } break;
+                    }
+                    break;
                     default: {
                         throw new IllegalArgumentException("Unknown message type: " + type);
                     }
@@ -665,6 +549,7 @@
             }
         };
 
+        @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             cacheDelayedMotionEvent(event, rawEvent, policyFlags);
             final int action = event.getActionMasked();
@@ -678,7 +563,7 @@
                     }
                     if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
                             && GestureUtils.isMultiTap(mLastDownEvent, event,
-                                    mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
+                            mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
                         Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
                                 policyFlags, 0, event);
                         mHandler.sendMessageDelayed(message,
@@ -690,7 +575,8 @@
                     }
                     clearLastDownEvent();
                     mLastDownEvent = MotionEvent.obtain(event);
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_POINTER_DOWN: {
                     if (mMagnificationController.isMagnifying()) {
                         transitionToState(STATE_MAGNIFIED_INTERACTION);
@@ -698,7 +584,8 @@
                     } else {
                         transitionToDelegatingStateAndClear();
                     }
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_MOVE: {
                     if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
                         final double distance = GestureUtils.computeDistance(mLastDownEvent,
@@ -707,7 +594,8 @@
                             transitionToDelegatingStateAndClear();
                         }
                     }
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_UP: {
                     if (mLastDownEvent == null) {
                         return;
@@ -715,8 +603,8 @@
                     mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
                     if (!mMagnificationController.magnifiedRegionContains(
                             event.getX(), event.getY())) {
-                         transitionToDelegatingStateAndClear();
-                         return;
+                        transitionToDelegatingStateAndClear();
+                        return;
                     }
                     if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
                             mTapDistanceSlop, 0)) {
@@ -739,13 +627,16 @@
                     }
                     clearLastTapUpEvent();
                     mLastTapUpEvent = MotionEvent.obtain(event);
-                } break;
+                }
+                break;
                 case MotionEvent.ACTION_POINTER_UP: {
                     /* do nothing */
-                } break;
+                }
+                break;
             }
         }
 
+        @Override
         public void clear() {
             mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
             mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
@@ -792,7 +683,7 @@
             while (mDelayedEventQueue != null) {
                 MotionEventInfo info = mDelayedEventQueue;
                 mDelayedEventQueue = info.mNext;
-                ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mRawEvent,
+                MagnificationGestureHandler.this.onMotionEvent(info.mEvent, info.mRawEvent,
                         info.mPolicyFlags);
                 info.recycle();
             }
@@ -816,9 +707,11 @@
             if (DEBUG_DETECTING) {
                 Slog.i(LOG_TAG, "onActionTap()");
             }
+
             if (!mMagnificationController.isMagnifying()) {
-                mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
-                        up.getX(), up.getY(), true);
+                final float targetScale = mMagnificationController.getPersistedScale();
+                final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
+                mMagnificationController.setScaleAndCenter(scale, up.getX(), up.getY(), true);
             } else {
                 mMagnificationController.reset(true);
             }
@@ -828,50 +721,36 @@
             if (DEBUG_DETECTING) {
                 Slog.i(LOG_TAG, "onActionTapAndHold()");
             }
+
             clear();
             mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
-            mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
-                    down.getX(), down.getY(), true);
+
+            final float targetScale = mMagnificationController.getPersistedScale();
+            final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
+            mMagnificationController.setScaleAndCenter(scale, down.getX(), down.getY(), true);
+
             transitionToState(STATE_VIEWPORT_DRAGGING);
         }
     }
 
-    private void persistScale(final float scale) {
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                Settings.Secure.putFloatForUser(mContext.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, mUserId);
-                return null;
-            }
-        }.execute();
-    }
-
-    private float getPersistedScale() {
-        return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
-                DEFAULT_MAGNIFICATION_SCALE, mUserId);
-    }
-
-    private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) {
-        return (Settings.Secure.getInt(context.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
-                DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
-    }
-
     private static final class MotionEventInfo {
 
         private static final int MAX_POOL_SIZE = 10;
 
         private static final Object sLock = new Object();
+
         private static MotionEventInfo sPool;
+
         private static int sPoolSize;
 
         private MotionEventInfo mNext;
+
         private boolean mInPool;
 
         public MotionEvent mEvent;
+
         public MotionEvent mRawEvent;
+
         public int mPolicyFlags;
 
         public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent,
@@ -922,47 +801,4 @@
             mPolicyFlags = 0;
         }
     }
-
-    private final class ScreenStateObserver extends BroadcastReceiver {
-        private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
-
-        private final Context mContext;
-        private final MagnificationController mMagnificationController;
-
-        private final Handler mHandler = new Handler() {
-            @Override
-            public void handleMessage(Message message) {
-                 switch (message.what) {
-                    case MESSAGE_ON_SCREEN_STATE_CHANGE: {
-                        String action = (String) message.obj;
-                        handleOnScreenStateChange(action);
-                    } break;
-                }
-            }
-        };
-
-        public ScreenStateObserver(Context context,
-                MagnificationController magnificationController) {
-            mContext = context;
-            mMagnificationController = magnificationController;
-            mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
-        }
-
-        public void destroy() {
-            mContext.unregisterReceiver(this);
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
-                    intent.getAction()).sendToTarget();
-        }
-
-        private void handleOnScreenStateChange(String action) {
-            if (mMagnificationController.isMagnifying()
-                    && isScreenMagnificationAutoUpdateEnabled(mContext)) {
-                mMagnificationController.reset(false);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index a5ddc12..504a7ef 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -25,6 +25,7 @@
 import android.app.IAlarmCompleteListener;
 import android.app.IAlarmListener;
 import android.app.IAlarmManager;
+import android.app.IUidObserver;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -41,6 +42,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -115,6 +117,7 @@
     final LocalLog mLog = new LocalLog(TAG);
 
     AppOpsManager mAppOps;
+    DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     final Object mLock = new Object();
 
@@ -458,7 +461,7 @@
             long newStart = 0;  // recalculate endpoints as we go
             long newEnd = Long.MAX_VALUE;
             int newFlags = 0;
-            for (int i = 0; i < alarms.size(); ) {
+            for (int i = alarms.size()-1; i >= 0; i--) {
                 Alarm alarm = alarms.get(i);
                 if (alarm.matches(packageName)) {
                     alarms.remove(i);
@@ -474,7 +477,42 @@
                         newEnd = alarm.maxWhenElapsed;
                     }
                     newFlags |= alarm.flags;
-                    i++;
+                }
+            }
+            if (didRemove) {
+                // commit the new batch bounds
+                start = newStart;
+                end = newEnd;
+                flags = newFlags;
+            }
+            return didRemove;
+        }
+
+        boolean removeForStopped(final int uid) {
+            boolean didRemove = false;
+            long newStart = 0;  // recalculate endpoints as we go
+            long newEnd = Long.MAX_VALUE;
+            int newFlags = 0;
+            for (int i = alarms.size()-1; i >= 0; i--) {
+                Alarm alarm = alarms.get(i);
+                try {
+                    if (alarm.uid == uid && ActivityManagerNative.getDefault().getAppStartMode(
+                            uid, alarm.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+                        alarms.remove(i);
+                        didRemove = true;
+                        if (alarm.alarmClock != null) {
+                            mNextAlarmClockMayChange = true;
+                        }
+                    } else {
+                        if (alarm.whenElapsed > newStart) {
+                            newStart = alarm.whenElapsed;
+                        }
+                        if (alarm.maxWhenElapsed < newEnd) {
+                            newEnd = alarm.maxWhenElapsed;
+                        }
+                        newFlags |= alarm.flags;
+                    }
+                } catch (RemoteException e) {
                 }
             }
             if (didRemove) {
@@ -889,6 +927,13 @@
             Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
         }
 
+        try {
+            ActivityManagerNative.getDefault().registerUidObserver(new UidObserver(),
+                    ActivityManager.UID_OBSERVER_IDLE);
+        } catch (RemoteException e) {
+            // ignored; both services live in system_server
+        }
+
         publishBinderService(Context.ALARM_SERVICE, mService);
     }
 
@@ -897,6 +942,8 @@
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             mConstants.start(getContext().getContentResolver());
             mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+            mLocalDeviceIdleController
+                    = LocalServices.getService(DeviceIdleController.LocalService.class);
         }
     }
 
@@ -1032,6 +1079,15 @@
         Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
                 operation, directReceiver, listenerTag, workSource, flags, alarmClock,
                 callingUid, callingPackage);
+        try {
+            if (ActivityManagerNative.getDefault().getAppStartMode(callingUid, callingPackage)
+                    == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+                        + " -- package not allowed to start");
+                return;
+            }
+        } catch (RemoteException e) {
+        }
         removeLocked(operation, directReceiver);
         setImplLocked(a, false, doValidate);
     }
@@ -1838,6 +1894,37 @@
         }
     }
 
+    void removeForStoppedLocked(int uid) {
+        boolean didRemove = false;
+        for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+            Batch b = mAlarmBatches.get(i);
+            didRemove |= b.removeForStopped(uid);
+            if (b.size() == 0) {
+                mAlarmBatches.remove(i);
+            }
+        }
+        for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+            final Alarm a = mPendingWhileIdleAlarms.get(i);
+            try {
+                if (a.uid == uid && ActivityManagerNative.getDefault().getAppStartMode(
+                        uid, a.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+                    // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                    mPendingWhileIdleAlarms.remove(i);
+                }
+            } catch (RemoteException e) {
+            }
+        }
+
+        if (didRemove) {
+            if (DEBUG_BATCH) {
+                Slog.v(TAG, "remove(package) changed bounds; rebatching");
+            }
+            rebatchAllAlarmsLocked(true);
+            rescheduleKernelAlarmsLocked();
+            updateNextAlarmClockLocked();
+        }
+    }
+
     void removeUserLocked(int userHandle) {
         boolean didRemove = false;
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
@@ -2468,10 +2555,9 @@
 
     private class AlarmHandler extends Handler {
         public static final int ALARM_EVENT = 1;
-        public static final int MINUTE_CHANGE_EVENT = 2;
-        public static final int DATE_CHANGE_EVENT = 3;
-        public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4;
-        public static final int LISTENER_TIMEOUT = 5;
+        public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2;
+        public static final int LISTENER_TIMEOUT = 3;
+        public static final int REPORT_ALARMS_ACTIVE = 4;
         
         public AlarmHandler() {
         }
@@ -2511,6 +2597,12 @@
                     mDeliveryTracker.alarmTimedOut((IBinder) msg.obj);
                     break;
 
+                case REPORT_ALARMS_ACTIVE:
+                    if (mLocalDeviceIdleController != null) {
+                        mLocalDeviceIdleController.setAlarmsActive(msg.arg1 != 0);
+                    }
+                    break;
+
                 default:
                     // nope, just ignore it
                     break;
@@ -2665,7 +2757,22 @@
             }
         }
     }
-    
+
+    final class UidObserver extends IUidObserver.Stub {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+            removeForStoppedLocked(uid);
+        }
+    };
+
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
         String pkg = pi.getCreatorPackage();
         int uid = pi.getCreatorUid();
@@ -2740,6 +2847,7 @@
             }
             mBroadcastRefCount--;
             if (mBroadcastRefCount == 0) {
+                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0).sendToTarget();
                 mWakeLock.release();
                 if (mInFlight.size() > 0) {
                     mLog.w("Finished all dispatches with " + mInFlight.size()
@@ -2873,6 +2981,7 @@
                         alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
                         true);
                 mWakeLock.acquire();
+                mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
             }
             final InFlight inflight = new InFlight(AlarmManagerService.this,
                     alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 96c1e2a..c131628 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -54,7 +54,6 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -557,12 +556,12 @@
                 ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
                 try {
                     if (reportedPackageNames == null) {
-                        callback.mCallback.opChanged(code, null);
+                        callback.mCallback.opChanged(code, uid, null);
                     } else {
                         final int reportedPackageCount = reportedPackageNames.size();
                         for (int j = 0; j < reportedPackageCount; j++) {
                             String reportedPackageName = reportedPackageNames.valueAt(j);
-                            callback.mCallback.opChanged(code, reportedPackageName);
+                            callback.mCallback.opChanged(code, uid, reportedPackageName);
                         }
                     }
                 } catch (RemoteException e) {
@@ -620,7 +619,7 @@
             try {
                 for (int i = 0; i < repCbs.size(); i++) {
                     try {
-                        repCbs.get(i).mCallback.opChanged(code, packageName);
+                        repCbs.get(i).mCallback.opChanged(code, uid, packageName);
                     } catch (RemoteException e) {
                     }
                 }
@@ -630,39 +629,51 @@
         }
     }
 
-    private static HashMap<Callback, ArrayList<Pair<String, Integer>>> addCallbacks(
-            HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks,
-            String packageName, int op, ArrayList<Callback> cbs) {
+    private static HashMap<Callback, ArrayList<ChangeRec>> addCallbacks(
+            HashMap<Callback, ArrayList<ChangeRec>> callbacks,
+            int op, int uid, String packageName, ArrayList<Callback> cbs) {
         if (cbs == null) {
             return callbacks;
         }
         if (callbacks == null) {
-            callbacks = new HashMap<Callback, ArrayList<Pair<String, Integer>>>();
+            callbacks = new HashMap<>();
         }
         boolean duplicate = false;
         for (int i=0; i<cbs.size(); i++) {
             Callback cb = cbs.get(i);
-            ArrayList<Pair<String, Integer>> reports = callbacks.get(cb);
+            ArrayList<ChangeRec> reports = callbacks.get(cb);
             if (reports == null) {
-                reports = new ArrayList<Pair<String, Integer>>();
+                reports = new ArrayList<>();
                 callbacks.put(cb, reports);
             } else {
                 final int reportCount = reports.size();
                 for (int j = 0; j < reportCount; j++) {
-                    Pair<String, Integer> report = reports.get(j);
-                    if (report.second == op && report.first.equals(packageName)) {
+                    ChangeRec report = reports.get(j);
+                    if (report.op == op && report.pkg.equals(packageName)) {
                         duplicate = true;
                         break;
                     }
                 }
             }
             if (!duplicate) {
-                reports.add(new Pair<>(packageName, op));
+                reports.add(new ChangeRec(op, uid, packageName));
             }
         }
         return callbacks;
     }
 
+    static final class ChangeRec {
+        final int op;
+        final int uid;
+        final String pkg;
+
+        ChangeRec(int _op, int _uid, String _pkg) {
+            op = _op;
+            uid = _uid;
+            pkg = _pkg;
+        }
+    }
+
     @Override
     public void resetAllModes(int reqUserId, String reqPackageName) {
         final int callingPid = Binder.getCallingPid();
@@ -682,7 +693,7 @@
             }
         }
 
-        HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks = null;
+        HashMap<Callback, ArrayList<ChangeRec>> callbacks = null;
         synchronized (this) {
             boolean changed = false;
             for (int i = mUidStates.size() - 1; i >= 0; i--) {
@@ -699,9 +710,9 @@
                                 uidState.opModes = null;
                             }
                             for (String packageName : getPackagesForUid(uidState.uid)) {
-                                callbacks = addCallbacks(callbacks, packageName, code,
+                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         mOpModeWatchers.get(code));
-                                callbacks = addCallbacks(callbacks, packageName, code,
+                                callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         mPackageModeWatchers.get(packageName));
                             }
                         }
@@ -734,9 +745,9 @@
                                 && curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
                             curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
                             changed = true;
-                            callbacks = addCallbacks(callbacks, packageName, curOp.op,
+                            callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
                                     mOpModeWatchers.get(curOp.op));
-                            callbacks = addCallbacks(callbacks, packageName, curOp.op,
+                            callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
                                     mPackageModeWatchers.get(packageName));
                             if (curOp.time == 0 && curOp.rejectTime == 0) {
                                 pkgOps.removeAt(j);
@@ -757,13 +768,13 @@
             }
         }
         if (callbacks != null) {
-            for (Map.Entry<Callback, ArrayList<Pair<String, Integer>>> ent : callbacks.entrySet()) {
+            for (Map.Entry<Callback, ArrayList<ChangeRec>> ent : callbacks.entrySet()) {
                 Callback cb = ent.getKey();
-                ArrayList<Pair<String, Integer>> reports = ent.getValue();
+                ArrayList<ChangeRec> reports = ent.getValue();
                 for (int i=0; i<reports.size(); i++) {
-                    Pair<String, Integer> rep = reports.get(i);
+                    ChangeRec rep = reports.get(i);
                     try {
-                        cb.mCallback.opChanged(rep.second, rep.first);
+                        cb.mCallback.opChanged(rep.op, rep.uid, rep.pkg);
                     } catch (RemoteException e) {
                     }
                 }
@@ -1163,8 +1174,10 @@
                     if (pkgUid != uid) {
                         // Oops!  The package name is not valid for the uid they are calling
                         // under.  Abort.
+                        RuntimeException ex = new RuntimeException("here");
+                        ex.fillInStackTrace();
                         Slog.w(TAG, "Bad call: specified package " + packageName
-                                + " under uid " + uid + " but it is really " + pkgUid);
+                                + " under uid " + uid + " but it is really " + pkgUid, ex);
                         return null;
                     }
                 } finally {
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 927b995..485e26b 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -48,6 +48,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.IDeviceIdleController;
 import android.os.Looper;
 import android.os.Message;
@@ -196,6 +197,12 @@
     private long mNextIdleDelay;
     private long mNextLightAlarmTime;
 
+    private int mActiveIdleOpCount;
+    private IBinder mDownloadServiceActive;
+    private boolean mSyncActive;
+    private boolean mJobsActive;
+    private boolean mAlarmsActive;
+
     public final AtomicFile mConfigFile;
 
     /**
@@ -282,16 +289,22 @@
                 }
             } else if (ACTION_STEP_LIGHT_IDLE_STATE.equals(intent.getAction())) {
                 synchronized (DeviceIdleController.this) {
-                    stepLightIdleStateLocked();
+                    stepLightIdleStateLocked("s:alarm");
                 }
             } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
                 synchronized (DeviceIdleController.this) {
-                    stepIdleStateLocked();
+                    stepIdleStateLocked("s:alarm");
                 }
             }
         }
     };
 
+    private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() {
+        @Override public void onReceive(Context context, Intent intent) {
+            decActiveIdleOps();
+        }
+    };
+
     private final DisplayManager.DisplayListener mDisplayListener
             = new DisplayManager.DisplayListener() {
         @Override public void onDisplayAdded(int displayId) {
@@ -733,7 +746,7 @@
                 // If we are currently sensing, it is time to move to locating.
                 synchronized (this) {
                     mNotMoving = true;
-                    stepIdleStateLocked();
+                    stepIdleStateLocked("s:stationary");
                 }
             } else if (mState == STATE_LOCATING) {
                 // If we are currently locating, note that we are not moving and step
@@ -741,7 +754,7 @@
                 synchronized (this) {
                     mNotMoving = true;
                     if (mLocated) {
-                        stepIdleStateLocked();
+                        stepIdleStateLocked("s:stationary");
                     }
                 }
             }
@@ -804,11 +817,18 @@
                     } catch (RemoteException e) {
                     }
                     if (fullChanged) {
-                        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+                        incActiveIdleOps();
+                        getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+                                null, mIdleStartedDoneReceiver, null, 0, null, null);
                     }
                     if (lightChanged) {
-                        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+                        incActiveIdleOps();
+                        getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+                                null, mIdleStartedDoneReceiver, null, 0, null, null);
                     }
+                    // Always start with one active op for the message being sent here.
+                    // Now we we done!
+                    decActiveIdleOps();
                     EventLogTags.writeDeviceIdleOffComplete();
                 } break;
                 case MSG_REPORT_ACTIVE: {
@@ -913,11 +933,23 @@
         }
 
         @Override public void exitIdle(String reason) {
-            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
                     null);
             exitIdleInternal(reason);
         }
 
+        @Override public void downloadServiceActive(IBinder token) {
+            getContext().enforceCallingOrSelfPermission(
+                    "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
+            DeviceIdleController.this.downloadServiceActive(token);
+        }
+
+        @Override public void downloadServiceInactive() {
+            getContext().enforceCallingOrSelfPermission(
+                    "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
+            DeviceIdleController.this.downloadServiceInactive();
+        }
+
         @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             DeviceIdleController.this.dump(fd, pw, args);
         }
@@ -937,6 +969,19 @@
         public void setNetworkPolicyTempWhitelistCallback(Runnable callback) {
             setNetworkPolicyTempWhitelistCallbackInternal(callback);
         }
+
+        public void setSyncActive(boolean active) {
+            DeviceIdleController.this.setSyncActive(active);
+        }
+
+        public void setJobsActive(boolean active) {
+            DeviceIdleController.this.setJobsActive(active);
+        }
+
+        // Up-call from alarm manager.
+        public void setAlarmsActive(boolean active) {
+            DeviceIdleController.this.setAlarmsActive(active);
+        }
     }
 
     public DeviceIdleController(Context context) {
@@ -1439,7 +1484,7 @@
         }
     }
 
-    void stepLightIdleStateLocked() {
+    void stepLightIdleStateLocked(String reason) {
         if (mLightState == LIGHT_STATE_OVERRIDE) {
             // If we are already in full device idle mode, then
             // there is nothing left to do for light mode.
@@ -1455,22 +1500,23 @@
                 scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
                 if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
                 mLightState = LIGHT_STATE_IDLE;
-                EventLogTags.writeDeviceIdleLight(mLightState, "step");
+                EventLogTags.writeDeviceIdleLight(mLightState, reason);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
                 break;
             case LIGHT_STATE_IDLE:
                 // We have been idling long enough, now it is time to do some work.
+                mActiveIdleOpCount = 1;
                 scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_PENDING_TIMEOUT);
                 if (DEBUG) Slog.d(TAG,
                         "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
                 mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
-                EventLogTags.writeDeviceIdleLight(mLightState, "step");
+                EventLogTags.writeDeviceIdleLight(mLightState, reason);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                 break;
         }
     }
 
-    void stepIdleStateLocked() {
+    void stepIdleStateLocked(String reason) {
         if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
         EventLogTags.writeDeviceIdleStep();
 
@@ -1494,12 +1540,12 @@
                 mNextIdleDelay = mConstants.IDLE_TIMEOUT;
                 mState = STATE_IDLE_PENDING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING.");
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 break;
             case STATE_IDLE_PENDING:
                 mState = STATE_SENSING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false);
                 cancelLocatingLocked();
                 mAnyMotionDetector.checkForAnyMotion();
@@ -1511,7 +1557,7 @@
             case STATE_SENSING:
                 mState = STATE_LOCATING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
                 if (mLocationManager != null
                         && mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
@@ -1553,23 +1599,101 @@
                     mLightState = LIGHT_STATE_OVERRIDE;
                     cancelLightAlarmLocked();
                 }
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
                 break;
             case STATE_IDLE:
                 // We have been idling long enough, now it is time to do some work.
+                mActiveIdleOpCount = 1;
                 scheduleAlarmLocked(mNextIdlePendingDelay, false);
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
                         "Next alarm in " + mNextIdlePendingDelay + " ms.");
                 mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                         (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
                 mState = STATE_IDLE_MAINTENANCE;
-                EventLogTags.writeDeviceIdle(mState, "step");
+                EventLogTags.writeDeviceIdle(mState, reason);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                 break;
         }
     }
 
+    void incActiveIdleOps() {
+        synchronized (this) {
+            mActiveIdleOpCount++;
+        }
+    }
+
+    void decActiveIdleOps() {
+        synchronized (this) {
+            mActiveIdleOpCount--;
+            if (mActiveIdleOpCount <= 0) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void downloadServiceActive(IBinder token) {
+        synchronized (this) {
+            mDownloadServiceActive = token;
+            try {
+                token.linkToDeath(new IBinder.DeathRecipient() {
+                    @Override public void binderDied() {
+                        downloadServiceInactive();
+                    }
+                }, 0);
+            } catch (RemoteException e) {
+                mDownloadServiceActive = null;
+            }
+        }
+    }
+
+    void downloadServiceInactive() {
+        synchronized (this) {
+            mDownloadServiceActive = null;
+            exitMaintenanceEarlyIfNeededLocked();
+        }
+    }
+
+    void setSyncActive(boolean active) {
+        synchronized (this) {
+            mSyncActive = active;
+            if (!active) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void setJobsActive(boolean active) {
+        synchronized (this) {
+            mJobsActive = active;
+            if (!active) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void setAlarmsActive(boolean active) {
+        synchronized (this) {
+            mAlarmsActive = active;
+            if (!active) {
+                exitMaintenanceEarlyIfNeededLocked();
+            }
+        }
+    }
+
+    void exitMaintenanceEarlyIfNeededLocked() {
+        if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
+            if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
+                    && !mSyncActive && !mJobsActive && !mAlarmsActive) {
+                if (mState == STATE_IDLE_MAINTENANCE) {
+                    stepIdleStateLocked("s:early");
+                } else {
+                    stepLightIdleStateLocked("s:early");
+                }
+            }
+        }
+    }
+
     void motionLocked() {
         if (DEBUG) Slog.d(TAG, "motionLocked()");
         // The motion sensor will have been disabled at this point
@@ -1612,7 +1736,7 @@
         }
         mLocated = true;
         if (mNotMoving) {
-            stepIdleStateLocked();
+            stepIdleStateLocked("s:location");
         }
     }
 
@@ -1628,7 +1752,7 @@
         }
         mLocated = true;
         if (mNotMoving) {
-            stepIdleStateLocked();
+            stepIdleStateLocked("s:gps");
         }
     }
 
@@ -1933,7 +2057,7 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     exitForceIdleLocked();
-                    stepIdleStateLocked();
+                    stepIdleStateLocked("s:shell");
                     pw.print("Stepped to: ");
                     pw.println(stateToString(mState));
                 } finally {
@@ -1947,7 +2071,7 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     exitForceIdleLocked();
-                    stepLightIdleStateLocked();
+                    stepLightIdleStateLocked("s:shell");
                     pw.print("Stepped to: "); pw.println(lightStateToString(mLightState));
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -1967,7 +2091,7 @@
                     becomeInactiveIfAppropriateLocked();
                     int curState = mState;
                     while (curState != STATE_IDLE) {
-                        stepIdleStateLocked();
+                        stepIdleStateLocked("s:shell");
                         if (curState == mState) {
                             pw.print("Unable to go idle; stopped at ");
                             pw.println(stateToString(mState));
@@ -2226,6 +2350,9 @@
             pw.println(lightStateToString(mLightState));
             pw.print("  mInactiveTimeout="); TimeUtils.formatDuration(mInactiveTimeout, pw);
             pw.println();
+            if (mActiveIdleOpCount != 0) {
+                pw.print("  mActiveIdleOpCount="); pw.println(mActiveIdleOpCount);
+            }
             if (mNextAlarmTime != 0) {
                 pw.print("  mNextAlarmTime=");
                 TimeUtils.formatDuration(mNextAlarmTime, SystemClock.elapsedRealtime(), pw);
@@ -2246,6 +2373,18 @@
                 TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
                 pw.println();
             }
+            if (mSyncActive) {
+                pw.print("  mSyncActive="); pw.println(mSyncActive);
+            }
+            if (mJobsActive) {
+                pw.print("  mJobsActive="); pw.println(mJobsActive);
+            }
+            if (mAlarmsActive) {
+                pw.print("  mAlarmsActive="); pw.println(mAlarmsActive);
+            }
+            if (mDownloadServiceActive != null) {
+                pw.print("  mDownloadServiceActive="); pw.println(mDownloadServiceActive);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 3359060..43d10c7 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -226,7 +226,7 @@
             final int N = a.length;
             boolean printedHeader = false;
             F filter;
-            if (collapseDuplicates) {
+            if (collapseDuplicates && !printFilter) {
                 found.clear();
                 for (int i=0; i<N && (filter=a[i]) != null; i++) {
                     if (packageName != null && !isPackageForFilter(packageName, filter)) {
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 37dd884..f89155d 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -56,6 +56,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -2768,6 +2769,12 @@
     }
 
     @Override
+    public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+        // TODO: Invoke vold to mount app fuse.
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public int mkdirs(String callingPkg, String appPath) {
         final int userId = UserHandle.getUserId(Binder.getCallingUid());
         final UserEnvironment userEnv = new UserEnvironment(userId);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 30565c6..4d05f9a 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -168,13 +168,10 @@
      */
     class ServiceMap extends Handler {
         final int mUserId;
-        final ArrayMap<ComponentName, ServiceRecord> mServicesByName
-                = new ArrayMap<ComponentName, ServiceRecord>();
-        final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent
-                = new ArrayMap<Intent.FilterComparison, ServiceRecord>();
+        final ArrayMap<ComponentName, ServiceRecord> mServicesByName = new ArrayMap<>();
+        final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = new ArrayMap<>();
 
-        final ArrayList<ServiceRecord> mDelayedStartList
-                = new ArrayList<ServiceRecord>();
+        final ArrayList<ServiceRecord> mDelayedStartList = new ArrayList<>();
         /* XXX eventually I'd like to have this based on processes instead of services.
          * That is, if we try to start two services in a row both running in the same
          * process, this should be one entry in mStartingBackground for that one process
@@ -185,8 +182,7 @@
                 = new ArrayList<DelayingProcess>();
         */
 
-        final ArrayList<ServiceRecord> mStartingBackground
-                = new ArrayList<ServiceRecord>();
+        final ArrayList<ServiceRecord> mStartingBackground = new ArrayList<>();
 
         static final int MSG_BG_START_TIMEOUT = 1;
 
@@ -338,7 +334,7 @@
         ServiceRecord r = res.record;
 
         if (!mAm.mUserController.exists(r.userId)) {
-            Slog.d(TAG, "Trying to start service with non-existent user! " + r.userId);
+            Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
             return null;
         }
 
@@ -510,6 +506,35 @@
         return 0;
     }
 
+    void stopInBackgroundLocked(int uid) {
+        // Stop all services associated with this uid due to it going to the background
+        // stopped state.
+        ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
+        ArrayList<ServiceRecord> stopping = null;
+        if (services != null) {
+            for (int i=services.mServicesByName.size()-1; i>=0; i--) {
+                ServiceRecord service = services.mServicesByName.valueAt(i);
+                if (service.appInfo.uid == uid && service.startRequested) {
+                    if (mAm.mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
+                            uid, service.packageName) != AppOpsManager.MODE_ALLOWED) {
+                        if (stopping == null) {
+                            stopping = new ArrayList<>();
+                            stopping.add(service);
+                        }
+                    }
+                }
+            }
+            if (stopping != null) {
+                for (int i=stopping.size()-1; i>=0; i--) {
+                    ServiceRecord service = stopping.get(i);
+                    service.delayed = false;
+                    services.ensureNotStartingBackground(service);
+                    stopServiceLocked(service);
+                }
+            }
+        }
+    }
+
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
                 Binder.getCallingPid(), Binder.getCallingUid(),
@@ -1069,6 +1094,17 @@
                 }
                 r = smap.mServicesByName.get(name);
                 if (r == null && createIfNeeded) {
+                    // Before going further -- if this app is not allowed to run in the background,
+                    // then at this point we aren't going to let it period.
+                    if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
+                            sInfo.packageName, callingPid)) {
+                        Slog.w(TAG, "Background execution not allowed: service "
+                                + r.intent + " to " + name.flattenToShortString()
+                                + " from pid=" + callingPid + " uid=" + callingUid
+                                + " pkg=" + callingPackage);
+                        return null;
+                    }
+
                     Intent.FilterComparison filter
                             = new Intent.FilterComparison(service.cloneFilter());
                     ServiceRestarter res = new ServiceRestarter();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 566065c..025126f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -60,7 +60,6 @@
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.appwidget.AppWidgetManager;
-import android.content.pm.AppsQueryHelper;
 import android.content.pm.PermissionInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -88,6 +87,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.DumpHeapActivity;
+import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ProcessMap;
@@ -109,15 +109,12 @@
 import com.android.server.IntentResolver;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
-import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityStack.ActivityState;
-import com.android.server.am.ActivityStackSupervisor.ActivityDisplay;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.pm.Installer;
-import com.android.server.pm.UserManagerService;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.AppTransition;
 import com.android.server.wm.WindowManagerService;
@@ -388,6 +385,10 @@
     // Maximum number of users we allow to be running at a time.
     static final int MAX_RUNNING_USERS = 3;
 
+    // This is the amount of time we allow an app to settle after it goes into the background,
+    // before we start restricting what it can do.
+    static final int BACKGROUND_SETTLE_TIME = 1*60*1000;
+
     // How long to wait in getAssistContextExtras for the activity and foreground services
     // to respond with the result.
     static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
@@ -716,6 +717,12 @@
     final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
 
     /**
+     * This is for verifying the UID report flow.
+     */
+    static final boolean VALIDATE_UID_STATES = true;
+    final SparseArray<UidRecord> mValidateUids = new SparseArray<>();
+
+    /**
      * Packages that the user has asked to have run in screen size
      * compatibility mode instead of filling the screen.
      */
@@ -1310,29 +1317,29 @@
         }
     }
 
-    static final int SHOW_ERROR_MSG = 1;
-    static final int SHOW_NOT_RESPONDING_MSG = 2;
-    static final int SHOW_FACTORY_ERROR_MSG = 3;
+    static final int SHOW_ERROR_UI_MSG = 1;
+    static final int SHOW_NOT_RESPONDING_UI_MSG = 2;
+    static final int SHOW_FACTORY_ERROR_UI_MSG = 3;
     static final int UPDATE_CONFIGURATION_MSG = 4;
     static final int GC_BACKGROUND_PROCESSES_MSG = 5;
-    static final int WAIT_FOR_DEBUGGER_MSG = 6;
+    static final int WAIT_FOR_DEBUGGER_UI_MSG = 6;
     static final int SERVICE_TIMEOUT_MSG = 12;
     static final int UPDATE_TIME_ZONE = 13;
-    static final int SHOW_UID_ERROR_MSG = 14;
-    static final int SHOW_FINGERPRINT_ERROR_MSG = 15;
+    static final int SHOW_UID_ERROR_UI_MSG = 14;
+    static final int SHOW_FINGERPRINT_ERROR_UI_MSG = 15;
     static final int PROC_START_TIMEOUT_MSG = 20;
     static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21;
     static final int KILL_APPLICATION_MSG = 22;
     static final int FINALIZE_PENDING_INTENT_MSG = 23;
     static final int POST_HEAVY_NOTIFICATION_MSG = 24;
     static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25;
-    static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26;
+    static final int SHOW_STRICT_MODE_VIOLATION_UI_MSG = 26;
     static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27;
     static final int CLEAR_DNS_CACHE_MSG = 28;
     static final int UPDATE_HTTP_PROXY_MSG = 29;
-    static final int SHOW_COMPAT_MODE_DIALOG_MSG = 30;
-    static final int DISPATCH_PROCESSES_CHANGED = 31;
-    static final int DISPATCH_PROCESS_DIED = 32;
+    static final int SHOW_COMPAT_MODE_DIALOG_UI_MSG = 30;
+    static final int DISPATCH_PROCESSES_CHANGED_UI_MSG = 31;
+    static final int DISPATCH_PROCESS_DIED_UI_MSG = 32;
     static final int REPORT_MEM_USAGE_MSG = 33;
     static final int REPORT_USER_SWITCH_MSG = 34;
     static final int CONTINUE_USER_SWITCH_MSG = 35;
@@ -1346,20 +1353,21 @@
     static final int SYSTEM_USER_CURRENT_MSG = 43;
     static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
     static final int FINISH_BOOTING_MSG = 45;
-    static final int START_USER_SWITCH_MSG = 46;
+    static final int START_USER_SWITCH_UI_MSG = 46;
     static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47;
-    static final int DISMISS_DIALOG_MSG = 48;
+    static final int DISMISS_DIALOG_UI_MSG = 48;
     static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG = 49;
     static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 50;
     static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 51;
     static final int DELETE_DUMPHEAP_MSG = 52;
     static final int FOREGROUND_PROFILE_CHANGED_MSG = 53;
-    static final int DISPATCH_UIDS_CHANGED_MSG = 54;
+    static final int DISPATCH_UIDS_CHANGED_UI_MSG = 54;
     static final int REPORT_TIME_TRACKER_MSG = 55;
     static final int REPORT_USER_SWITCH_COMPLETE_MSG = 56;
     static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 57;
     static final int APP_BOOST_DEACTIVATE_MSG = 58;
     static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 59;
+    static final int IDLE_UIDS_MSG = 60;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1394,7 +1402,7 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case SHOW_ERROR_MSG: {
+            case SHOW_ERROR_UI_MSG: {
                 HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                 boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                         Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
@@ -1439,7 +1447,7 @@
 
                 ensureBootCompleted();
             } break;
-            case SHOW_NOT_RESPONDING_MSG: {
+            case SHOW_NOT_RESPONDING_UI_MSG: {
                 synchronized (ActivityManagerService.this) {
                     HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                     ProcessRecord proc = (ProcessRecord)data.get("app");
@@ -1471,7 +1479,7 @@
 
                 ensureBootCompleted();
             } break;
-            case SHOW_STRICT_MODE_VIOLATION_MSG: {
+            case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
                 HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                 synchronized (ActivityManagerService.this) {
                     ProcessRecord proc = (ProcessRecord) data.get("app");
@@ -1497,13 +1505,13 @@
                 }
                 ensureBootCompleted();
             } break;
-            case SHOW_FACTORY_ERROR_MSG: {
+            case SHOW_FACTORY_ERROR_UI_MSG: {
                 Dialog d = new FactoryErrorDialog(
                     mContext, msg.getData().getCharSequence("msg"));
                 d.show();
                 ensureBootCompleted();
             } break;
-            case WAIT_FOR_DEBUGGER_MSG: {
+            case WAIT_FOR_DEBUGGER_UI_MSG: {
                 synchronized (ActivityManagerService.this) {
                     ProcessRecord app = (ProcessRecord)msg.obj;
                     if (msg.arg1 != 0) {
@@ -1523,7 +1531,7 @@
                     }
                 }
             } break;
-            case SHOW_UID_ERROR_MSG: {
+            case SHOW_UID_ERROR_UI_MSG: {
                 if (mShowDialogs) {
                     AlertDialog d = new BaseErrorDialog(mContext);
                     d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
@@ -1531,11 +1539,11 @@
                     d.setTitle(mContext.getText(R.string.android_system_label));
                     d.setMessage(mContext.getText(R.string.system_error_wipe_data));
                     d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
-                            obtainMessage(DISMISS_DIALOG_MSG, d));
+                            obtainMessage(DISMISS_DIALOG_UI_MSG, d));
                     d.show();
                 }
             } break;
-            case SHOW_FINGERPRINT_ERROR_MSG: {
+            case SHOW_FINGERPRINT_ERROR_UI_MSG: {
                 if (mShowDialogs) {
                     AlertDialog d = new BaseErrorDialog(mContext);
                     d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
@@ -1543,11 +1551,11 @@
                     d.setTitle(mContext.getText(R.string.android_system_label));
                     d.setMessage(mContext.getText(R.string.system_error_manufacturer));
                     d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
-                            obtainMessage(DISMISS_DIALOG_MSG, d));
+                            obtainMessage(DISMISS_DIALOG_UI_MSG, d));
                     d.show();
                 }
             } break;
-            case SHOW_COMPAT_MODE_DIALOG_MSG: {
+            case SHOW_COMPAT_MODE_DIALOG_UI_MSG: {
                 synchronized (ActivityManagerService.this) {
                     ActivityRecord ar = (ActivityRecord) msg.obj;
                     if (mCompatModeDialog != null) {
@@ -1575,26 +1583,26 @@
                 }
                 break;
             }
-            case START_USER_SWITCH_MSG: {
+            case START_USER_SWITCH_UI_MSG: {
                 mUserController.showUserSwitchDialog(msg.arg1, (String) msg.obj);
                 break;
             }
-            case DISMISS_DIALOG_MSG: {
+            case DISMISS_DIALOG_UI_MSG: {
                 final Dialog d = (Dialog) msg.obj;
                 d.dismiss();
                 break;
             }
-            case DISPATCH_PROCESSES_CHANGED: {
+            case DISPATCH_PROCESSES_CHANGED_UI_MSG: {
                 dispatchProcessesChanged();
                 break;
             }
-            case DISPATCH_PROCESS_DIED: {
+            case DISPATCH_PROCESS_DIED_UI_MSG: {
                 final int pid = msg.arg1;
                 final int uid = msg.arg2;
                 dispatchProcessDied(pid, uid);
                 break;
             }
-            case DISPATCH_UIDS_CHANGED_MSG: {
+            case DISPATCH_UIDS_CHANGED_UI_MSG: {
                 dispatchUidsChanged();
             } break;
             }
@@ -2046,7 +2054,7 @@
                 // it is finished we make sure it is reset to its default.
                 mUserIsMonkey = false;
             } break;
-            case APP_BOOST_DEACTIVATE_MSG : {
+            case APP_BOOST_DEACTIVATE_MSG: {
                 synchronized(ActivityManagerService.this) {
                     if (mIsBoosted) {
                         if (mBoostStartTime < (SystemClock.uptimeMillis() - APP_BOOST_TIMEOUT)) {
@@ -2060,6 +2068,9 @@
                     }
                 }
             } break;
+            case IDLE_UIDS_MSG: {
+                idleUids();
+            } break;
             }
         }
     };
@@ -2352,6 +2363,17 @@
         mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
 
         mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
+        mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
+                new IAppOpsCallback.Stub() {
+                    @Override public void opChanged(int op, int uid, String packageName) {
+                        if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
+                            if (mAppOpsService.checkOperation(op, uid, packageName)
+                                    != AppOpsManager.MODE_ALLOWED) {
+                                runInBackgroundDisabled(uid);
+                            }
+                        }
+                    }
+                });
 
         mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
 
@@ -2766,7 +2788,7 @@
 
     final void showAskCompatModeDialogLocked(ActivityRecord r) {
         Message msg = Message.obtain();
-        msg.what = SHOW_COMPAT_MODE_DIALOG_MSG;
+        msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
         msg.obj = r.task.askedCompatMode ? null : r;
         mUiHandler.sendMessage(msg);
     }
@@ -3337,15 +3359,6 @@
             if ("1".equals(SystemProperties.get("debug.checkjni"))) {
                 debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
             }
-            String jitDebugProperty = SystemProperties.get("debug.usejit");
-            if ("true".equals(jitDebugProperty)) {
-                debugFlags |= Zygote.DEBUG_ENABLE_JIT;
-            } else if (!"false".equals(jitDebugProperty)) {
-                // If we didn't force disable by setting false, defer to the dalvik vm options.
-                if ("true".equals(SystemProperties.get("dalvik.vm.usejit"))) {
-                    debugFlags |= Zygote.DEBUG_ENABLE_JIT;
-                }
-            }
             String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info");
             if ("true".equals(genDebugInfoProperty)) {
                 debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
@@ -3797,8 +3810,10 @@
             for (int i=0; i<N; i++) {
                 final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
                 mActiveUidChanges[i] = change;
-                change.uidRecord.pendingChange = null;
-                change.uidRecord = null;
+                if (change.uidRecord != null) {
+                    change.uidRecord.pendingChange = null;
+                    change.uidRecord = null;
+                }
             }
             mPendingUidChanges.clear();
             if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
@@ -3808,7 +3823,8 @@
         if (mLocalPowerManager != null) {
             for (int j=0; j<N; j++) {
                 UidRecord.ChangeItem item = mActiveUidChanges[j];
-                if (item.gone) {
+                if (item.change == UidRecord.CHANGE_GONE
+                        || item.change == UidRecord.CHANGE_GONE_IDLE) {
                     mLocalPowerManager.uidGone(item.uid);
                 } else {
                     mLocalPowerManager.updateUidProcState(item.uid, item.processState);
@@ -3820,19 +3836,66 @@
         while (i > 0) {
             i--;
             final IUidObserver observer = mUidObservers.getBroadcastItem(i);
+            final int which = (Integer)mUidObservers.getBroadcastCookie(i);
             if (observer != null) {
                 try {
                     for (int j=0; j<N; j++) {
                         UidRecord.ChangeItem item = mActiveUidChanges[j];
-                        if (item.gone) {
-                            if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                    "UID gone uid=" + item.uid);
-                            observer.onUidGone(item.uid);
+                        final int change = item.change;
+                        UidRecord validateUid = null;
+                        if (VALIDATE_UID_STATES && i == 0) {
+                            validateUid = mValidateUids.get(item.uid);
+                            if (validateUid == null && change != UidRecord.CHANGE_GONE
+                                    && change != UidRecord.CHANGE_GONE_IDLE) {
+                                validateUid = new UidRecord(item.uid);
+                                mValidateUids.put(item.uid, validateUid);
+                            }
+                        }
+                        if (change == UidRecord.CHANGE_IDLE
+                                || change == UidRecord.CHANGE_GONE_IDLE) {
+                            if ((which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID idle uid=" + item.uid);
+                                observer.onUidIdle(item.uid);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                if (validateUid != null) {
+                                    validateUid.idle = true;
+                                }
+                            }
+                        } else if (change == UidRecord.CHANGE_ACTIVE) {
+                            if ((which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID active uid=" + item.uid);
+                                observer.onUidActive(item.uid);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                validateUid.idle = false;
+                            }
+                        }
+                        if (change == UidRecord.CHANGE_GONE
+                                || change == UidRecord.CHANGE_GONE_IDLE) {
+                            if ((which & ActivityManager.UID_OBSERVER_GONE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID gone uid=" + item.uid);
+                                observer.onUidGone(item.uid);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                if (validateUid != null) {
+                                    mValidateUids.remove(item.uid);
+                                }
+                            }
                         } else {
-                            if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                                    "UID CHANGED uid=" + item.uid
-                                    + ": " + item.processState);
-                            observer.onUidStateChanged(item.uid, item.processState);
+                            if ((which & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+                                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                                        "UID CHANGED uid=" + item.uid
+                                                + ": " + item.processState);
+                                observer.onUidStateChanged(item.uid, item.processState);
+                            }
+                            if (VALIDATE_UID_STATES && i == 0) {
+                                validateUid.curProcState = validateUid.setProcState
+                                        = item.processState;
+                            }
                         }
                     }
                 } catch (RemoteException e) {
@@ -5102,7 +5165,7 @@
             // Bring up the infamous App Not Responding dialog
             Message msg = Message.obtain();
             HashMap<String, Object> map = new HashMap<String, Object>();
-            msg.what = SHOW_NOT_RESPONDING_MSG;
+            msg.what = SHOW_NOT_RESPONDING_UI_MSG;
             msg.obj = map;
             msg.arg1 = aboveSystem ? 1 : 0;
             map.put("app", app);
@@ -5881,7 +5944,7 @@
                 // No more processes using this uid, tell clients it is gone.
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "No more processes in " + old.uidRecord);
-                enqueueUidChangeLocked(old.uidRecord, true);
+                enqueueUidChangeLocked(old.uidRecord, -1, UidRecord.CHANGE_GONE);
                 mActiveUids.remove(uid);
             }
             old.uidRecord = null;
@@ -5907,7 +5970,7 @@
             if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                     "Creating new process uid: " + uidRec);
             mActiveUids.put(proc.uid, uidRec);
-            enqueueUidChangeLocked(uidRec, false);
+            enqueueUidChangeLocked(uidRec, -1, UidRecord.CHANGE_ACTIVE);
         }
         proc.uidRecord = uidRec;
         uidRec.numProcs++;
@@ -6715,9 +6778,11 @@
         if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
             activity = ActivityRecord.isInStackLocked(token);
             if (activity == null) {
+                Slog.w(TAG, "Failed createPendingResult: activity " + token + " not in any stack");
                 return null;
             }
             if (activity.finishing) {
+                Slog.w(TAG, "Failed createPendingResult: activity " + activity + " is finishing");
                 return null;
             }
         }
@@ -7269,6 +7334,36 @@
         return readMet && writeMet;
     }
 
+    public int getAppStartMode(int uid, String packageName) {
+        synchronized (this) {
+            boolean bg = checkAllowBackgroundLocked(uid, packageName, -1);
+            return bg ? ActivityManager.APP_START_MODE_NORMAL
+                    : ActivityManager.APP_START_MODE_DISABLED;
+        }
+    }
+
+    boolean checkAllowBackgroundLocked(int uid, String packageName, int callingPid) {
+        UidRecord uidRec = mActiveUids.get(uid);
+        if (uidRec == null || uidRec.idle) {
+            if (callingPid >= 0) {
+                ProcessRecord proc;
+                synchronized (mPidsSelfLocked) {
+                    proc = mPidsSelfLocked.get(callingPid);
+                }
+                if (proc != null && proc.curProcState < ActivityManager.PROCESS_STATE_RECEIVER) {
+                    // Whoever is instigating this is in the foreground, so we will allow it
+                    // to go through.
+                    return true;
+                }
+            }
+            if (mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private ProviderInfo getProviderInfoLocked(String authority, int userHandle) {
         ProviderInfo pi = null;
         ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle);
@@ -8268,7 +8363,7 @@
             if (app == null) return;
 
             Message msg = Message.obtain();
-            msg.what = WAIT_FOR_DEBUGGER_MSG;
+            msg.what = WAIT_FOR_DEBUGGER_UI_MSG;
             msg.obj = app;
             msg.arg1 = waiting ? 1 : 0;
             mUiHandler.sendMessage(msg);
@@ -11213,11 +11308,12 @@
         }
     }
 
-    public void registerUidObserver(IUidObserver observer) {
+    @Override
+    public void registerUidObserver(IUidObserver observer, int which) {
         enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerUidObserver()");
         synchronized (this) {
-            mUidObservers.register(observer);
+            mUidObservers.register(observer, which);
         }
     }
 
@@ -12056,7 +12152,7 @@
                     mTopData = null;
                     mTopComponent = null;
                     Message msg = Message.obtain();
-                    msg.what = SHOW_FACTORY_ERROR_MSG;
+                    msg.what = SHOW_FACTORY_ERROR_UI_MSG;
                     msg.getData().putCharSequence("msg", errorMsg);
                     mUiHandler.sendMessage(msg);
                 }
@@ -12110,14 +12206,14 @@
                 if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
                     Slog.e(TAG, "UIDs on the system are inconsistent, you need to wipe your"
                             + " data partition or your device will be unstable.");
-                    mUiHandler.obtainMessage(SHOW_UID_ERROR_MSG).sendToTarget();
+                    mUiHandler.obtainMessage(SHOW_UID_ERROR_UI_MSG).sendToTarget();
                 }
             } catch (RemoteException e) {
             }
 
             if (!Build.isBuildConsistent()) {
                 Slog.e(TAG, "Build fingerprint is not consistent, warning user");
-                mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_MSG).sendToTarget();
+                mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_UI_MSG).sendToTarget();
             }
 
             long ident = Binder.clearCallingIdentity();
@@ -12398,7 +12494,7 @@
                 final long origId = Binder.clearCallingIdentity();
 
                 Message msg = Message.obtain();
-                msg.what = SHOW_STRICT_MODE_VIOLATION_MSG;
+                msg.what = SHOW_STRICT_MODE_VIOLATION_UI_MSG;
                 HashMap<String, Object> data = new HashMap<String, Object>();
                 data.put("result", result);
                 data.put("app", r);
@@ -12855,7 +12951,7 @@
             }
 
             Message msg = Message.obtain();
-            msg.what = SHOW_ERROR_MSG;
+            msg.what = SHOW_ERROR_UI_MSG;
             HashMap data = new HashMap();
             data.put("result", result);
             data.put("app", r);
@@ -13484,6 +13580,39 @@
         }
     }
 
+    boolean dumpUids(PrintWriter pw, String dumpPackage, SparseArray<UidRecord> uids,
+            String header, boolean needSep) {
+        boolean printed = false;
+        int whichAppId = -1;
+        if (dumpPackage != null) {
+            try {
+                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+                        dumpPackage, 0);
+                whichAppId = UserHandle.getAppId(info.uid);
+            } catch (NameNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+        for (int i=0; i<uids.size(); i++) {
+            UidRecord uidRec = uids.valueAt(i);
+            if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+                continue;
+            }
+            if (!printed) {
+                printed = true;
+                if (needSep) {
+                    pw.println();
+                }
+                pw.print("  ");
+                pw.println(header);
+                needSep = true;
+            }
+            pw.print("    UID "); UserHandle.formatUid(pw, uidRec.uid);
+            pw.print(": "); pw.println(uidRec);
+        }
+        return printed;
+    }
+
     void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
@@ -13540,33 +13669,13 @@
         }
 
         if (mActiveUids.size() > 0) {
-            boolean printed = false;
-            int whichAppId = -1;
-            if (dumpPackage != null) {
-                try {
-                    ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
-                            dumpPackage, 0);
-                    whichAppId = UserHandle.getAppId(info.uid);
-                } catch (NameNotFoundException e) {
-                    e.printStackTrace();
-                }
+            if (dumpUids(pw, dumpPackage, mActiveUids, "UID states:", needSep)) {
+                printedAnything = needSep = true;
             }
-            for (int i=0; i<mActiveUids.size(); i++) {
-                UidRecord uidRec = mActiveUids.valueAt(i);
-                if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
-                    continue;
-                }
-                if (!printed) {
-                    printed = true;
-                    if (needSep) {
-                        pw.println();
-                    }
-                    pw.println("  UID states:");
-                    needSep = true;
-                    printedAnything = true;
-                }
-                pw.print("    UID "); UserHandle.formatUid(pw, uidRec.uid);
-                pw.print(": "); pw.println(uidRec);
+        }
+        if (mValidateUids.size() > 0) {
+            if (dumpUids(pw, dumpPackage, mValidateUids, "UID validation:", needSep)) {
+                printedAnything = needSep = true;
             }
         }
 
@@ -14457,6 +14566,9 @@
                 if (object1.first.setAdj != object2.first.setAdj) {
                     return object1.first.setAdj > object2.first.setAdj ? -1 : 1;
                 }
+                if (object1.first.setProcState != object2.first.setProcState) {
+                    return object1.first.setProcState > object2.first.setProcState ? -1 : 1;
+                }
                 if (object1.second.intValue() != object2.second.intValue()) {
                     return object1.second.intValue() > object2.second.intValue() ? -1 : 1;
                 }
@@ -15829,7 +15941,8 @@
                 mAvailProcessChanges.add(item);
             }
         }
-        mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
+        mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED_UI_MSG, app.pid, app.info.uid,
+                null).sendToTarget();
 
         // If the caller is restarting this app, then leave it in its
         // current lists and let the caller take care of it.
@@ -18894,8 +19007,6 @@
                         }
                     }
                 }
-                Process.setSwappiness(app.pid,
-                        app.curSchedGroup <= Process.THREAD_GROUP_BG_NONINTERACTIVE);
             }
         }
         if (app.repForegroundActivities != app.foregroundActivities) {
@@ -19020,7 +19131,7 @@
                 if (mPendingProcessChanges.size() == 0) {
                     if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
                             "*** Enqueueing dispatch processes changed!");
-                    mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
+                    mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG).sendToTarget();
                 }
                 mPendingProcessChanges.add(item);
             }
@@ -19039,29 +19150,46 @@
         return success;
     }
 
-    private final void enqueueUidChangeLocked(UidRecord uidRec, boolean gone) {
-        if (uidRec.pendingChange == null) {
+    private final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+        final UidRecord.ChangeItem pendingChange;
+        if (uidRec == null || uidRec.pendingChange == null) {
             if (mPendingUidChanges.size() == 0) {
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "*** Enqueueing dispatch uid changed!");
-                mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_MSG).sendToTarget();
+                mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_UI_MSG).sendToTarget();
             }
             final int NA = mAvailUidChanges.size();
             if (NA > 0) {
-                uidRec.pendingChange = mAvailUidChanges.remove(NA-1);
+                pendingChange = mAvailUidChanges.remove(NA-1);
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Retrieving available item: " + uidRec.pendingChange);
+                        "Retrieving available item: " + pendingChange);
             } else {
-                uidRec.pendingChange = new UidRecord.ChangeItem();
+                pendingChange = new UidRecord.ChangeItem();
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "Allocating new item: " + uidRec.pendingChange);
+                        "Allocating new item: " + pendingChange);
             }
-            uidRec.pendingChange.uidRecord = uidRec;
-            uidRec.pendingChange.uid = uidRec.uid;
-            mPendingUidChanges.add(uidRec.pendingChange);
+            if (uidRec != null) {
+                uidRec.pendingChange = pendingChange;
+                if (change == UidRecord.CHANGE_GONE && !uidRec.idle) {
+                    // If this uid is going away, and we haven't yet reported it is gone,
+                    // then do so now.
+                    change = UidRecord.CHANGE_GONE_IDLE;
+                }
+            } else if (uid < 0) {
+                throw new IllegalArgumentException("No UidRecord or uid");
+            }
+            pendingChange.uidRecord = uidRec;
+            pendingChange.uid = uidRec != null ? uidRec.uid : uid;
+            mPendingUidChanges.add(pendingChange);
+        } else {
+            pendingChange = uidRec.pendingChange;
+            if (change == UidRecord.CHANGE_GONE && pendingChange.change == UidRecord.CHANGE_IDLE) {
+                change = UidRecord.CHANGE_GONE_IDLE;
+            }
         }
-        uidRec.pendingChange.gone = gone;
-        uidRec.pendingChange.processState = uidRec.setProcState;
+        pendingChange.change = change;
+        pendingChange.processState = uidRec != null
+                ? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
     }
 
     private void maybeUpdateProviderUsageStatsLocked(ProcessRecord app, String providerPkgName,
@@ -19608,12 +19736,31 @@
         // Update from any uid changes.
         for (int i=mActiveUids.size()-1; i>=0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
+            int uidChange = UidRecord.CHANGE_PROCSTATE;
             if (uidRec.setProcState != uidRec.curProcState) {
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
                         "Changes in " + uidRec + ": proc state from " + uidRec.setProcState
                         + " to " + uidRec.curProcState);
+                if (ActivityManager.isProcStateBackground(uidRec.curProcState)) {
+                    if (!ActivityManager.isProcStateBackground(uidRec.setProcState)) {
+                        uidRec.lastBackgroundTime = nowElapsed;
+                        if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {
+                            // Note: the background settle time is in elapsed realtime, while
+                            // the handler time base is uptime.  All this means is that we may
+                            // stop background uids later than we had intended, but that only
+                            // happens because the device was sleeping so we are okay anyway.
+                            mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, BACKGROUND_SETTLE_TIME);
+                        }
+                    }
+                } else {
+                    if (uidRec.idle) {
+                        uidChange = UidRecord.CHANGE_ACTIVE;
+                        uidRec.idle = false;
+                    }
+                    uidRec.lastBackgroundTime = 0;
+                }
                 uidRec.setProcState = uidRec.curProcState;
-                enqueueUidChangeLocked(uidRec, false);
+                enqueueUidChangeLocked(uidRec, -1, uidChange);
             }
         }
 
@@ -19638,6 +19785,53 @@
         }
     }
 
+    final void idleUids() {
+        synchronized (this) {
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long maxBgTime = nowElapsed - BACKGROUND_SETTLE_TIME;
+            long nextTime = 0;
+            for (int i=mActiveUids.size()-1; i>=0; i--) {
+                final UidRecord uidRec = mActiveUids.valueAt(i);
+                final long bgTime = uidRec.lastBackgroundTime;
+                if (bgTime > 0 && !uidRec.idle) {
+                    if (bgTime <= maxBgTime) {
+                        uidRec.idle = true;
+                        doStopUidLocked(uidRec.uid, uidRec);
+                    } else {
+                        if (nextTime == 0 || nextTime > bgTime) {
+                            nextTime = bgTime;
+                        }
+                    }
+                }
+            }
+            if (nextTime > 0) {
+                mHandler.removeMessages(IDLE_UIDS_MSG);
+                mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+                        nextTime + BACKGROUND_SETTLE_TIME - nowElapsed);
+            }
+        }
+    }
+
+    final void runInBackgroundDisabled(int uid) {
+        synchronized (this) {
+            UidRecord uidRec = mActiveUids.get(uid);
+            if (uidRec != null) {
+                // This uid is actually running...  should it be considered background now?
+                if (uidRec.idle) {
+                    doStopUidLocked(uidRec.uid, uidRec);
+                }
+            } else {
+                // This uid isn't actually running...  still send a report about it being "stopped".
+                doStopUidLocked(uid, null);
+            }
+        }
+    }
+
+    final void doStopUidLocked(int uid, final UidRecord uidRec) {
+        mServices.stopInBackgroundLocked(uid);
+        enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
+    }
+
     final void trimApplications() {
         synchronized (this) {
             int i;
@@ -19973,8 +20167,9 @@
             userName = userInfo.name;
             mUserController.setTargetUserIdLocked(userId);
         }
-        mUiHandler.removeMessages(START_USER_SWITCH_MSG);
-        mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
+        mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
+        mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_UI_MSG, userId, 0,
+                userName));
         return true;
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d1e7e85..13c1417 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -73,7 +73,7 @@
         String opt;
         while ((opt = getNextOption()) != null) {
             if (opt.equals("--user")) {
-                userId = parseUserArg(getNextArgRequired());
+                userId = UserHandle.parseUserArg(getNextArgRequired());
             } else {
                 pw.println("Error: Unknown option: " + opt);
                 return -1;
@@ -89,7 +89,7 @@
         String opt;
         while ((opt=getNextOption()) != null) {
             if (opt.equals("--user")) {
-                userId = parseUserArg(getNextArgRequired());
+                userId = UserHandle.parseUserArg(getNextArgRequired());
             } else {
                 pw.println("Error: Unknown option: " + opt);
                 return -1;
@@ -141,22 +141,6 @@
         return 0;
     }
 
-    int parseUserArg(String arg) {
-        int userId;
-        if ("all".equals(arg)) {
-            userId = UserHandle.USER_ALL;
-        } else if ("current".equals(arg) || "cur".equals(arg)) {
-            userId = UserHandle.USER_CURRENT;
-        } else {
-            try {
-                userId = Integer.parseInt(arg);
-            } catch (NumberFormatException e) {
-                throw new IllegalArgumentException("Bad user number: " + arg);
-            }
-        }
-        return userId;
-    }
-
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 13d3ee1..0ec4b18 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3354,11 +3354,10 @@
             // preserve the old window until the new one is drawn. This prevents having a gap
             // between the removal and addition, in which no window is visible. We also want the
             // entrance of the new window to be properly animated.
-            mWindowManager.setReplacingWindow(topActivity.appToken, true /* animate */);
+            mWindowManager.setReplacingWindow(topActivity.appToken);
         }
-        final ActivityStack stack =
-                moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus,
-                        "moveTaskToStack:" + reason);
+        final ActivityStack stack = moveTaskToStackUncheckedLocked(
+                task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
 
         // Make sure the task has the appropriate bounds/size for the stack it is in.
         if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index d317791..fb37eda 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -556,6 +556,17 @@
                     + " (uid " + r.callingUid + ")");
             skip = true;
         }
+        if (!skip) {
+            if (!mService.checkAllowBackgroundLocked(filter.receiverList.uid, filter.packageName,
+                    -1)) {
+                Slog.w(TAG, "Background execution not allowed: receiving "
+                        + r.intent
+                        + " to " + filter.receiverList.app
+                        + " (pid=" + filter.receiverList.pid
+                        + ", uid=" + filter.receiverList.uid + ")");
+                skip = true;
+            }
+        }
 
         if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                 r.callingPid, r.resolvedType, filter.receiverList.uid)) {
@@ -938,6 +949,15 @@
                 skip = true;
             }
             if (!skip) {
+                if (!mService.checkAllowBackgroundLocked(info.activityInfo.applicationInfo.uid,
+                        info.activityInfo.packageName, -1)) {
+                    Slog.w(TAG, "Background execution not allowed: receiving "
+                            + r.intent + " to "
+                            + component.flattenToShortString());
+                    skip = true;
+                }
+            }
+            if (!skip) {
                 skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                         r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
             }
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index b4efbf0..d24c3a5 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -17,7 +17,9 @@
 package com.android.server.am;
 
 import android.app.ActivityManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.util.TimeUtils;
 
 /**
  * Overall information about a uid that has actively running processes.
@@ -26,12 +28,20 @@
     final int uid;
     int curProcState;
     int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+    long lastBackgroundTime;
+    boolean idle;
     int numProcs;
 
+    static final int CHANGE_PROCSTATE = 0;
+    static final int CHANGE_GONE = 1;
+    static final int CHANGE_GONE_IDLE = 2;
+    static final int CHANGE_IDLE = 3;
+    static final int CHANGE_ACTIVE = 4;
+
     static final class ChangeItem {
         UidRecord uidRecord;
         int uid;
-        boolean gone;
+        int change;
         int processState;
     }
 
@@ -54,9 +64,16 @@
         UserHandle.formatUid(sb, uid);
         sb.append(' ');
         sb.append(ProcessList.makeProcStateString(curProcState));
-        sb.append(" / ");
+        if (lastBackgroundTime > 0) {
+            sb.append(" bg:");
+            TimeUtils.formatDuration(SystemClock.elapsedRealtime()-lastBackgroundTime, sb);
+        }
+        if (idle) {
+            sb.append(" idle");
+        }
+        sb.append(" procs:");
         sb.append(numProcs);
-        sb.append(" procs}");
+        sb.append("}");
         return sb.toString();
     }
 }
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index b766894..75a74c0 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -466,7 +466,7 @@
                 if (cname == null) {
                     info = new SyncStorageEngine.EndPoint(account, authority, userId);
                 } else {
-                    info = new SyncStorageEngine.EndPoint(cname, userId);
+                    info = new SyncStorageEngine.EndPoint(cname, userId, -1);
                 }
                 syncManager.clearScheduledSyncOperations(info);
                 syncManager.cancelActiveSync(info, null /* all syncs for this adapter */);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 82e0eaf..4f53882 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -21,8 +21,10 @@
 import android.accounts.AccountManager;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
+import android.app.IUidObserver;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -79,11 +81,14 @@
 import android.util.Log;
 import android.util.Pair;
 
+import android.util.Slog;
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
 import com.android.server.accounts.AccountManagerService;
 import com.android.server.content.SyncStorageEngine.AuthorityInfo;
 import com.android.server.content.SyncStorageEngine.EndPoint;
@@ -207,6 +212,7 @@
     volatile private boolean mDataConnectionIsConnected = false;
     volatile private boolean mStorageIsLow = false;
     volatile private boolean mDeviceIsIdle = false;
+    volatile private boolean mReportedSyncActive = false;
 
     private final NotificationManager mNotificationMgr;
     private AlarmManager mAlarmService = null;
@@ -234,7 +240,7 @@
 
     private final AppIdleMonitor mAppIdleMonitor;
 
-    private BroadcastReceiver mStorageIntentReceiver =
+    private final BroadcastReceiver mStorageIntentReceiver =
             new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
@@ -257,7 +263,7 @@
                 }
             };
 
-    private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             boolean idle = mPowerManager.isDeviceIdleMode()
                     || mPowerManager.isLightDeviceIdleMode();
@@ -267,12 +273,18 @@
                         SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
                         null /* any sync */);
             } else {
+                if (mLocalDeviceIdleController != null) {
+                    if (!mReportedSyncActive) {
+                        mReportedSyncActive = true;
+                        mLocalDeviceIdleController.setSyncActive(true);
+                    }
+                }
                 sendCheckAlarmsMessage();
             }
         }
     };
 
-    private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             mBootCompleted = true;
@@ -280,7 +292,7 @@
         }
     };
 
-    private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             updateRunningAccounts();
@@ -291,7 +303,23 @@
         }
     };
 
+    private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+            cancelSyncsForUid(uid);
+        }
+    };
+
     private final PowerManager mPowerManager;
+    DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     // Use this as a random offset to seed all periodic syncs.
     private int mSyncRandomOffsetMillis;
@@ -494,6 +522,13 @@
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
 
+        try {
+            ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
+                    ActivityManager.UID_OBSERVER_IDLE);
+        } catch (RemoteException e) {
+            // ignored; both services live in system_server
+        }
+
         if (!factoryTest) {
             mNotificationMgr = (NotificationManager)
                 context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -651,6 +686,26 @@
             Log.d(TAG, "one off sync for: " + cname + " " + extras.toString());
         }
 
+        final android.content.pm.ServiceInfo sinfo;
+        try {
+            sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "Not scheduling sync " + cname
+                    + " -- can't find service for user " + userId);
+            return;
+        }
+        final int sUid = sinfo.applicationInfo.uid;
+
+        try {
+            if (ActivityManagerNative.getDefault().getAppStartMode(sUid, cname.getPackageName())
+                    == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Not scheduling sync " + sUid + ":" + cname
+                        + " -- package not allowed to start");
+                return;
+            }
+        } catch (RemoteException e) {
+        }
+
         Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
         if (expedited) {
             runtimeMillis = -1; // this means schedule at the front of the queue
@@ -678,7 +733,7 @@
             }
             return;
         }
-        SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId);
+        SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId, sUid);
         Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
         long delayUntil = mSyncStorageEngine.getDelayUntilTime(info);
         final long backoffTime = backoff != null ? backoff.first : 0;
@@ -692,7 +747,7 @@
                         + ", extras " + extras);
         }
         scheduleSyncOperation(
-                new SyncOperation(cname, userId, uid, source, extras,
+                new SyncOperation(cname, userId, sUid, cname.getPackageName(), uid, source, extras,
                         runtimeMillis /* runtime */,
                         beforeRunTimeMillis /* flextime */,
                         backoffTime,
@@ -827,6 +882,18 @@
                 if (syncAdapterInfo == null) {
                     continue;
                 }
+                final int owningUid = syncAdapterInfo.uid;
+                final String owningPackage = syncAdapterInfo.componentName.getPackageName();
+                try {
+                    if (ActivityManagerNative.getDefault().getAppStartMode(owningUid,
+                            owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+                        Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":"
+                                + syncAdapterInfo.componentName
+                                + " -- package not allowed to start");
+                        return;
+                    }
+                } catch (RemoteException e) {
+                }
                 final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
                 final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
                 if (isSyncable < 0 && isAlwaysSyncable) {
@@ -876,7 +943,8 @@
                                 + ", extras " + newExtras);
                     }
                     scheduleSyncOperation(
-                            new SyncOperation(account.account, account.userId, reason, source,
+                            new SyncOperation(account.account, account.userId,
+                                    owningUid, owningPackage, reason, source,
                                     authority, newExtras, 0 /* immediate */, 0 /* No flex time*/,
                                     backoffTime, delayUntil, allowParallelSyncs));
                 }
@@ -892,7 +960,8 @@
                                 + ", extras " + extras);
                     }
                     scheduleSyncOperation(
-                            new SyncOperation(account.account, account.userId, reason, source,
+                            new SyncOperation(account.account, account.userId,
+                                    owningUid, owningPackage, reason, source,
                                     authority, extras, runtimeMillis, beforeRuntimeMillis,
                                     backoffTime, delayUntil, allowParallelSyncs));
                 }
@@ -1299,6 +1368,14 @@
         }
     }
 
+    void cancelSyncsForUid(int uid) {
+        synchronized (mSyncQueue) {
+            if (mSyncQueue.removeUidIfNeededLocked(uid)) {
+                sendCheckAlarmsMessage();
+            }
+        }
+    }
+
     /**
      * @hide
      */
@@ -1470,6 +1547,7 @@
         }
         pw.print("memory low: "); pw.println(mStorageIsLow);
         pw.print("device idle: "); pw.println(mDeviceIsIdle);
+        pw.print("reported active: "); pw.println(mReportedSyncActive);
 
         final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
 
@@ -2155,8 +2233,8 @@
         /**
          * Stash any messages that come to the handler before boot is complete or before the device
          * is properly provisioned (i.e. out of set-up wizard).
-         * {@link #onBootCompleted()} and {@link #onDeviceProvisioned(boolean)} both need to come
-         * in before we start syncing.
+         * {@link #onBootCompleted()} and {@link SyncHandler#onDeviceProvisioned} both
+         * need to come in before we start syncing.
          * @param msg Message to dispatch at a later point.
          * @return true if a message was enqueued, false otherwise. This is to avoid losing the
          * message if we manage to acquire the lock but by the time we do boot has completed.
@@ -2492,6 +2570,8 @@
                             }
                             scheduleSyncOperation(
                                     new SyncOperation(target.account, target.userId,
+                                            syncAdapterInfo.uid,
+                                            syncAdapterInfo.componentName.getPackageName(),
                                             SyncOperation.REASON_PERIODIC,
                                             SyncStorageEngine.SOURCE_PERIODIC,
                                             target.provider, extras,
@@ -2502,6 +2582,7 @@
                         } else if (target.target_service) {
                             scheduleSyncOperation(
                                     new SyncOperation(target.service, target.userId,
+                                            target.serviceUid, target.service.getPackageName(),
                                             SyncOperation.REASON_PERIODIC,
                                             SyncStorageEngine.SOURCE_PERIODIC,
                                             extras,
@@ -2544,6 +2625,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2551,6 +2633,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: memory low, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2558,6 +2641,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: device idle, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2567,6 +2651,7 @@
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
                 }
+                setSyncActive(false);
                 return Long.MAX_VALUE;
             }
 
@@ -2772,9 +2857,25 @@
                 dispatchSyncOperation(candidate);
             }
 
+            setSyncActive(mActiveSyncContexts.size() > 0);
+
             return nextReadyToRunTime;
         }
 
+        void setSyncActive(boolean active) {
+            if (mLocalDeviceIdleController == null) {
+                mLocalDeviceIdleController
+                        = LocalServices.getService(DeviceIdleController.LocalService.class);
+            }
+            if (mLocalDeviceIdleController != null) {
+                if (mReportedSyncActive != active) {
+                    mReportedSyncActive = active;
+                    mLocalDeviceIdleController.setSyncActive(active);
+                }
+            }
+
+        }
+
         private boolean isSyncNotUsingNetworkH(ActiveSyncContext activeSyncContext) {
             final long bytesTransferredCurrent =
                     getTotalBytesTransferredByUid(activeSyncContext.mSyncAdapterUid);
@@ -3114,6 +3215,7 @@
                 if (syncResult != null && syncResult.fullSyncRequested) {
                     scheduleSyncOperation(
                             new SyncOperation(info.account, info.userId,
+                                    syncOperation.owningUid, syncOperation.owningPackage,
                                 syncOperation.reason,
                                 syncOperation.syncSource, info.provider, new Bundle(),
                                 0 /* delay */, 0 /* flex */,
@@ -3124,6 +3226,7 @@
                 if (syncResult != null && syncResult.fullSyncRequested) {
                     scheduleSyncOperation(
                             new SyncOperation(info.service, info.userId,
+                                    syncOperation.owningUid, syncOperation.owningPackage,
                                 syncOperation.reason,
                                 syncOperation.syncSource, new Bundle(),
                                 0 /* delay */, 0 /* flex */,
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 10efe81..ab777ae 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -22,6 +22,7 @@
 import android.content.ContentResolver;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Log;
 
 /**
@@ -62,6 +63,8 @@
 
     /** Identifying info for the target for this operation. */
     public final SyncStorageEngine.EndPoint target;
+    public final int owningUid;
+    public final String owningPackage;
     /** Why this sync was kicked off. {@link #REASON_NAMES} */
     public final int reason;
     /** Where this sync was initiated. */
@@ -93,25 +96,28 @@
     /** Whether this sync op was recently skipped due to the app being idle */
     public boolean appIdle;
 
-    public SyncOperation(Account account, int userId, int reason, int source, String provider,
-            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
-            long delayUntil, boolean allowParallelSyncs) {
-        this(new SyncStorageEngine.EndPoint(account, provider, userId),
+    public SyncOperation(Account account, int userId, int owningUid, String owningPackage,
+            int reason, int source, String provider, Bundle extras, long runTimeFromNow,
+            long flexTime, long backoff, long delayUntil, boolean allowParallelSyncs) {
+        this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage,
                 reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
                 allowParallelSyncs);
     }
 
-    public SyncOperation(ComponentName service, int userId, int reason, int source,
-            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
+    public SyncOperation(ComponentName service, int userId, int owningUid, String owningPackage,
+            int reason, int source, Bundle extras, long runTimeFromNow, long flexTime, long backoff,
             long delayUntil) {
-        this(new SyncStorageEngine.EndPoint(service, userId), reason, source, extras,
-                runTimeFromNow, flexTime, backoff, delayUntil, true /* allowParallelSyncs */);
+        this(new SyncStorageEngine.EndPoint(service, userId, owningUid), owningUid, owningPackage,
+                reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
+                true /* allowParallelSyncs */);
     }
 
-    private SyncOperation(SyncStorageEngine.EndPoint info, int reason, int source, Bundle extras,
-            long runTimeFromNow, long flexTime, long backoff, long delayUntil,
-            boolean allowParallelSyncs) {
+    private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
+            int reason, int source, Bundle extras, long runTimeFromNow, long flexTime,
+            long backoff, long delayUntil, boolean allowParallelSyncs) {
         this.target = info;
+        this.owningUid = owningUid;
+        this.owningPackage = owningPackage;
         this.reason = reason;
         this.syncSource = source;
         this.extras = new Bundle(extras);
@@ -142,7 +148,8 @@
 
     /** Used to reschedule a sync at a new point in time. */
     public SyncOperation(SyncOperation other, long newRunTimeFromNow) {
-        this(other.target, other.reason, other.syncSource, new Bundle(other.extras),
+        this(other.target, other.owningUid, other.owningPackage, other.reason, other.syncSource,
+                new Bundle(other.extras),
                 newRunTimeFromNow,
                 0L /* In back-off so no flex */,
                 other.backoff,
@@ -228,6 +235,13 @@
         }
         sb.append(", reason: ");
         sb.append(reasonToString(pm, reason));
+        if (!useOneLine) {
+            sb.append("\n    ");
+            sb.append("owningUid=");
+            UserHandle.formatUid(sb, owningUid);
+            sb.append(" owningPackage=");
+            sb.append(owningPackage);
+        }
         if (!useOneLine && !extras.keySet().isEmpty()) {
             sb.append("\n    ");
             extrasToStringBuilder(extras, sb);
diff --git a/services/core/java/com/android/server/content/SyncQueue.java b/services/core/java/com/android/server/content/SyncQueue.java
index 587de1c..b15d0d8 100644
--- a/services/core/java/com/android/server/content/SyncQueue.java
+++ b/services/core/java/com/android/server/content/SyncQueue.java
@@ -16,16 +16,20 @@
 
 package com.android.server.content;
 
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.content.pm.PackageManager;
 import android.content.SyncAdapterType;
 import android.content.SyncAdaptersCache;
 import android.content.pm.RegisteredServicesCache.ServiceInfo;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Pair;
 
+import android.util.Slog;
 import com.google.android.collect.Maps;
 
 import java.util.ArrayList;
@@ -74,8 +78,9 @@
                     continue;
                 }
                 operationToAdd = new SyncOperation(
-                        info.account, info.userId, op.reason, op.syncSource, info.provider,
-                        op.extras,
+                        info.account, info.userId, syncAdapterInfo.uid,
+                        syncAdapterInfo.componentName.getPackageName(), op.reason,
+                        op.syncSource, info.provider, op.extras,
                         op.expedited ? -1 : 0 /* delay */,
                         0 /* flex */,
                         backoff != null ? backoff.first : 0L,
@@ -84,16 +89,24 @@
                 operationToAdd.pendingOperation = op;
                 add(operationToAdd, op);
             } else if (info.target_service) {
+                android.content.pm.ServiceInfo sinfo;
                 try {
-                    mPackageManager.getServiceInfo(info.service, 0);
+                    sinfo = mPackageManager.getServiceInfo(info.service, info.userId);
                 } catch (PackageManager.NameNotFoundException e) {
                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
                         Log.w(TAG, "Missing sync service for authority " + op.target);
                     }
                     continue;
                 }
+                if (sinfo == null) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.w(TAG, "Missing sync service for authority " + op.target);
+                    }
+                    continue;
+                }
                 operationToAdd = new SyncOperation(
-                        info.service, info.userId, op.reason, op.syncSource,
+                        info.service, info.userId, sinfo.applicationInfo.uid,
+                        info.service.getPackageName(), op.reason, op.syncSource,
                         op.extras,
                         op.expedited ? -1 : 0 /* delay */,
                         0 /* flex */,
@@ -161,9 +174,38 @@
                 opsToRemove.add(op);
             }
         }
-            for (SyncOperation op : opsToRemove) {
-                remove(op);
+        for (SyncOperation op : opsToRemove) {
+            remove(op);
+        }
+    }
+
+    public boolean removeUidIfNeededLocked(int uid) {
+        ArrayList<SyncOperation> opsToRemove = null;
+        for (SyncOperation op : mOperationsMap.values()) {
+            if (op.owningUid != uid) {
+                continue;
             }
+            try {
+                if (ActivityManagerNative.getDefault().getAppStartMode(op.owningUid,
+                        op.owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+                    Slog.w(TAG, "Removing sync " + op.owningUid + ":" + op
+                            + " -- package not allowed to start");
+                    continue;
+                }
+            } catch (RemoteException e) {
+            }
+            if (opsToRemove == null) {
+                opsToRemove = new ArrayList<SyncOperation>();
+            }
+            opsToRemove.add(op);
+        }
+        if (opsToRemove == null) {
+            return false;
+        }
+        for (SyncOperation op : opsToRemove) {
+            remove(op);
+        }
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index cca0c16..8266c08 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -27,6 +27,7 @@
 import android.content.SyncInfo;
 import android.content.SyncRequest;
 import android.content.SyncStatusInfo;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
@@ -42,6 +43,7 @@
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.ArrayMap;
 import android.util.Xml;
@@ -227,14 +229,16 @@
         public final static EndPoint USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL =
                 new EndPoint(null, null, UserHandle.USER_ALL);
         final ComponentName service;
+        final int serviceUid;           // -1 for "any"
         final Account account;
         final int userId;
         final String provider;
         final boolean target_service;
         final boolean target_provider;
 
-        public EndPoint(ComponentName service, int userId) {
+        public EndPoint(ComponentName service, int userId, int uid) {
             this.service = service;
+            this.serviceUid = uid;
             this.userId = userId;
             this.account = null;
             this.provider = null;
@@ -247,6 +251,7 @@
             this.provider = provider;
             this.userId = userId;
             this.service = null;
+            this.serviceUid = -1;
             this.target_service = false;
             this.target_provider = true;
         }
@@ -264,6 +269,11 @@
                 return false;
             }
             if (target_service && spec.target_service) {
+                if (serviceUid != spec.serviceUid
+                    && serviceUid >= 0
+                    && spec.serviceUid >= 0) {
+                    return false;
+                }
                 return service.equals(spec.service);
             } else if (target_provider && spec.target_provider) {
                 boolean accountsMatch;
@@ -290,8 +300,9 @@
                     .append("/")
                     .append(provider == null ? "ALL PDRS" : provider);
             } else if (target_service) {
-                sb.append(service.getPackageName() + "/")
-                  .append(service.getClassName());
+                service.appendShortString(sb);
+                sb.append(":");
+                UserHandle.formatUid(sb,serviceUid);
             } else {
                 sb.append("invalid target");
             }
@@ -737,7 +748,7 @@
         synchronized (mAuthorities) {
             if (cname != null) {
                 AuthorityInfo authority = getAuthorityLocked(
-                        new EndPoint(cname, userId),
+                        new EndPoint(cname, userId, -1),
                         "get service active");
                 if (authority == null) {
                     return false;
@@ -749,7 +760,7 @@
     }
 
     public void setIsTargetServiceActive(ComponentName cname, int userId, boolean active) {
-        setSyncableStateForEndPoint(new EndPoint(cname, userId), active ?
+        setSyncableStateForEndPoint(new EndPoint(cname, userId, -1), active ?
                 AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE);
     }
 
@@ -2064,18 +2075,30 @@
                             new Account(accountName, accountType),
                             authorityName, userId);
                 } else {
-                    info = new EndPoint(
-                            new ComponentName(packageName, className),
-                            userId);
+                    final ComponentName cname = new ComponentName(packageName, className);
+                    android.content.pm.ServiceInfo sinfo = null;
+                    try {
+                        sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.w(TAG, "Not restoring sync " + cname
+                                + " -- can't find service for user " + userId);
+                    }
+                    if (sinfo != null) {
+                        info = new EndPoint(cname, userId, sinfo.applicationInfo.uid);
+                    } else {
+                        info = null;
+                    }
                 }
-                authority = getOrCreateAuthorityLocked(info, id, false);
-                // If the version is 0 then we are upgrading from a file format that did not
-                // know about periodic syncs. In that case don't clear the list since we
-                // want the default, which is a daily periodic sync.
-                // Otherwise clear out this default list since we will populate it later with
-                // the periodic sync descriptions that are read from the configuration file.
-                if (version > 0) {
-                    authority.periodicSyncs.clear();
+                if (info != null) {
+                    authority = getOrCreateAuthorityLocked(info, id, false);
+                    // If the version is 0 then we are upgrading from a file format that did not
+                    // know about periodic syncs. In that case don't clear the list since we
+                    // want the default, which is a daily periodic sync.
+                    // Otherwise clear out this default list since we will populate it later with
+                    // the periodic sync descriptions that are read from the configuration file.
+                    if (version > 0) {
+                        authority.periodicSyncs.clear();
+                    }
                 }
             }
             if (authority != null) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 2b535b9..4d7df9c 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -23,7 +23,9 @@
 import java.util.List;
 
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
+import android.app.IUidObserver;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
@@ -51,6 +53,8 @@
 import android.util.SparseArray;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
 import com.android.server.job.controllers.AppIdleController;
 import com.android.server.job.controllers.BatteryController;
 import com.android.server.job.controllers.ConnectivityController;
@@ -83,6 +87,7 @@
 
     static final int MSG_JOB_EXPIRED = 0;
     static final int MSG_CHECK_JOB = 1;
+    static final int MSG_STOP_JOB = 2;
 
     // Policy constants
     /**
@@ -127,6 +132,7 @@
 
     IBatteryStats mBatteryStats;
     PowerManager mPowerManager;
+    DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     /**
      * Set to true once we are allowed to run third party apps.
@@ -139,6 +145,11 @@
     boolean mDeviceIdleMode;
 
     /**
+     * What we last reported to DeviceIdleController about wheter we are active.
+     */
+    boolean mReportedActive;
+
+    /**
      * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
      * still clean up. On reinstall the package will have a new uid.
      */
@@ -154,7 +165,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
                     }
-                    cancelJobsForUid(uidRemoved);
+                    cancelJobsForUid(uidRemoved, true);
                 }
             } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -172,6 +183,21 @@
         }
     };
 
+    final private IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
+        }
+
+        @Override public void onUidGone(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+            cancelJobsForUid(uid, false);
+        }
+    };
+
     @Override
     public void onStartUser(int userHandle) {
         mStartedUsers.add(userHandle);
@@ -194,6 +220,15 @@
     public int schedule(JobInfo job, int uId) {
         JobStatus jobStatus = new JobStatus(job, uId);
         cancelJob(uId, job.getId());
+        try {
+            if (ActivityManagerNative.getDefault().getAppStartMode(uId,
+                    job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+                        + " -- package not allowed to start");
+                return JobScheduler.RESULT_FAILURE;
+            }
+        } catch (RemoteException e) {
+        }
         startTrackingJob(jobStatus);
         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
         return JobScheduler.RESULT_SUCCESS;
@@ -229,14 +264,26 @@
      * This will remove the job from the master list, and cancel the job if it was staged for
      * execution or being executed.
      * @param uid Uid to check against for removal of a job.
+     * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
+     * whose apps are stopped.
      */
-    public void cancelJobsForUid(int uid) {
+    public void cancelJobsForUid(int uid, boolean forceAll) {
         List<JobStatus> jobsForUid;
         synchronized (mJobs) {
             jobsForUid = mJobs.getJobsByUid(uid);
         }
         for (int i=0; i<jobsForUid.size(); i++) {
             JobStatus toRemove = jobsForUid.get(i);
+            if (!forceAll) {
+                String packageName = toRemove.getServiceComponent().getPackageName();
+                try {
+                    if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
+                            != ActivityManager.APP_START_MODE_DISABLED) {
+                        continue;
+                    }
+                } catch (RemoteException e) {
+                }
+            }
             cancelJobImpl(toRemove);
         }
     }
@@ -268,6 +315,7 @@
             mPendingJobs.remove(cancelled);
             // Cancel if running.
             stopJobOnServiceContextLocked(cancelled);
+            reportActive();
         }
     }
 
@@ -299,12 +347,39 @@
                     }
                 } else {
                     // When coming out of idle, allow thing to start back up.
+                    if (rocking) {
+                        if (mLocalDeviceIdleController != null) {
+                            if (!mReportedActive) {
+                                mReportedActive = true;
+                                mLocalDeviceIdleController.setJobsActive(true);
+                            }
+                        }
+                    }
                     mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
                 }
             }
         }
     }
 
+    void reportActive() {
+        boolean active = false;
+        if (mPendingJobs.size() <= 0) {
+            for (int i=0; i<mActiveServices.size(); i++) {
+                JobServiceContext jsc = mActiveServices.get(i);
+                if (!jsc.isAvailable()) {
+                    active = true;
+                    break;
+                }
+            }
+        }
+        if (mLocalDeviceIdleController != null) {
+            if (mReportedActive != active) {
+                mReportedActive = active;
+                mLocalDeviceIdleController.setJobsActive(active);
+            }
+        }
+    }
+
     /**
      * Initializes the system service.
      * <p>
@@ -348,12 +423,20 @@
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
             mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
+            try {
+                ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
+                        ActivityManager.UID_OBSERVER_IDLE);
+            } catch (RemoteException e) {
+                // ignored; both services live in system_server
+            }
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             synchronized (mJobs) {
                 // Let's go!
                 mReadyToRock = true;
                 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                         BatteryStats.SERVICE_NAME));
+                mLocalDeviceIdleController
+                        = LocalServices.getService(DeviceIdleController.LocalService.class);
                 // Create the "runners".
                 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
                     mActiveServices.add(
@@ -597,6 +680,9 @@
                         maybeQueueReadyJobsForExecutionLockedH();
                     }
                     break;
+                case MSG_STOP_JOB:
+                    cancelJobImpl((JobStatus)message.obj);
+                    break;
             }
             maybeRunPendingJobsH();
             // Don't remove JOB_EXPIRED in case one came along while processing the queue.
@@ -623,6 +709,7 @@
                     stopJobOnServiceContextLocked(job);
                 }
             }
+            reportActive();
             if (DEBUG) {
                 final int queuedJobs = mPendingJobs.size();
                 if (queuedJobs == 0) {
@@ -647,11 +734,22 @@
             int idleCount =  0;
             int backoffCount = 0;
             int connectivityCount = 0;
-            List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
+            List<JobStatus> runnableJobs = null;
             ArraySet<JobStatus> jobs = mJobs.getJobs();
             for (int i=0; i<jobs.size(); i++) {
                 JobStatus job = jobs.valueAt(i);
                 if (isReadyToBeExecutedLocked(job)) {
+                    try {
+                        if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
+                                job.getJob().getService().getPackageName())
+                                == ActivityManager.APP_START_MODE_DISABLED) {
+                            Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+                                    + job.getJob().toString() + " -- package not allowed to start");
+                            mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
+                            continue;
+                        }
+                    } catch (RemoteException e) {
+                    }
                     if (job.getNumFailures() > 0) {
                         backoffCount++;
                     }
@@ -664,6 +762,9 @@
                     if (job.hasChargingConstraint()) {
                         chargingCount++;
                     }
+                    if (runnableJobs == null) {
+                        runnableJobs = new ArrayList<>();
+                    }
                     runnableJobs.add(job);
                 } else if (isReadyToBeCancelledLocked(job)) {
                     stopJobOnServiceContextLocked(job);
@@ -673,7 +774,7 @@
                     idleCount >= MIN_IDLE_COUNT ||
                     connectivityCount >= MIN_CONNECTIVITY_COUNT ||
                     chargingCount >= MIN_CHARGING_COUNT ||
-                    runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
+                    (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
                 if (DEBUG) {
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
                 }
@@ -685,6 +786,7 @@
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
                 }
             }
+            reportActive();
             if (DEBUG) {
                 Slog.d(TAG, "idle=" + idleCount + " connectivity=" +
                 connectivityCount + " charging=" + chargingCount + " tot=" +
@@ -766,6 +868,7 @@
                         it.remove();
                     }
                 }
+                reportActive();
             }
         }
     }
@@ -867,7 +970,7 @@
 
             long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJobsForUid(uid);
+                JobSchedulerService.this.cancelJobsForUid(uid, true);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -948,6 +1051,7 @@
             pw.println();
             pw.print("mReadyToRock="); pw.println(mReadyToRock);
             pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
+            pw.print("mReportedActive="); pw.println(mReportedActive);
         }
         pw.println();
     }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 41aea04..2ac0ba6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -459,7 +459,8 @@
         updateScreenOn();
 
         try {
-            mActivityManager.registerUidObserver(mUidObserver);
+            mActivityManager.registerUidObserver(mUidObserver,
+                    ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE);
             mNetworkManager.registerObserver(mAlertObserver);
         } catch (RemoteException e) {
             // ignored; both services live in system_server
@@ -541,6 +542,12 @@
                 removeUidStateLocked(uid);
             }
         }
+
+        @Override public void onUidActive(int uid) throws RemoteException {
+        }
+
+        @Override public void onUidIdle(int uid) throws RemoteException {
+        }
     };
 
     final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b84811f..fd1e9dd 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,6 +17,8 @@
 package com.android.server.notification;
 
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_LIGHTS;
+import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_PEEK;
 import static android.service.notification.NotificationListenerService.TRIM_FULL;
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -78,7 +80,6 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.Condition;
-import android.service.notification.IConditionListener;
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.IStatusBarNotificationHolder;
@@ -2511,7 +2512,9 @@
         // light
         // release the light
         boolean wasShowLights = mLights.remove(record.getKey());
-        if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold) {
+        if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
+                && ((record.getSuppressedVisualEffects()
+                & NotificationListenerService.SUPPRESSED_EFFECT_LIGHTS) == 0)) {
             mLights.add(record.getKey());
             updateLightsLocked();
             if (mUseAttentionLight) {
@@ -2701,6 +2704,11 @@
     // let zen mode evaluate this record
     private void applyZenModeLocked(NotificationRecord record) {
         record.setIntercepted(mZenModeHelper.shouldIntercept(record));
+        if (record.isIntercepted()) {
+            int suppressed = (mZenModeHelper.shouldSuppressLight() ? SUPPRESSED_EFFECT_LIGHTS : 0)
+                    | (mZenModeHelper.shouldSuppressPeek() ? SUPPRESSED_EFFECT_PEEK : 0);
+            record.setSuppressedVisualEffects(suppressed);
+        }
     }
 
     // lock on mNotificationList
@@ -3234,6 +3242,7 @@
         ArrayList<String> keys = new ArrayList<String>(N);
         ArrayList<String> interceptedKeys = new ArrayList<String>(N);
         Bundle visibilityOverrides = new Bundle();
+        Bundle suppressedVisualEffects = new Bundle();
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!isVisibleToListener(record.sbn, info)) {
@@ -3242,7 +3251,10 @@
             keys.add(record.sbn.getKey());
             if (record.isIntercepted()) {
                 interceptedKeys.add(record.sbn.getKey());
+
             }
+            suppressedVisualEffects.putInt(
+                    record.sbn.getKey(), record.getSuppressedVisualEffects());
             if (record.getPackageVisibilityOverride()
                     != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
                 visibilityOverrides.putInt(record.sbn.getKey(),
@@ -3264,7 +3276,7 @@
         String[] keysAr = keys.toArray(new String[keys.size()]);
         String[] interceptedKeysAr = interceptedKeys.toArray(new String[interceptedKeys.size()]);
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
-                speedBumpIndex);
+                speedBumpIndex, suppressedVisualEffects);
     }
 
     private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index f37702c..2a7568d 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -83,6 +83,8 @@
     private String mGlobalSortKey;
     private int mPackageVisibility;
 
+    private int mSuppressedVisualEffects = 0;
+
     @VisibleForTesting
     public NotificationRecord(StatusBarNotification sbn, int score)
     {
@@ -199,6 +201,7 @@
         pw.println(prefix + "  mCreationTimeMs=" + mCreationTimeMs);
         pw.println(prefix + "  mVisibleSinceMs=" + mVisibleSinceMs);
         pw.println(prefix + "  mUpdateTimeMs=" + mUpdateTimeMs);
+        pw.println(prefix + "  mSuppressedVisualEffects= " + mSuppressedVisualEffects);
     }
 
 
@@ -274,6 +277,14 @@
         return mIntercept;
     }
 
+    public void setSuppressedVisualEffects(int effects) {
+        mSuppressedVisualEffects = effects;
+    }
+
+    public int getSuppressedVisualEffects() {
+        return mSuppressedVisualEffects;
+    }
+
     public boolean isCategory(String category) {
         return Objects.equals(getNotification().category, category);
     }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index a1f8c41..dbdc3f4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -138,6 +138,18 @@
         }
     }
 
+    public boolean shouldSuppressLight() {
+        synchronized (mConfig) {
+            return !mConfig.allowLights;
+        }
+    }
+
+    public boolean shouldSuppressPeek() {
+        synchronized (mConfig) {
+            return !mConfig.allowPeek;
+        }
+    }
+
     public void addCallback(Callback callback) {
         mCallbacks.add(callback);
     }
@@ -394,11 +406,11 @@
             return;
         }
         pw.printf("allow(calls=%s,callsFrom=%s,repeatCallers=%s,messages=%s,messagesFrom=%s,"
-                + "events=%s,reminders=%s)\n",
+                + "events=%s,reminders=%s,lights=%s,peek=%s)\n",
                 config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
                 config.allowRepeatCallers, config.allowMessages,
                 ZenModeConfig.sourceToString(config.allowMessagesFrom),
-                config.allowEvents, config.allowReminders);
+                config.allowEvents, config.allowReminders, config.allowLights, config.allowPeek);
         pw.print(prefix); pw.print("  manualRule="); pw.println(config.manualRule);
         if (config.automaticRules.isEmpty()) return;
         final int N = config.automaticRules.size();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c152514..992919e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1375,7 +1375,9 @@
                             // Now that we successfully installed the package, grant runtime
                             // permissions if requested before broadcasting the install.
                             if ((args.installFlags
-                                    & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0) {
+                                    & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+                                    && res.pkg.applicationInfo.targetSdkVersion
+                                            >= Build.VERSION_CODES.M) {
                                 grantRequestedRuntimePermissions(res.pkg, args.user.getIdentifier(),
                                         args.installGrantPermissions);
                             }
@@ -1414,18 +1416,18 @@
                                 }
                             }
                             sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                                    packageName, extras, null, null, firstUsers);
+                                    packageName, extras, 0, null, null, firstUsers);
                             final boolean update = res.removedInfo.removedPackage != null;
                             if (update) {
                                 extras.putBoolean(Intent.EXTRA_REPLACING, true);
                             }
                             sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                                    packageName, extras, null, null, updateUsers);
+                                    packageName, extras, 0, null, null, updateUsers);
                             if (update) {
                                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                                        packageName, extras, null, null, updateUsers);
+                                        packageName, extras, 0, null, null, updateUsers);
                                 sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
-                                        null, null, packageName, null, updateUsers);
+                                        null, null, 0, packageName, null, updateUsers);
 
                                 // treat asec-hosted packages like removable media on upgrade
                                 if (res.pkg.isForwardLocked() || isExternal(res.pkg)) {
@@ -1956,8 +1958,7 @@
             mUserAppDataDir = new File(dataDir, "user");
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
 
-            sUserManager = new UserManagerService(context, this,
-                    mInstallLock, mPackages);
+            sUserManager = new UserManagerService(context, this, mPackages);
 
             // Propagate permission configuration in to package manager.
             ArrayMap<String, SystemConfig.PermissionEntry> permConfig
@@ -3568,6 +3569,11 @@
                 return;
             }
 
+            if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+                return;
+            }
+
             final int result = permissionsState.grantRuntimePermission(bp, userId);
             switch (result) {
                 case PermissionsState.PERMISSION_OPERATION_FAILURE: {
@@ -9371,8 +9377,8 @@
         }
     };
 
-    final void sendPackageBroadcast(final String action, final String pkg,
-            final Bundle extras, final String targetPkg, final IIntentReceiver finishedReceiver,
+    final void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
+            final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
             final int[] userIds) {
         mHandler.post(new Runnable() {
             @Override
@@ -9402,7 +9408,7 @@
                             intent.putExtra(Intent.EXTRA_UID, uid);
                         }
                         intent.putExtra(Intent.EXTRA_USER_HANDLE, id);
-                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
                         if (DEBUG_BROADCASTS) {
                             RuntimeException here = new RuntimeException("here");
                             here.fillInStackTrace();
@@ -9594,7 +9600,7 @@
         extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId));
 
         sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
-                packageName, extras, null, null, new int[] {userId});
+                packageName, extras, 0, null, null, new int[] {userId});
         try {
             IActivityManager am = ActivityManagerNative.getDefault();
             final boolean isSystem =
@@ -12904,11 +12910,11 @@
                 extras.putBoolean(Intent.EXTRA_REPLACING, true);
 
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                        extras, null, null, null);
+                        extras, 0, null, null, null);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                        extras, null, null, null);
+                        extras, 0, null, null, null);
                 sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null,
-                        null, packageName, null, null);
+                        null, 0, packageName, null, null);
             }
         }
         // Force a gc here.
@@ -12943,14 +12949,14 @@
             extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers);
             if (removedPackage != null) {
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
-                        extras, null, null, removedUsers);
+                        extras, 0, null, null, removedUsers);
                 if (fullRemove && !replacing) {
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage,
-                            extras, null, null, removedUsers);
+                            extras, 0, null, null, removedUsers);
                 }
             }
             if (removedAppId >= 0) {
-                sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null,
+                sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, 0, null, null,
                         removedUsers);
             }
         }
@@ -14650,7 +14656,12 @@
         extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
         extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
         extras.putInt(Intent.EXTRA_UID, packageUid);
-        sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED,  packageName, extras, null, null,
+        // If this is not reporting a change of the overall package, then only send it
+        // to registered receivers.  We don't want to launch a swath of apps for every
+        // little component state change.
+        final int flags = !componentNames.contains(packageName)
+                ? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
+        sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED,  packageName, extras, flags, null, null,
                 new int[] {UserHandle.getUserId(packageUid)});
     }
 
@@ -14838,20 +14849,23 @@
     static class DumpState {
         public static final int DUMP_LIBS = 1 << 0;
         public static final int DUMP_FEATURES = 1 << 1;
-        public static final int DUMP_RESOLVERS = 1 << 2;
-        public static final int DUMP_PERMISSIONS = 1 << 3;
-        public static final int DUMP_PACKAGES = 1 << 4;
-        public static final int DUMP_SHARED_USERS = 1 << 5;
-        public static final int DUMP_MESSAGES = 1 << 6;
-        public static final int DUMP_PROVIDERS = 1 << 7;
-        public static final int DUMP_VERIFIERS = 1 << 8;
-        public static final int DUMP_PREFERRED = 1 << 9;
-        public static final int DUMP_PREFERRED_XML = 1 << 10;
-        public static final int DUMP_KEYSETS = 1 << 11;
-        public static final int DUMP_VERSION = 1 << 12;
-        public static final int DUMP_INSTALLS = 1 << 13;
-        public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 14;
-        public static final int DUMP_DOMAIN_PREFERRED = 1 << 15;
+        public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
+        public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
+        public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
+        public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
+        public static final int DUMP_PERMISSIONS = 1 << 6;
+        public static final int DUMP_PACKAGES = 1 << 7;
+        public static final int DUMP_SHARED_USERS = 1 << 8;
+        public static final int DUMP_MESSAGES = 1 << 9;
+        public static final int DUMP_PROVIDERS = 1 << 10;
+        public static final int DUMP_VERIFIERS = 1 << 11;
+        public static final int DUMP_PREFERRED = 1 << 12;
+        public static final int DUMP_PREFERRED_XML = 1 << 13;
+        public static final int DUMP_KEYSETS = 1 << 14;
+        public static final int DUMP_VERSION = 1 << 15;
+        public static final int DUMP_INSTALLS = 1 << 16;
+        public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+        public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
 
         public static final int OPTION_SHOW_FILTERS = 1 << 0;
 
@@ -14950,9 +14964,9 @@
                 pw.println("    -h: print this help");
                 pw.println("  cmd may be one of:");
                 pw.println("    l[ibraries]: list known shared libraries");
-                pw.println("    f[ibraries]: list device features");
+                pw.println("    f[eatures]: list device features");
                 pw.println("    k[eysets]: print known keysets");
-                pw.println("    r[esolvers]: dump intent resolvers");
+                pw.println("    r[esolvers] [activity|service|receiver|content]: dump intent resolvers");
                 pw.println("    perm[issions]: dump permissions");
                 pw.println("    permission [name ...]: dump declaration and use of given permission");
                 pw.println("    pref[erred]: print preferred package settings");
@@ -15019,7 +15033,29 @@
             } else if ("f".equals(cmd) || "features".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_FEATURES);
             } else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_RESOLVERS);
+                if (opti >= args.length) {
+                    dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS
+                            | DumpState.DUMP_SERVICE_RESOLVERS
+                            | DumpState.DUMP_RECEIVER_RESOLVERS
+                            | DumpState.DUMP_CONTENT_RESOLVERS);
+                } else {
+                    while (opti < args.length) {
+                        String name = args[opti];
+                        if ("a".equals(name) || "activity".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS);
+                        } else if ("s".equals(name) || "service".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_SERVICE_RESOLVERS);
+                        } else if ("r".equals(name) || "receiver".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_RECEIVER_RESOLVERS);
+                        } else if ("c".equals(name) || "content".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_CONTENT_RESOLVERS);
+                        } else {
+                            pw.println("Error: unknown resolver table type: " + name);
+                            return;
+                        }
+                        opti++;
+                    }
+                }
             } else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_PERMISSIONS);
             } else if ("permission".equals(cmd)) {
@@ -15186,22 +15222,28 @@
                 }
             }
 
-            if (!checkin && dumpState.isDumping(DumpState.DUMP_RESOLVERS)) {
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
                 if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
                         : "Activity Resolver Table:", "  ", packageName,
                         dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
                     dumpState.setTitlePrinted(true);
                 }
+            }
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
                 if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:"
                         : "Receiver Resolver Table:", "  ", packageName,
                         dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
                     dumpState.setTitlePrinted(true);
                 }
+            }
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
                 if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:"
                         : "Service Resolver Table:", "  ", packageName,
                         dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
                     dumpState.setTitlePrinted(true);
                 }
+            }
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
                 if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:"
                         : "Provider Resolver Table:", "  ", packageName,
                         dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
@@ -15648,7 +15690,7 @@
             }
             String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
                     : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
-            sendPackageBroadcast(action, null, extras, null, finishedReceiver, null);
+            sendPackageBroadcast(action, null, extras, 0, null, finishedReceiver, null);
         }
     }
 
@@ -16353,23 +16395,26 @@
     }
 
     /** Called by UserManagerService */
-    void cleanUpUserLILPw(UserManagerService userManager, int userHandle) {
-        mDirtyUsers.remove(userHandle);
-        mSettings.removeUserLPw(userHandle);
-        mPendingBroadcasts.remove(userHandle);
-        if (mInstaller != null) {
-            // Technically, we shouldn't be doing this with the package lock
-            // held.  However, this is very rare, and there is already so much
-            // other disk I/O going on, that we'll let it slide for now.
-            final StorageManager storage = mContext.getSystemService(StorageManager.class);
-            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
-                final String volumeUuid = vol.getFsUuid();
-                if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
-                mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+    void cleanUpUser(UserManagerService userManager, int userHandle) {
+        synchronized (mPackages) {
+            mDirtyUsers.remove(userHandle);
+            mUserNeedsBadging.delete(userHandle);
+            mSettings.removeUserLPw(userHandle);
+            mPendingBroadcasts.remove(userHandle);
+        }
+        synchronized (mInstallLock) {
+            if (mInstaller != null) {
+                final StorageManager storage = mContext.getSystemService(StorageManager.class);
+                for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+                    final String volumeUuid = vol.getFsUuid();
+                    if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
+                    mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+                }
+            }
+            synchronized (mPackages) {
+                removeUnusedPackagesLILPw(userManager, userHandle);
             }
         }
-        mUserNeedsBadging.delete(userHandle);
-        removeUnusedPackagesLILPw(userManager, userHandle);
     }
 
     /**
@@ -16419,12 +16464,18 @@
     }
 
     /** Called by UserManagerService */
-    void createNewUserLILPw(int userHandle) {
+    void createNewUser(int userHandle) {
         if (mInstaller != null) {
-            mInstaller.createUserConfig(userHandle);
-            mSettings.createNewUserLILPw(this, mInstaller, userHandle);
-            applyFactoryDefaultBrowserLPw(userHandle);
-            primeDomainVerificationsLPw(userHandle);
+            synchronized (mInstallLock) {
+                synchronized (mPackages) {
+                    mInstaller.createUserConfig(userHandle);
+                    mSettings.createNewUserLILPw(this, mInstaller, userHandle);
+                }
+            }
+            synchronized (mPackages) {
+                applyFactoryDefaultBrowserLPw(userHandle);
+                primeDomainVerificationsLPw(userHandle);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index d7176fd..2cedc9c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -35,6 +35,7 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.ResolveInfo;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.net.Uri;
@@ -46,6 +47,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 
+import android.util.PrintWriterPrinter;
 import com.android.internal.util.SizedInputStream;
 
 import libcore.io.IoUtils;
@@ -56,6 +58,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -68,6 +71,7 @@
     final IPackageManager mInterface;
     final private WeakHashMap<String, Resources> mResourceCache =
             new WeakHashMap<String, Resources>();
+    int mTargetUser;
 
     PackageManagerShellCommand(PackageManagerService service) {
         mInterface = service;
@@ -97,6 +101,12 @@
                     return runList();
                 case "uninstall":
                     return runUninstall();
+                case "query-intent-activities":
+                    return runQueryIntentActivities();
+                case "query-intent-services":
+                    return runQueryIntentServices();
+                case "query-intent-receivers":
+                    return runQueryIntentReceivers();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -340,7 +350,7 @@
                         listThirdParty = true;
                         break;
                     case "--user":
-                        userId = Integer.parseInt(getNextArg());
+                        userId = UserHandle.parseUserArg(getNextArgRequired());
                         break;
                     default:
                         pw.println("Error: Unknown option: " + opt);
@@ -487,7 +497,7 @@
                     flags |= PackageManager.DELETE_KEEP_DATA;
                     break;
                 case "--user":
-                    userId = Integer.parseInt(getNextArg());
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
                     break;
                 default:
                     pw.println("Error: Unknown option: " + opt);
@@ -538,6 +548,104 @@
         }
     }
 
+    private Intent parseIntentAndUser() throws URISyntaxException {
+        mTargetUser = UserHandle.USER_CURRENT;
+        Intent intent = Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
+            @Override
+            public boolean handleOption(String opt, ShellCommand cmd) {
+                if ("--user".equals(opt)) {
+                    mTargetUser = UserHandle.parseUserArg(cmd.getNextArgRequired());
+                    return true;
+                }
+                return false;
+            }
+        });
+        mTargetUser = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), mTargetUser, false, false, null, null);
+        return intent;
+    }
+
+    private int runQueryIntentActivities() {
+        Intent intent;
+        try {
+            intent = parseIntentAndUser();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+        try {
+            List<ResolveInfo> result = mInterface.queryIntentActivities(intent, null, 0,
+                    mTargetUser);
+            PrintWriter pw = getOutPrintWriter();
+            if (result == null || result.size() <= 0) {
+                pw.println("No activities found");
+            } else {
+                pw.print(result.size()); pw.println(" activities found:");
+                PrintWriterPrinter pr = new PrintWriterPrinter(pw);
+                for (int i=0; i<result.size(); i++) {
+                    pw.print("  Activity #"); pw.print(i); pw.println(":");
+                    result.get(i).dump(pr, "    ");
+                }
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed calling service", e);
+        }
+        return 0;
+    }
+
+    private int runQueryIntentServices() {
+        Intent intent;
+        try {
+            intent = parseIntentAndUser();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+        try {
+            List<ResolveInfo> result = mInterface.queryIntentServices(intent, null, 0,
+                    mTargetUser);
+            PrintWriter pw = getOutPrintWriter();
+            if (result == null || result.size() <= 0) {
+                pw.println("No services found");
+            } else {
+                pw.print(result.size()); pw.println(" services found:");
+                PrintWriterPrinter pr = new PrintWriterPrinter(pw);
+                for (int i=0; i<result.size(); i++) {
+                    pw.print("  Service #"); pw.print(i); pw.println(":");
+                    result.get(i).dump(pr, "    ");
+                }
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed calling service", e);
+        }
+        return 0;
+    }
+
+    private int runQueryIntentReceivers() {
+        Intent intent;
+        try {
+            intent = parseIntentAndUser();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+        try {
+            List<ResolveInfo> result = mInterface.queryIntentReceivers(intent, null, 0,
+                    mTargetUser);
+            PrintWriter pw = getOutPrintWriter();
+            if (result == null || result.size() <= 0) {
+                pw.println("No receivers found");
+            } else {
+                pw.print(result.size()); pw.println(" receivers found:");
+                PrintWriterPrinter pr = new PrintWriterPrinter(pw);
+                for (int i=0; i<result.size(); i++) {
+                    pw.print("  Receiver #"); pw.print(i); pw.println(":");
+                    result.get(i).dump(pr, "    ");
+                }
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed calling service", e);
+        }
+        return 0;
+    }
+
     private static class InstallParams {
         SessionParams sessionParams;
         String installerPackageName;
@@ -598,7 +706,7 @@
                     sessionParams.abiOverride = checkAbiArgument(getNextArg());
                     break;
                 case "--user":
-                    params.userId = Integer.parseInt(getNextArg());
+                    params.userId = UserHandle.parseUserArg(getNextArgRequired());
                     break;
                 case "--install-location":
                     sessionParams.installLocation = Integer.parseInt(getNextArg());
@@ -908,7 +1016,14 @@
         pw.println("      -s: short summary");
         pw.println("      -d: only list dangerous permissions");
         pw.println("      -u: list only the permissions users will see");
-        pw.println("");
+        pw.println("  query-intent-activities [--user USER_ID] INTENT");
+        pw.println("    Prints all activities that can handle the given Intent.");
+        pw.println("  query-intent-services [--user USER_ID] INTENT");
+        pw.println("    Prints all services that can handle the given Intent.");
+        pw.println("  query-intent-receivers [--user USER_ID] INTENT");
+        pw.println("    Prints all broadcast receivers that can handle the given Intent.");
+        pw.println();
+        Intent.printIntentArgsHelp(pw , "");
     }
 
     private static class LocalIntentReceiver {
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 5d8b1d2..903d12b 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -103,6 +103,9 @@
     // Append privapp to existing seinfo label
     private static final String PRIVILEGED_APP_STR = ":privapp";
 
+    // Append autoplay to existing seinfo label
+    private static final String AUTOPLAY_APP_STR = ":autoplayapp";
+
     /**
      * Load the mac_permissions.xml file containing all seinfo assignments used to
      * label apps. The loaded mac_permissions.xml file is determined by the
@@ -316,6 +319,9 @@
             }
         }
 
+        if (pkg.applicationInfo.isAutoPlayApp())
+            pkg.applicationInfo.seinfo += AUTOPLAY_APP_STR;
+
         if (pkg.applicationInfo.isPrivilegedApp())
             pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index de14739..1d299d7 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3861,7 +3861,7 @@
             if (pkgSetting.getNotLaunched(userId)) {
                 if (pkgSetting.installerPackageName != null) {
                     yucky.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH,
-                            pkgSetting.name, null,
+                            pkgSetting.name, null, 0,
                             pkgSetting.installerPackageName, null, new int[] {userId});
                 }
                 pkgSetting.setNotLaunched(false, userId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3a1d2de..ab0b182 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -97,8 +97,7 @@
  *
  * Method naming convention:
  * <ul>
- * <li> Methods suffixed with "LILP" should be called within {@link #mInstallLock} and
- * {@link #mPackagesLock} locks obtained in the respective order.
+ * <li> Methods suffixed with "LP" should be called within the {@link #mPackagesLock} lock.
  * <li> Methods suffixed with "LR" should be called within the {@link #mRestrictionsLock} lock.
  * <li> Methods suffixed with "LU" should be called within the {@link #mUsersLock} lock.
  * </ul>
@@ -164,7 +163,6 @@
 
     private final Context mContext;
     private final PackageManagerService mPm;
-    private final Object mInstallLock;
     private final Object mPackagesLock;
     // Short-term lock for internal state, when interaction/sync with PM is not required
     private final Object mUsersLock = new Object();
@@ -215,6 +213,7 @@
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
 
+    @GuardedBy("mGuestRestrictions")
     private final Bundle mGuestRestrictions = new Bundle();
 
     /**
@@ -226,6 +225,7 @@
 
     @GuardedBy("mUsersLock")
     private int[] mUserIds;
+    @GuardedBy("mPackagesLock")
     private int mNextSerialNumber;
     private int mUserVersion = 0;
 
@@ -245,11 +245,9 @@
         }
     }
 
-    /**
-     * Available for testing purposes.
-     */
-    UserManagerService(File dataDir, File baseUserPath) {
-        this(null, null, new Object(), new Object(), dataDir, baseUserPath);
+    @VisibleForTesting
+    UserManagerService(File dataDir) {
+        this(null, null, new Object(), dataDir);
     }
 
     /**
@@ -257,68 +255,53 @@
      * associated with the package manager, and the given lock is the
      * package manager's own lock.
      */
-    UserManagerService(Context context, PackageManagerService pm,
-            Object installLock, Object packagesLock) {
-        this(context, pm, installLock, packagesLock,
-                Environment.getDataDirectory(),
-                new File(Environment.getDataDirectory(), "user"));
+    UserManagerService(Context context, PackageManagerService pm, Object packagesLock) {
+        this(context, pm, packagesLock, Environment.getDataDirectory());
     }
 
-    /**
-     * Available for testing purposes.
-     */
     private UserManagerService(Context context, PackageManagerService pm,
-            Object installLock, Object packagesLock,
-            File dataDir, File baseUserPath) {
+            Object packagesLock, File dataDir) {
         mContext = context;
         mPm = pm;
-        mInstallLock = installLock;
         mPackagesLock = packagesLock;
         mHandler = new MainHandler();
-        synchronized (mInstallLock) {
-            synchronized (mPackagesLock) {
-                mUsersDir = new File(dataDir, USER_INFO_DIR);
-                mUsersDir.mkdirs();
-                // Make zeroth user directory, for services to migrate their files to that location
-                File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
-                userZeroDir.mkdirs();
-                FileUtils.setPermissions(mUsersDir.toString(),
-                        FileUtils.S_IRWXU|FileUtils.S_IRWXG
-                        |FileUtils.S_IROTH|FileUtils.S_IXOTH,
-                        -1, -1);
-                mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
-                initDefaultGuestRestrictions();
-                readUserListLILP();
-                sInstance = this;
-            }
+        synchronized (mPackagesLock) {
+            mUsersDir = new File(dataDir, USER_INFO_DIR);
+            mUsersDir.mkdirs();
+            // Make zeroth user directory, for services to migrate their files to that location
+            File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
+            userZeroDir.mkdirs();
+            FileUtils.setPermissions(mUsersDir.toString(),
+                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
+                    -1, -1);
+            mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
+            initDefaultGuestRestrictions();
+            readUserListLP();
+            sInstance = this;
         }
         mLocalService = new LocalService();
         LocalServices.addService(UserManagerInternal.class, mLocalService);
     }
 
     void systemReady() {
-        synchronized (mInstallLock) {
-            synchronized (mPackagesLock) {
-                synchronized (mUsersLock) {
-                    // Prune out any partially created/partially removed users.
-                    ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
-                    final int userSize = mUsers.size();
-                    for (int i = 0; i < userSize; i++) {
-                        UserInfo ui = mUsers.valueAt(i);
-                        if ((ui.partial || ui.guestToRemove) && i != 0) {
-                            partials.add(ui);
-                        }
-                    }
-                    final int partialsSize = partials.size();
-                    for (int i = 0; i < partialsSize; i++) {
-                        UserInfo ui = partials.get(i);
-                        Slog.w(LOG_TAG, "Removing partially created user " + ui.id
-                                + " (name=" + ui.name + ")");
-                        removeUserStateLILP(ui.id);
-                    }
+        // Prune out any partially created/partially removed users.
+        ArrayList<UserInfo> partials = new ArrayList<>();
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                UserInfo ui = mUsers.valueAt(i);
+                if ((ui.partial || ui.guestToRemove) && i != 0) {
+                    partials.add(ui);
                 }
             }
         }
+        final int partialsSize = partials.size();
+        for (int i = 0; i < partialsSize; i++) {
+            UserInfo ui = partials.get(i);
+            Slog.w(LOG_TAG, "Removing partially created user " + ui.id
+                    + " (name=" + ui.name + ")");
+            removeUserState(ui.id);
+        }
         onUserForeground(UserHandle.USER_SYSTEM);
         mAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -627,17 +610,22 @@
 
     public void makeInitialized(int userId) {
         checkManageUsersPermission("makeInitialized");
-        synchronized (mPackagesLock) {
-            UserInfo info = getUserInfoNoChecks(userId);
+        boolean scheduleWriteUser = false;
+        UserInfo info;
+        synchronized (mUsersLock) {
+            info = mUsers.get(userId);
             if (info == null || info.partial) {
                 Slog.w(LOG_TAG, "makeInitialized: unknown user #" + userId);
-                // TODO Check if we should return here instead of a null check below
+                return;
             }
-            if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+            if ((info.flags & UserInfo.FLAG_INITIALIZED) == 0) {
                 info.flags |= UserInfo.FLAG_INITIALIZED;
-                scheduleWriteUser(info);
+                scheduleWriteUser = true;
             }
         }
+        if (scheduleWriteUser) {
+            scheduleWriteUser(info);
+        }
     }
 
     /**
@@ -645,17 +633,18 @@
      * restrictions.
      */
     private void initDefaultGuestRestrictions() {
-        if (mGuestRestrictions.isEmpty()) {
-            mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
-            mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+        synchronized (mGuestRestrictions) {
+            if (mGuestRestrictions.isEmpty()) {
+                mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
+                mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+            }
         }
     }
 
     @Override
     public Bundle getDefaultGuestRestrictions() {
         checkManageUsersPermission("getDefaultGuestRestrictions");
-        // TODO Switch to mGuestRestrictions for locking
-        synchronized (mPackagesLock) {
+        synchronized (mGuestRestrictions) {
             return new Bundle(mGuestRestrictions);
         }
     }
@@ -663,12 +652,12 @@
     @Override
     public void setDefaultGuestRestrictions(Bundle restrictions) {
         checkManageUsersPermission("setDefaultGuestRestrictions");
-        synchronized (mInstallLock) {
-            synchronized (mPackagesLock) {
-                mGuestRestrictions.clear();
-                mGuestRestrictions.putAll(restrictions);
-                writeUserListLILP();
-            }
+        synchronized (mGuestRestrictions) {
+            mGuestRestrictions.clear();
+            mGuestRestrictions.putAll(restrictions);
+        }
+        synchronized (mPackagesLock) {
+            writeUserListLP();
         }
     }
 
@@ -775,7 +764,7 @@
             Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
                     != newRestrictions);
             mBaseUserRestrictions.put(userId, newRestrictions);
-            scheduleWriteUser(mUsers.get(userId));
+            scheduleWriteUser(getUserInfoNoChecks(userId));
         }
 
         final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
@@ -996,9 +985,9 @@
         }
     }
 
-    private void readUserListLILP() {
+    private void readUserListLP() {
         if (!mUserListFile.exists()) {
-            fallbackToSingleUserLILP();
+            fallbackToSingleUserLP();
             return;
         }
         FileInputStream fis = null;
@@ -1015,7 +1004,7 @@
 
             if (type != XmlPullParser.START_TAG) {
                 Slog.e(LOG_TAG, "Unable to read user list");
-                fallbackToSingleUserLILP();
+                fallbackToSingleUserLP();
                 return;
             }
 
@@ -1036,7 +1025,7 @@
                     final String name = parser.getName();
                     if (name.equals(TAG_USER)) {
                         String id = parser.getAttributeValue(null, ATTR_ID);
-                        UserInfo user = readUserLILP(Integer.parseInt(id));
+                        UserInfo user = readUserLP(Integer.parseInt(id));
 
                         if (user != null) {
                             synchronized (mUsersLock) {
@@ -1051,8 +1040,10 @@
                                 && type != XmlPullParser.END_TAG) {
                             if (type == XmlPullParser.START_TAG) {
                                 if (parser.getName().equals(TAG_RESTRICTIONS)) {
-                                    UserRestrictionsUtils
-                                            .readRestrictions(parser, mGuestRestrictions);
+                                    synchronized (mGuestRestrictions) {
+                                        UserRestrictionsUtils
+                                                .readRestrictions(parser, mGuestRestrictions);
+                                    }
                                 }
                                 break;
                             }
@@ -1061,25 +1052,18 @@
                 }
             }
             updateUserIds();
-            upgradeIfNecessaryLILP();
-        } catch (IOException ioe) {
-            fallbackToSingleUserLILP();
-        } catch (XmlPullParserException pe) {
-            fallbackToSingleUserLILP();
+            upgradeIfNecessaryLP();
+        } catch (IOException | XmlPullParserException e) {
+            fallbackToSingleUserLP();
         } finally {
-            if (fis != null) {
-                try {
-                    fis.close();
-                } catch (IOException e) {
-                }
-            }
+            IoUtils.closeQuietly(fis);
         }
     }
 
     /**
      * Upgrade steps between versions, either for fixing bugs or changing the data format.
      */
-    private void upgradeIfNecessaryLILP() {
+    private void upgradeIfNecessaryLP() {
         int userVersion = mUserVersion;
         if (userVersion < 1) {
             // Assign a proper name for the owner, if not initialized correctly before
@@ -1132,11 +1116,11 @@
                     + USER_VERSION);
         } else {
             mUserVersion = userVersion;
-            writeUserListLILP();
+            writeUserListLP();
         }
     }
 
-    private void fallbackToSingleUserLILP() {
+    private void fallbackToSingleUserLP() {
         int flags = UserInfo.FLAG_INITIALIZED;
         // In split system user mode, the admin and primary flags are assigned to the first human
         // user.
@@ -1161,7 +1145,7 @@
         updateUserIds();
         initDefaultGuestRestrictions();
 
-        writeUserListLILP();
+        writeUserListLP();
         writeUserLP(system);
     }
 
@@ -1247,8 +1231,7 @@
      *   <user id="2"></user>
      * </users>
      */
-    private void writeUserListLILP() {
-        // TODO Investigate removing a dependency on mInstallLock
+    private void writeUserListLP() {
         FileOutputStream fos = null;
         AtomicFile userListFile = new AtomicFile(mUserListFile);
         try {
@@ -1266,8 +1249,10 @@
             serializer.attribute(null, ATTR_USER_VERSION, Integer.toString(mUserVersion));
 
             serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
-            UserRestrictionsUtils
-                    .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+            synchronized (mGuestRestrictions) {
+                UserRestrictionsUtils
+                        .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+            }
             serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
             int[] userIdsToWrite;
             synchronized (mUsersLock) {
@@ -1293,7 +1278,7 @@
         }
     }
 
-    private UserInfo readUserLILP(int id) {
+    private UserInfo readUserLP(int id) {
         int flags = 0;
         int serialNumber = id;
         String name = null;
@@ -1468,8 +1453,7 @@
     }
 
     private UserInfo createUserInternal(String name, int flags, int parentId) {
-        if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
-                UserManager.DISALLOW_ADD_USER, false)) {
+        if (hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.getCallingUserId())) {
             Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
             return null;
         }
@@ -1480,120 +1464,114 @@
         final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
         final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
         final long ident = Binder.clearCallingIdentity();
-        UserInfo userInfo = null;
+        UserInfo userInfo;
         final int userId;
         try {
-            synchronized (mInstallLock) {
-                synchronized (mPackagesLock) {
-                    UserInfo parent = null;
-                    if (parentId != UserHandle.USER_NULL) {
-                        synchronized (mUsersLock) {
-                            parent = getUserInfoLU(parentId);
-                        }
-                        if (parent == null) return null;
+            synchronized (mPackagesLock) {
+                UserInfo parent = null;
+                if (parentId != UserHandle.USER_NULL) {
+                    synchronized (mUsersLock) {
+                        parent = getUserInfoLU(parentId);
                     }
-                    if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
-                        Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
+                    if (parent == null) return null;
+                }
+                if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
+                    Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
+                    return null;
+                }
+                if (!isGuest && !isManagedProfile && isUserLimitReached()) {
+                    // If we're not adding a guest user or a managed profile and the limit has
+                    // been reached, cannot add a user.
+                    return null;
+                }
+                // If we're adding a guest and there already exists one, bail.
+                if (isGuest && findCurrentGuestUser() != null) {
+                    return null;
+                }
+                // In legacy mode, restricted profile's parent can only be the owner user
+                if (isRestricted && !UserManager.isSplitSystemUser()
+                        && (parentId != UserHandle.USER_SYSTEM)) {
+                    Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+                    return null;
+                }
+                if (isRestricted && UserManager.isSplitSystemUser()) {
+                    if (parent == null) {
+                        Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+                                + "specified");
                         return null;
                     }
-                    if (!isGuest && !isManagedProfile && isUserLimitReached()) {
-                        // If we're not adding a guest user or a managed profile and the limit has
-                        // been reached, cannot add a user.
+                    if (!parent.canHaveProfile()) {
+                        Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+                                + "created for the specified parent user id " + parentId);
                         return null;
                     }
-                    // If we're adding a guest and there already exists one, bail.
-                    if (isGuest && findCurrentGuestUser() != null) {
-                        return null;
+                }
+                // In split system user mode, we assign the first human user the primary flag.
+                // And if there is no device owner, we also assign the admin flag to primary user.
+                if (UserManager.isSplitSystemUser()
+                        && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
+                    flags |= UserInfo.FLAG_PRIMARY;
+                    DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
+                            mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+                    if (devicePolicyManager == null
+                            || devicePolicyManager.getDeviceOwner() == null) {
+                        flags |= UserInfo.FLAG_ADMIN;
                     }
-                    // In legacy mode, restricted profile's parent can only be the owner user
-                    if (isRestricted && !UserManager.isSplitSystemUser()
-                            && (parentId != UserHandle.USER_SYSTEM)) {
-                        Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
-                        return null;
-                    }
-                    if (isRestricted && UserManager.isSplitSystemUser()) {
-                        if (parent == null) {
-                            Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
-                                    + "specified");
-                            return null;
-                        }
-                        if (!parent.canHaveProfile()) {
-                            Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
-                                    + "created for the specified parent user id " + parentId);
-                            return null;
-                        }
-                    }
-                    // In split system user mode, we assign the first human user the primary flag.
-                    // And if there is no device owner, we also assign the admin flag to primary
-                    // user.
-                    if (UserManager.isSplitSystemUser()
-                            && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
-                        flags |= UserInfo.FLAG_PRIMARY;
-                        DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
-                                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-                        if (devicePolicyManager == null
-                                || devicePolicyManager.getDeviceOwner() == null) {
-                            flags |= UserInfo.FLAG_ADMIN;
-                        }
-                    }
-                    userId = getNextAvailableId();
-                    userInfo = new UserInfo(userId, name, null, flags);
-                    userInfo.serialNumber = mNextSerialNumber++;
-                    long now = System.currentTimeMillis();
-                    userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
-                    userInfo.partial = true;
-                    Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+                }
+                userId = getNextAvailableId();
+                userInfo = new UserInfo(userId, name, null, flags);
+                userInfo.serialNumber = mNextSerialNumber++;
+                long now = System.currentTimeMillis();
+                userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
+                userInfo.partial = true;
+                Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+                synchronized (mUsersLock) {
                     mUsers.put(userId, userInfo);
-                    writeUserListLILP();
-                    if (parent != null) {
-                        if (isManagedProfile) {
-                            if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
-                                parent.profileGroupId = parent.id;
-                                scheduleWriteUser(parent);
-                            }
-                            userInfo.profileGroupId = parent.profileGroupId;
-                        } else if (isRestricted) {
-                            if (!parent.canHaveProfile()) {
-                                Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
-                            }
-                            if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
-                                parent.restrictedProfileParentId = parent.id;
-                                scheduleWriteUser(parent);
-                            }
-                            userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
+                }
+                writeUserListLP();
+                if (parent != null) {
+                    if (isManagedProfile) {
+                        if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+                            parent.profileGroupId = parent.id;
+                            writeUserLP(parent);
                         }
-                    }
-
-                    final StorageManager storage = mContext.getSystemService(StorageManager.class);
-                    storage.createUserKey(userId, userInfo.serialNumber);
-                    for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
-                        final String volumeUuid = vol.getFsUuid();
-                        try {
-                            final File userDir = Environment.getDataUserDirectory(volumeUuid,
-                                    userId);
-                            storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber);
-                            enforceSerialNumber(userDir, userInfo.serialNumber);
-                        } catch (IOException e) {
-                            Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
+                        userInfo.profileGroupId = parent.profileGroupId;
+                    } else if (isRestricted) {
+                        if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
+                            parent.restrictedProfileParentId = parent.id;
+                            writeUserLP(parent);
                         }
-                    }
-                    mPm.createNewUserLILPw(userId);
-                    userInfo.partial = false;
-                    scheduleWriteUser(userInfo);
-                    updateUserIds();
-                    Bundle restrictions = new Bundle();
-                    synchronized (mRestrictionsLock) {
-                        mBaseUserRestrictions.append(userId, restrictions);
+                        userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
                     }
                 }
             }
-            mPm.newUserCreated(userId);
-            if (userInfo != null) {
-                Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
-                addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
-                mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
-                        android.Manifest.permission.MANAGE_USERS);
+            final StorageManager storage = mContext.getSystemService(StorageManager.class);
+            storage.createUserKey(userId, userInfo.serialNumber);
+            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+                final String volumeUuid = vol.getFsUuid();
+                try {
+                    final File userDir = Environment.getDataUserDirectory(volumeUuid, userId);
+                    storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber);
+                    enforceSerialNumber(userDir, userInfo.serialNumber);
+                } catch (IOException e) {
+                    Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
+                }
             }
+            mPm.createNewUser(userId);
+            userInfo.partial = false;
+            synchronized (mPackagesLock) {
+                writeUserLP(userInfo);
+            }
+            updateUserIds();
+            Bundle restrictions = new Bundle();
+            synchronized (mRestrictionsLock) {
+                mBaseUserRestrictions.append(userId, restrictions);
+            }
+            mPm.newUserCreated(userId);
+            Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
+            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
+                    android.Manifest.permission.MANAGE_USERS);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -1782,11 +1760,7 @@
                                     // Clean up any ActivityManager state
                                     LocalServices.getService(ActivityManagerInternal.class)
                                             .onUserRemoved(userHandle);
-                                    synchronized (mInstallLock) {
-                                        synchronized (mPackagesLock) {
-                                            removeUserStateLILP(userHandle);
-                                        }
-                                    }
+                                    removeUserState(userHandle);
                                 }
                             }.start();
                         }
@@ -1798,10 +1772,10 @@
         }
     }
 
-    private void removeUserStateLILP(final int userHandle) {
+    private void removeUserState(final int userHandle) {
         mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
         // Cleanup package manager settings
-        mPm.cleanUpUserLILPw(this, userHandle);
+        mPm.cleanUpUser(this, userHandle);
 
         // Remove this user from the list
         synchronized (mUsersLock) {
@@ -1811,7 +1785,9 @@
         AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
         userFile.delete();
         // Update the user list
-        writeUserListLILP();
+        synchronized (mPackagesLock) {
+            writeUserListLP();
+        }
         updateUserIds();
         removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
     }
@@ -2146,17 +2122,15 @@
      * @param userId the user that was just foregrounded
      */
     public void onUserForeground(int userId) {
-        synchronized (mPackagesLock) {
-            UserInfo user = getUserInfoNoChecks(userId);
-            long now = System.currentTimeMillis();
-            if (user == null || user.partial) {
-                Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
-                return;
-            }
-            if (now > EPOCH_PLUS_30_YEARS) {
-                user.lastLoggedInTime = now;
-                scheduleWriteUser(user);
-            }
+        UserInfo user = getUserInfoNoChecks(userId);
+        if (user == null || user.partial) {
+            Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
+            return;
+        }
+        long now = System.currentTimeMillis();
+        if (now > EPOCH_PLUS_30_YEARS) {
+            user.lastLoggedInTime = now;
+            scheduleWriteUser(user);
         }
     }
 
@@ -2164,7 +2138,6 @@
      * Returns the next available user id, filling in any holes in the ids.
      * TODO: May not be a good idea to recycle ids, in case it results in confusion
      * for data and battery stats collection, or unexpected cross-talk.
-     * @return
      */
     private int getNextAvailableId() {
         synchronized (mUsersLock) {
@@ -2348,7 +2321,9 @@
             }
             pw.println();
             pw.println("Guest restrictions:");
-            UserRestrictionsUtils.dumpRestrictions(pw, "  ", mGuestRestrictions);
+            synchronized (mGuestRestrictions) {
+                UserRestrictionsUtils.dumpRestrictions(pw, "  ", mGuestRestrictions);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index d713751..c246609 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -409,6 +409,7 @@
 
             private final Region mMagnifiedBounds = new Region();
             private final Region mOldMagnifiedBounds = new Region();
+            private final Region mOldAvailableBounds = new Region();
 
             private final Path mCircularPath;
 
@@ -537,29 +538,39 @@
                         screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
                         Region.Op.INTERSECT);
 
-                if (!mOldMagnifiedBounds.equals(magnifiedBounds)) {
-                    Region bounds = Region.obtain();
-                    bounds.set(magnifiedBounds);
-                    mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED,
-                            bounds).sendToTarget();
+                final boolean magnifiedChanged = !mOldMagnifiedBounds.equals(magnifiedBounds);
+                final boolean availableChanged = !mOldAvailableBounds.equals(availableBounds);
+                if (magnifiedChanged || availableChanged) {
+                    if (magnifiedChanged) {
+                        mWindow.setBounds(magnifiedBounds);
+                        Rect dirtyRect = mTempRect1;
+                        if (mFullRedrawNeeded) {
+                            mFullRedrawNeeded = false;
+                            dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
+                                    screenWidth - mDrawBorderInset,
+                                    screenHeight - mDrawBorderInset);
+                            mWindow.invalidate(dirtyRect);
+                        } else {
+                            Region dirtyRegion = mTempRegion3;
+                            dirtyRegion.set(magnifiedBounds);
+                            dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
+                            dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
+                            dirtyRegion.getBounds(dirtyRect);
+                            mWindow.invalidate(dirtyRect);
+                        }
 
-                    mWindow.setBounds(magnifiedBounds);
-                    Rect dirtyRect = mTempRect1;
-                    if (mFullRedrawNeeded) {
-                        mFullRedrawNeeded = false;
-                        dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
-                                screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset);
-                        mWindow.invalidate(dirtyRect);
-                    } else {
-                        Region dirtyRegion = mTempRegion3;
-                        dirtyRegion.set(magnifiedBounds);
-                        dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
-                        dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
-                        dirtyRegion.getBounds(dirtyRect);
-                        mWindow.invalidate(dirtyRect);
+                        mOldMagnifiedBounds.set(magnifiedBounds);
                     }
 
-                    mOldMagnifiedBounds.set(magnifiedBounds);
+                    if (availableChanged) {
+                        mOldAvailableBounds.set(availableBounds);
+                    }
+
+                    final SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = Region.obtain(magnifiedBounds);
+                    args.arg2 = Region.obtain(availableBounds);
+                    mHandler.obtainMessage(
+                            MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
                 }
             }
 
@@ -867,9 +878,12 @@
             public void handleMessage(Message message) {
                 switch (message.what) {
                     case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: {
-                        Region bounds = (Region) message.obj;
-                        mCallbacks.onMagnifedBoundsChanged(bounds);
-                        bounds.recycle();
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final Region magnifiedBounds = (Region) args.arg1;
+                        final Region availableBounds = (Region) args.arg2;
+                        mCallbacks.onMagnifiedBoundsChanged(magnifiedBounds, availableBounds);
+                        magnifiedBounds.recycle();
+                        availableBounds.recycle();
                     } break;
 
                     case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 89f5658..943c9ed 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -139,7 +139,7 @@
     private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
 
     private final Context mContext;
-    private final Handler mH;
+    private final WindowManagerService mService;
 
     private int mNextAppTransition = TRANSIT_UNSET;
 
@@ -208,15 +208,10 @@
 
     private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
     private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
-    private final Object mServiceLock;
-    private final WindowSurfacePlacer mWindowSurfacePlacer;
 
-    AppTransition(Context context, Handler h, Object serviceLock,
-            WindowSurfacePlacer windowSurfacePlacer) {
+    AppTransition(Context context, WindowManagerService service) {
         mContext = context;
-        mH = h;
-        mServiceLock = serviceLock;
-        mWindowSurfacePlacer = windowSurfacePlacer;
+        mService = service;
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.linear_out_slow_in);
         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -971,7 +966,7 @@
 
                 @Override
                 public void onAnimationEnd(Animation animation) {
-                    mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget();
+                    mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget();
                 }
 
                 @Override
@@ -1326,7 +1321,8 @@
 
     void postAnimationCallback() {
         if (mNextAppTransitionCallback != null) {
-            mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, mNextAppTransitionCallback));
+            mService.mH.sendMessage(mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK,
+                    mNextAppTransitionCallback));
             mNextAppTransitionCallback = null;
         }
     }
@@ -1478,14 +1474,15 @@
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to fetch app transition specs: " + e);
                     }
-                    synchronized (mServiceLock) {
+                    synchronized (mService.mWindowMap) {
                         mNextAppTransitionAnimationsSpecsPending = false;
                         overridePendingAppTransitionMultiThumb(specs,
                                 mNextAppTransitionFutureCallback, null /* finishedCallback */,
                                 mNextAppTransitionScaleUp);
                         mNextAppTransitionFutureCallback = null;
-                        mWindowSurfacePlacer.requestTraversal();
+                        mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
                     }
+                    mService.requestTraversal();
                 }
             });
         }
@@ -1672,8 +1669,8 @@
         }
         boolean prepared = prepare();
         if (isTransitionSet()) {
-            mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
-            mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
+            mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
+            mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
         }
         return prepared;
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2905269..dfd01ef 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -37,6 +37,10 @@
 public class AppWindowAnimator {
     static final String TAG = "AppWindowAnimator";
 
+    private static final int PROLONG_ANIMATION_DISABLED = 0;
+    static final int PROLONG_ANIMATION_AT_END = 1;
+    static final int PROLONG_ANIMATION_AT_START = 2;
+
     final AppWindowToken mAppToken;
     final WindowManagerService mService;
     final WindowAnimator mAnimator;
@@ -85,7 +89,7 @@
     // If true when the animation hits the last frame, it will keep running on that last frame.
     // This is used to synchronize animation with Recents and we wait for Recents to tell us to
     // finish or for a new animation be set as fail-safe mechanism.
-    private boolean mProlongAnimation;
+    private int mProlongAnimation;
     // Whether the prolong animation can be removed when animation is set. The purpose of this is
     // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it
     // when new animation is set.
@@ -142,7 +146,7 @@
             anim.setBackgroundColor(0);
         }
         if (mClearProlongedAnimation) {
-            mProlongAnimation = false;
+            mProlongAnimation = PROLONG_ANIMATION_DISABLED;
         } else {
             mClearProlongedAnimation = true;
         }
@@ -266,6 +270,10 @@
             return false;
         }
         transformation.clear();
+        if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
+            animation.setStartTime(currentTime);
+            currentTime += 1;
+        }
         boolean hasMoreFrames = animation.getTransformation(currentTime, transformation);
         if (!hasMoreFrames) {
             if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
@@ -278,7 +286,7 @@
                         "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames +
                         ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation);
                 deferFinalFrameCleanup = false;
-                if (mProlongAnimation) {
+                if (mProlongAnimation == PROLONG_ANIMATION_AT_END) {
                     hasMoreFrames = true;
                 } else {
                     animation = null;
@@ -434,13 +442,13 @@
         }
     }
 
-    void startProlongAnimation() {
-        mProlongAnimation = true;
+    void startProlongAnimation(int prolongType) {
+        mProlongAnimation = prolongType;
         mClearProlongedAnimation = false;
     }
 
     void endProlongedAnimation() {
-        mProlongAnimation = false;
+        mProlongAnimation = PROLONG_ANIMATION_DISABLED;
     }
 
     // This is an animation that does nothing: it just immediately finishes
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 425ff9b..3f4eaac 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -128,8 +128,6 @@
     boolean mWillReplaceWindow;
     // If true, the replaced window was already requested to be removed.
     boolean mReplacingRemoveRequested;
-    // Whether the replacement of the window should trigger app transition animation.
-    boolean mAnimateReplacingWindow;
     // If not null, the window that will be used to replace the old one. This is being set when
     // the window is added and unset when this window reports its first draw.
     WindowState mReplacingWindow;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e264c43..328c043 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -22,7 +22,6 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerService.TAG;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
-import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
 
 import android.app.ActivityManager.StackId;
 import android.graphics.Rect;
@@ -55,17 +54,6 @@
      * from mDisplayWindows; */
     private final WindowList mWindows = new WindowList();
 
-    // This protects the following display size properties, so that
-    // getDisplaySize() doesn't need to acquire the global lock.  This is
-    // needed because the window manager sometimes needs to use ActivityThread
-    // while it has its global state locked (for example to load animation
-    // resources), but the ActivityThread also needs get the current display
-    // size sometimes when it has its package lock held.
-    //
-    // These will only be modified with both mWindowMap and mDisplaySizeLock
-    // held (in that order) so the window manager doesn't need to acquire this
-    // lock when needing these values in its normal operation.
-    final Object mDisplaySizeLock = new Object();
     int mInitialDisplayWidth = 0;
     int mInitialDisplayHeight = 0;
     int mInitialDisplayDensity = 0;
@@ -202,18 +190,16 @@
     }
 
     void initializeDisplayBaseInfo() {
-        synchronized(mDisplaySizeLock) {
-            // Bootstrap the default logical display from the display manager.
-            final DisplayInfo newDisplayInfo =
-                    mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId);
-            if (newDisplayInfo != null) {
-                mDisplayInfo.copyFrom(newDisplayInfo);
-            }
-            mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
-            mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
-            mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
-            mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+        // Bootstrap the default logical display from the display manager.
+        final DisplayInfo newDisplayInfo =
+                mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId);
+        if (newDisplayInfo != null) {
+            mDisplayInfo.copyFrom(newDisplayInfo);
         }
+        mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
+        mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
+        mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
+        mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
     }
 
     void getLogicalDisplayRect(Rect out) {
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 2be7ab8..4926352 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputWindowHandle;
 import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 3c3123f..1f351cb 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -171,10 +171,10 @@
 
     private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle,
             final WindowState child, int flags, final int type, final boolean isVisible,
-            final boolean hasFocus, final boolean hasWallpaper, DisplayContent displayContent) {
+            final boolean hasFocus, final boolean hasWallpaper) {
         // Add a window to our list of input windows.
         inputWindowHandle.name = child.toString();
-        flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags, this);
+        flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags);
         inputWindowHandle.layoutParamsFlags = flags;
         inputWindowHandle.layoutParamsType = type;
         inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
@@ -308,8 +308,8 @@
                     mService.mDragState.sendDragStartedIfNeededLw(child);
                 }
 
-                addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, hasFocus,
-                        hasWallpaper, displayContent);
+                addInputWindowHandleLw(
+                        inputWindowHandle, child, flags, type, isVisible, hasFocus, hasWallpaper);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 92eacd6..a9bd71f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -50,10 +50,12 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 
-import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
 
 import android.Manifest;
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.app.ActivityManagerNative;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
@@ -910,7 +912,7 @@
                 PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
         mScreenFrozenLock.setReferenceCounted(false);
 
-        mAppTransition = new AppTransition(context, mH, mWindowMap, mWindowPlacerLocked);
+        mAppTransition = new AppTransition(context, this);
         mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
 
         mActivityManager = ActivityManagerNative.getDefault();
@@ -2077,7 +2079,7 @@
     }
 
     private void prepareWindowReplacementTransition(AppWindowToken atoken) {
-        if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
+        if (atoken == null || !atoken.mWillReplaceWindow) {
             return;
         }
         atoken.allDrawn = false;
@@ -3693,22 +3695,26 @@
         synchronized (mWindowMap) {
             mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
                     onAnimationFinishedCallback, scaleUp);
-            if (!scaleUp) {
-                // This is used by freeform to recents windows transition. We need to synchronize
-                // the animation with the appearance of the content of recents, so we will make
-                // animation stay on the last frame a little longer.
-                mTmpTaskIds.clear();
-                for (int i = specs.length - 1; i >= 0; i--) {
-                    mTmpTaskIds.put(specs[i].taskId, 0);
-                }
-                for (final WindowState win : mWindowMap.values()) {
-                    final Task task = win.getTask();
-                    if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) {
-                        final AppWindowToken appToken = win.mAppToken;
-                        if (appToken != null && appToken.mAppAnimator != null) {
-                            appToken.mAppAnimator.startProlongAnimation();
-                        }
-                    }
+            prolongAnimationsFromSpecs(specs, scaleUp);
+
+        }
+    }
+
+    void prolongAnimationsFromSpecs(AppTransitionAnimationSpec[] specs, boolean scaleUp) {
+        // This is used by freeform <-> recents windows transition. We need to synchronize
+        // the animation with the appearance of the content of recents, so we will make
+        // animation stay on the first or last frame a little longer.
+        mTmpTaskIds.clear();
+        for (int i = specs.length - 1; i >= 0; i--) {
+            mTmpTaskIds.put(specs[i].taskId, 0);
+        }
+        for (final WindowState win : mWindowMap.values()) {
+            final Task task = win.getTask();
+            if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) {
+                final AppWindowToken appToken = win.mAppToken;
+                if (appToken != null && appToken.mAppAnimator != null) {
+                    appToken.mAppAnimator.startProlongAnimation(scaleUp ?
+                            PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END);
                 }
             }
         }
@@ -6908,27 +6914,25 @@
         final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
         final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        synchronized(displayContent.mDisplaySizeLock) {
-            displayInfo.rotation = mRotation;
-            displayInfo.logicalWidth = dw;
-            displayInfo.logicalHeight = dh;
-            displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
-            displayInfo.appWidth = appWidth;
-            displayInfo.appHeight = appHeight;
-            displayInfo.getLogicalMetrics(mRealDisplayMetrics,
-                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
-            displayInfo.getAppMetrics(mDisplayMetrics);
-            if (displayContent.mDisplayScalingDisabled) {
-                displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
-            } else {
-                displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
-            }
-
-            mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
-                    displayContent.getDisplayId(), displayInfo);
-
-            displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
+        displayInfo.rotation = mRotation;
+        displayInfo.logicalWidth = dw;
+        displayInfo.logicalHeight = dh;
+        displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
+        displayInfo.appWidth = appWidth;
+        displayInfo.appHeight = appHeight;
+        displayInfo.getLogicalMetrics(mRealDisplayMetrics,
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+        displayInfo.getAppMetrics(mDisplayMetrics);
+        if (displayContent.mDisplayScalingDisabled) {
+            displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
+        } else {
+            displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
         }
+
+        mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
+                displayContent.getDisplayId(), displayInfo);
+
+        displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
         if (false) {
             Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
         }
@@ -8060,10 +8064,8 @@
         synchronized (mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
-                synchronized(displayContent.mDisplaySizeLock) {
-                    size.x = displayContent.mInitialDisplayWidth;
-                    size.y = displayContent.mInitialDisplayHeight;
-                }
+                size.x = displayContent.mInitialDisplayWidth;
+                size.y = displayContent.mInitialDisplayHeight;
             }
         }
     }
@@ -8073,10 +8075,8 @@
         synchronized (mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
-                synchronized(displayContent.mDisplaySizeLock) {
-                    size.x = displayContent.mBaseDisplayWidth;
-                    size.y = displayContent.mBaseDisplayHeight;
-                }
+                size.x = displayContent.mBaseDisplayWidth;
+                size.y = displayContent.mBaseDisplayHeight;
             }
         }
     }
@@ -8145,13 +8145,9 @@
         }
     }
 
-    private void setForcedDisplayScalingModeLocked(DisplayContent displayContent,
-            int mode) {
+    private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, int mode) {
         Slog.i(TAG, "Using display scaling mode: " + (mode == 0 ? "auto" : "off"));
-
-        synchronized(displayContent.mDisplaySizeLock) {
-            displayContent.mDisplayScalingDisabled = (mode != 0);
-        }
+        displayContent.mDisplayScalingDisabled = (mode != 0);
         reconfigureDisplayLocked(displayContent);
     }
 
@@ -8169,13 +8165,11 @@
                 try {
                     width = Integer.parseInt(sizeStr.substring(0, pos));
                     height = Integer.parseInt(sizeStr.substring(pos+1));
-                    synchronized(displayContent.mDisplaySizeLock) {
-                        if (displayContent.mBaseDisplayWidth != width
-                                || displayContent.mBaseDisplayHeight != height) {
-                            Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height);
-                            displayContent.mBaseDisplayWidth = width;
-                            displayContent.mBaseDisplayHeight = height;
-                        }
+                    if (displayContent.mBaseDisplayWidth != width
+                            || displayContent.mBaseDisplayHeight != height) {
+                        Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height);
+                        displayContent.mBaseDisplayWidth = width;
+                        displayContent.mBaseDisplayHeight = height;
                     }
                 } catch (NumberFormatException ex) {
                 }
@@ -8192,11 +8186,9 @@
             int density;
             try {
                 density = Integer.parseInt(densityStr);
-                synchronized(displayContent.mDisplaySizeLock) {
-                    if (displayContent.mBaseDisplayDensity != density) {
-                        Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density);
-                        displayContent.mBaseDisplayDensity = density;
-                    }
+                if (displayContent.mBaseDisplayDensity != density) {
+                    Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density);
+                    displayContent.mBaseDisplayDensity = density;
                 }
             } catch (NumberFormatException ex) {
             }
@@ -8206,21 +8198,16 @@
         int mode = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.DISPLAY_SCALING_FORCE, 0);
         if (mode != 0) {
-            synchronized(displayContent.mDisplaySizeLock) {
-                Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED");
-                displayContent.mDisplayScalingDisabled = true;
-            }
+            Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED");
+            displayContent.mDisplayScalingDisabled = true;
         }
     }
 
     // displayContent must not be null
     private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
         Slog.i(TAG, "Using new display size: " + width + "x" + height);
-
-        synchronized(displayContent.mDisplaySizeLock) {
-            displayContent.mBaseDisplayWidth = width;
-            displayContent.mBaseDisplayHeight = height;
-        }
+        displayContent.mBaseDisplayWidth = width;
+        displayContent.mBaseDisplayHeight = height;
         reconfigureDisplayLocked(displayContent);
     }
 
@@ -8256,9 +8243,7 @@
         synchronized (mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
-                synchronized(displayContent.mDisplaySizeLock) {
-                    return displayContent.mInitialDisplayDensity;
-                }
+                return displayContent.mInitialDisplayDensity;
             }
         }
         return -1;
@@ -8269,9 +8254,7 @@
         synchronized (mWindowMap) {
             final DisplayContent displayContent = getDisplayContentLocked(displayId);
             if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
-                synchronized(displayContent.mDisplaySizeLock) {
-                    return displayContent.mBaseDisplayDensity;
-                }
+                return displayContent.mBaseDisplayDensity;
             }
         }
         return -1;
@@ -8306,10 +8289,7 @@
     // displayContent must not be null
     private void setForcedDisplayDensityLocked(DisplayContent displayContent, int density) {
         Slog.i(TAG, "Using new display density: " + density);
-
-        synchronized(displayContent.mDisplaySizeLock) {
-            displayContent.mBaseDisplayDensity = density;
-        }
+        displayContent.mBaseDisplayDensity = density;
         reconfigureDisplayLocked(displayContent);
     }
 
@@ -8400,12 +8380,10 @@
     private void setOverscanLocked(DisplayContent displayContent,
             int left, int top, int right, int bottom) {
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        synchronized (displayContent.mDisplaySizeLock) {
-            displayInfo.overscanLeft = left;
-            displayInfo.overscanTop = top;
-            displayInfo.overscanRight = right;
-            displayInfo.overscanBottom = bottom;
-        }
+        displayInfo.overscanLeft = left;
+        displayInfo.overscanTop = top;
+        displayInfo.overscanRight = right;
+        displayInfo.overscanBottom = bottom;
 
         mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, displayInfo.name, left, top,
                 right, bottom);
@@ -8584,8 +8562,7 @@
             } else if (wtoken != null) {
                 winAnimator.mAnimLayer =
                         w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
-                if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow &&
-                        wtoken.mReplacingWindow != w) {
+                if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w) {
                     // We know that we will be animating a relaunching window in the near future,
                     // which will receive a z-order increase. We want the replaced window to
                     // immediately receive the same treatment, e.g. to be above the dock divider.
@@ -9992,14 +9969,11 @@
         DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final Rect rect = new Rect();
         mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
-        synchronized (displayContent.mDisplaySizeLock) {
-            displayInfo.overscanLeft = rect.left;
-            displayInfo.overscanTop = rect.top;
-            displayInfo.overscanRight = rect.right;
-            displayInfo.overscanBottom = rect.bottom;
-            mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
-                    displayId, displayInfo);
-        }
+        displayInfo.overscanLeft = rect.left;
+        displayInfo.overscanTop = rect.top;
+        displayInfo.overscanRight = rect.right;
+        displayInfo.overscanBottom = rect.bottom;
+        mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(displayId, displayInfo);
         configureDisplayPolicyLocked(displayContent);
 
         // TODO: Create an input channel for each display with touch capability.
@@ -10125,9 +10099,8 @@
      * Hint to a token that its activity will relaunch, which will trigger removal and addition of
      * a window.
      * @param token Application token for which the activity will be relaunched.
-     * @param animate Whether to animate the addition of the new window.
      */
-    public void setReplacingWindow(IBinder token, boolean animate) {
+    public void setReplacingWindow(IBinder token) {
         synchronized (mWindowMap) {
             AppWindowToken appWindowToken = findAppWindowToken(token);
             if (appWindowToken == null) {
@@ -10138,7 +10111,13 @@
                     + " as replacing window.");
             appWindowToken.mWillReplaceWindow = true;
             appWindowToken.mHasReplacedWindow = false;
-            appWindowToken.mAnimateReplacingWindow = animate;
+
+            // Set-up dummy animation so we can start treating windows associated with this token
+            // like they are in transition before the new app window is ready for us to run the
+            // real transition animation.
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                    "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
+            appWindowToken.mAppAnimator.setDummyAnimation();
         }
     }
 
@@ -10207,7 +10186,7 @@
         }
 
         @Override
-        public void setMagnificationCallbacks(MagnificationCallbacks callbacks) {
+        public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) {
             synchronized (mWindowMap) {
                 if (mAccessibilityController == null) {
                     mAccessibilityController = new AccessibilityController(
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 673c21f..c6f7f4c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,11 +16,57 @@
 
 package com.android.server.wm;
 
+import com.android.server.input.InputWindowHandle;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IApplicationToken;
+import android.view.IWindow;
+import android.view.IWindowFocusObserver;
+import android.view.IWindowId;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
@@ -36,8 +82,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
-import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
-import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
@@ -46,47 +90,6 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.graphics.Point;
-import android.os.PowerManager;
-import android.os.RemoteCallbackList;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.WorkSource;
-import android.util.DisplayMetrics;
-import android.util.TimeUtils;
-import android.view.Display;
-import android.view.IWindowFocusObserver;
-import android.view.IWindowId;
-
-import com.android.server.input.InputWindowHandle;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Matrix;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.view.DisplayInfo;
-import android.view.Gravity;
-import android.view.IApplicationToken;
-import android.view.IWindow;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
 class WindowList extends ArrayList<WindowState> {
 }
 
@@ -106,8 +109,6 @@
     // to capture touch events in that area.
     static final int RESIZE_HANDLE_WIDTH_IN_DP = 30;
 
-    static final boolean BOUNDS_FOR_TOUCH = true;
-
     static final int DRAG_RESIZE_MODE_FREEFORM = 0;
     static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
 
@@ -1388,7 +1389,6 @@
                 && token.mHasReplacedWindow) {
             if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
             token.mWillReplaceWindow = false;
-            token.mAnimateReplacingWindow = false;
             token.mReplacingRemoveRequested = false;
             token.mReplacingWindow = null;
             token.mHasReplacedWindow = false;
@@ -1411,12 +1411,11 @@
         return mAppToken != null && mAppToken.mTask != null && mAppToken.mTask.inDockedWorkspace();
     }
 
-    int getTouchableRegion(Region region, int flags, InputMonitor inputMonitor) {
-        final boolean modal = (flags & (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) == 0;
+    int getTouchableRegion(Region region, int flags) {
+        final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
         if (modal && mAppToken != null) {
             // Limit the outer touch to the activity stack region.
-            flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+            flags |= FLAG_NOT_TOUCH_MODAL;
             // If this is a modal window we need to dismiss it if it's not full screen and the
             // touch happens outside of the frame that displays the content. This means we
             // need to intercept touches outside of that window. The dim layer user
@@ -1437,6 +1436,7 @@
                 mTmpRect.inset(-delta, -delta);
             }
             region.set(mTmpRect);
+            cropRegionToStackBoundsIfNeeded(region);
         } else {
             // Not modal or full screen modal
             getTouchableRegion(region);
@@ -1772,26 +1772,41 @@
                 frame.right - inset.right, frame.bottom - inset.bottom);
     }
 
-    public void getTouchableRegion(Region outRegion) {
+    void getTouchableRegion(Region outRegion) {
         final Rect frame = mFrame;
         switch (mTouchableInsets) {
             default:
-            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+            case TOUCHABLE_INSETS_FRAME:
                 outRegion.set(frame);
                 break;
-            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+            case TOUCHABLE_INSETS_CONTENT:
                 applyInsets(outRegion, frame, mGivenContentInsets);
                 break;
-            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+            case TOUCHABLE_INSETS_VISIBLE:
                 applyInsets(outRegion, frame, mGivenVisibleInsets);
                 break;
-            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: {
+            case TOUCHABLE_INSETS_REGION: {
                 final Region givenTouchableRegion = mGivenTouchableRegion;
                 outRegion.set(givenTouchableRegion);
                 outRegion.translate(frame.left, frame.top);
                 break;
             }
         }
+        cropRegionToStackBoundsIfNeeded(outRegion);
+    }
+
+    void cropRegionToStackBoundsIfNeeded(Region region) {
+        if (mAppToken == null || !mAppToken.mCropWindowsToStack) {
+            return;
+        }
+
+        final TaskStack stack = getStack();
+        if (stack == null) {
+            return;
+        }
+
+        stack.getDimBounds(mTmpRect);
+        region.op(mTmpRect, Region.Op.INTERSECT);
     }
 
     WindowList getWindowList() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index f7805b1..6faf3a7 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -44,7 +44,6 @@
 import android.os.Debug;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.MagnificationSpec;
 import android.view.Surface.OutOfResourcesException;
@@ -124,16 +123,12 @@
     Rect mLastClipRect = new Rect();
     Rect mTmpStackBounds = new Rect();
 
-    // Used to save animation distances between the time they are calculated and when they are
-    // used.
-    int mAnimDw;
-    int mAnimDh;
+    // Used to save animation distances between the time they are calculated and when they are used.
+    private int mAnimDx;
+    private int mAnimDy;
 
     /** Is the next animation to be started a window move animation? */
-    boolean mAnimateMove = false;
-
-    /** Are we currently running a window move animation? */
-    boolean mAnimatingMove = false;
+    private boolean mAnimateMove = false;
 
     float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1;
     float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1;
@@ -198,8 +193,8 @@
         final DisplayContent displayContent = win.getDisplayContent();
         if (displayContent != null) {
             final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-            mAnimDw = displayInfo.appWidth;
-            mAnimDh = displayInfo.appHeight;
+            mAnimDx = displayInfo.appWidth;
+            mAnimDy = displayInfo.appHeight;
         } else {
             Slog.w(TAG, "WindowStateAnimator ctor: Display has been removed");
             // This is checked on return and dealt with.
@@ -299,19 +294,19 @@
                         TAG, "Starting animation in " + this +
                         " @ " + currentTime + ": ww=" + mWin.mFrame.width() +
                         " wh=" + mWin.mFrame.height() +
-                        " dw=" + mAnimDw + " dh=" + mAnimDh +
+                        " dx=" + mAnimDx + " dy=" + mAnimDy +
                         " scale=" + mService.getWindowAnimationScaleLocked());
                     final DisplayInfo displayInfo = displayContent.getDisplayInfo();
                     if (mAnimateMove) {
                         mAnimateMove = false;
                         mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
-                                mAnimDw, mAnimDh);
+                                mAnimDx, mAnimDy);
                     } else {
                         mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
                                 displayInfo.appWidth, displayInfo.appHeight);
                     }
-                    mAnimDw = displayInfo.appWidth;
-                    mAnimDh = displayInfo.appHeight;
+                    mAnimDx = displayInfo.appWidth;
+                    mAnimDy = displayInfo.appHeight;
                     mAnimation.setStartTime(mAnimationStartTime != -1
                             ? mAnimationStartTime
                             : currentTime);
@@ -368,7 +363,6 @@
 
         mAnimating = false;
         mKeyguardGoingAwayAnimation = false;
-        mAnimatingMove = false;
         mLocalAnimating = false;
         if (mAnimation != null) {
             mAnimation.cancel();
@@ -773,7 +767,14 @@
                         mPendingDestroySurface = mSurfaceController;
                     }
                 } else {
-                    WindowManagerService.logSurface(mWin, "DESTROY", null);
+                    if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                        RuntimeException e = null;
+                        if (!WindowManagerService.HIDE_STACK_CRAWLS) {
+                            e = new RuntimeException();
+                            e.fillInStackTrace();
+                        }
+                        WindowManagerService.logSurface(mWin, "DESTROY", null);
+                    }
                     destroySurface();
                 }
                 // Don't hide wallpaper if we're deferring the surface destroy
@@ -1606,7 +1607,7 @@
         fadeOut.setDuration(fadeDuration);
         fadeOut.setStartOffset(elapsed);
         newAnimation.addAnimation(fadeOut);
-        newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDw, mAnimDh);
+        newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDx, mAnimDy);
         mAnimation = newAnimation;
     }
 
@@ -1680,4 +1681,13 @@
         mSurfaceController.destroyInTransaction();
         mSurfaceController = null;
     }
+
+    void setMoveAnimation(int left, int top) {
+        final Animation a = AnimationUtils.loadAnimation(mContext,
+                com.android.internal.R.anim.window_move_from_decor);
+        setAnimation(a);
+        mAnimDx = mWin.mLastFrame.left - left;
+        mAnimDy = mWin.mLastFrame.top - top;
+        mAnimateMove = true;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index ae96658..979f55b 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -17,7 +17,6 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.WindowManagerService.DEBUG;
 import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
-import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS;
@@ -57,7 +56,6 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -679,20 +677,17 @@
                 final WindowStateAnimator winAnimator = w.mWinAnimator;
 
                 // If the window has moved due to its containing content frame changing, then
-                // notify the listeners and optionally animate it.
+                // notify the listeners and optionally animate it. Simply checking a change of
+                // position is not enough, because being move due to dock divider is not a trigger
+                // for animation.
                 if (w.hasMoved()) {
                     // Frame has moved, containing content frame has also moved, and we're not
                     // currently animating... let's do something.
                     final int left = w.mFrame.left;
                     final int top = w.mFrame.top;
-                    if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0) {
-                        Animation a = AnimationUtils.loadAnimation(mService.mContext,
-                                com.android.internal.R.anim.window_move_from_decor);
-                        winAnimator.setAnimation(a);
-                        winAnimator.mAnimDw = w.mLastFrame.left - left;
-                        winAnimator.mAnimDh = w.mLastFrame.top - top;
-                        winAnimator.mAnimateMove = true;
-                        winAnimator.mAnimatingMove = true;
+                    if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
+                            && !w.isDragResizing()) {
+                        winAnimator.setMoveAnimation(left, top);
                     }
 
                     //TODO (multidisplay): Accessibility supported only for the default display.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c611503..c743f24 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6706,11 +6706,28 @@
 
     @Override
     public boolean isProvisioningAllowed(String action) {
-        if (mOwners.hasDeviceOwner()) {
-            return false;
-        }
         final int callingUserId = mInjector.userHandleGetCallingUserId();
         if (DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(action)) {
+            if (mOwners.hasDeviceOwner()) {
+                if (!mInjector.userManagerIsSplitSystemUser()) {
+                    // Only split-system-user systems support managed-profiles in combination with
+                    // device-owner.
+                    return false;
+                }
+                if (mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM) {
+                    // Only system device-owner supports managed-profiles. Non-system device-owner
+                    // doesn't.
+                    return false;
+                }
+                if (callingUserId == UserHandle.USER_SYSTEM) {
+                    // Managed-profiles cannot be setup on the system user, only regular users.
+                    return false;
+                }
+            }
+            if (getProfileOwner(callingUserId) != null) {
+                // Managed user cannot have a managed profile.
+                return false;
+            }
             try {
                 if (!mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
                     return false;
@@ -6730,7 +6747,7 @@
         } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE.equals(action)) {
             return isDeviceOwnerProvisioningAllowed(callingUserId);
         } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_USER.equals(action)) {
-            if (!UserManager.isSplitSystemUser()) {
+            if (!mInjector.userManagerIsSplitSystemUser()) {
                 // ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
                 return false;
             }
@@ -6739,7 +6756,7 @@
             }
             return true;
         } else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE.equals(action)) {
-            if (!UserManager.isSplitSystemUser()) {
+            if (!mInjector.userManagerIsSplitSystemUser()) {
                 // ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
                 return false;
             }
@@ -6749,6 +6766,9 @@
     }
 
     private boolean isDeviceOwnerProvisioningAllowed(int callingUserId) {
+        if (mOwners.hasDeviceOwner()) {
+            return false;
+        }
         if (getProfileOwner(callingUserId) != null) {
             return false;
         }
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
index b0296a0..c174a92 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -61,7 +61,7 @@
         b2.putBoolean("b2", true);
 
         SyncOperation op1 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b1,
@@ -73,7 +73,7 @@
 
         // Same as op1 but different time infos
         SyncOperation op2 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b1,
@@ -85,7 +85,7 @@
 
         // Same as op1 but different authority
         SyncOperation op3 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority2",
                 b1,
@@ -97,7 +97,7 @@
 
         // Same as op1 but different account
         SyncOperation op4 = new SyncOperation(account2, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b1,
@@ -109,7 +109,7 @@
 
         // Same as op1 but different bundle
         SyncOperation op5 = new SyncOperation(account1, 0,
-                1,
+                1, "foo", 0,
                 SyncOperation.REASON_PERIODIC,
                 "authority1",
                 b2,
@@ -131,21 +131,21 @@
         long soonFlex = 50;
         long after = 1500;
         long afterFlex = 100;
-        SyncOperation op1 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+        SyncOperation op1 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
                 "authority1", mEmpty, soon, soonFlex, mUnimportantLong, mUnimportantLong, true);
 
         // Interval disjoint from and after op1.
-        SyncOperation op2 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+        SyncOperation op2 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
                 "authority1", mEmpty, after, afterFlex, mUnimportantLong, mUnimportantLong, true);
 
         // Interval equivalent to op1, but expedited.
         Bundle b2 = new Bundle();
         b2.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
-        SyncOperation op3 = new SyncOperation(mDummy, 0, 0, 0,
+        SyncOperation op3 = new SyncOperation(mDummy, 0, 0, "foo", 0, 0,
                 "authority1", b2, -1, soonFlex, mUnimportantLong, mUnimportantLong, true);
 
         // Interval overlaps but not equivalent to op1.
-        SyncOperation op4 = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_PERIODIC,
+        SyncOperation op4 = new SyncOperation(mDummy, 0, 0, "foo", 0, SyncOperation.REASON_PERIODIC,
                 "authority1", mEmpty, soon + 100, soonFlex + 100, mUnimportantLong, mUnimportantLong, true);
 
         assertTrue(op1.compareTo(op2) == -1);
@@ -165,7 +165,8 @@
 
         Bundle withExpedited = new Bundle();
         withExpedited.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
-        SyncOperation op = new SyncOperation(mDummy, 0, 0, SyncOperation.REASON_USER_START,
+        SyncOperation op = new SyncOperation(mDummy, 0, 0, "foo", 0,
+                SyncOperation.REASON_USER_START,
                 mAuthority, withExpedited, fiveSecondsFromNow, twoSecondsFlex,
                 eightSeconds /* backoff */, fourSeconds /* delayUntil */, true);
         // Create another sync op to be rerun in 5 minutes.
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
index ae1967e..b22eb53 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
@@ -41,8 +41,6 @@
 import java.io.FileOutputStream;
 import java.util.List;
 
-import com.android.server.content.SyncStorageEngine.EndPoint;
-
 public class SyncStorageEngineTest extends AndroidTestCase {
 
     protected Account account1;
@@ -96,7 +94,7 @@
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
                 new TestContext(mockResolver, getContext()));
         long time0 = 1000;
-        SyncOperation op = new SyncOperation(account, 0,
+        SyncOperation op = new SyncOperation(account, 0, 0, "foo",
                 SyncOperation.REASON_PERIODIC,
                 SyncStorageEngine.SOURCE_LOCAL,
                 authority,
@@ -112,7 +110,7 @@
     @MediumTest
     public void testAppendPending() throws Exception {
         SyncOperation sop = new SyncOperation(account1,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_PERIODIC,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
                 0 /* runtime */, 0 /* flex */, 0 /* backoff */, 0 /* delayuntil */,
@@ -140,19 +138,19 @@
      */
     public void testWritePendingOperationsLocked() throws Exception {
         SyncOperation sop = new SyncOperation(account1,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_IS_SYNCABLE,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
                 1000L /* runtime */, 57L /* flex */, 0 /* backoff */, 0 /* delayuntil */,
                 true /* expedited */);
         SyncOperation sop1 = new SyncOperation(account2,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_PERIODIC,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, defaultBundle,
                 0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
                 false /* expedited */);
         SyncOperation deleted = new SyncOperation(account2,
-                DEFAULT_USER,
+                DEFAULT_USER, 0, "foo",
                 SyncOperation.REASON_SYNC_AUTO,
                 SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
                 0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
@@ -456,14 +454,14 @@
 
         // Test service component read
         List<PeriodicSync> syncs = engine.getPeriodicSyncs(
-                new SyncStorageEngine.EndPoint(syncService1, 0));
+                new SyncStorageEngine.EndPoint(syncService1, 0, 0));
         assertEquals(1, syncs.size());
         assertEquals(true, engine.getIsTargetServiceActive(syncService1, 0));
     }
 
     @SmallTest
     public void testComponentSettings() throws Exception {
-        EndPoint target1 = new EndPoint(syncService1, 0);
+        EndPoint target1 = new EndPoint(syncService1, 0, 0);
         engine.updateOrAddPeriodicSync(target1, dayPoll, dayFuzz, Bundle.EMPTY);
         
         engine.setIsTargetServiceActive(target1.service, 0, true);
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index f2a1878..d292f62 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -747,7 +747,13 @@
         }
     }
 
-    std::vector<Attribute::Symbol> items;
+    struct SymbolComparator {
+        bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
+            return a.symbol.name.value() < b.symbol.name.value();
+        }
+    };
+
+    std::set<Attribute::Symbol, SymbolComparator> items;
 
     std::u16string comment;
     bool error = false;
@@ -785,15 +791,27 @@
             }
 
             if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
+                Attribute::Symbol& symbol = s.value();
                 ParsedResource childResource;
-                childResource.name = s.value().symbol.name.value();
+                childResource.name = symbol.symbol.name.value();
                 childResource.source = itemSource;
                 childResource.value = util::make_unique<Id>();
                 outResource->childResources.push_back(std::move(childResource));
 
-                s.value().symbol.setComment(std::move(comment));
-                s.value().symbol.setSource(itemSource);
-                items.push_back(std::move(s.value()));
+                symbol.symbol.setComment(std::move(comment));
+                symbol.symbol.setSource(itemSource);
+
+                auto insertResult = items.insert(std::move(symbol));
+                if (!insertResult.second) {
+                    const Attribute::Symbol& existingSymbol = *insertResult.first;
+                    mDiag->error(DiagMessage(itemSource)
+                                 << "duplicate symbol '" << existingSymbol.symbol.name.value().entry
+                                 << "'");
+
+                    mDiag->note(DiagMessage(existingSymbol.symbol.getSource())
+                                << "first defined here");
+                    error = true;
+                }
             } else {
                 error = true;
             }
@@ -810,7 +828,7 @@
     }
 
     std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
-    attr->symbols.swap(items);
+    attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
     attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
     outResource->value = std::move(attr);
     return true;
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index d3c3c10..b1a4c7d 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -23,22 +23,28 @@
 namespace aapt {
 namespace ResourceUtils {
 
-void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
                          StringPiece16* outType, StringPiece16* outEntry) {
+    bool hasPackageSeparator = false;
+    bool hasTypeSeparator = false;
     const char16_t* start = str.data();
     const char16_t* end = start + str.size();
     const char16_t* current = start;
     while (current != end) {
         if (outType->size() == 0 && *current == u'/') {
+            hasTypeSeparator = true;
             outType->assign(start, current - start);
             start = current + 1;
         } else if (outPackage->size() == 0 && *current == u':') {
+            hasPackageSeparator = true;
             outPackage->assign(start, current - start);
             start = current + 1;
         }
         current++;
     }
     outEntry->assign(start, end - start);
+
+    return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
 }
 
 bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
@@ -62,26 +68,34 @@
         StringPiece16 package;
         StringPiece16 type;
         StringPiece16 entry;
-        extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type,
-                            &entry);
+        if (!extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
+                                 &package, &type, &entry)) {
+            return false;
+        }
 
         const ResourceType* parsedType = parseResourceType(type);
         if (!parsedType) {
             return false;
         }
 
+        if (entry.empty()) {
+            return false;
+        }
+
         if (create && *parsedType != ResourceType::kId) {
             return false;
         }
 
-        if (outRef != nullptr) {
+        if (outRef) {
             outRef->package = package;
             outRef->type = *parsedType;
             outRef->entry = entry;
         }
+
         if (outCreate) {
             *outCreate = create;
         }
+
         if (outPrivate) {
             *outPrivate = priv;
         }
@@ -104,20 +118,33 @@
         StringPiece16 package;
         StringPiece16 type;
         StringPiece16 entry;
-        extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
+        if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
+                                 &package, &type, &entry)) {
+            return false;
+        }
 
         if (!type.empty() && type != u"attr") {
             return false;
         }
 
-        outRef->package = package;
-        outRef->type = ResourceType::kAttr;
-        outRef->entry = entry;
+        if (entry.empty()) {
+            return false;
+        }
+
+        if (outRef) {
+            outRef->package = package;
+            outRef->type = ResourceType::kAttr;
+            outRef->entry = entry;
+        }
         return true;
     }
     return false;
 }
 
+bool isAttributeReference(const StringPiece16& str) {
+    return tryParseAttributeReference(str, nullptr);
+}
+
 /*
  * Style parent's are a bit different. We accept the following formats:
  *
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 851edc8..34daa66 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -34,8 +34,9 @@
  *
  * where the package can be empty. Validation must be performed on each
  * individual extracted piece to verify that the pieces are valid.
+ * Returns false if there was no package but a ':' was present.
  */
-void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
                          StringPiece16* outType, StringPiece16* outEntry);
 
 /*
@@ -54,12 +55,17 @@
 bool isReference(const StringPiece16& str);
 
 /*
- * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
+ * Returns true if the string was parsed as an attribute reference (?[package:][type/]name),
  * with `outReference` set to the parsed reference.
  */
 bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
 
 /**
+ * Returns true if the string is in the form of an attribute reference(?[package:][type/]name).
+ */
+bool isAttributeReference(const StringPiece16& str);
+
+/**
  * Returns true if the value is a boolean, putting the result in `outValue`.
  */
 bool tryParseBool(const StringPiece16& str, bool* outValue);
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 7de8f41..3d2a6e1 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -90,6 +90,26 @@
                                                   &privateRef));
 }
 
+TEST(ResourceUtilsTest, ParseAttributeReferences) {
+    EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android"));
+    EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:foo"));
+    EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?attr/foo"));
+    EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:attr/foo"));
+}
+
+TEST(ResourceUtilsTest, FailParseIncompleteReference) {
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?style/foo"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:style/foo"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:attr/"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/foo"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/foo"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?attr/"));
+    EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?/foo"));
+}
+
 TEST(ResourceUtilsTest, ParseStyleParentReference) {
     const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" };
     const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" };
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
index 9a46bcb..721bf5b 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -215,10 +215,13 @@
         for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
             if (name.package == iter->prefix) {
                 if (iter->package.empty()) {
-                    return ResourceName{ localPackage.toString(), name.type, name.entry };
-                } else {
+                    if (localPackage != name.package) {
+                        return ResourceName{ localPackage.toString(), name.type, name.entry };
+                    }
+                } else if (iter->package != name.package) {
                     return ResourceName{ iter->package, name.type, name.entry };
                 }
+                break;
             }
         }
         return {};
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 93f2dc6f..97be774 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -50,7 +50,7 @@
     std::vector<std::string> includePaths;
     std::vector<std::string> overlayFiles;
     Maybe<std::string> generateJavaClassPath;
-    std::vector<std::string> extraJavaPackages;
+    std::set<std::string> extraJavaPackages;
     Maybe<std::string> generateProguardRulesPath;
     bool noAutoVersion = false;
     bool staticLib = false;
@@ -485,7 +485,7 @@
             }
         }
 
-        mFilesToProcess[resName.toResourceName()] = FileToProcess{ Source(input), std::move(file) };
+        mFilesToProcess.insert(FileToProcess{ std::move(file), Source(input) });
         return true;
     }
 
@@ -640,8 +640,7 @@
             }
         }
 
-        for (auto& pair : mFilesToProcess) {
-            FileToProcess& file = pair.second;
+        for (const FileToProcess& file : mFilesToProcess) {
             if (file.file.name.type != ResourceType::kRaw &&
                     util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
                 if (mOptions.verbose) {
@@ -760,7 +759,7 @@
                 return 1;
             }
 
-            for (std::string& extraPackage : mOptions.extraJavaPackages) {
+            for (const std::string& extraPackage : mOptions.extraJavaPackages) {
                 if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage),
                                    options)) {
                     return 1;
@@ -795,16 +794,24 @@
     std::unique_ptr<TableMerger> mTableMerger;
 
     struct FileToProcess {
-        Source source;
         ResourceFile file;
+        Source source;
     };
-    std::map<ResourceName, FileToProcess> mFilesToProcess;
+
+    struct FileToProcessComparator {
+        bool operator()(const FileToProcess& a, const FileToProcess& b) {
+            return std::tie(a.file.name, a.file.config) < std::tie(b.file.name, b.file.config);
+        }
+    };
+
+    std::set<FileToProcess, FileToProcessComparator> mFilesToProcess;
 };
 
 int link(const std::vector<StringPiece>& args) {
     LinkOptions options;
     Maybe<std::string> privateSymbolsPackage;
     Maybe<std::string> minSdkVersion, targetSdkVersion;
+    std::vector<std::string> extraJavaPackages;
     Flags flags = Flags()
             .requiredFlag("-o", "Output path", &options.outputPath)
             .requiredFlag("--manifest", "Path to the Android manifest to build",
@@ -833,7 +840,7 @@
                           "If not specified, public and private symbols will use the application's "
                           "package name", &privateSymbolsPackage)
             .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
-                              "package names", &options.extraJavaPackages)
+                              "package names", &extraJavaPackages)
             .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
 
     if (!flags.parse("aapt2 link", args, &std::cerr)) {
@@ -852,6 +859,14 @@
         options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
     }
 
+    // Populate the set of extra packages for which to generate R.java.
+    for (std::string& extraPackage : extraJavaPackages) {
+        // A given package can actually be a colon separated list of packages.
+        for (StringPiece package : util::split(extraPackage, ':')) {
+            options.extraJavaPackages.insert(package.toString());
+        }
+    }
+
     LinkCommand cmd(options);
     return cmd.run(flags.getArgs());
 }
diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h
index c70531b..a4cb596 100644
--- a/tools/aapt2/link/ReferenceLinkerVisitor.h
+++ b/tools/aapt2/link/ReferenceLinkerVisitor.h
@@ -80,7 +80,7 @@
                 return;
             }
 
-            DiagMessage errorMsg;
+            DiagMessage errorMsg(reference->getSource());
             errorMsg << "reference to " << reference->name.value();
             if (realName) {
                 errorMsg << " (aka " << realName.value() << ")";
@@ -92,7 +92,7 @@
         }
 
         if (!mSymbols->findById(reference->id.value())) {
-            mContext->getDiagnostics()->error(DiagMessage()
+            mContext->getDiagnostics()->error(DiagMessage(reference->getSource())
                                               << "reference to " << reference->id.value()
                                               << " was not found");
             mError = true;
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 147b9bf..caab9b8 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -33,6 +33,7 @@
 private:
     IAaptContext* mContext;
     ISymbolTable* mSymbols;
+    Source mSource;
     std::set<int>* mSdkLevelsFound;
     ReferenceLinkerVisitor mReferenceLinkerVisitor;
     bool mError = false;
@@ -40,13 +41,14 @@
 public:
     using xml::PackageAwareVisitor::visit;
 
-    XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
+    XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source,
                               std::set<int>* sdkLevelsFound) :
-            mContext(context), mSymbols(symbols), mSdkLevelsFound(sdkLevelsFound),
+            mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound),
             mReferenceLinkerVisitor(context, symbols, this) {
     }
 
     void visit(xml::Element* el) override {
+        const Source source = mSource.withLine(el->lineNumber);
         for (xml::Attribute& attr : el->attributes) {
             Maybe<std::u16string> maybePackage =
                     util::extractPackageFromNamespace(attr.namespaceUri);
@@ -76,15 +78,16 @@
                             !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) {
                         // We won't be able to encode this as a string.
                         mContext->getDiagnostics()->error(
-                                DiagMessage() << "'" << attr.value << "' "
-                                              << "is incompatible with attribute "
-                                              << package << ":" << attr.name << " " << *attribute);
+                                DiagMessage(source) << "'" << attr.value << "' "
+                                                    << "is incompatible with attribute "
+                                                    << package << ":" << attr.name << " "
+                                                    << *attribute);
                         mError = true;
                     }
                 } else {
-                    mContext->getDiagnostics()->error(
-                            DiagMessage() << "attribute '" << package << ":" << attr.name
-                                          << "' was not found");
+                    mContext->getDiagnostics()->error(DiagMessage(source)
+                                                      << "attribute '" << package << ":"
+                                                      << attr.name << "' was not found");
                     mError = true;
 
                 }
@@ -95,6 +98,7 @@
 
             if (attr.compiledValue) {
                 // With a compiledValue, we must resolve the reference and assign it an ID.
+                attr.compiledValue->setSource(source);
                 attr.compiledValue->accept(&mReferenceLinkerVisitor);
             }
         }
@@ -123,7 +127,8 @@
 
 bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) {
     mSdkLevelsFound.clear();
-    XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound);
+    XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
+                                      &mSdkLevelsFound);
     if (resource->root) {
         resource->root->accept(&visitor);
         return !visitor.hasError();
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 9a8b263..bb33ea7 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -77,16 +77,8 @@
 }
 
 
-static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResTable& table,
-                                                             ResourceId id) {
-    android::Res_value val = {};
-    ssize_t block = table.getResource(id.id, &val, true);
-    if (block >= 0) {
-        std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>();
-        s->id = id;
-        return s;
-    }
-
+static std::shared_ptr<ISymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table,
+                                                                    ResourceId id) {
     // Try as a bag.
     const android::ResTable::bag_entry* entry;
     ssize_t count = table.lockBag(id.id, &entry);
@@ -148,14 +140,23 @@
     for (const auto& asset : mAssets) {
         const android::ResTable& table = asset->getResources(false);
         StringPiece16 typeStr = toString(name.type);
+        uint32_t typeSpecFlags = 0;
         ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(),
                                                    typeStr.data(), typeStr.size(),
-                                                   name.package.data(), name.package.size());
+                                                   name.package.data(), name.package.size(),
+                                                   &typeSpecFlags);
         if (!resId.isValid()) {
             continue;
         }
 
-        std::shared_ptr<Symbol> s = lookupIdInTable(table, resId);
+        std::shared_ptr<Symbol> s;
+        if (name.type == ResourceType::kAttr) {
+            s = lookupAttributeInTable(table, resId);
+        } else {
+            s = std::make_shared<Symbol>();
+            s->id = resId;
+        }
+
         if (s) {
             mCache.put(name, s);
             return s.get();
@@ -173,7 +174,28 @@
     for (const auto& asset : mAssets) {
         const android::ResTable& table = asset->getResources(false);
 
-        std::shared_ptr<Symbol> s = lookupIdInTable(table, id);
+        android::ResTable::resource_name name;
+        if (!table.getResourceName(id.id, true, &name)) {
+            continue;
+        }
+
+        bool isAttr = false;
+        if (name.type) {
+            if (const ResourceType* t = parseResourceType(StringPiece16(name.type, name.typeLen))) {
+                isAttr = (*t == ResourceType::kAttr);
+            }
+        } else if (name.type8) {
+            isAttr = (StringPiece(name.type8, name.typeLen) == "attr");
+        }
+
+        std::shared_ptr<Symbol> s;
+        if (isAttr) {
+            s = lookupAttributeInTable(table, id);
+        } else {
+            s = std::make_shared<Symbol>();
+            s->id = id;
+        }
+
         if (s) {
             mIdCache.put(id, s);
             return s.get();
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 0d17e84..3048334 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -304,6 +304,11 @@
         return false;
     }
 
+    // There can be multiple packages in a table, so
+    // clear the type and key pool in case they were set from a previous package.
+    mTypePool.uninit();
+    mKeyPool.uninit();
+
     ResChunkPullParser parser(getChunkData(&packageHeader->header),
                               getChunkDataLen(&packageHeader->header));
     while (ResChunkPullParser::isGoodEvent(parser.next())) {
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 80552a5..324afb3 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -229,11 +229,12 @@
     private:
         friend class Tokenizer<Char>;
 
-        iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok);
+        iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end);
 
-        BasicStringPiece<Char> str;
-        Char separator;
-        BasicStringPiece<Char> token;
+        BasicStringPiece<Char> mStr;
+        Char mSeparator;
+        BasicStringPiece<Char> mToken;
+        bool mEnd;
     };
 
     Tokenizer(BasicStringPiece<Char> str, Char sep);
@@ -252,36 +253,38 @@
 
 template <typename Char>
 typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() {
-    const Char* start = token.end();
-    const Char* end = str.end();
+    const Char* start = mToken.end();
+    const Char* end = mStr.end();
     if (start == end) {
-        token.assign(token.end(), 0);
+        mEnd = true;
+        mToken.assign(mToken.end(), 0);
         return *this;
     }
 
     start += 1;
     const Char* current = start;
     while (current != end) {
-        if (*current == separator) {
-            token.assign(start, current - start);
+        if (*current == mSeparator) {
+            mToken.assign(start, current - start);
             return *this;
         }
         ++current;
     }
-    token.assign(start, end - start);
+    mToken.assign(start, end - start);
     return *this;
 }
 
 template <typename Char>
 inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() {
-    return token;
+    return mToken;
 }
 
 template <typename Char>
 inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const {
     // We check equality here a bit differently.
     // We need to know that the addresses are the same.
-    return token.begin() == rhs.token.begin() && token.end() == rhs.token.end();
+    return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() &&
+            mEnd == rhs.mEnd;
 }
 
 template <typename Char>
@@ -291,8 +294,8 @@
 
 template <typename Char>
 inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep,
-                                           BasicStringPiece<Char> tok) :
-        str(s), separator(sep), token(tok) {
+                                           BasicStringPiece<Char> tok, bool end) :
+        mStr(s), mSeparator(sep), mToken(tok), mEnd(end) {
 }
 
 template <typename Char>
@@ -307,8 +310,8 @@
 
 template <typename Char>
 inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) :
-        mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))),
-        mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) {
+        mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)),
+        mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) {
 }
 
 inline uint16_t hostToDevice16(uint16_t value) {
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 9db9fb7..9208e07 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -101,6 +101,15 @@
     ASSERT_EQ(tokenizer.end(), iter);
 }
 
+TEST(UtilTest, TokenizeEmptyString) {
+    auto tokenizer = util::tokenize(StringPiece16(u""), u'|');
+    auto iter = tokenizer.begin();
+    ASSERT_NE(tokenizer.end(), iter);
+    ASSERT_EQ(StringPiece16(), *iter);
+    ++iter;
+    ASSERT_EQ(tokenizer.end(), iter);
+}
+
 TEST(UtilTest, TokenizeAtEnd) {
     auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.');
     auto iter = tokenizer.begin();
diff --git a/tools/layoutlib/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml
index e206d70..f758959 100644
--- a/tools/layoutlib/.idea/encodings.xml
+++ b/tools/layoutlib/.idea/encodings.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
-</project>
-
+  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index 868c6d3..b67afeb 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -16,10 +16,13 @@
 
 package com.android.layoutlib.bridge.bars;
 
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
 import com.android.ide.common.rendering.api.RenderResources;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.ide.common.rendering.api.SessionParams;
 import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.impl.ResourceHelper;
 import com.android.resources.ResourceType;
@@ -45,6 +48,8 @@
 
     private Object mWindowDecorActionBar;
     private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
+    // This is used on v23.1.1 and later.
+    private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
     private Class<?> mWindowActionBarClass;
 
     /**
@@ -70,14 +75,26 @@
         try {
             Class[] constructorParams = {View.class};
             Object[] constructorArgs = {getDecorContent()};
-            mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS,
-                    constructorParams, constructorArgs);
+            LayoutlibCallback callback = params.getLayoutlibCallback();
+            // First try to load the class as was available before appcompat v23.1.1, without
+            // logging warnings.
+            try {
+                mWindowDecorActionBar = callback.loadClass(WINDOW_ACTION_BAR_CLASS,
+                        constructorParams, constructorArgs);
+            } catch (ClassNotFoundException ignore) {
+            }
+            if (mWindowDecorActionBar == null) {
+                // If failed, load the new class, while logging warnings.
+                mWindowDecorActionBar = callback.loadView(WINDOW_ACTION_BAR_CLASS_NEW,
+                        constructorParams, constructorArgs);
+            }
 
             mWindowActionBarClass = mWindowDecorActionBar == null ? null :
                     mWindowDecorActionBar.getClass();
             setupActionBar();
         } catch (Exception e) {
-            e.printStackTrace();
+            Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+                    "Failed to load AppCompat ActionBar with unknown error.", e);
         }
     }
 
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e611ea4..6e42391 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -737,7 +737,9 @@
     public String toString() {
         StringBuffer sb = new StringBuffer();
         for (String key : mFields.keySet()) {
-            sb.append(key).append(" ").append(mFields.get(key)).append("\n");
+            // Don't display password in toString().
+            String value = (key == PASSWORD_KEY) ? "<removed>" : mFields.get(key);
+            sb.append(key).append(" ").append(value).append("\n");
         }
         return sb.toString();
     }
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index c26ca6e..5534cad 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -168,6 +168,22 @@
          * to wake up at fixed interval
          */
         public int maxScansToCache;
+        /**
+         * if maxPeriodInMs is non zero or different than period, then this bucket is
+         * an exponential backoff bucket and the scan period will grow exponentially
+         * as per formula: actual_period(N) = period ^ (N/(step_count+1))
+         * to a maximum period of max_period.
+         */
+        public int maxPeriodInMs;
+        /**
+         * for exponential back off bucket: multiplier: new_period=old_period*exponent
+         */
+        public int exponent;
+        /**
+         * for exponential back off bucket, number of scans performed at a given
+         * period and until the exponent is applied
+         */
+        public int stepCount;
 
         /** Implement the Parcelable interface {@hide} */
         public int describeContents() {
@@ -181,6 +197,9 @@
             dest.writeInt(reportEvents);
             dest.writeInt(numBssidsPerScan);
             dest.writeInt(maxScansToCache);
+            dest.writeInt(maxPeriodInMs);
+            dest.writeInt(exponent);
+            dest.writeInt(stepCount);
 
             if (channels != null) {
                 dest.writeInt(channels.length);
@@ -206,6 +225,9 @@
                         settings.reportEvents = in.readInt();
                         settings.numBssidsPerScan = in.readInt();
                         settings.maxScansToCache = in.readInt();
+                        settings.maxPeriodInMs = in.readInt();
+                        settings.exponent = in.readInt();
+                        settings.stepCount = in.readInt();
                         int num_channels = in.readInt();
                         settings.channels = new ChannelSpec[num_channels];
                         for (int i = 0; i < num_channels; i++) {