Merge "Fix ConnectivityServiceTest testRequestBenchmark"
diff --git a/api/current.txt b/api/current.txt
index 8a00273..3914021 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1357,6 +1357,7 @@
     field public static final int toYDelta = 16843209; // 0x10101c9
     field public static final int toYScale = 16843205; // 0x10101c5
     field public static final int toolbarStyle = 16843946; // 0x10104aa
+    field public static final int tooltip = 16844084; // 0x1010534
     field public static final int top = 16843182; // 0x10101ae
     field public static final int topBright = 16842955; // 0x10100cb
     field public static final int topDark = 16842951; // 0x10100c7
@@ -19914,6 +19915,7 @@
     field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
     field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
     field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
+    field public static final int STREAM_ACCESSIBILITY = 10; // 0xa
     field public static final int STREAM_ALARM = 4; // 0x4
     field public static final int STREAM_DTMF = 8; // 0x8
     field public static final int STREAM_MUSIC = 3; // 0x3
@@ -37629,6 +37631,7 @@
     method public int getCallState();
     method public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
+    method public boolean getDataEnabled();
     method public int getDataNetworkType();
     method public int getDataState();
     method public java.lang.String getDeviceId();
@@ -37675,6 +37678,7 @@
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
+    method public void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
     method public boolean setPreferredNetworkTypeToGlobal();
@@ -37682,6 +37686,7 @@
     field public static final java.lang.String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL";
     field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
     field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
+    field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
     field public static final int APPTYPE_CSIM = 4; // 0x4
     field public static final int APPTYPE_ISIM = 5; // 0x5
     field public static final int APPTYPE_RUIM = 3; // 0x3
@@ -37701,11 +37706,15 @@
     field public static final int DATA_CONNECTING = 1; // 0x1
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
+    field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
     field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
+    field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
+    field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
     field public static final java.lang.String EXTRA_STATE = "state";
     field public static final java.lang.String EXTRA_STATE_IDLE;
     field public static final java.lang.String EXTRA_STATE_OFFHOOK;
     field public static final java.lang.String EXTRA_STATE_RINGING;
+    field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
     field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
     field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
     field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -42827,6 +42836,7 @@
     method public java.lang.Object getTag(int);
     method public int getTextAlignment();
     method public int getTextDirection();
+    method public final java.lang.CharSequence getTooltip();
     method public final int getTop();
     method protected float getTopFadingEdgeStrength();
     method protected int getTopPaddingOffset();
@@ -43115,6 +43125,7 @@
     method public void setTag(int, java.lang.Object);
     method public void setTextAlignment(int);
     method public void setTextDirection(int);
+    method public final void setTooltip(java.lang.CharSequence);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
     method public final void setTransitionName(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 3ecc8aa..bcf4897 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1468,6 +1468,7 @@
     field public static final int toYDelta = 16843209; // 0x10101c9
     field public static final int toYScale = 16843205; // 0x10101c5
     field public static final int toolbarStyle = 16843946; // 0x10104aa
+    field public static final int tooltip = 16844084; // 0x1010534
     field public static final int top = 16843182; // 0x10101ae
     field public static final int topBright = 16842955; // 0x10100cb
     field public static final int topDark = 16842951; // 0x10100c7
@@ -21476,6 +21477,7 @@
     field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
     field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
     field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
+    field public static final int STREAM_ACCESSIBILITY = 10; // 0xa
     field public static final int STREAM_ALARM = 4; // 0x4
     field public static final int STREAM_DTMF = 8; // 0x8
     field public static final int STREAM_MUSIC = 3; // 0x3
@@ -35380,6 +35382,7 @@
     field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS";
     field public static final java.lang.String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS";
+    field public static final java.lang.String ACTION_WIFI_SAVED_NETWORK_SETTINGS = "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS";
     field public static final java.lang.String ACTION_WIRELESS_SETTINGS = "android.settings.WIRELESS_SETTINGS";
     field public static final java.lang.String AUTHORITY = "settings";
@@ -45995,6 +45998,7 @@
     method public java.lang.Object getTag(int);
     method public int getTextAlignment();
     method public int getTextDirection();
+    method public final java.lang.CharSequence getTooltip();
     method public final int getTop();
     method protected float getTopFadingEdgeStrength();
     method protected int getTopPaddingOffset();
@@ -46283,6 +46287,7 @@
     method public void setTag(int, java.lang.Object);
     method public void setTextAlignment(int);
     method public void setTextDirection(int);
+    method public final void setTooltip(java.lang.CharSequence);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
     method public final void setTransitionName(java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 13b0c94..2dcb743 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1357,6 +1357,7 @@
     field public static final int toYDelta = 16843209; // 0x10101c9
     field public static final int toYScale = 16843205; // 0x10101c5
     field public static final int toolbarStyle = 16843946; // 0x10104aa
+    field public static final int tooltip = 16844084; // 0x1010534
     field public static final int top = 16843182; // 0x10101ae
     field public static final int topBright = 16842955; // 0x10100cb
     field public static final int topDark = 16842951; // 0x10100c7
@@ -19995,6 +19996,7 @@
     field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
     field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
     field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
+    field public static final int STREAM_ACCESSIBILITY = 10; // 0xa
     field public static final int STREAM_ALARM = 4; // 0x4
     field public static final int STREAM_DTMF = 8; // 0x8
     field public static final int STREAM_MUSIC = 3; // 0x3
@@ -37719,6 +37721,7 @@
     method public int getCallState();
     method public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
+    method public boolean getDataEnabled();
     method public int getDataNetworkType();
     method public int getDataState();
     method public java.lang.String getDeviceId();
@@ -37765,6 +37768,7 @@
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
+    method public void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
     method public boolean setPreferredNetworkTypeToGlobal();
@@ -37772,6 +37776,7 @@
     field public static final java.lang.String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL";
     field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
     field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
+    field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
     field public static final int APPTYPE_CSIM = 4; // 0x4
     field public static final int APPTYPE_ISIM = 5; // 0x5
     field public static final int APPTYPE_RUIM = 3; // 0x3
@@ -37791,11 +37796,15 @@
     field public static final int DATA_CONNECTING = 1; // 0x1
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
+    field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
     field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
+    field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
+    field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
     field public static final java.lang.String EXTRA_STATE = "state";
     field public static final java.lang.String EXTRA_STATE_IDLE;
     field public static final java.lang.String EXTRA_STATE_OFFHOOK;
     field public static final java.lang.String EXTRA_STATE_RINGING;
+    field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
     field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
     field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
     field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -43072,6 +43081,8 @@
     method public java.lang.Object getTag(int);
     method public int getTextAlignment();
     method public int getTextDirection();
+    method public final java.lang.CharSequence getTooltip();
+    method public android.view.View getTooltipView();
     method public final int getTop();
     method protected float getTopFadingEdgeStrength();
     method protected int getTopPaddingOffset();
@@ -43360,6 +43371,7 @@
     method public void setTag(int, java.lang.Object);
     method public void setTextAlignment(int);
     method public void setTextDirection(int);
+    method public final void setTooltip(java.lang.CharSequence);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
     method public final void setTransitionName(java.lang.String);
@@ -43642,10 +43654,14 @@
     method public static deprecated int getEdgeSlop();
     method public static deprecated int getFadingEdgeLength();
     method public static deprecated long getGlobalActionKeyTimeout();
+    method public static int getHoverTooltipHideShortTimeout();
+    method public static int getHoverTooltipHideTimeout();
+    method public static int getHoverTooltipShowTimeout();
     method public static int getJumpTapTimeout();
     method public static int getKeyRepeatDelay();
     method public static int getKeyRepeatTimeout();
     method public static int getLongPressTimeout();
+    method public static int getLongPressTooltipHideTimeout();
     method public static deprecated int getMaximumDrawingCacheSize();
     method public static deprecated int getMaximumFlingVelocity();
     method public static deprecated int getMinimumFlingVelocity();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1aa13a9..f052bf7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1309,8 +1309,19 @@
         }
 
         @Override
-        public final void updateTimePrefs(boolean is24Hour) {
-            DateFormat.set24HourTimePref(is24Hour);
+        public final void updateTimePrefs(int timeFormatPreference) {
+            final Boolean timeFormatPreferenceBool;
+            // For convenience we are using the Intent extra values.
+            if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR) {
+                timeFormatPreferenceBool = Boolean.FALSE;
+            } else if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR) {
+                timeFormatPreferenceBool = Boolean.TRUE;
+            } else {
+                // timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT
+                // (or unknown).
+                timeFormatPreferenceBool = null;
+            }
+            DateFormat.set24HourTimePref(timeFormatPreferenceBool);
         }
 
         @Override
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 191cc49..ba6bc15 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -240,7 +240,9 @@
     /** @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 = 64;
+    public static final int OP_AUDIO_ACCESSIBILITY_VOLUME = 64;
+    /** @hide */
+    public static final int _NUM_OP = 65;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -452,6 +454,7 @@
             OP_TURN_SCREEN_ON,
             OP_GET_ACCOUNTS,
             OP_RUN_IN_BACKGROUND,
+            OP_AUDIO_ACCESSIBILITY_VOLUME,
     };
 
     /**
@@ -523,6 +526,7 @@
             null,
             OPSTR_GET_ACCOUNTS,
             null,
+            null, // OP_AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
@@ -594,6 +598,7 @@
             "TURN_ON_SCREEN",
             "GET_ACCOUNTS",
             "RUN_IN_BACKGROUND",
+            "AUDIO_ACCESSIBILITY_VOLUME",
     };
 
     /**
@@ -665,6 +670,7 @@
             null, // no permission for turning the screen on
             Manifest.permission.GET_ACCOUNTS,
             null, // no permission for running in background
+            null, // no permission for changing accessibility volume
     };
 
     /**
@@ -737,6 +743,7 @@
             null, // TURN_ON_SCREEN
             null, // GET_ACCOUNTS
             null, // RUN_IN_BACKGROUND
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
@@ -808,6 +815,7 @@
             false, // TURN_ON_SCREEN
             false, // GET_ACCOUNTS
             false, // RUN_IN_BACKGROUND
+            false, // AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
@@ -878,6 +886,7 @@
             AppOpsManager.MODE_ALLOWED,  // OP_TURN_ON_SCREEN
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,  // OP_RUN_IN_BACKGROUND
+            AppOpsManager.MODE_ALLOWED,  // OP_AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
@@ -952,6 +961,7 @@
             false,
             false,
             false,
+            false, // OP_AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 496c05f..827e026 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -33,6 +33,7 @@
 import android.content.ReceiverCallNotAllowedException;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -159,7 +160,7 @@
     private final String mOpPackageName;
 
     private final @NonNull ResourcesManager mResourcesManager;
-    private @NonNull Resources mResources;
+    private final @NonNull Resources mResources;
     private @Nullable Display mDisplay; // may be null if default display
 
     private final int mFlags;
@@ -1834,19 +1835,6 @@
         }
     }
 
-    private static Resources createResources(IBinder activityToken, LoadedApk pi, int displayId,
-            Configuration overrideConfig, CompatibilityInfo compatInfo) {
-        return ResourcesManager.getInstance().getResources(activityToken,
-                pi.getResDir(),
-                pi.getSplitResDirs(),
-                pi.getOverlayDirs(),
-                pi.getApplicationInfo().sharedLibraryFiles,
-                displayId,
-                overrideConfig,
-                compatInfo,
-                pi.getClassLoader());
-    }
-
     @Override
     public Context createApplicationContext(ApplicationInfo application, int flags)
             throws NameNotFoundException {
@@ -1854,13 +1842,8 @@
                 flags | CONTEXT_REGISTER_PACKAGE);
         if (pi != null) {
             ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
-                    new UserHandle(UserHandle.getUserId(application.uid)), flags);
-
-            final int displayId = mDisplay != null
-                    ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
-
-            c.mResources = createResources(mActivityToken, pi, displayId, null,
-                    getDisplayAdjustments(displayId).getCompatibilityInfo());
+                    new UserHandle(UserHandle.getUserId(application.uid)), flags,
+                    mDisplay, null, Display.INVALID_DISPLAY);
             if (c.mResources != null) {
                 return c;
             }
@@ -1881,21 +1864,15 @@
     public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
             throws NameNotFoundException {
         if (packageName.equals("system") || packageName.equals("android")) {
-            // The system resources are loaded in every application, so we can safely copy
-            // the context without reloading Resources.
-            return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, user, flags);
+            return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+                    user, flags, mDisplay, null, Display.INVALID_DISPLAY);
         }
 
         LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
                 flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
         if (pi != null) {
-            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, user, flags);
-
-            final int displayId = mDisplay != null
-                    ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
-
-            c.mResources = createResources(mActivityToken, pi, displayId, null,
-                    getDisplayAdjustments(displayId).getCompatibilityInfo());
+            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
+                    user, flags, mDisplay, null, Display.INVALID_DISPLAY);
             if (c.mResources != null) {
                 return c;
             }
@@ -1912,14 +1889,8 @@
             throw new IllegalArgumentException("overrideConfiguration must not be null");
         }
 
-        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags);
-
-        final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
-        context.mResources = createResources(mActivityToken, mPackageInfo, displayId,
-                overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo());
-        context.mDisplay = mDisplay;
-        return context;
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+                mUser, mFlags, mDisplay, overrideConfiguration, Display.INVALID_DISPLAY);
     }
 
     @Override
@@ -1928,28 +1899,24 @@
             throw new IllegalArgumentException("display must not be null");
         }
 
-        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags);
-
-        final int displayId = display.getDisplayId();
-        context.mResources = createResources(mActivityToken, mPackageInfo, displayId, null,
-                getDisplayAdjustments(displayId).getCompatibilityInfo());
-        context.mDisplay = display;
-        return context;
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+                mUser, mFlags, display, null, Display.INVALID_DISPLAY);
     }
 
     @Override
     public Context createDeviceProtectedStorageContext() {
         final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
                 | Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
-        return new ContextImplFlagContextWrapper(this, flags);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+                mUser, flags, mDisplay, null, Display.INVALID_DISPLAY);
     }
 
     @Override
     public Context createCredentialProtectedStorageContext() {
         final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
                 | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
-        return new ContextImplFlagContextWrapper(this, flags);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
+                mUser, flags, mDisplay, null, Display.INVALID_DISPLAY);
     }
 
     @Override
@@ -2036,8 +2003,8 @@
 
     static ContextImpl createSystemContext(ActivityThread mainThread) {
         LoadedApk packageInfo = new LoadedApk(mainThread);
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
-        context.mResources = packageInfo.getResources(mainThread);
+        ContextImpl context = new ContextImpl(null, mainThread,
+                packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
         context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
                 context.mResourcesManager.getDisplayMetrics());
         return context;
@@ -2045,35 +2012,21 @@
 
     static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
         if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
-        context.mResources = packageInfo.getResources(mainThread);
-        return context;
+        return new ContextImpl(null, mainThread,
+                packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
     }
 
     static ContextImpl createActivityContext(ActivityThread mainThread,
             LoadedApk packageInfo, IBinder activityToken, int displayId,
             Configuration overrideConfiguration) {
         if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
-
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityToken, null,
-                0);
-
-        // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
-        displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
-
-        final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
-                ? packageInfo.getCompatibilityInfo()
-                : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
-
-        context.mResources = createResources(activityToken, packageInfo, displayId,
-                overrideConfiguration, compatInfo);
-        context.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId,
-                context.mResources.getDisplayAdjustments());
-        return context;
+        return new ContextImpl(null, mainThread, packageInfo, activityToken, null, 0,
+                null, overrideConfiguration, displayId);
     }
 
     private ContextImpl(ContextImpl container, ActivityThread mainThread,
-            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags) {
+            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
+            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
         mOuterContext = this;
 
         // If creator didn't specify which storage to use, use the default
@@ -2100,11 +2053,64 @@
         mPackageInfo = packageInfo;
         mResourcesManager = ResourcesManager.getInstance();
 
+        final int displayId = (createDisplayWithId != Display.INVALID_DISPLAY)
+                ? createDisplayWithId
+                : (display != null) ? display.getDisplayId() : Display.DEFAULT_DISPLAY;
+
+        CompatibilityInfo compatInfo = null;
+        if (container != null) {
+            compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo();
+        }
+        if (compatInfo == null) {
+            compatInfo = (displayId == Display.DEFAULT_DISPLAY)
+                    ? packageInfo.getCompatibilityInfo()
+                    : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+        }
+
+        Resources resources = packageInfo.getResources(mainThread);
+        if (resources != null) {
+            if (displayId != Display.DEFAULT_DISPLAY
+                    || overrideConfiguration != null
+                    || (compatInfo != null && compatInfo.applicationScale
+                            != resources.getCompatibilityInfo().applicationScale)) {
+
+                if (container != null) {
+                    // This is a nested Context, so it can't be a base Activity context.
+                    // Just create a regular Resources object associated with the Activity.
+                    resources = mResourcesManager.getResources(
+                            activityToken,
+                            packageInfo.getResDir(),
+                            packageInfo.getSplitResDirs(),
+                            packageInfo.getOverlayDirs(),
+                            packageInfo.getApplicationInfo().sharedLibraryFiles,
+                            displayId,
+                            overrideConfiguration,
+                            compatInfo,
+                            packageInfo.getClassLoader());
+                } else {
+                    // This is not a nested Context, so it must be the root Activity context.
+                    // All other nested Contexts will inherit the configuration set here.
+                    resources = mResourcesManager.createBaseActivityResources(
+                            activityToken,
+                            packageInfo.getResDir(),
+                            packageInfo.getSplitResDirs(),
+                            packageInfo.getOverlayDirs(),
+                            packageInfo.getApplicationInfo().sharedLibraryFiles,
+                            displayId,
+                            overrideConfiguration,
+                            compatInfo,
+                            packageInfo.getClassLoader());
+                }
+            }
+        }
+        mResources = resources;
+
+        mDisplay = (createDisplayWithId == Display.INVALID_DISPLAY) ? display
+                : mResourcesManager.getAdjustedDisplay(displayId, mResources.getDisplayAdjustments());
+
         if (container != null) {
             mBasePackageName = container.mBasePackageName;
             mOpPackageName = container.mOpPackageName;
-            mResources = container.getResources();
-            mDisplay = container.getDisplay();
         } else {
             mBasePackageName = packageInfo.mPackageName;
             ApplicationInfo ainfo = packageInfo.getApplicationInfo();
diff --git a/core/java/android/app/ContextImplFlagContextWrapper.java b/core/java/android/app/ContextImplFlagContextWrapper.java
deleted file mode 100644
index a83f130..0000000
--- a/core/java/android/app/ContextImplFlagContextWrapper.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.app;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-
-class ContextImplFlagContextWrapper extends ContextWrapper {
-    private final int mFlags;
-
-    public ContextImplFlagContextWrapper(Context base, int flags) {
-        super(base);
-        mFlags = flags;
-    }
-
-    @Override
-    public boolean isRestricted() {
-        return (mFlags & Context.CONTEXT_RESTRICTED) != 0;
-    }
-
-    @Override
-    public boolean isDeviceProtectedStorage() {
-        return (mFlags & Context.CONTEXT_DEVICE_PROTECTED_STORAGE) != 0;
-    }
-
-    @Override
-    public boolean isCredentialProtectedStorage() {
-        return (mFlags & Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) != 0;
-    }
-}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 8c9837b..6b962b9 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -138,7 +138,7 @@
     void scheduleTranslucentConversionComplete(IBinder token, boolean timeout);
     void setProcessState(int state);
     void scheduleInstallProvider(in ProviderInfo provider);
-    void updateTimePrefs(boolean is24Hour);
+    void updateTimePrefs(int timeFormatPreference);
     void scheduleCancelVisibleBehind(IBinder token);
     void scheduleBackgroundVisibleBehindChanged(IBinder token, boolean enabled);
     void scheduleEnterAnimationComplete(IBinder token);
@@ -152,4 +152,4 @@
             IVoiceInteractor voiceInteractor);
     void handleTrustStorageUpdate();
     void attachAgent(String path);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 5d27662..94d03e5 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -235,6 +235,7 @@
         // >=0: registered and advertising started
         private int mAdvertiserId;
         private boolean mIsAdvertising = false;
+        private int registrationError = AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR;
 
         public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
                 AdvertiseData advertiseData, AdvertiseData scanResponse,
@@ -262,12 +263,11 @@
                     mLeAdvertisers.put(mAdvertiseCallback, this);
                 } else if (mAdvertiserId < 0) {
 
-                    // Registration timeout, reset mClientIf to -1 so no subsequent operations can
+                    // Registration timeout, reset mClientIf to -2 so no subsequent operations can
                     // proceed.
-                    if (mAdvertiserId == 0) mAdvertiserId = -2;
+                    if (mAdvertiserId == -1) mAdvertiserId = -2;
                     // Post internal error if registration failed.
-                    postStartFailure(mAdvertiseCallback,
-                            AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
+                    postStartFailure(mAdvertiseCallback, registrationError);
                 } else {
                     // Unregister application if it's already registered but advertise failed.
                     try {
@@ -318,6 +318,8 @@
                     } catch (RemoteException e) {
                         Log.e(TAG, "failed to start advertising", e);
                     }
+                } else if (status == AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) {
+                    registrationError = status;
                 }
                 // Registration failed.
                 mAdvertiserId = -2;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c87de9a..50589fe 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4186,13 +4186,21 @@
             = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
 
     /**
-     * Optional boolean extra for {@link #ACTION_TIME_CHANGED} that indicates the
-     * user has set their time format preferences to the 24 hour format.
+     * Optional int extra for {@link #ACTION_TIME_CHANGED} that indicates the
+     * user has set their time format preference. See {@link #EXTRA_TIME_PREF_VALUE_USE_12_HOUR},
+     * {@link #EXTRA_TIME_PREF_VALUE_USE_24_HOUR} and
+     * {@link #EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT}. The value must not be negative.
      *
      * @hide for internal use only.
      */
     public static final String EXTRA_TIME_PREF_24_HOUR_FORMAT =
             "android.intent.extra.TIME_PREF_24_HOUR_FORMAT";
+    /** @hide */
+    public static final int EXTRA_TIME_PREF_VALUE_USE_12_HOUR = 0;
+    /** @hide */
+    public static final int EXTRA_TIME_PREF_VALUE_USE_24_HOUR = 1;
+    /** @hide */
+    public static final int EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT = 2;
 
     /** {@hide} */
     public static final String EXTRA_REASON = "android.intent.extra.REASON";
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 44ae6ee..760df45 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -316,6 +316,16 @@
         stringToFile(file.getAbsolutePath(), string);
     }
 
+    /*
+     * Writes the bytes given in {@code content} to the file whose absolute path
+     * is {@code filename}.
+     */
+    public static void bytesToFile(String filename, byte[] content) throws IOException {
+        try (FileOutputStream fos = new FileOutputStream(filename)) {
+            fos.write(content);
+        }
+    }
+
     /**
      * Writes string to file. Basically same as "echo -n $string > $filename"
      *
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index d31036c..6a751e8 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -77,6 +77,7 @@
     private static native boolean native_get_boolean(String key, boolean def);
     private static native void native_set(String key, String def);
     private static native void native_add_change_callback();
+    private static native void native_report_sysprop_change();
 
     /**
      * Get the value for the given key.
@@ -195,4 +196,11 @@
             }
         }
     }
+
+    /*
+     * Notifies listeners that a system property has changed
+     */
+    public static void reportSyspropChanged() {
+        native_report_sysprop_change();
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index afbc09b..0946906 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -369,6 +369,22 @@
             "android.settings.WIFI_IP_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of Wi-Fi saved networks.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_WIFI_SAVED_NETWORK_SETTINGS =
+            "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of Bluetooth.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/util/BootTimingsTraceLog.java b/core/java/android/util/BootTimingsTraceLog.java
new file mode 100644
index 0000000..2e4319c
--- /dev/null
+++ b/core/java/android/util/BootTimingsTraceLog.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 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 android.util;
+
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.Trace;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * Helper class for reporting boot timing metrics.
+ * @hide
+ */
+public class BootTimingsTraceLog {
+    // Debug boot time for every step if it's non-user build.
+    private static final boolean DEBUG_BOOT_TIME = !"user".equals(Build.TYPE);
+    private final Deque<Pair<String, Long>> mStartTimes
+            = DEBUG_BOOT_TIME ? new ArrayDeque<>() : null;
+    private final String mTag;
+    private long mTraceTag;
+
+    public BootTimingsTraceLog(String tag, long traceTag) {
+        mTag = tag;
+        mTraceTag = traceTag;
+    }
+
+    public void traceBegin(String name) {
+        Trace.traceBegin(mTraceTag, name);
+        if (DEBUG_BOOT_TIME) {
+            mStartTimes.push(Pair.create(name, SystemClock.elapsedRealtime()));
+        }
+    }
+
+    public void traceEnd() {
+        Trace.traceEnd(mTraceTag);
+        if (!DEBUG_BOOT_TIME) {
+            return;
+        }
+        if (mStartTimes.peek() == null) {
+            Slog.w(mTag, "traceEnd called more times than traceBegin");
+            return;
+        }
+        Pair<String, Long> event = mStartTimes.pop();
+        // Log the duration so it can be parsed by external tools for performance reporting
+        Slog.d(mTag, event.first + " took to complete: "
+                + (SystemClock.elapsedRealtime() - event.second) + "ms");
+    }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 02a8521..84d7548 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -37,6 +37,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
+import android.annotation.TestApi;
 import android.annotation.UiThread;
 import android.content.ClipData;
 import android.content.Context;
@@ -111,6 +112,7 @@
 
 import com.android.internal.R;
 import com.android.internal.util.Predicate;
+import com.android.internal.view.TooltipPopup;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.widget.ScrollBarUtils;
 
@@ -1196,6 +1198,12 @@
 
     private static Paint sDebugPaint;
 
+    /**
+     * <p>Indicates this view can display a tooltip on hover or long press.</p>
+     * {@hide}
+     */
+    static final int TOOLTIP = 0x40000000;
+
     /** @hide */
     @IntDef(flag = true,
             value = {
@@ -3619,6 +3627,39 @@
 
     ListenerInfo mListenerInfo;
 
+    private static class TooltipInfo {
+        /**
+         * Text to be displayed in a tooltip popup.
+         */
+        @Nullable
+        CharSequence mTooltip;
+
+        /**
+         * View-relative position of the tooltip anchor point.
+         */
+        int mAnchorX;
+        int mAnchorY;
+
+        /**
+         * The tooltip popup.
+         */
+        @Nullable
+        TooltipPopup mTooltipPopup;
+
+        /**
+         * Set to true if the tooltip was shown as a result of a long click.
+         */
+        boolean mTooltipFromLongClick;
+
+        /**
+         * Keep these Runnables so that they can be used to reschedule.
+         */
+        Runnable mShowTooltipRunnable;
+        Runnable mHideTooltipRunnable;
+    }
+
+    TooltipInfo mTooltipInfo;
+
     // Temporary values used to hold (x,y) coordinates when delegating from the
     // two-arg performLongClick() method to the legacy no-arg version.
     private float mLongClickX = Float.NaN;
@@ -4576,6 +4617,9 @@
                     }
                     break;
 
+                case R.styleable.View_tooltip:
+                    setTooltip(a.getText(attr));
+                    break;
             }
         }
 
@@ -5712,6 +5756,11 @@
             final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
             handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
         }
+        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+            if (!handled) {
+                handled = showLongClickTooltip((int) x, (int) y);
+            }
+        }
         if (handled) {
             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
         }
@@ -10603,17 +10652,21 @@
                 return true;
             }
 
-            // Long clickable items don't necessarily have to be clickable.
-            if (((mViewFlags & CLICKABLE) == CLICKABLE
-                    || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
-                    && (event.getRepeatCount() == 0)) {
-                // For the purposes of menu anchoring and drawable hotspots,
-                // key events are considered to be at the center of the view.
-                final float x = getWidth() / 2f;
-                final float y = getHeight() / 2f;
-                setPressed(true, x, y);
-                checkForLongClick(0, x, y);
-                return true;
+            if (event.getRepeatCount() == 0) {
+                // Long clickable items don't necessarily have to be clickable.
+                final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
+                        || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
+                if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
+                    // For the purposes of menu anchoring and drawable hotspots,
+                    // key events are considered to be at the center of the view.
+                    final float x = getWidth() / 2f;
+                    final float y = getHeight() / 2f;
+                    if (clickable) {
+                        setPressed(true, x, y);
+                    }
+                    checkForLongClick(0, x, y);
+                    return true;
+                }
             }
         }
 
@@ -11160,15 +11213,17 @@
         final int viewFlags = mViewFlags;
         final int action = event.getAction();
 
+        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
+                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
+                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
+
         if ((viewFlags & ENABLED_MASK) == DISABLED) {
             if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                 setPressed(false);
             }
             // A disabled view that is clickable still consumes the touch
             // events, it just doesn't respond to them.
-            return (((viewFlags & CLICKABLE) == CLICKABLE
-                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
-                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
+            return clickable;
         }
         if (mTouchDelegate != null) {
             if (mTouchDelegate.onTouchEvent(event)) {
@@ -11176,11 +11231,20 @@
             }
         }
 
-        if (((viewFlags & CLICKABLE) == CLICKABLE ||
-                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
-                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
+        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
             switch (action) {
                 case MotionEvent.ACTION_UP:
+                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
+                        handleTooltipUp();
+                    }
+                    if (!clickable) {
+                        removeTapCallback();
+                        removeLongPressCallback();
+                        mInContextButtonPress = false;
+                        mHasPerformedLongPress = false;
+                        mIgnoreNextUpEvent = false;
+                        break;
+                    }
                     boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                     if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                         // take focus if we don't have it already and we should in
@@ -11196,7 +11260,7 @@
                             // state now (before scheduling the click) to ensure
                             // the user sees it.
                             setPressed(true, x, y);
-                       }
+                        }
 
                         if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                             // This is a tap, so remove the longpress check
@@ -11236,6 +11300,11 @@
                 case MotionEvent.ACTION_DOWN:
                     mHasPerformedLongPress = false;
 
+                    if (!clickable) {
+                        checkForLongClick(0, x, y);
+                        break;
+                    }
+
                     if (performButtonActionOnTouchDown(event)) {
                         break;
                     }
@@ -11261,7 +11330,9 @@
                     break;
 
                 case MotionEvent.ACTION_CANCEL:
-                    setPressed(false);
+                    if (clickable) {
+                        setPressed(false);
+                    }
                     removeTapCallback();
                     removeLongPressCallback();
                     mInContextButtonPress = false;
@@ -11270,16 +11341,17 @@
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    drawableHotspotChanged(x, y);
+                    if (clickable) {
+                        drawableHotspotChanged(x, y);
+                    }
 
                     // Be lenient about moving outside of buttons
                     if (!pointInView(x, y, mTouchSlop)) {
                         // Outside button
+                        // Remove any future long press/tap checks
                         removeTapCallback();
+                        removeLongPressCallback();
                         if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
-                            // Remove any future long press/tap checks
-                            removeLongPressCallback();
-
                             setPressed(false);
                         }
                     }
@@ -11311,7 +11383,7 @@
      */
     private void removeLongPressCallback() {
         if (mPendingCheckForLongPress != null) {
-          removeCallbacks(mPendingCheckForLongPress);
+            removeCallbacks(mPendingCheckForLongPress);
         }
     }
 
@@ -15379,6 +15451,10 @@
 
         cleanupDraw();
         mCurrentAnimation = null;
+
+        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+            hideTooltip();
+        }
     }
 
     private void cleanupDraw() {
@@ -21031,7 +21107,7 @@
     }
 
     private void checkForLongClick(int delayOffset, float x, float y) {
-        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
+        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
             mHasPerformedLongPress = false;
 
             if (mPendingCheckForLongPress == null) {
@@ -21039,6 +21115,7 @@
             }
             mPendingCheckForLongPress.setAnchor(x, y);
             mPendingCheckForLongPress.rememberWindowAttachCount();
+            mPendingCheckForLongPress.rememberPressedState();
             postDelayed(mPendingCheckForLongPress,
                     ViewConfiguration.getLongPressTimeout() - delayOffset);
         }
@@ -22439,10 +22516,11 @@
         private int mOriginalWindowAttachCount;
         private float mX;
         private float mY;
+        private boolean mOriginalPressedState;
 
         @Override
         public void run() {
-            if (isPressed() && (mParent != null)
+            if ((mOriginalPressedState == isPressed()) && (mParent != null)
                     && mOriginalWindowAttachCount == mWindowAttachCount) {
                 if (performLongClick(mX, mY)) {
                     mHasPerformedLongPress = true;
@@ -22458,6 +22536,10 @@
         public void rememberWindowAttachCount() {
             mOriginalWindowAttachCount = mWindowAttachCount;
         }
+
+        public void rememberPressedState() {
+            mOriginalPressedState = isPressed();
+        }
     }
 
     private final class CheckForTap implements Runnable {
@@ -23246,6 +23328,12 @@
          */
         public Surface mDragSurface;
 
+
+        /**
+         * The view that currently has a tooltip displayed.
+         */
+        View mTooltipHost;
+
         /**
          * Creates a new set of attachment information with the specified
          * events handler and thread.
@@ -23982,4 +24070,167 @@
         return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft()
                 && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop();
     }
+
+    /**
+     * Sets the tooltip text which will be displayed in a small popup next to the view.
+     * <p>
+     * The tooltip will be displayed:
+     * <li>On long click, unless is not handled otherwise (by OnLongClickListener or a context
+     * menu). </li>
+     * <li>On hover, after a brief delay since the pointer has stopped moving </li>
+     *
+     * @param tooltip the tooltip text, or null if no tooltip is required
+     */
+    public final void setTooltip(@Nullable CharSequence tooltip) {
+        if (TextUtils.isEmpty(tooltip)) {
+            setFlags(0, TOOLTIP);
+            hideTooltip();
+            mTooltipInfo = null;
+        } else {
+            setFlags(TOOLTIP, TOOLTIP);
+            if (mTooltipInfo == null) {
+                mTooltipInfo = new TooltipInfo();
+                mTooltipInfo.mShowTooltipRunnable = this::showHoverTooltip;
+                mTooltipInfo.mHideTooltipRunnable = this::hideTooltip;
+            }
+            mTooltipInfo.mTooltip = tooltip;
+            if (mTooltipInfo.mTooltipPopup != null && mTooltipInfo.mTooltipPopup.isShowing()) {
+                mTooltipInfo.mTooltipPopup.updateContent(mTooltipInfo.mTooltip);
+            }
+        }
+    }
+
+    /**
+     * Returns the view's tooltip text.
+     *
+     * @return the tooltip text
+     */
+    @Nullable
+    public final CharSequence getTooltip() {
+        return mTooltipInfo != null ? mTooltipInfo.mTooltip : null;
+    }
+
+    private boolean showTooltip(int x, int y, boolean fromLongClick) {
+        if (mAttachInfo == null) {
+            return false;
+        }
+        if ((mViewFlags & ENABLED_MASK) != ENABLED) {
+            return false;
+        }
+        final CharSequence tooltipText = getTooltip();
+        if (TextUtils.isEmpty(tooltipText)) {
+            return false;
+        }
+        hideTooltip();
+        mTooltipInfo.mTooltipFromLongClick = fromLongClick;
+        mTooltipInfo.mTooltipPopup = new TooltipPopup(getContext());
+        mTooltipInfo.mTooltipPopup.show(this, x, y, tooltipText);
+        mAttachInfo.mTooltipHost = this;
+        return true;
+    }
+
+    void hideTooltip() {
+        if (mTooltipInfo == null) {
+            return;
+        }
+        removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+        if (mTooltipInfo.mTooltipPopup == null) {
+            return;
+        }
+        mTooltipInfo.mTooltipPopup.hide();
+        mTooltipInfo.mTooltipPopup = null;
+        mTooltipInfo.mTooltipFromLongClick = false;
+        if (mAttachInfo != null) {
+            mAttachInfo.mTooltipHost = null;
+        }
+    }
+
+    private boolean showLongClickTooltip(int x, int y) {
+        removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+        removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+        return showTooltip(x, y, true);
+    }
+
+    private void showHoverTooltip() {
+        showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false);
+    }
+
+    boolean dispatchTooltipHoverEvent(MotionEvent event) {
+        if (mTooltipInfo == null) {
+            return false;
+        }
+        switch(event.getAction()) {
+            case MotionEvent.ACTION_HOVER_MOVE:
+                if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) {
+                    break;
+                }
+                if (!mTooltipInfo.mTooltipFromLongClick) {
+                    if (mTooltipInfo.mTooltipPopup == null) {
+                        // Schedule showing the tooltip after a timeout.
+                        mTooltipInfo.mAnchorX = (int) event.getX();
+                        mTooltipInfo.mAnchorY = (int) event.getY();
+                        removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+                        postDelayed(mTooltipInfo.mShowTooltipRunnable,
+                                ViewConfiguration.getHoverTooltipShowTimeout());
+                    }
+
+                    // Hide hover-triggered tooltip after a period of inactivity.
+                    // Match the timeout used by NativeInputManager to hide the mouse pointer
+                    // (depends on SYSTEM_UI_FLAG_LOW_PROFILE being set).
+                    final int timeout;
+                    if ((getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LOW_PROFILE)
+                            == SYSTEM_UI_FLAG_LOW_PROFILE) {
+                        timeout = ViewConfiguration.getHoverTooltipHideShortTimeout();
+                    } else {
+                        timeout = ViewConfiguration.getHoverTooltipHideTimeout();
+                    }
+                    removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+                    postDelayed(mTooltipInfo.mHideTooltipRunnable, timeout);
+                }
+                return true;
+
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (!mTooltipInfo.mTooltipFromLongClick) {
+                    hideTooltip();
+                }
+                break;
+        }
+        return false;
+    }
+
+    void handleTooltipKey(KeyEvent event) {
+        switch (event.getAction()) {
+            case KeyEvent.ACTION_DOWN:
+                if (event.getRepeatCount() == 0) {
+                    hideTooltip();
+                }
+                break;
+
+            case KeyEvent.ACTION_UP:
+                handleTooltipUp();
+                break;
+        }
+    }
+
+    private void handleTooltipUp() {
+        if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+            return;
+        }
+        removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+        postDelayed(mTooltipInfo.mHideTooltipRunnable,
+                ViewConfiguration.getLongPressTooltipHideTimeout());
+    }
+
+    /**
+     * @return The content view of the tooltip popup currently being shown, or null if the tooltip
+     * is not showing.
+     * @hide
+     */
+    @TestApi
+    public View getTooltipView() {
+        if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+            return null;
+        }
+        return mTooltipInfo.mTooltipPopup.getContentView();
+    }
 }
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 33b488f..6d2f850 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.TestApi;
 import android.app.AppGlobals;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -230,6 +231,29 @@
     private static final long ACTION_MODE_HIDE_DURATION_DEFAULT = 2000;
 
     /**
+     * Defines the duration in milliseconds before an end of a long press causes a tooltip to be
+     * hidden.
+     */
+    private static final int LONG_PRESS_TOOLTIP_HIDE_TIMEOUT = 1500;
+
+    /**
+     * Defines the duration in milliseconds before a hover event causes a tooltip to be shown.
+     */
+    private static final int HOVER_TOOLTIP_SHOW_TIMEOUT = 500;
+
+    /**
+     * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden.
+     * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+     */
+    private static final int HOVER_TOOLTIP_HIDE_TIMEOUT = 15000;
+
+    /**
+     * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+     * (short version to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+     */
+    private static final int HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT = 3000;
+
+    /**
      * Configuration values for overriding {@link #hasPermanentMenuKey()} behavior.
      * These constants must match the definition in res/values/config.xml.
      */
@@ -800,4 +824,43 @@
     public boolean isFadingMarqueeEnabled() {
         return mFadingMarqueeEnabled;
     }
+
+    /**
+     * @return the duration in milliseconds before an end of a long press causes a tooltip to be
+     * hidden
+     * @hide
+     */
+    @TestApi
+    public static int getLongPressTooltipHideTimeout() {
+        return LONG_PRESS_TOOLTIP_HIDE_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds before a hover event causes a tooltip to be shown
+     * @hide
+     */
+    @TestApi
+    public static int getHoverTooltipShowTimeout() {
+        return HOVER_TOOLTIP_SHOW_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+     * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+     * @hide
+     */
+    @TestApi
+    public static int getHoverTooltipHideTimeout() {
+        return HOVER_TOOLTIP_HIDE_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+     * (shorter variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+     * @hide
+     */
+    @TestApi
+    public static int getHoverTooltipHideShortTimeout() {
+        return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;
+    }
 }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e39cb96..c0191ce 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -199,6 +199,13 @@
     // It might not have actually handled the hover event.
     private boolean mHoveredSelf;
 
+    // The child capable of showing a tooltip and currently under the pointer.
+    private View mTooltipHoverTarget;
+
+    // True if the view group is capable of showing a tooltip and the pointer is directly
+    // over the view group but not one of its child views.
+    private boolean mTooltipHoveredSelf;
+
     /**
      * Internal flags.
      *
@@ -1970,6 +1977,104 @@
         }
     }
 
+    @Override
+    boolean dispatchTooltipHoverEvent(MotionEvent event) {
+        final int action = event.getAction();
+        switch (action) {
+            case MotionEvent.ACTION_HOVER_ENTER:
+                break;
+
+            case MotionEvent.ACTION_HOVER_MOVE:
+                View newTarget = null;
+
+                // Check what the child under the pointer says about the tooltip.
+                final int childrenCount = mChildrenCount;
+                if (childrenCount != 0) {
+                    final float x = event.getX();
+                    final float y = event.getY();
+
+                    final ArrayList<View> preorderedList = buildOrderedChildList();
+                    final boolean customOrder = preorderedList == null
+                            && isChildrenDrawingOrderEnabled();
+                    final View[] children = mChildren;
+                    for (int i = childrenCount - 1; i >= 0; i--) {
+                        final int childIndex =
+                                getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+                        final View child =
+                                getAndVerifyPreorderedView(preorderedList, children, childIndex);
+                        final PointF point = getLocalPoint();
+                        if (isTransformedTouchPointInView(x, y, child, point)) {
+                            if (dispatchTooltipHoverEvent(event, child)) {
+                                newTarget = child;
+                            }
+                            break;
+                        }
+                    }
+                    if (preorderedList != null) preorderedList.clear();
+                }
+
+                if (mTooltipHoverTarget != newTarget) {
+                    if (mTooltipHoverTarget != null) {
+                        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                        mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+                        event.setAction(action);
+                    }
+                    mTooltipHoverTarget = newTarget;
+                }
+
+                if (mTooltipHoverTarget != null) {
+                    if (mTooltipHoveredSelf) {
+                        mTooltipHoveredSelf = false;
+                        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                        super.dispatchTooltipHoverEvent(event);
+                        event.setAction(action);
+                    }
+                    return true;
+                }
+
+                mTooltipHoveredSelf = super.dispatchTooltipHoverEvent(event);
+                return mTooltipHoveredSelf;
+
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (mTooltipHoverTarget != null) {
+                    mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+                    mTooltipHoverTarget = null;
+                } else if (mTooltipHoveredSelf) {
+                    super.dispatchTooltipHoverEvent(event);
+                    mTooltipHoveredSelf = false;
+                }
+                break;
+        }
+        return false;
+    }
+
+    private boolean dispatchTooltipHoverEvent(MotionEvent event, View child) {
+        final boolean result;
+        if (!child.hasIdentityMatrix()) {
+            MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
+            result = child.dispatchTooltipHoverEvent(transformedEvent);
+            transformedEvent.recycle();
+        } else {
+            final float offsetX = mScrollX - child.mLeft;
+            final float offsetY = mScrollY - child.mTop;
+            event.offsetLocation(offsetX, offsetY);
+            result = child.dispatchTooltipHoverEvent(event);
+            event.offsetLocation(-offsetX, -offsetY);
+        }
+        return result;
+    }
+
+    private void exitTooltipHoverTargets() {
+        if (mTooltipHoveredSelf || mTooltipHoverTarget != null) {
+            final long now = SystemClock.uptimeMillis();
+            MotionEvent event = MotionEvent.obtain(now, now,
+                    MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
+            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            dispatchTooltipHoverEvent(event);
+            event.recycle();
+        }
+    }
+
     /** @hide */
     @Override
     protected boolean hasHoveredChild() {
@@ -3186,6 +3291,7 @@
 
         // Similarly, set ACTION_EXIT to all hover targets and clear them.
         exitHoverTargets();
+        exitTooltipHoverTargets();
 
         // In case view is detached while transition is running
         mLayoutCalledWhileSuppressed = false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1ff8fb0..e030e76 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3589,6 +3589,10 @@
                         mAttachInfo.mKeyDispatchState.reset();
                         mView.dispatchWindowFocusChanged(hasWindowFocus);
                         mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+
+                        if (mAttachInfo.mTooltipHost != null) {
+                            mAttachInfo.mTooltipHost.hideTooltip();
+                        }
                     }
 
                     // Note: must be done after the focus change callbacks,
@@ -4206,6 +4210,10 @@
         private int processKeyEvent(QueuedInputEvent q) {
             final KeyEvent event = (KeyEvent)q.mEvent;
 
+            if (mAttachInfo.mTooltipHost != null) {
+                mAttachInfo.mTooltipHost.handleTooltipKey(event);
+            }
+
             // If the key's purpose is to exit touch mode then we consume it
             // and consider it handled.
             if (checkForLeavingTouchModeAndConsume(event)) {
@@ -4232,6 +4240,10 @@
                 ensureTouchMode(true);
             }
 
+            if (action == MotionEvent.ACTION_DOWN && mAttachInfo.mTooltipHost != null) {
+                mAttachInfo.mTooltipHost.hideTooltip();
+            }
+
             // Offset the scroll position.
             if (mCurScrollY != 0) {
                 event.offsetLocation(0, mCurScrollY);
@@ -4425,6 +4437,7 @@
             mAttachInfo.mHandlingPointerEvent = true;
             boolean handled = eventTarget.dispatchPointerEvent(event);
             maybeUpdatePointerIcon(event);
+            maybeUpdateTooltip(event);
             mAttachInfo.mHandlingPointerEvent = false;
             if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                 mUnbufferedInputDispatch = true;
@@ -4512,6 +4525,27 @@
         return true;
     }
 
+    private void maybeUpdateTooltip(MotionEvent event) {
+        if (event.getPointerCount() != 1) {
+            return;
+        }
+        final int action = event.getActionMasked();
+        if (action != MotionEvent.ACTION_HOVER_ENTER
+                && action != MotionEvent.ACTION_HOVER_MOVE
+                && action != MotionEvent.ACTION_HOVER_EXIT) {
+            return;
+        }
+        AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+        if (manager.isEnabled() && manager.isTouchExplorationEnabled()) {
+            return;
+        }
+        if (mView == null) {
+            Slog.d(mTag, "maybeUpdateTooltip called after view was removed");
+            return;
+        }
+        mView.dispatchTooltipHoverEvent(event);
+    }
+
     /**
      * Performs synthesis of new input events from unhandled input events.
      */
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 594b6ab..c03bcdf 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -17,6 +17,8 @@
 package com.android.internal.os;
 
 import android.os.Process;
+import android.os.Trace;
+import android.util.BootTimingsTraceLog;
 import android.util.Slog;
 
 import dalvik.system.VMRuntime;
@@ -75,7 +77,8 @@
             }
 
             // Mimic system Zygote preloading.
-            ZygoteInit.preload();
+            ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
+                    Trace.TRACE_TAG_DALVIK));
 
             // Launch the application.
             String[] runtimeArgs = new String[args.length - 2];
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e1118d8..cdd267e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -36,11 +36,13 @@
 import android.system.Os;
 import android.system.OsConstants;
 import android.text.Hyphenator;
+import android.util.BootTimingsTraceLog;
 import android.util.EventLog;
 import android.util.Log;
 import android.webkit.WebViewFactory;
 import android.widget.TextView;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.InstallerConnection.InstallerException;
 
 import dalvik.system.DexFile;
@@ -106,20 +108,20 @@
     private static final int ROOT_UID = 0;
     private static final int ROOT_GID = 0;
 
-    static void preload() {
+    static void preload(BootTimingsTraceLog bootTimingsTraceLog) {
         Log.d(TAG, "begin preload");
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "BeginIcuCachePinning");
+        bootTimingsTraceLog.traceBegin("BeginIcuCachePinning");
         beginIcuCachePinning();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadClasses");
+        bootTimingsTraceLog.traceEnd(); // BeginIcuCachePinning
+        bootTimingsTraceLog.traceBegin("PreloadClasses");
         preloadClasses();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources");
+        bootTimingsTraceLog.traceEnd(); // PreloadClasses
+        bootTimingsTraceLog.traceBegin("PreloadResources");
         preloadResources();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
+        bootTimingsTraceLog.traceEnd(); // PreloadResources
+        bootTimingsTraceLog.traceBegin("PreloadOpenGL");
         preloadOpenGL();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+        bootTimingsTraceLog.traceEnd(); // PreloadOpenGL
         preloadSharedLibraries();
         preloadTextResources();
         // Ask the WebViewFactory to do any initialization that must run in the zygote process,
@@ -639,7 +641,13 @@
         }
 
         try {
-            Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygoteInit");
+            // Report Zygote start time to tron
+            MetricsLogger.histogram(null, "boot_zygote_init", (int) SystemClock.uptimeMillis());
+
+            String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";
+            BootTimingsTraceLog bootTimingsTraceLog = new BootTimingsTraceLog(bootTimeTag,
+                    Trace.TRACE_TAG_DALVIK);
+            bootTimingsTraceLog.traceBegin("ZygoteInit");
             RuntimeInit.enableDdms();
             // Start profiling the zygote initialization.
             SamplingProfilerIntegration.start();
@@ -664,22 +672,23 @@
             }
 
             zygoteServer.registerServerSocket(socketName);
-            Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygotePreload");
+            bootTimingsTraceLog.traceBegin("ZygotePreload");
             EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                 SystemClock.uptimeMillis());
-            preload();
+            preload(bootTimingsTraceLog);
             EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                 SystemClock.uptimeMillis());
-            Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+            bootTimingsTraceLog.traceEnd(); // ZygotePreload
 
             // Finish profiling the zygote initialization.
             SamplingProfilerIntegration.writeZygoteSnapshot();
 
             // Do an initial gc to clean up after startup
-            Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PostZygoteInitGC");
+            bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
             gcAndFinalize();
-            Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+            bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC
 
+            bootTimingsTraceLog.traceEnd(); // ZygoteInit
             // Disable tracing so that forked processes do not inherit stale tracing tags from
             // Zygote.
             Trace.setTracingEnabled(false);
diff --git a/core/java/com/android/internal/view/TooltipPopup.java b/core/java/com/android/internal/view/TooltipPopup.java
new file mode 100644
index 0000000..4f48b96
--- /dev/null
+++ b/core/java/com/android/internal/view/TooltipPopup.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 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.view;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+public class TooltipPopup {
+    private final Context mContext;
+
+    private final View mContentView;
+    private final TextView mMessageView;
+
+    private final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
+    private final Rect mTmpDisplayFrame = new Rect();
+    private final int[] mTmpAnchorPos = new int[2];
+
+    public TooltipPopup(Context context) {
+        mContext = context;
+
+        mContentView = LayoutInflater.from(mContext).inflate(
+                com.android.internal.R.layout.tooltip, null);
+        mMessageView = (TextView) mContentView.findViewById(
+                com.android.internal.R.id.message);
+
+        mLayoutParams.setTitle(
+                mContext.getString(com.android.internal.R.string.tooltip_popup_title));
+        mLayoutParams.packageName = mContext.getOpPackageName();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+        mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+        mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        mLayoutParams.format = PixelFormat.TRANSLUCENT;
+        mLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Tooltip;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+    }
+
+    public void show(View anchorView, int anchorX, int anchorY, CharSequence tooltipText) {
+        if (isShowing()) {
+            hide();
+        }
+
+        mMessageView.setText(tooltipText);
+
+        computePosition(anchorView, anchorX, anchorY, mLayoutParams);
+
+        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        wm.addView(mContentView, mLayoutParams);
+    }
+
+    public void hide() {
+        if (!isShowing()) {
+            return;
+        }
+
+        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        wm.removeView(mContentView);
+    }
+
+    public View getContentView() {
+        return mContentView;
+    }
+
+    public boolean isShowing() {
+        return mContentView.getParent() != null;
+    }
+
+    public void updateContent(CharSequence tooltipText) {
+        mMessageView.setText(tooltipText);
+    }
+
+    private void computePosition(View anchorView, int anchorX, int anchorY,
+            WindowManager.LayoutParams outParams) {
+        final int tooltipPreciseAnchorThreshold = mContext.getResources().getDimensionPixelOffset(
+                com.android.internal.R.dimen.tooltip_precise_anchor_threshold);
+
+        final int offsetX;
+        if (anchorView.getWidth() >= tooltipPreciseAnchorThreshold) {
+            // Wide view. Align the tooltip horizontally to the precise X position.
+            offsetX = anchorX;
+        } else {
+            // Otherwise anchor the tooltip to the view center.
+            offsetX = anchorView.getWidth() / 2;  // Center on the view horizontally.
+        }
+
+        final int offsetBelow;
+        final int offsetAbove;
+        if (anchorView.getHeight() >= tooltipPreciseAnchorThreshold) {
+            // Tall view. Align the tooltip vertically to the precise Y position.
+            offsetBelow = anchorY;
+            offsetAbove = anchorY;
+        } else {
+            // Otherwise anchor the tooltip to the view center.
+            offsetBelow = anchorView.getHeight();  // Place below the view in most cases.
+            offsetAbove = 0;  // Place above the view if the tooltip does not fit below.
+        }
+
+        outParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
+
+        final int tooltipOffset = mContext.getResources().getDimensionPixelOffset(
+                com.android.internal.R.dimen.tooltip_y_offset);
+
+        anchorView.getWindowVisibleDisplayFrame(mTmpDisplayFrame);
+        anchorView.getLocationInWindow(mTmpAnchorPos);
+        outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+        outParams.y = mTmpAnchorPos[1] + offsetBelow + tooltipOffset;
+
+        final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        mContentView.measure(spec, spec);
+        final int tooltipHeight = mContentView.getMeasuredHeight();
+
+        if (outParams.y + tooltipHeight > mTmpDisplayFrame.height()) {
+            // The tooltip does not fit below the anchor point, show above instead.
+            outParams.y = mTmpAnchorPos[1] + offsetAbove - (tooltipOffset + tooltipHeight);
+        }
+    }
+}
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
index 4b279f63..b23757e 100644
--- a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -22,256 +22,89 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 
-#include "activity_recognition.h"
-
-
-// keep base connection data from the HAL
-static activity_recognition_module_t* sModule = NULL;
-static activity_recognition_device_t* sDevice = NULL;
-
-static jobject sCallbacksObject = NULL;
-static jmethodID sOnActivityChanged = NULL;
-
-
-static void check_and_clear_exceptions(JNIEnv* env, const char* method_name) {
-    if (!env->ExceptionCheck()) {
-        return;
-    }
-
-    ALOGE("An exception was thrown by '%s'.", method_name);
-    LOGE_EX(env);
-    env->ExceptionClear();
-}
-
-static jint attach_thread(JNIEnv** env) {
-    JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
-    assert(java_vm != NULL);
-
-    JavaVMAttachArgs args = {
-        JNI_VERSION_1_6,
-        "ActivityRecognition HAL callback.",
-        NULL /* group */
-    };
-
-    jint result = java_vm->AttachCurrentThread(env, &args);
-    if (result != JNI_OK) {
-        ALOGE("Attach to callback thread failed: %d", result);
-    }
-
-    return result;
-}
-
-static jint detach_thread() {
-    JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
-    assert(java_vm != NULL);
-
-    jint result = java_vm->DetachCurrentThread();
-    if (result != JNI_OK) {
-        ALOGE("Detach of callback thread failed: %d", result);
-    }
-
-    return result;
-}
-
-
-/**
- * Handle activity recognition events from HAL.
- */
-static void activity_callback(
-        const activity_recognition_callback_procs_t* procs,
-        const activity_event_t* events,
-        int count) {
-    if (sOnActivityChanged == NULL) {
-        ALOGE("Dropping activity_callback because onActivityChanged handler is null.");
-        return;
-    }
-
-    if (events == NULL || count <= 0) {
-        ALOGE("Invalid activity_callback. Count: %d, Events: %p", count, events);
-        return;
-    }
-
-    JNIEnv* env = NULL;
-    int result = attach_thread(&env);
-    if (result != JNI_OK) {
-        ALOGE("Unable to attach thread with JNI.");
-        return;
-    }
-
-    jclass event_class =
-            env->FindClass("android/hardware/location/ActivityRecognitionHardware$Event");
-    jmethodID event_ctor = env->GetMethodID(event_class, "<init>", "()V");
-    jfieldID activity_field = env->GetFieldID(event_class, "activity", "I");
-    jfieldID type_field = env->GetFieldID(event_class, "type", "I");
-    jfieldID timestamp_field = env->GetFieldID(event_class, "timestamp", "J");
-
-    jobjectArray events_array = env->NewObjectArray(count, event_class, NULL);
-    for (int i = 0; i < count; ++i) {
-        const activity_event_t* event = &events[i];
-        jobject event_object = env->NewObject(event_class, event_ctor);
-        env->SetIntField(event_object, activity_field, event->activity);
-        env->SetIntField(event_object, type_field, event->event_type);
-        env->SetLongField(event_object, timestamp_field, event->timestamp);
-        env->SetObjectArrayElement(events_array, i, event_object);
-        env->DeleteLocalRef(event_object);
-    }
-
-    env->CallVoidMethod(sCallbacksObject, sOnActivityChanged, events_array);
-    check_and_clear_exceptions(env, __FUNCTION__);
-
-    // TODO: ideally we'd let the HAL register the callback thread only once
-    detach_thread();
-}
-
-activity_recognition_callback_procs_t sCallbacks = {
-    activity_callback,
-};
+// #include "activity_recognition.h"
+// The activity recognition HAL is being deprecated. This means -
+//    i) Android framework code shall not depend on activity recognition
+//       being provided through the activity_recognition.h interface.
+//   ii) activity recognition HAL will not be binderized as the other HALs.
+//
 
 /**
  * Initializes the ActivityRecognitionHardware class from the native side.
  */
-static void class_init(JNIEnv* env, jclass clazz) {
-    // open the hardware module
-    int error = hw_get_module(
-            ACTIVITY_RECOGNITION_HARDWARE_MODULE_ID,
-            (const hw_module_t**) &sModule);
-    if (error != 0) {
-        ALOGE("Error hw_get_module: %d", error);
-        return;
-    }
-
-    error = activity_recognition_open(&sModule->common, &sDevice);
-    if (error != 0) {
-        ALOGE("Error opening device: %d", error);
-        return;
-    }
-
-    // get references to the Java provided methods
-    sOnActivityChanged = env->GetMethodID(
-            clazz,
-            "onActivityChanged",
-            "([Landroid/hardware/location/ActivityRecognitionHardware$Event;)V");
-    if (sOnActivityChanged == NULL) {
-        ALOGE("Error obtaining ActivityChanged callback.");
-        return;
-    }
-
-    // register callbacks
-    sDevice->register_activity_callback(sDevice, &sCallbacks);
+static void class_init(JNIEnv* /*env*/, jclass /*clazz*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
 }
 
 /**
  * Initializes and connect the callbacks handlers in the HAL.
  */
-static void initialize(JNIEnv* env, jobject obj) {
-    if (sCallbacksObject == NULL) {
-        sCallbacksObject = env->NewGlobalRef(obj);
-    } else {
-        ALOGD("Callbacks Object was already initialized.");
-    }
-
-    if (sDevice != NULL) {
-        sDevice->register_activity_callback(sDevice, &sCallbacks);
-    } else {
-        ALOGD("ActivityRecognition device not found during initialization.");
-    }
+static void initialize(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
 }
 
 /**
  * De-initializes the ActivityRecognitionHardware from the native side.
  */
-static void release(JNIEnv* env, jobject obj) {
-    if (sDevice == NULL) {
-        return;
-    }
-
-    int error = activity_recognition_close(sDevice);
-    if (error != 0) {
-        ALOGE("Error closing device: %d", error);
-        return;
-    }
+static void release(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
 }
 
 /**
  * Returns true if ActivityRecognition HAL is supported, false otherwise.
  */
-static jboolean is_supported(JNIEnv* env, jclass clazz) {
-    if (sModule != NULL && sDevice != NULL ) {
-        return JNI_TRUE;
-    }
+static jboolean is_supported(JNIEnv* /*env*/, jclass /*clazz*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
     return JNI_FALSE;
 }
 
 /**
  * Gets an array representing the supported activities.
  */
-static jobjectArray get_supported_activities(JNIEnv* env, jobject obj) {
-    if (sModule == NULL) {
-        return NULL;
-    }
-
-    char const* const* list = NULL;
-    int list_size = sModule->get_supported_activities_list(sModule, &list);
-    if (list_size <= 0 || list == NULL) {
-        return NULL;
-    }
-
-    jclass string_class = env->FindClass("java/lang/String");
-    if (string_class == NULL) {
-        ALOGE("Unable to find String class for supported activities.");
-        return NULL;
-    }
-
-    jobjectArray string_array = env->NewObjectArray(list_size, string_class, NULL);
-    if (string_array == NULL) {
-        ALOGE("Unable to create string array for supported activities.");
-        return NULL;
-    }
-
-    for (int i = 0; i < list_size; ++i) {
-        const char* string_ptr = const_cast<const char*>(list[i]);
-        jstring string = env->NewStringUTF(string_ptr);
-        env->SetObjectArrayElement(string_array, i, string);
-    }
-
-    return string_array;
+static jobjectArray get_supported_activities(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return NULL;
 }
 
 /**
  * Enables a given activity event to be actively monitored.
  */
 static int enable_activity_event(
-        JNIEnv* env,
-        jobject obj,
-        jint activity_handle,
-        jint event_type,
-        jlong report_latency_ns) {
-    return sDevice->enable_activity_event(
-            sDevice,
-            (uint32_t) activity_handle,
-            (uint32_t) event_type,
-            report_latency_ns);
+        JNIEnv* /*env*/,
+        jobject /*obj*/,
+        jint /*activity_handle*/,
+        jint /*event_type*/,
+        jlong /*report_latency_ns*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return android::NO_INIT;
 }
 
 /**
  * Disables a given activity event from being actively monitored.
  */
 static int disable_activity_event(
-        JNIEnv* env,
-        jobject obj,
-        jint activity_handle,
-        jint event_type) {
-    return sDevice->disable_activity_event(
-            sDevice,
-            (uint32_t) activity_handle,
-            (uint32_t) event_type);
+        JNIEnv* /*env*/,
+        jobject /*obj*/,
+        jint /*activity_handle*/,
+        jint /*event_type*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return android::NO_INIT;
 }
 
 /**
  * Request flush for al batch buffers.
  */
-static int flush(JNIEnv* env, jobject obj) {
-    return sDevice->flush(sDevice);
+static int flush(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return android::NO_INIT;
 }
 
 
diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp
index 5dace6b..8844fb0 100644
--- a/core/jni/android_os_SystemProperties.cpp
+++ b/core/jni/android_os_SystemProperties.cpp
@@ -220,6 +220,11 @@
     }
 }
 
+static void SystemProperties_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/)
+{
+    report_sysprop_change();
+}
+
 static const JNINativeMethod method_table[] = {
     { "native_get", "(Ljava/lang/String;)Ljava/lang/String;",
       (void*) SystemProperties_getS },
@@ -235,6 +240,8 @@
       (void*) SystemProperties_set },
     { "native_add_change_callback", "()V",
       (void*) SystemProperties_add_change_callback },
+    { "native_report_sysprop_change", "()V",
+      (void*) SystemProperties_report_sysprop_change },
 };
 
 int register_android_os_SystemProperties(JNIEnv *env)
diff --git a/core/res/res/anim/tooltip_enter.xml b/core/res/res/anim/tooltip_enter.xml
new file mode 100644
index 0000000..7eceb4c
--- /dev/null
+++ b/core/res/res/anim/tooltip_enter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@interpolator/decelerate_quad"
+        android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_tooltipAnimTime" />
diff --git a/core/res/res/anim/tooltip_exit.xml b/core/res/res/anim/tooltip_exit.xml
new file mode 100644
index 0000000..e346ca9
--- /dev/null
+++ b/core/res/res/anim/tooltip_exit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@interpolator/accelerate_quad"
+        android:fromAlpha="1.0" android:toAlpha="0.0"
+        android:duration="@android:integer/config_tooltipAnimTime" />
diff --git a/core/res/res/drawable/tooltip_frame.xml b/core/res/res/drawable/tooltip_frame.xml
new file mode 100644
index 0000000..14130c8
--- /dev/null
+++ b/core/res/res/drawable/tooltip_frame.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="?attr/tooltipBackgroundColor" />
+    <corners android:radius="@dimen/tooltip_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout/tooltip.xml b/core/res/res/layout/tooltip.xml
new file mode 100644
index 0000000..0aa6a87
--- /dev/null
+++ b/core/res/res/layout/tooltip.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@android:id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/tooltip_height"
+        android:layout_margin="@dimen/tooltip_margin"
+        android:paddingStart="@dimen/tooltip_horizontal_padding"
+        android:paddingEnd="@dimen/tooltip_horizontal_padding"
+        android:gravity="center"
+        android:background="?android:attr/tooltipFrameBackground"
+        android:textAppearance="@style/TextAppearance.Tooltip"
+        android:textColor="?android:attr/tooltipForegroundColor"
+        android:singleLine="true"
+        android:ellipsize="end"
+    />
+
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2d61e6e..acafbcf 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -582,7 +582,7 @@
         <!-- Image elements -->
         <!-- ============== -->
         <eat-comment />
-i
+
         <!-- Background that can be used behind parts of a UI that provide
              details on data the user is selecting.  For example, this is
              the background element of PreferenceActivity's embedded
@@ -1008,6 +1008,15 @@
         <!-- Background to use for toasts -->
         <attr name="toastFrameBackground" format="reference" />
 
+        <!-- Background to use for tooltip popups -->
+        <attr name="tooltipFrameBackground" format="reference" />
+
+        <!-- Foreground color to use for tooltip popups -->
+        <attr name="tooltipForegroundColor" format="reference|color" />
+
+        <!-- Background color to use for tooltip popups -->
+        <attr name="tooltipBackgroundColor" format="reference|color" />
+
         <!-- Theme to use for Search Dialogs -->
         <attr name="searchDialogTheme" format="reference" />
 
@@ -2865,6 +2874,9 @@
              {@link android.view.View#forceHasOverlappingRendering(boolean)}. -->
         <attr name="forceHasOverlappingRendering" format="boolean" />
 
+        <!-- Defines text displayed in a small popup window on hover or long press. -->
+        <attr name="tooltip" format="string" localization="suggested" />
+
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index de86cef..0995bc3 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -186,4 +186,7 @@
 
     <color name="resize_shadow_start_color">#2a000000</color>
     <color name="resize_shadow_end_color">#00000000</color>
+
+    <color name="tooltip_background_dark">#e6616161</color>
+    <color name="tooltip_background_light">#e6FFFFFF</color>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d4119d0..a7c5b2a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -128,6 +128,9 @@
     <integer name="config_activityShortDur">150</integer>
     <integer name="config_activityDefaultDur">220</integer>
 
+    <!-- The duration (in milliseconds) of the tooltip show/hide animations. -->
+    <integer name="config_tooltipAnimTime">150</integer>
+
     <!-- Duration for the dim animation behind a dialog.  This may be either
          a percentage, which is relative to the duration of the enter/open
          animation of the window being shown that is dimming behind, or it may
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 91d7227..5efa55c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -487,4 +487,19 @@
 
     <!-- Minimum "smallest width" of the display for cascading menus to be enabled. -->
     <dimen name="cascading_menus_min_smallest_width">720dp</dimen>
+
+    <!-- Tooltip dimensions. -->
+    <!-- Vertical offset from the edge of the anchor view. -->
+    <dimen name="tooltip_y_offset">20dp</dimen>
+    <!-- Height of the tooltip. -->
+    <dimen name="tooltip_height">32dp</dimen>
+    <!-- The tooltip does not get closer than this to the window edge -->
+    <dimen name="tooltip_margin">8dp</dimen>
+    <!-- Left/right padding of the tooltip text. -->
+    <dimen name="tooltip_horizontal_padding">16dp</dimen>
+    <!-- Border corner radius of the tooltip window. -->
+    <dimen name="tooltip_corner_radius">2dp</dimen>
+    <!-- View with the height equal or above this threshold will have a tooltip anchored
+    to the mouse/touch position -->
+    <dimen name="tooltip_precise_anchor_threshold">96dp</dimen>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ed68582..ae82128 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2759,6 +2759,7 @@
         <public name="fontStyle" />
         <public name="font" />
         <public name="fontWeight" />
+        <public name="tooltip" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d42ec90..baf3cd8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4450,4 +4450,7 @@
 
     <!-- Label used by Telephony code, assigned as the display name for conference calls [CHAR LIMIT=60] -->
     <string name="conference_call">Conference Call</string>
+
+    <!-- Title for a tooltip popup window [CHAR LIMIT=NONE] -->
+    <string name="tooltip_popup_title">Tooltip Popup</string>
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 937428b..0f756b9 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -159,6 +159,11 @@
         <item name="windowExitAnimation">@anim/toast_exit</item>
     </style>
 
+    <style name="Animation.Tooltip">
+        <item name="windowEnterAnimation">@anim/tooltip_enter</item>
+        <item name="windowExitAnimation">@anim/tooltip_exit</item>
+    </style>
+
     <style name="Animation.DropDownDown">
         <item name="windowEnterAnimation">@anim/grow_fade_in</item>
         <item name="windowExitAnimation">@anim/shrink_fade_out</item>
@@ -950,6 +955,11 @@
         <item name="fontFamily">sans-serif-condensed</item>
     </style>
 
+    <style name="TextAppearance.Tooltip">
+        <item name="fontFamily">sans-serif</item>
+        <item name="textSize">14sp</item>
+    </style>
+
     <style name="Widget.ActivityChooserView">
         <item name="gravity">center</item>
         <item name="background">@drawable/ab_share_pack_holo_dark</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c719664..5b608b8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -435,6 +435,8 @@
   <java-symbol type="dimen" name="search_view_preferred_height" />
   <java-symbol type="dimen" name="textview_error_popup_default_width" />
   <java-symbol type="dimen" name="toast_y_offset" />
+  <java-symbol type="dimen" name="tooltip_precise_anchor_threshold" />
+  <java-symbol type="dimen" name="tooltip_y_offset" />
   <java-symbol type="dimen" name="action_bar_stacked_max_height" />
   <java-symbol type="dimen" name="action_bar_stacked_tab_max_width" />
   <java-symbol type="dimen" name="notification_text_size" />
@@ -1123,6 +1125,7 @@
   <java-symbol type="string" name="demo_starting_message" />
   <java-symbol type="string" name="demo_restarting_message" />
   <java-symbol type="string" name="conference_call" />
+  <java-symbol type="string" name="tooltip_popup_title" />
 
 
   <java-symbol type="plurals" name="bugreport_countdown" />
@@ -1373,6 +1376,7 @@
   <java-symbol type="layout" name="textview_hint" />
   <java-symbol type="layout" name="time_picker_legacy" />
   <java-symbol type="layout" name="time_picker_dialog" />
+  <java-symbol type="layout" name="tooltip" />
   <java-symbol type="layout" name="transient_notification" />
   <java-symbol type="layout" name="voice_interaction_session" />
   <java-symbol type="layout" name="web_text_view_dropdown" />
@@ -1423,6 +1427,7 @@
   <java-symbol type="style" name="Animation.DropDownUp" />
   <java-symbol type="style" name="Animation.DropDownDown" />
   <java-symbol type="style" name="Animation.PopupWindow" />
+  <java-symbol type="style" name="Animation.Tooltip" />
   <java-symbol type="style" name="Animation.TypingFilter" />
   <java-symbol type="style" name="Animation.TypingFilterRestore" />
   <java-symbol type="style" name="Animation.Dream" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 5b2522f..357eb4b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -439,6 +439,11 @@
         <item name="lightRadius">@dimen/light_radius</item>
         <item name="ambientShadowAlpha">@dimen/ambient_shadow_alpha</item>
         <item name="spotShadowAlpha">@dimen/spot_shadow_alpha</item>
+
+        <!-- Tooltip popup properties -->
+        <item name="tooltipFrameBackground">@drawable/tooltip_frame</item>
+        <item name="tooltipForegroundColor">@color/bright_foreground_light</item>
+        <item name="tooltipBackgroundColor">@color/tooltip_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme} with no title bar -->
@@ -553,6 +558,10 @@
         <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_light</item>
         <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_light</item>
         <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_light</item>
+
+        <!-- Tooltip popup colors -->
+        <item name="tooltipForegroundColor">@color/bright_foreground_dark</item>
+        <item name="tooltipBackgroundColor">@color/tooltip_background_dark</item>
     </style>
 
     <!-- Variant of {@link #Theme_Light} with no title bar -->
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 0de773b..92bb3ea 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -398,6 +398,10 @@
         <item name="colorControlHighlight">@color/ripple_material_dark</item>
         <item name="colorButtonNormal">@color/btn_default_material_dark</item>
         <item name="colorSwitchThumbNormal">@color/switch_thumb_material_dark</item>
+
+        <!-- Tooltip popup properties -->
+        <item name="tooltipForegroundColor">@color/foreground_material_light</item>
+        <item name="tooltipBackgroundColor">@color/tooltip_background_light</item>
     </style>
 
     <!-- Material theme (light version). -->
@@ -762,6 +766,10 @@
         <item name="colorControlHighlight">@color/ripple_material_light</item>
         <item name="colorButtonNormal">@color/btn_default_material_light</item>
         <item name="colorSwitchThumbNormal">@color/switch_thumb_material_light</item>
+
+        <!-- Tooltip popup properties -->
+        <item name="tooltipForegroundColor">@color/foreground_material_dark</item>
+        <item name="tooltipBackgroundColor">@color/tooltip_background_dark</item>
     </style>
 
     <!-- Variant of the material (light) theme that has a solid (opaque) action bar
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index e6d3158..0a32e43 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -349,6 +349,7 @@
         assertCanBeHandled(new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIFI_IP_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SETTINGS));
+        assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SAVED_NETWORK_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
     }
 
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index fdf4d52..eff2499 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -181,6 +181,7 @@
     external/skia/include/private \
     external/skia/src/core \
     external/skia/src/effects \
+    external/skia/src/image \
     external/harfbuzz_ng/src \
     external/freetype/include
 
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 2087fca..749efdd 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -116,6 +116,9 @@
         bool canDrawThisFrame = true;
     } out;
 
+    // This flag helps to disable projection for receiver nodes that do not have any backward
+    // projected children.
+    bool hasBackwardProjectedNodes = false;
     // TODO: Damage calculations
 };
 
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 7dcbbd0..da9002d 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -24,8 +24,41 @@
 namespace uirenderer {
 namespace skiapipeline {
 
+void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
+        int nestLevel) {
+    LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
+    for (auto& child : displayList.mChildNodes) {
+        const RenderProperties& childProperties = child.getNodeProperties();
+
+        //immediate children cannot be projected on their parent
+        if (childProperties.getProjectBackwards() && nestLevel > 0) {
+            SkAutoCanvasRestore acr2(canvas, true);
+            //Apply recorded matrix, which is a total matrix saved at recording time to avoid
+            //replaying all DL commands.
+            canvas->concat(child.getRecordedMatrix());
+            child.drawContent(canvas);
+        }
+
+        //skip walking sub-nodes if current display list contains a receiver with exception of
+        //level 0, which is a known receiver
+        if (0 == nestLevel || !displayList.containsProjectionReceiver()) {
+            SkAutoCanvasRestore acr(canvas, true);
+            SkMatrix nodeMatrix;
+            mat4 hwuiMatrix(child.getRecordedMatrix());
+            auto childNode = child.getRenderNode();
+            childNode->applyViewPropertyTransforms(hwuiMatrix);
+            hwuiMatrix.copyTo(nodeMatrix);
+            canvas->concat(nodeMatrix);
+            SkiaDisplayList* childDisplayList = static_cast<SkiaDisplayList*>(
+                (const_cast<DisplayList*>(childNode->getDisplayList())));
+            if (childDisplayList) {
+                drawBackwardsProjectedNodes(canvas, *childDisplayList, nestLevel+1);
+            }
+        }
+    }
+}
+
 static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect* pendingClip) {
-    SkASSERT(outline.willClip());
     Rect possibleRect;
     float radius;
     LOG_ALWAYS_FATAL_IF(!outline.getAsRoundRect(&possibleRect, &radius),
@@ -74,53 +107,25 @@
     SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList();
 
     SkAutoCanvasRestore acr(canvas, true);
-
     const RenderProperties& properties = this->getNodeProperties();
-    if (displayList->mIsProjectionReceiver) {
-        // this node is a projection receiver. We will gather the projected nodes as we draw our
-        // children, and then draw them on top of this node's content.
-        std::vector<ProjectedChild> newList;
-        for (auto& child : displayList->mChildNodes) {
-            // our direct children are not supposed to project into us (nodes project to, at the
-            // nearest, their grandparents). So we "delay" the list's activation one level by
-            // passing it into mNextProjectedChildrenTarget rather than mProjectedChildrenTarget.
-            child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
-            child.mNextProjectedChildrenTarget = &newList;
+    //pass this outline to the children that may clip backward projected nodes
+    displayList->mProjectedOutline = displayList->containsProjectionReceiver()
+            ? &properties.getOutline() : nullptr;
+    if (!properties.getProjectBackwards()) {
+        drawContent(canvas);
+        if (mProjectedDisplayList) {
+            acr.restore(); //draw projected children using parent matrix
+            LOG_ALWAYS_FATAL_IF(!mProjectedDisplayList->mProjectedOutline);
+            const bool shouldClip = mProjectedDisplayList->mProjectedOutline->getPath();
+            SkAutoCanvasRestore acr2(canvas, shouldClip);
+            canvas->setMatrix(mProjectedDisplayList->mProjectedReceiverParentMatrix);
+            if (shouldClip) {
+                clipOutline(*mProjectedDisplayList->mProjectedOutline, canvas, nullptr);
+            }
+            drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList);
         }
-        // draw ourselves and our children. As a side effect, this will add projected nodes to
-        // newList.
-        this->drawContent(canvas);
-        bool willClip = properties.getOutline().willClip();
-        if (willClip) {
-            canvas->save();
-            clipOutline(properties.getOutline(), canvas, nullptr);
-        }
-        // draw the collected projected nodes
-        for (auto& projectedChild : newList) {
-            canvas->setMatrix(projectedChild.matrix);
-            projectedChild.node->drawContent(canvas);
-        }
-        if (willClip) {
-            canvas->restore();
-        }
-    } else {
-        if (properties.getProjectBackwards() && mProjectedChildrenTarget) {
-            // We are supposed to project this node, so add it to the list and do not actually draw
-            // yet. It will be drawn by its projection receiver.
-            mProjectedChildrenTarget->push_back({ this, canvas->getTotalMatrix() });
-            return;
-        }
-        for (auto& child : displayList->mChildNodes) {
-            // storing these values in the nodes themselves is a bit ugly; they should "really" be
-            // function parameters, but we have to go through the preexisting draw() method and
-            // therefore cannot add additional parameters to it
-            child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
-            child.mNextProjectedChildrenTarget = mNextProjectedChildrenTarget;
-        }
-        this->drawContent(canvas);
     }
-    mProjectedChildrenTarget = nullptr;
-    mNextProjectedChildrenTarget = nullptr;
+    displayList->mProjectedOutline = nullptr;
 }
 
 static bool layerNeedsPaint(const LayerProperties& properties,
@@ -148,6 +153,10 @@
     if (mComposeLayer) {
         setViewProperties(properties, canvas, &alphaMultiplier);
     }
+    SkiaDisplayList* displayList = (SkiaDisplayList*)mRenderNode->getDisplayList();
+    if (displayList->containsProjectionReceiver()) {
+        displayList->mProjectedReceiverParentMatrix = canvas->getTotalMatrix();
+    }
 
     //TODO should we let the bound of the drawable do this for us?
     const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index a2ffc6c..3eed647 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -29,6 +29,8 @@
 
 namespace skiapipeline {
 
+class SkiaDisplayList;
+
 /**
  * This drawable wraps a RenderNode and enables it to be recorded into a list
  * of Skia drawing commands.
@@ -36,18 +38,6 @@
 class RenderNodeDrawable : public SkDrawable {
 public:
     /**
-     * This struct contains a pointer to a node that is to be
-     * projected into the drawing order of its closest ancestor
-     * (excluding its parent) that is marked as a projection
-     * receiver. The matrix is used to ensure that the node is
-     * drawn with same matrix as it would have prior to projection.
-     */
-    struct ProjectedChild {
-        const RenderNodeDrawable* node;
-        const SkMatrix matrix;
-    };
-
-    /**
      * Creates a new RenderNodeDrawable backed by a render node.
      *
      * @param node that has to be drawn
@@ -86,6 +76,14 @@
      */
     const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; }
 
+    /**
+     * Sets a pointer to a display list of the parent render node. The display list is used when
+     * drawing backward projected nodes, when this node is a projection receiver.
+     */
+    void setProjectedDisplayList(SkiaDisplayList* projectedDisplayList) {
+        mProjectedDisplayList = projectedDisplayList;
+    }
+
 protected:
     /*
      * Return the (conservative) bounds of what the drawable will draw.
@@ -108,6 +106,16 @@
     sp<RenderNode> mRenderNode;
 
     /**
+     * Walks recursively the display list and draws the content of backward projected nodes.
+     *
+     * @param canvas used to draw the backward projected nodes
+     * @param displayList is a display list that contains a projection receiver
+     * @param nestLevel should be always 0. Used to track how far we are from the receiver.
+     */
+    void drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
+            int nestLevel = 0);
+
+    /**
      * Applies the rendering properties of a view onto a SkCanvas.
      */
     static void setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
@@ -126,19 +134,6 @@
      */
     const bool mComposeLayer;
 
-    /**
-     * List to which we will add any projected children we encounter while walking our descendents.
-     * This pointer is valid only while the node (including its children) is actively being drawn.
-     */
-    std::vector<ProjectedChild>* mProjectedChildrenTarget = nullptr;
-
-    /**
-     * The value to which we should set our children's mProjectedChildrenTarget. We use two pointers
-     * (mProjectedChildrenTarget and mNextProjectedChildrenTarget) because we need to skip over our
-     * parent when looking for a projection receiver.
-     */
-    std::vector<ProjectedChild>* mNextProjectedChildrenTarget = nullptr;
-
     /*
      * True if the render node is in a reordering section
      */
@@ -148,6 +143,11 @@
      *  Draw the content into a canvas, depending on the render node layer type and mComposeLayer.
      */
     void drawContent(SkCanvas* canvas) const;
+
+    /*
+     * display list that is searched for any render nodes with getProjectBackwards==true
+     */
+    SkiaDisplayList* mProjectedDisplayList = nullptr;
 };
 
 }; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 2ad7f74..9db8cd3 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -64,16 +64,31 @@
         info.canvasContext.unpinImages();
     }
 
+    bool hasBackwardProjectedNodesHere = false;
+    bool hasBackwardProjectedNodesSubtree= false;
+
     for (auto& child : mChildNodes) {
+        hasBackwardProjectedNodesHere |= child.getNodeProperties().getProjectBackwards();
         RenderNode* childNode = child.getRenderNode();
         Matrix4 mat4(child.getRecordedMatrix());
         info.damageAccumulator->pushTransform(&mat4);
         // TODO: a layer is needed if the canvas is rotated or has a non-rect clip
-        bool childFunctorsNeedLayer = functorsNeedLayer;
-        childFn(childNode, info, childFunctorsNeedLayer);
+        info.hasBackwardProjectedNodes = false;
+        childFn(childNode, info, functorsNeedLayer);
+        hasBackwardProjectedNodesSubtree |= info.hasBackwardProjectedNodes;
         info.damageAccumulator->popTransform();
     }
 
+    //The purpose of next block of code is to reset projected display list if there are no
+    //backward projected nodes. This speeds up drawing, by avoiding an extra walk of the tree
+    if (mProjectionReceiver) {
+        mProjectionReceiver->setProjectedDisplayList(hasBackwardProjectedNodesSubtree ? this : nullptr);
+        info.hasBackwardProjectedNodes = hasBackwardProjectedNodesHere;
+    } else {
+        info.hasBackwardProjectedNodes = hasBackwardProjectedNodesSubtree
+                || hasBackwardProjectedNodesHere;
+    }
+
     bool isDirty = false;
     for (auto& vectorDrawable : mVectorDrawables) {
         // If any vector drawable in the display list needs update, damage the node.
@@ -86,7 +101,7 @@
 }
 
 void SkiaDisplayList::reset(SkRect bounds) {
-    mIsProjectionReceiver = false;
+    mProjectionReceiver = nullptr;
 
     mDrawable->reset(bounds);
 
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index f34b485..ff86fd1 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -26,6 +26,9 @@
 
 namespace android {
 namespace uirenderer {
+
+class Outline;
+
 namespace skiapipeline {
 
 /**
@@ -119,6 +122,11 @@
     void updateChildren(std::function<void(RenderNode*)> updateFn) override;
 
     /**
+     *  Returns true if there is a child render node that is a projection receiver.
+     */
+    inline bool containsProjectionReceiver() const { return mProjectionReceiver; }
+
+    /**
      * We use std::deque here because (1) we need to iterate through these
      * elements and (2) mDrawable holds pointers to the elements, so they cannot
      * relocate.
@@ -129,7 +137,22 @@
     std::vector<VectorDrawableRoot*> mVectorDrawables;
     sk_sp<SkLiteDL> mDrawable;
 
-    bool mIsProjectionReceiver = false;
+    //mProjectionReceiver points to a child node (stored in mChildNodes) that is as a projection
+    //receiver. It is set at record time and used at both prepare and draw tree traversals to
+    //make sure backward projected nodes are found and drawn immediately after mProjectionReceiver.
+    RenderNodeDrawable* mProjectionReceiver = nullptr;
+
+    //mProjectedOutline is valid only when render node tree is traversed during the draw pass.
+    //Render nodes that have a child receiver node, will store a pointer to their outline in
+    //mProjectedOutline. Child receiver node will apply the clip before any backward projected
+    //node is drawn.
+    const Outline* mProjectedOutline = nullptr;
+
+    //mProjectedReceiverParentMatrix is valid when render node tree is traversed during the draw
+    //pass. Render nodes that have a child receiver node, will store their matrix in
+    //mProjectedReceiverParentMatrix. Child receiver node will set the matrix and then clip with the
+    //outline of their parent.
+    SkMatrix mProjectedReceiverParentMatrix;
 };
 
 }; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 621816a..6df544f 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -108,11 +108,12 @@
 
     // record the child node
     mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
-    drawDrawable(&mDisplayList->mChildNodes.back());
+    auto& renderNodeDrawable = mDisplayList->mChildNodes.back();
+    drawDrawable(&renderNodeDrawable);
 
     // use staging property, since recording on UI thread
     if (renderNode->stagingProperties().isProjectionReceiver()) {
-        mDisplayList->mIsProjectionReceiver = true;
+        mDisplayList->mProjectionReceiver = &renderNodeDrawable;
         // set projectionReceiveIndex so that RenderNode.hasProjectionReceiver returns true
         mDisplayList->projectionReceiveIndex = mDisplayList->mChildNodes.size() - 1;
     }
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 01046e1..8c956e5 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -1487,6 +1487,8 @@
     *layerHandle = nullptr;
 }
 
+namespace {
+
 static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
     SkPaint paint;
     // order put in blue channel, transparent so overlapped content doesn't get rejected
@@ -1502,15 +1504,30 @@
     node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
     canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
 }
-RENDERTHREAD_TEST(FrameBuilder, zReorder) {
-    class ZReorderTestRenderer : public TestRendererBase {
-    public:
-        void onRectOp(const RectOp& op, const BakedOpState& state) override {
-            int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
-            EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
-        }
-    };
 
+static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder,
+        std::function<void(RenderProperties& props, RecordingCanvas& canvas)> setup) {
+    auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [expectedDrawOrder, setup](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedRect(&canvas, expectedDrawOrder);
+        if (setup) {
+             setup(props, canvas);
+        }
+    });
+    canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
+
+class ZReorderTestRenderer : public TestRendererBase {
+public:
+    void onRectOp(const RectOp& op, const BakedOpState& state) override {
+        int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
+        EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+    }
+};
+
+} // end anonymous namespace
+
+RENDERTHREAD_TEST(FrameBuilder, zReorder) {
     auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
             [](RenderProperties& props, RecordingCanvas& canvas) {
         drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
@@ -2238,5 +2255,349 @@
     EXPECT_EQ(1, renderer.getIndex());
 }
 
+TEST(FrameBuilder, projectionReorderProjectedInMiddle) {
+    /* R is backward projected on B
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderProjectLast) {
+    /* R is backward projected on E
+                  A
+                / | \
+               /  |  \
+              B   C   E
+                  |
+                  R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0,  nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //drawn as 2
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //drawn as 3
+            props.setProjectionReceiver(true);
+        } ); //nodeE
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderNoReceivable) {
+    /* R is backward projected without receiver
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 255,  [](RenderProperties& props, RecordingCanvas& canvas) {
+                //not having a projection receiver is an undefined behavior
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderParentReceivable) {
+    /* R is backward projected on C
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderSameNodeReceivable) {
+     auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) {
+                //having a node that is projected on itself is an undefined/unexpected behavior
+                props.setProjectionReceiver(true);
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderProjectedSibling) {
+    //TODO: this test together with the next "projectionReorderProjectedSibling2" likely expose a
+    //bug in HWUI. First test draws R, while the second test does not draw R for a nearly identical
+    //tree setup. The correct behaviour is to not draw R, because the receiver cannot be a sibling
+    /* R is backward projected on B. R is not expected to be drawn (see Sibling2 outcome below),
+       but for some reason it is drawn.
+                A
+               /|\
+              / | \
+             B  C  R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+        } ); //nodeC
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectBackwards(true);
+            props.setClipToBounds(false);
+        } ); //nodeR
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderProjectedSibling2) {
+    /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
+                A
+                |
+                G
+               /|\
+              / | \
+             B  C  R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+                props.setProjectionReceiver(true);
+            } ); //nodeB
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+            } ); //nodeC
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeG
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderGrandparentReceivable) {
+    /* R is backward projected on B
+                A
+                |
+                B
+                |
+                C
+                |
+                R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+                drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+                    props.setProjectBackwards(true);
+                    props.setClipToBounds(false);
+                } ); //nodeR
+            } ); //nodeC
+        } ); //nodeB
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderTwoReceivables) {
+    /* B and G are receivables, R is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderTwoReceivablesLikelyScenario) {
+    /* B and G are receivables, G is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderTwoReceivablesDeeper) {
+    /* B and G are receivables, R is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   D
+                    |
+                    R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 4, [](RenderProperties& props, RecordingCanvas& canvas) { //D
+                drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+                    props.setProjectBackwards(true);
+                    props.setClipToBounds(false);
+                } ); //nodeR
+            } ); //nodeD
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 623d971..ae4f0f4 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -21,11 +21,14 @@
 #include "DamageAccumulator.h"
 #include "IContextFactory.h"
 #include "pipeline/skia/SkiaDisplayList.h"
+#include "pipeline/skia/SkiaPipeline.h"
 #include "pipeline/skia/SkiaRecordingCanvas.h"
 #include "renderthread/CanvasContext.h"
 #include "tests/common/TestUtils.h"
 #include "SkiaCanvas.h"
+#include <SkSurface_Base.h>
 #include <SkLiteRecorder.h>
+#include <SkClipStack.h>
 #include <string.h>
 
 
@@ -51,6 +54,8 @@
     ASSERT_EQ(drawable.getRecordedMatrix(), canvas.getTotalMatrix());
 }
 
+namespace {
+
 static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
     SkPaint paint;
     // order put in blue channel, transparent so overlapped content doesn't get rejected
@@ -67,18 +72,33 @@
     canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
 }
 
-TEST(RenderNodeDrawable, zReorder) {
-    class ZReorderCanvas : public SkCanvas {
-    public:
-        ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
-        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
-            int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
-            EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder,
+        std::function<void(RenderProperties& props, SkiaRecordingCanvas& canvas)> setup) {
+    auto node = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [expectedDrawOrder, setup](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedRect(&canvas, expectedDrawOrder);
+        if (setup) {
+             setup(props, canvas);
         }
-        int getIndex() { return mIndex; }
-    protected:
-        int mIndex = 0;
-    };
+    });
+    canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
+
+class ZReorderCanvas : public SkCanvas {
+public:
+    ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
+    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+        int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
+        EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+    }
+    int getIndex() { return mIndex; }
+protected:
+    int mIndex = 0;
+};
+
+} // end anonymous namespace
+
+TEST(RenderNodeDrawable, zReorder) {
 
     auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
             [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
@@ -136,42 +156,622 @@
     rootNode->setLayerSurface(sk_sp<SkSurface>());
 }
 
-//TODO: refactor to cover test cases from FrameBuilderTests_projectionReorder
-//validate with bounds and projection path mask.
-//TODO: research if we could hook in and mock/validate different aspects of the drawing,
-//instead of validating pixels
-TEST(RenderNodeDrawable, projectDraw) {
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
-    SkCanvas& canvas = *surface->getCanvas();
-    canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+namespace {
+class ContextFactory : public IContextFactory {
+public:
+    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+        return new AnimationContext(clock);
+    }
+};
 
-    auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
-        [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
-            redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
-        }, "redNode");
+inline SkRect getBounds(const SkCanvas* canvas) {
+    SkClipStack::BoundsType boundType;
+    SkRect clipBounds;
+    canvas->getClipStack()->getBounds(&clipBounds, &boundType);
+    return clipBounds;
+}
+inline SkRect getLocalBounds(const SkCanvas* canvas) {
+    SkMatrix invertedTotalMatrix;
+    EXPECT_TRUE(canvas->getTotalMatrix().invert(&invertedTotalMatrix));
+    SkRect outlineInDeviceCoord = getBounds(canvas);
+    SkRect outlineInLocalCoord;
+    invertedTotalMatrix.mapRect(&outlineInLocalCoord, outlineInDeviceCoord);
+    return outlineInLocalCoord;
+}
+} // end anonymous namespace
 
-    auto greenNodeWithRedChild = TestUtils::createSkiaNode(0, 0, 1, 1,
-        [&](RenderProperties& props, SkiaRecordingCanvas& greenCanvasWithRedChild) {
-            greenCanvasWithRedChild.drawRenderNode(redNode.get());
-            greenCanvasWithRedChild.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver);
-        }, "greenNodeWithRedChild");
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) {
+    static const int SCROLL_X = 5;
+    static const int SCROLL_Y = 10;
+    class ProjectionTestCanvas : public SkCanvas {
+    public:
+        ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
+        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+            const int index = mIndex++;
+            SkMatrix expectedMatrix;;
+            switch (index) {
+            case 0:  //this is node "B"
+                EXPECT_EQ(SkRect::MakeWH(100, 100), rect);
+                EXPECT_EQ(SK_ColorWHITE, paint.getColor());
+                expectedMatrix.reset();
+                EXPECT_EQ(SkRect::MakeLTRB(0, 0, 100, 100), getBounds(this));
+                break;
+            case 1:  //this is node "P"
+                EXPECT_EQ(SkRect::MakeLTRB(-10, -10, 60, 60), rect);
+                EXPECT_EQ(SK_ColorDKGRAY, paint.getColor());
+                expectedMatrix.setTranslate(50 - SCROLL_X, 50 - SCROLL_Y);
+                EXPECT_EQ(SkRect::MakeLTRB(-35, -30, 45, 50), getLocalBounds(this));
+                break;
+            case 2:  //this is node "C"
+                EXPECT_EQ(SkRect::MakeWH(100, 50), rect);
+                EXPECT_EQ(SK_ColorBLUE, paint.getColor());
+                expectedMatrix.setTranslate(-SCROLL_X, 50 - SCROLL_Y);
+                EXPECT_EQ(SkRect::MakeLTRB(0, 40, 95, 90), getBounds(this));
+                break;
+            default:
+                ADD_FAILURE();
+            }
+            EXPECT_EQ(expectedMatrix, getTotalMatrix());
+        }
 
-    auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
-        [&](RenderProperties& props, SkiaRecordingCanvas& rootCanvas) {
-            rootCanvas.drawRenderNode(greenNodeWithRedChild.get());
-        }, "rootNode");
-    SkiaDisplayList* rootDisplayList = static_cast<SkiaDisplayList*>(
-        (const_cast<DisplayList*>(rootNode->getDisplayList())));
+        int getIndex() { return mIndex; }
+    protected:
+        int mIndex = 0;
+    };
 
-    RenderNodeDrawable rootDrawable(rootNode.get(), &canvas, false);
-    canvas.drawDrawable(&rootDrawable);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorGREEN);
+    /**
+     * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
+     * with a projecting child (P) of its own. P would normally draw between B and C's "background"
+     * draw, but because it is projected backwards, it's drawn in between B and C.
+     *
+     * The parent is scrolled by SCROLL_X/SCROLL_Y, but this does not affect the background
+     * (which isn't affected by scroll).
+     */
+    auto receiverBackground = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectionReceiver(true);
+        // scroll doesn't apply to background, so undone via translationX/Y
+        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+        properties.setTranslationX(SCROLL_X);
+        properties.setTranslationY(SCROLL_Y);
 
-    //project redNode on rootNode, which will change the test outcome,
-    //because redNode will draw after greenNodeWithRedChild
-    rootDisplayList->mIsProjectionReceiver = true;
-    redNode->animatorProperties().setProjectBackwards(true);
-    canvas.drawDrawable(&rootDrawable);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    }, "B");
+
+    auto projectingRipple = TestUtils::createSkiaNode(50, 0, 100, 50,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectBackwards(true);
+        properties.setClipToBounds(false);
+        SkPaint paint;
+        paint.setColor(SK_ColorDKGRAY);
+        canvas.drawRect(-10, -10, 60, 60, paint);
+    }, "P");
+    auto child = TestUtils::createSkiaNode(0, 50, 100, 100,
+            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorBLUE);
+        canvas.drawRect(0, 0, 100, 50, paint);
+        canvas.drawRenderNode(projectingRipple.get());
+    }, "C");
+    auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [&receiverBackground, &child](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        // Set a rect outline for the projecting ripple to be masked against.
+        properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
+
+        canvas.save(SaveFlags::MatrixClip);
+        canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
+        canvas.drawRenderNode(receiverBackground.get());
+        canvas.drawRenderNode(child.get());
+        canvas.restore();
+    }, "A");
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+            renderThread, false, parent.get(), &contextFactory));
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    info.observer = nullptr;
+    parent->prepareTree(info);
+
+    //parent(A)             -> (receiverBackground, child)
+    //child(C)              -> (rect[0, 0, 100, 50], projectingRipple)
+    //projectingRipple(P)   -> (rect[-10, -10, 60, 60]) -> projects backwards
+    //receiverBackground(B) -> (rect[0, 0, 100, 100]) -> projection receiver
+
+    //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+    ProjectionTestCanvas canvas(100, 100);
+    RenderNodeDrawable drawable(parent.get(), &canvas, true);
+    canvas.drawDrawable(&drawable);
+    EXPECT_EQ(3, canvas.getIndex());
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
+    /* R is backward projected on B and C is a layer.
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+    static const int SCROLL_X = 5;
+    static const int SCROLL_Y = 10;
+    static const int CANVAS_WIDTH = 400;
+    static const int CANVAS_HEIGHT = 400;
+    static const int LAYER_WIDTH = 200;
+    static const int LAYER_HEIGHT = 200;
+    class ProjectionTestCanvas : public SkCanvas {
+    public:
+        ProjectionTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+        void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
+                const SkPaint&) override {
+            EXPECT_EQ(0, mIndex++); //part of painting the layer
+            EXPECT_EQ(SkRect::MakeLTRB(0, 0, LAYER_WIDTH, LAYER_HEIGHT), getBounds(this));
+        }
+        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this));
+        }
+        void onDrawOval(const SkRect&, const SkPaint&) override {
+            EXPECT_EQ(2, mIndex++);
+            SkMatrix expectedMatrix;
+            expectedMatrix.setTranslate(100 - SCROLL_X, 100 - SCROLL_Y);
+            EXPECT_EQ(expectedMatrix, getTotalMatrix());
+            EXPECT_EQ(SkRect::MakeLTRB(-85, -80, 295, 300), getLocalBounds(this));
+        }
+        int mIndex = 0;
+    };
+
+    class ProjectionLayer : public SkSurface_Base {
+    public:
+        ProjectionLayer(ProjectionTestCanvas *canvas)
+            : SkSurface_Base(SkImageInfo::MakeN32Premul(LAYER_WIDTH, LAYER_HEIGHT), nullptr)
+            , mCanvas(canvas) {
+        }
+        void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override {
+            EXPECT_EQ(3, mCanvas->mIndex++);
+            EXPECT_EQ(SkRect::MakeLTRB(100 - SCROLL_X, 100 - SCROLL_Y, 300 - SCROLL_X,
+                   300 - SCROLL_Y), getBounds(mCanvas));
+        }
+        SkCanvas* onNewCanvas() override {
+            mCanvas->ref();
+            return mCanvas;
+        }
+        sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
+            return sk_sp<SkSurface>();
+        }
+        sk_sp<SkImage> onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode) override {
+            return sk_sp<SkImage>();
+        }
+        void onCopyOnWrite(ContentChangeMode) override {}
+        ProjectionTestCanvas* mCanvas;
+    };
+
+    auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectionReceiver(true);
+        // scroll doesn't apply to background, so undone via translationX/Y
+        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+        properties.setTranslationX(SCROLL_X);
+        properties.setTranslationY(SCROLL_Y);
+
+        canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
+    }, "B"); //B
+    auto projectingRipple = TestUtils::createSkiaNode(0, 0, LAYER_WIDTH, LAYER_HEIGHT,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectBackwards(true);
+        properties.setClipToBounds(false);
+        canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
+    }, "R"); //R
+    auto child = TestUtils::createSkiaNode(100, 100, 300, 300,
+            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        canvas.drawRenderNode(projectingRipple.get());
+        canvas.drawArc(0, 0, LAYER_WIDTH, LAYER_HEIGHT, 0.0f, 280.0f, true, SkPaint());
+    }, "C"); //C
+    auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [&receiverBackground, &child](RenderProperties& properties,
+            SkiaRecordingCanvas& canvas) {
+        // Set a rect outline for the projecting ripple to be masked against.
+        properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
+        canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
+        canvas.drawRenderNode(receiverBackground.get());
+        canvas.drawRenderNode(child.get());
+    }, "A"); //A
+
+    //prepareTree is required to find, which receivers have backward projected nodes
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+            renderThread, false, parent.get(), &contextFactory));
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    info.observer = nullptr;
+    parent->prepareTree(info);
+
+    sk_sp<ProjectionTestCanvas> canvas(new ProjectionTestCanvas());
+    //set a layer after prepareTree to avoid layer logic there
+    child->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+    sk_sp<SkSurface> surfaceLayer1(new ProjectionLayer(canvas.get()));
+    child->setLayerSurface(surfaceLayer1);
+    Matrix4 windowTransform;
+    windowTransform.loadTranslate(100, 100, 0);
+    child->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+
+    LayerUpdateQueue layerUpdateQueue;
+    layerUpdateQueue.enqueueLayerWithDamage(child.get(),
+            android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
+    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true);
+    EXPECT_EQ(1, canvas->mIndex);  //assert index 0 is drawn on the layer
+
+    RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
+    canvas->drawDrawable(&drawable);
+    EXPECT_EQ(4, canvas->mIndex);
+
+    // clean up layer pointer, so we can safely destruct RenderNode
+    child->setLayerSurface(nullptr);
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) {
+    /* R is backward projected on B.
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+    static const int SCROLL_X = 500000;
+    static const int SCROLL_Y = 0;
+    static const int CANVAS_WIDTH = 400;
+    static const int CANVAS_HEIGHT = 400;
+    class ProjectionChildScrollTestCanvas : public SkCanvas {
+    public:
+        ProjectionChildScrollTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_TRUE(getTotalMatrix().isIdentity());
+        }
+        void onDrawOval(const SkRect&, const SkPaint&) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this));
+            EXPECT_TRUE(getTotalMatrix().isIdentity());
+        }
+        int mIndex = 0;
+    };
+
+    auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectionReceiver(true);
+        canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
+    }, "B"); //B
+    auto projectingRipple = TestUtils::createSkiaNode(0, 0, 200, 200,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        // scroll doesn't apply to background, so undone via translationX/Y
+        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+        properties.setTranslationX(SCROLL_X);
+        properties.setTranslationY(SCROLL_Y);
+        properties.setProjectBackwards(true);
+        properties.setClipToBounds(false);
+        canvas.drawOval(0, 0, 200, 200, SkPaint());
+    }, "R"); //R
+    auto child = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        // Record time clip will be ignored by projectee
+        canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op);
+
+        canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
+        canvas.drawRenderNode(projectingRipple.get());
+    }, "C"); //C
+    auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [&receiverBackground, &child](RenderProperties& properties,
+            SkiaRecordingCanvas& canvas) {
+        canvas.drawRenderNode(receiverBackground.get());
+        canvas.drawRenderNode(child.get());
+    }, "A"); //A
+
+    //prepareTree is required to find, which receivers have backward projected nodes
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+            renderThread, false, parent.get(), &contextFactory));
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    info.observer = nullptr;
+    parent->prepareTree(info);
+
+    sk_sp<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas());
+    RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
+    canvas->drawDrawable(&drawable);
+    EXPECT_EQ(2, canvas->mIndex);
+}
+
+namespace {
+static int drawNode(RenderThread& renderThread, const sp<RenderNode>& renderNode)
+{
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+            renderThread, false, renderNode.get(), &contextFactory));
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    info.observer = nullptr;
+    renderNode->prepareTree(info);
+
+    //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+    ZReorderCanvas canvas(100, 100);
+    RenderNodeDrawable drawable(renderNode.get(), &canvas, false);
+    canvas.drawDrawable(&drawable);
+    return canvas.getIndex();
+}
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedInMiddle) {
+    /* R is backward projected on B
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectLast) {
+    /* R is backward projected on E
+                  A
+                / | \
+               /  |  \
+              B   C   E
+                  |
+                  R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 2
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 3
+            props.setProjectionReceiver(true);
+        } ); //nodeE
+    }); //nodeA
+    EXPECT_EQ(4, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderNoReceivable) {
+    /* R is backward projected without receiver
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                //not having a projection receiver is an undefined behavior
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(2, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderParentReceivable) {
+    /* R is backward projected on C
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderSameNodeReceivable) {
+    /* R is backward projected on R
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                //having a node that is projected on itself is an undefined/unexpected behavior
+                props.setProjectionReceiver(true);
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(2, drawNode(renderThread, nodeA));
+}
+
+//Note: the outcome for this test is different in HWUI
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling) {
+    /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
+                A
+               /|\
+              / | \
+             B  C  R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        } ); //nodeC
+        drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectBackwards(true);
+            props.setClipToBounds(false);
+        } ); //nodeR
+    }); //nodeA
+    EXPECT_EQ(2, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling2) {
+    /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
+                A
+                |
+                G
+               /|\
+              / | \
+             B  C  R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                props.setProjectionReceiver(true);
+            } ); //nodeB
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            } ); //nodeC
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeG
+    }); //nodeA
+    EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderGrandparentReceivable) {
+    /* R is backward projected on B
+                A
+                |
+                B
+                |
+                C
+                |
+                R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                    props.setProjectBackwards(true);
+                    props.setClipToBounds(false);
+                } ); //nodeR
+            } ); //nodeC
+        } ); //nodeB
+    }); //nodeA
+    EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivables) {
+    /* B and G are receivables, R is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(4, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesLikelyScenario) {
+    /* B and G are receivables, G is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(4, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesDeeper) {
+    /* B and G are receivables, R is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   D
+                    |
+                    R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 4, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //D
+                drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
+                    props.setProjectBackwards(true);
+                    props.setClipToBounds(false);
+                } ); //nodeR
+            } ); //nodeD
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(5, drawNode(renderThread, nodeA));
 }
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 67fb78a..899758a 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -33,7 +33,7 @@
     SkRect bounds = SkRect::MakeWH(200, 200);
     SkiaDisplayList skiaDL(bounds);
     ASSERT_TRUE(skiaDL.isEmpty());
-    ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
+    ASSERT_FALSE(skiaDL.mProjectionReceiver);
     ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
 }
 
@@ -42,12 +42,13 @@
     SkiaDisplayList skiaDL(bounds);
 
     SkCanvas dummyCanvas;
+    RenderNodeDrawable drawable(nullptr, &dummyCanvas);
     skiaDL.mChildNodes.emplace_back(nullptr, &dummyCanvas);
     skiaDL.mChildFunctors.emplace_back(nullptr, nullptr, &dummyCanvas);
     skiaDL.mMutableImages.push_back(nullptr);
     skiaDL.mVectorDrawables.push_back(nullptr);
     skiaDL.mDrawable->drawAnnotation(bounds, "testAnnotation", nullptr);
-    skiaDL.mIsProjectionReceiver = true;
+    skiaDL.mProjectionReceiver = &drawable;
 
     ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
     ASSERT_FALSE(skiaDL.mChildNodes.empty());
@@ -55,7 +56,7 @@
     ASSERT_FALSE(skiaDL.mMutableImages.empty());
     ASSERT_FALSE(skiaDL.mVectorDrawables.empty());
     ASSERT_FALSE(skiaDL.isEmpty());
-    ASSERT_TRUE(skiaDL.mIsProjectionReceiver);
+    ASSERT_TRUE(skiaDL.mProjectionReceiver);
 
     bounds = SkRect::MakeWH(100, 100);
     skiaDL.reset(bounds);
@@ -66,7 +67,7 @@
     ASSERT_TRUE(skiaDL.mMutableImages.empty());
     ASSERT_TRUE(skiaDL.mVectorDrawables.empty());
     ASSERT_TRUE(skiaDL.isEmpty());
-    ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
+    ASSERT_FALSE(skiaDL.mProjectionReceiver);
 }
 
 TEST(SkiaDisplayList, reuseDisplayList) {
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 89709ee..5440f0f 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -584,6 +584,10 @@
          * @return the same Builder instance.
          */
         public Builder setLegacyStreamType(int streamType) {
+            if (streamType == AudioManager.STREAM_ACCESSIBILITY) {
+                throw new IllegalArgumentException("STREAM_ACCESSIBILITY is not a legacy stream "
+                        + "type that was used for audio playback");
+            }
             return setInternalLegacyStreamType(streamType);
         }
 
@@ -624,12 +628,15 @@
                     mContentType = CONTENT_TYPE_SONIFICATION;
                     break;
                 case AudioSystem.STREAM_TTS:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_ACCESSIBILITY:
                     mContentType = CONTENT_TYPE_SPEECH;
                     break;
                 default:
                     Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes");
             }
-            mUsage = usageForLegacyStreamType(streamType);
+            mUsage = usageForStreamType(streamType);
             return this;
         }
 
@@ -842,8 +849,7 @@
         }
     }
 
-    /** @hide */
-    public static int usageForLegacyStreamType(int streamType) {
+    private static int usageForStreamType(int streamType) {
         switch(streamType) {
             case AudioSystem.STREAM_VOICE_CALL:
                 return USAGE_VOICE_COMMUNICATION;
@@ -862,8 +868,9 @@
                 return USAGE_VOICE_COMMUNICATION;
             case AudioSystem.STREAM_DTMF:
                 return USAGE_VOICE_COMMUNICATION_SIGNALLING;
-            case AudioSystem.STREAM_TTS:
+            case AudioSystem.STREAM_ACCESSIBILITY:
                 return USAGE_ASSISTANCE_ACCESSIBILITY;
+            case AudioSystem.STREAM_TTS:
             default:
                 return USAGE_UNKNOWN;
         }
@@ -915,7 +922,6 @@
         switch (aa.getUsage()) {
             case USAGE_MEDIA:
             case USAGE_GAME:
-            case USAGE_ASSISTANCE_ACCESSIBILITY:
             case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
                 return AudioSystem.STREAM_MUSIC;
             case USAGE_ASSISTANCE_SONIFICATION:
@@ -935,6 +941,8 @@
             case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
             case USAGE_NOTIFICATION_EVENT:
                 return AudioSystem.STREAM_NOTIFICATION;
+            case USAGE_ASSISTANCE_ACCESSIBILITY:
+                return AudioSystem.STREAM_ACCESSIBILITY;
             case USAGE_UNKNOWN:
                 return fromGetVolumeControlStream ?
                         AudioManager.USE_DEFAULT_STREAM_TYPE : AudioSystem.STREAM_MUSIC;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f24bf09..435e6ba 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -335,6 +335,9 @@
     /** @hide Used to identify the volume of audio streams exclusively transmitted through the
      *        speaker (TTS) of the device */
     public static final int STREAM_TTS = AudioSystem.STREAM_TTS;
+    /** Used to identify the volume of audio streams for accessibility prompts */
+    public static final int STREAM_ACCESSIBILITY = AudioSystem.STREAM_ACCESSIBILITY;
+
     /** Number of audio streams */
     /**
      * @deprecated Do not iterate on volume stream type values.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 384be5c..28c7253 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -63,13 +63,15 @@
     /** Used to identify the volume of audio streams exclusively transmitted through the
      *  speaker (TTS) of the device */
     public static final int STREAM_TTS = 9;
+    /** Used to identify the volume of audio streams for accessibility prompts */
+    public static final int STREAM_ACCESSIBILITY = 10;
     /**
      * @deprecated Use {@link #numStreamTypes() instead}
      */
     public static final int NUM_STREAMS = 5;
 
     // Expose only the getter method publicly so we can change it in the future
-    private static final int NUM_STREAM_TYPES = 10;
+    private static final int NUM_STREAM_TYPES = 11;
     public static final int getNumStreamTypes() { return NUM_STREAM_TYPES; }
 
     public static final String[] STREAM_NAMES = new String[] {
@@ -82,7 +84,8 @@
         "STREAM_BLUETOOTH_SCO",
         "STREAM_SYSTEM_ENFORCED",
         "STREAM_DTMF",
-        "STREAM_TTS"
+        "STREAM_TTS",
+        "STREAM_ACCESSIBILITY"
     };
 
     /*
@@ -773,7 +776,8 @@
         7,  // STREAM_BLUETOOTH_SCO
         7,  // STREAM_SYSTEM_ENFORCED
         11, // STREAM_DTMF
-        11  // STREAM_TTS
+        11, // STREAM_TTS
+        11, // STREAM_ACCESSIBILITY
     };
 
     public static String streamToString(int stream) {
diff --git a/media/mca/filterfw/jni/jni_gl_environment.cpp b/media/mca/filterfw/jni/jni_gl_environment.cpp
index 096120e..823e88a 100644
--- a/media/mca/filterfw/jni/jni_gl_environment.cpp
+++ b/media/mca/filterfw/jni/jni_gl_environment.cpp
@@ -63,7 +63,8 @@
 };
 
 jboolean Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz) {
-  return ToJBool(WrapObjectInJava(new GLEnv(), env, thiz, true));
+  std::unique_ptr<GLEnv> glEnv(new GLEnv());
+  return ToJBool(WrapOwnedObjectInJava(std::move(glEnv), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_gl_frame.cpp b/media/mca/filterfw/jni/jni_gl_frame.cpp
index b55bc5d..27b4cd2 100644
--- a/media/mca/filterfw/jni/jni_gl_frame.cpp
+++ b/media/mca/filterfw/jni/jni_gl_frame.cpp
@@ -48,13 +48,11 @@
                                                            jint height) {
   GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
   if (!gl_env_ptr) return JNI_FALSE;
-  GLFrame* frame = new GLFrame(gl_env_ptr);
+  std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
   if (frame->Init(width, height)) {
-    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
-  } else {
-    delete frame;
-    return JNI_FALSE;
+    return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
   }
+  return JNI_FALSE;
 }
 
 jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithTexture(JNIEnv* env,
@@ -65,13 +63,11 @@
                                                                       jint height) {
   GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
   if (!gl_env_ptr) return JNI_FALSE;
-  GLFrame* frame = new GLFrame(gl_env_ptr);
+  std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
   if (frame->InitWithTexture(tex_id, width, height)) {
-    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
-  } else {
-    delete frame;
-    return JNI_FALSE;
+    return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
   }
+  return JNI_FALSE;
 }
 
 jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithFbo(JNIEnv* env,
@@ -82,13 +78,11 @@
                                                                   jint height) {
   GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
   if (!gl_env_ptr) return JNI_FALSE;
-  GLFrame* frame = new GLFrame(gl_env_ptr);
+  std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
   if (frame->InitWithFbo(fbo_id, width, height)) {
-    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
-  } else {
-    delete frame;
-    return JNI_FALSE;
+    return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
   }
+  return JNI_FALSE;
 }
 
 jboolean Java_android_filterfw_core_GLFrame_nativeAllocateExternal(JNIEnv* env,
@@ -96,13 +90,11 @@
                                                                    jobject gl_env) {
   GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
   if (!gl_env_ptr) return JNI_FALSE;
-  GLFrame* frame = new GLFrame(gl_env_ptr);
+  std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
   if (frame->InitWithExternalTexture()) {
-    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
-  } else {
-    delete frame;
-    return JNI_FALSE;
+    return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
   }
+  return JNI_FALSE;
 }
 
 jboolean Java_android_filterfw_core_GLFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_native_frame.cpp b/media/mca/filterfw/jni/jni_native_frame.cpp
index c8f2352..1a11a19 100644
--- a/media/mca/filterfw/jni/jni_native_frame.cpp
+++ b/media/mca/filterfw/jni/jni_native_frame.cpp
@@ -35,7 +35,8 @@
 jboolean Java_android_filterfw_core_NativeFrame_nativeAllocate(JNIEnv* env,
                                                                jobject thiz,
                                                                jint size) {
-  return ToJBool(WrapObjectInJava(new NativeFrame(size), env, thiz, true));
+  std::unique_ptr<NativeFrame> frame(new NativeFrame(size));
+  return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_NativeFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_native_program.cpp b/media/mca/filterfw/jni/jni_native_program.cpp
index b30b769..1424607 100644
--- a/media/mca/filterfw/jni/jni_native_program.cpp
+++ b/media/mca/filterfw/jni/jni_native_program.cpp
@@ -28,7 +28,8 @@
 using android::filterfw::NativeProgram;
 
 jboolean Java_android_filterfw_core_NativeProgram_allocate(JNIEnv* env, jobject thiz) {
-  return ToJBool(WrapObjectInJava(new NativeProgram(), env, thiz, true));
+  std::unique_ptr<NativeProgram> program(new NativeProgram());
+  return ToJBool(WrapOwnedObjectInJava(std::move(program), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_NativeProgram_deallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_shader_program.cpp b/media/mca/filterfw/jni/jni_shader_program.cpp
index 19f43cd..98be04c 100644
--- a/media/mca/filterfw/jni/jni_shader_program.cpp
+++ b/media/mca/filterfw/jni/jni_shader_program.cpp
@@ -46,21 +46,14 @@
   // Create the shader
   if (!fragment_shader || !gl_env_ptr)
     return false;
-  else if (!vertex_shader)
-    return ToJBool(WrapObjectInJava(new ShaderProgram(
-      gl_env_ptr,
-      ToCppString(env, fragment_shader)),
-      env,
-      thiz,
-      true));
+
+  std::unique_ptr<ShaderProgram> shader;
+  if (!vertex_shader)
+    shader.reset(new ShaderProgram(gl_env_ptr, ToCppString(env, fragment_shader)));
   else
-    return ToJBool(WrapObjectInJava(new ShaderProgram(
-      gl_env_ptr,
-      ToCppString(env, vertex_shader),
-      ToCppString(env, fragment_shader)),
-      env,
-      thiz,
-      true));
+    shader.reset(new ShaderProgram(gl_env_ptr, ToCppString(env, vertex_shader),
+                                   ToCppString(env, fragment_shader)));
+  return ToJBool(WrapOwnedObjectInJava(std::move(shader), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_ShaderProgram_deallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_util.h b/media/mca/filterfw/jni/jni_util.h
index 11c0871..803ed29 100644
--- a/media/mca/filterfw/jni/jni_util.h
+++ b/media/mca/filterfw/jni/jni_util.h
@@ -16,6 +16,7 @@
 
 #include <jni.h>
 
+#include <memory>
 #include <unordered_map>
 #include <string>
 
@@ -214,6 +215,17 @@
   return pool ? pool->WrapObject(c_object, env, j_object, owns) : false;
 }
 
+// Calls WrapObjectInJava, safely freeing c_object if object creation fails.
+template<typename T>
+bool WrapOwnedObjectInJava(std::unique_ptr<T> c_object, JNIEnv* env,
+                           jobject j_object, bool owns) {
+  if (!WrapObjectInJava<T>(c_object.get(), env, j_object, owns))
+    return false;
+  // If we succeeded, a Java object now owns our c object; don't free it.
+  c_object.release();
+  return true;
+}
+
 // Creates a new Java instance, which wraps the passed C++ instance. Returns
 // the wrapped object or JNI_NULL if there was an error. Pass true to owns, if
 // the Java layer should own the object.
diff --git a/media/mca/filterfw/jni/jni_vertex_frame.cpp b/media/mca/filterfw/jni/jni_vertex_frame.cpp
index caae938..d0439fe 100644
--- a/media/mca/filterfw/jni/jni_vertex_frame.cpp
+++ b/media/mca/filterfw/jni/jni_vertex_frame.cpp
@@ -24,7 +24,8 @@
 jboolean Java_android_filterfw_core_VertexFrame_nativeAllocate(JNIEnv* env,
                                                                jobject thiz,
                                                                jint size) {
-  return ToJBool(WrapObjectInJava(new VertexFrame(size), env, thiz, true));
+  std::unique_ptr<VertexFrame> frame(new VertexFrame(size));
+  return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_VertexFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
new file mode 100644
index 0000000..651a7cb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 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.drawer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class TileUtilsTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Test
+    public void getTilesForIntent_shouldParseCategory() {
+        final String testCategory = "category1";
+        Intent intent = new Intent();
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        info.add(newInfo(true, testCategory));
+
+        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+                null /* defaultCategory */, outTiles, false /* usePriority */,
+                false /* checkCategory */);
+
+        assertThat(outTiles.size()).isEqualTo(1);
+        assertThat(outTiles.get(0).category).isEqualTo(testCategory);
+    }
+
+    @Test
+    public void getTilesForIntent_shouldSkipNonSystemApp() {
+        final String testCategory = "category1";
+        Intent intent = new Intent();
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        info.add(newInfo(false, testCategory));
+
+        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+                null /* defaultCategory */, outTiles, false /* usePriority */,
+                false /* checkCategory */);
+
+        assertThat(outTiles.isEmpty()).isTrue();
+    }
+
+    private ResolveInfo newInfo(boolean systemApp, String category) {
+        ResolveInfo info = new ResolveInfo();
+        info.system = systemApp;
+        info.activityInfo = new ActivityInfo();
+        info.activityInfo.packageName = "abc";
+        info.activityInfo.name = "123";
+        info.activityInfo.metaData = new Bundle();
+        info.activityInfo.metaData.putString("com.android.settings.category", category);
+        info.activityInfo.applicationInfo = new ApplicationInfo();
+        return info;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 995f4dd..29c0705 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -137,7 +137,8 @@
 
     private void applyFocusableFlag(State state) {
         boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
-        if (state.bouncerShowing || BaseStatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
+        if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
+                || BaseStatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index de70026..5eceb9f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5342,6 +5342,7 @@
     // notify only this one new request of the current state
     protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
         int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
+        mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
         if (nri.mPendingIntent == null) {
             callCallbackForRequest(nri, nai, notifyType, 0);
         } else {
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index f4ddc06..6701431 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -249,16 +249,13 @@
         try {
             randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
             String newPassword = String.valueOf(HexEncoding.encode(randomLockSeed));
-            tieProfileLockToParent(managedUserId, newPassword);
             setLockPasswordInternal(newPassword, managedUserPassword, managedUserId);
             // We store a private credential for the managed user that's unlocked by the primary
             // account holder's credential. As such, the user will never be prompted to enter this
             // password directly, so we always store a password.
             setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
                     DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, managedUserId);
-        } catch (KeyStoreException e) {
-            // Bug: 32490092
-            Slog.e(TAG, "Not able to set keys to keystore", e);
+            tieProfileLockToParent(managedUserId, newPassword);
         } catch (NoSuchAlgorithmException | RemoteException e) {
             Slog.e(TAG, "Fail to tie managed profile", e);
             // Nothing client can do to fix this issue, so we do not throw exception out
@@ -779,7 +776,6 @@
     }
 
     private void unlockChildProfile(int profileHandle) throws RemoteException {
-        if (DEBUG) Slog.v(TAG, "Unlock child profile");
         try {
             doVerifyPassword(getDecryptedPasswordForTiedProfile(profileHandle), false,
                     0 /* no challenge */, profileHandle, null /* progressCallback */);
@@ -1039,7 +1035,7 @@
         }
     }
 
-    private void tieProfileLockToParent(int userId, String password) throws KeyStoreException {
+    private void tieProfileLockToParent(int userId, String password) {
         if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
         byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
         byte[] encryptionResult;
@@ -1081,7 +1077,7 @@
                 keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId);
             }
         } catch (CertificateException | UnrecoverableKeyException
-                | IOException | BadPaddingException | IllegalBlockSizeException
+                | IOException | BadPaddingException | IllegalBlockSizeException | KeyStoreException
                 | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
             throw new RuntimeException("Failed to encrypt key", e);
         }
@@ -1223,11 +1219,7 @@
         } finally {
             if (managedUserId != -1 && managedUserDecryptedPassword != null) {
                 if (DEBUG) Slog.v(TAG, "Restore tied profile lock");
-                try {
-                    tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
-                } catch (KeyStoreException e) {
-                    throw new RuntimeException("Failed to tie profile lock", e);
-                }
+                tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
             }
         }
     }
diff --git a/services/core/java/com/android/server/accounts/AccountsDb.java b/services/core/java/com/android/server/accounts/AccountsDb.java
index cb594f6..a160b3a 100644
--- a/services/core/java/com/android/server/accounts/AccountsDb.java
+++ b/services/core/java/com/android/server/accounts/AccountsDb.java
@@ -54,7 +54,7 @@
     private static final String DATABASE_NAME = "accounts.db";
     private static final int PRE_N_DATABASE_VERSION = 9;
     private static final int CE_DATABASE_VERSION = 10;
-    private static final int DE_DATABASE_VERSION = 1;
+    private static final int DE_DATABASE_VERSION = 2; // Added visibility support in O.
 
 
     static final String TABLE_ACCOUNTS = "accounts";
@@ -73,6 +73,11 @@
     private static final String AUTHTOKENS_TYPE = "type";
     private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
 
+    private static final String TABLE_VISIBILITY = "visibility";
+    private static final String VISIBILITY_ACCOUNTS_ID = "accounts_id";
+    private static final String VISIBILITY_UID = "_uid";
+    private static final String VISIBILITY_VALUE = "value";
+
     private static final String TABLE_GRANTS = "grants";
     private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
     private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
@@ -153,14 +158,12 @@
             + " AND " + ACCOUNTS_NAME + "=?"
             + " AND " + ACCOUNTS_TYPE + "=?";
 
-    private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
-            AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
+    private static final String SELECTION_ACCOUNTS_ID_BY_ACCOUNT =
+        "accounts_id=(select _id FROM accounts WHERE name=? AND type=?)";
 
-    private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
-            AUTHTOKENS_AUTHTOKEN};
+    private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN =
+            {AUTHTOKENS_TYPE, AUTHTOKENS_AUTHTOKEN};
 
-    private static final String SELECTION_USERDATA_BY_ACCOUNT =
-            EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
     private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
 
     private static final String ACCOUNT_ACCESS_GRANTS = ""
@@ -334,7 +337,7 @@
         HashMap<String, String> authTokensForAccount = new HashMap<>();
         Cursor cursor = db.query(CE_TABLE_AUTHTOKENS,
                 COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
-                SELECTION_AUTHTOKENS_BY_ACCOUNT,
+                SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
                 new String[] {account.name, account.type},
                 null, null, null);
         try {
@@ -438,7 +441,7 @@
         String[] selectionArgs = {account.name, account.type};
         try (Cursor cursor = db.query(CE_TABLE_EXTRAS,
                 COLUMNS_EXTRAS_KEY_AND_VALUE,
-                SELECTION_USERDATA_BY_ACCOUNT,
+                SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
                 selectionArgs,
                 null, null, null)) {
             while (cursor.moveToNext()) {
@@ -523,6 +526,8 @@
             createSharedAccountsTable(db);
             createAccountsDeletionTrigger(db);
             createDebugTable(db);
+            createAccountsVisibilityTable(db);
+            createAccountsDeletionVisibilityCleanupTrigger(db);
         }
 
         private void createSharedAccountsTable(SQLiteDatabase db) {
@@ -551,6 +556,14 @@
                     +   "," + GRANTS_GRANTEE_UID + "))");
         }
 
+        private void createAccountsVisibilityTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_VISIBILITY + " ( "
+                  + VISIBILITY_ACCOUNTS_ID + " INTEGER NOT NULL, "
+                  + VISIBILITY_UID + " TEXT NOT NULL, "
+                  + VISIBILITY_VALUE + " INTEGER, "
+                  + "PRIMARY KEY(" + VISIBILITY_ACCOUNTS_ID + "," + VISIBILITY_UID + "))");
+        }
+
         static void createDebugTable(SQLiteDatabase db) {
             db.execSQL("CREATE TABLE " + TABLE_DEBUG + " ( "
                     + ACCOUNTS_ID + " INTEGER,"
@@ -563,10 +576,26 @@
                     + DEBUG_TABLE_TIMESTAMP + ")");
         }
 
+        private void createAccountsDeletionVisibilityCleanupTrigger(SQLiteDatabase db) {
+            db.execSQL(""
+                   + " CREATE TRIGGER "
+                   + TABLE_ACCOUNTS + "DeleteVisibility DELETE ON " + TABLE_ACCOUNTS
+                   + " BEGIN"
+                   + "   DELETE FROM " + TABLE_VISIBILITY
+                   + "     WHERE " + VISIBILITY_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+                   + " END");
+        }
+
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
 
+            if (oldVersion == 1) {
+              createAccountsVisibilityTable(db);
+              createAccountsDeletionVisibilityCleanupTrigger(db);
+              ++oldVersion;
+            }
+
             if (oldVersion != newVersion) {
                 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
             }
@@ -861,6 +890,84 @@
                 new String[] {Integer.toString(uid)}) > 0;
     }
 
+    boolean setAccountVisibility(long accountId, int uid, int visibility) {
+        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(VISIBILITY_ACCOUNTS_ID, String.valueOf(accountId));
+        values.put(VISIBILITY_UID, String.valueOf(uid));
+        values.put(VISIBILITY_VALUE, String.valueOf(visibility));
+        return (db.replace(TABLE_VISIBILITY, VISIBILITY_VALUE, values) != -1);
+    }
+
+    Integer findAccountVisibility(Account account, int uid) {
+        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
+                SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_UID + "=? ",
+                new String[] {account.name, account.type, String.valueOf(uid)}, null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                return cursor.getInt(0);
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    Integer findAccountVisibility(long accountId, int uid) {
+        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
+                VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_UID + "=? ",
+                new String[] {String.valueOf(accountId), String.valueOf(uid)}, null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                return cursor.getInt(0);
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    Account findDeAccountByAccountId(long accountId) {
+        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+        final Cursor cursor = db.query(TABLE_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
+                ACCOUNTS_ID + "=? ", new String[] {String.valueOf(accountId)}, null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                return new Account(cursor.getString(0), cursor.getString(1));
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    /**
+     * Returns a map from uid to visibility value.
+     */
+    Map<Integer, Integer> findAccountVisibilityForAccountId(long accountId) {
+        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+        Map<Integer, Integer> result = new HashMap<>();
+        final Cursor cursor = db.query(TABLE_VISIBILITY,
+                new String[] {VISIBILITY_UID, VISIBILITY_VALUE}, VISIBILITY_ACCOUNTS_ID + "=? ",
+                new String[] {String.valueOf(accountId)}, null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                result.put(cursor.getInt(0), cursor.getInt(1));
+            }
+        } finally {
+            cursor.close();
+        }
+        return result;
+    }
+
+    boolean deleteAccountVisibilityForUid(int uid) {
+        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        return db.delete(TABLE_VISIBILITY, VISIBILITY_UID + "=? ",
+                new String[] {Integer.toString(uid)}) > 0;
+    }
+
     long insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid) {
         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
         ContentValues values = new ContentValues();
@@ -1199,4 +1306,4 @@
         return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
     }
 
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8bd7506..c8ed872 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1520,7 +1520,7 @@
     static final int PERSIST_URI_GRANTS_MSG = 38;
     static final int REQUEST_ALL_PSS_MSG = 39;
     static final int START_PROFILES_MSG = 40;
-    static final int UPDATE_TIME = 41;
+    static final int UPDATE_TIME_PREFERENCE_MSG = 41;
     static final int SYSTEM_USER_START_MSG = 42;
     static final int SYSTEM_USER_CURRENT_MSG = 43;
     static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
@@ -2026,15 +2026,18 @@
                 }
                 break;
             }
-            case UPDATE_TIME: {
+            case UPDATE_TIME_PREFERENCE_MSG: {
+                // The user's time format preference might have changed.
+                // For convenience we re-use the Intent extra values.
                 synchronized (ActivityManagerService.this) {
-                    for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+                    for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
                         ProcessRecord r = mLruProcesses.get(i);
                         if (r.thread != null) {
                             try {
-                                r.thread.updateTimePrefs(msg.arg1 == 0 ? false : true);
+                                r.thread.updateTimePrefs(msg.arg1);
                             } catch (RemoteException ex) {
-                                Slog.w(TAG, "Failed to update preferences for: " + r.info.processName);
+                                Slog.w(TAG, "Failed to update preferences for: "
+                                        + r.info.processName);
                             }
                         }
                     }
@@ -18169,11 +18172,20 @@
                     mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
                     break;
                 case Intent.ACTION_TIME_CHANGED:
-                    // If the user set the time, let all running processes know.
-                    final int is24Hour =
-                            intent.getBooleanExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, false) ? 1
-                                    : 0;
-                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME, is24Hour, 0));
+                    // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
+                    // the tri-state value it may contain and "unknown".
+                    // For convenience we re-use the Intent extra values.
+                    final int NO_EXTRA_VALUE_FOUND = -1;
+                    final int timeFormatPreferenceMsgValue = intent.getIntExtra(
+                            Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+                            NO_EXTRA_VALUE_FOUND /* defaultValue */);
+                    // Only send a message if the time preference is available.
+                    if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
+                        Message updateTimePreferenceMsg =
+                                mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
+                                        timeFormatPreferenceMsgValue, 0);
+                        mHandler.sendMessage(updateTimePreferenceMsg);
+                    }
                     BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
                     synchronized (stats) {
                         stats.noteCurrentTimeChangedLocked();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index da016da..ac9545c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -272,7 +272,8 @@
         15, // STREAM_BLUETOOTH_SCO
         7,  // STREAM_SYSTEM_ENFORCED
         15, // STREAM_DTMF
-        15  // STREAM_TTS
+        15, // STREAM_TTS
+        15  // STREAM_ACCESSIBILITY
     };
 
     /** Minimum volume index values for audio streams */
@@ -286,7 +287,8 @@
         0,  // STREAM_BLUETOOTH_SCO
         0,  // STREAM_SYSTEM_ENFORCED
         0,  // STREAM_DTMF
-        0   // STREAM_TTS
+        0,  // STREAM_TTS
+        0   // STREAM_ACCESSIBILITY
     };
 
     /* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings
@@ -308,7 +310,8 @@
         AudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCO
         AudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCED
         AudioSystem.STREAM_RING,            // STREAM_DTMF
-        AudioSystem.STREAM_MUSIC            // STREAM_TTS
+        AudioSystem.STREAM_MUSIC,           // STREAM_TTS
+        AudioSystem.STREAM_MUSIC            // STREAM_ACCESSIBILITY
     };
     private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
         AudioSystem.STREAM_MUSIC,       // STREAM_VOICE_CALL
@@ -320,7 +323,8 @@
         AudioSystem.STREAM_MUSIC,       // STREAM_BLUETOOTH_SCO
         AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM_ENFORCED
         AudioSystem.STREAM_MUSIC,       // STREAM_DTMF
-        AudioSystem.STREAM_MUSIC        // STREAM_TTS
+        AudioSystem.STREAM_MUSIC,       // STREAM_TTS
+        AudioSystem.STREAM_MUSIC        // STREAM_ACCESSIBILITY
     };
     private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
         AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
@@ -332,7 +336,8 @@
         AudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCO
         AudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCED
         AudioSystem.STREAM_RING,            // STREAM_DTMF
-        AudioSystem.STREAM_MUSIC            // STREAM_TTS
+        AudioSystem.STREAM_MUSIC,           // STREAM_TTS
+        AudioSystem.STREAM_MUSIC            // STREAM_ACCESSIBILITY
     };
     private int[] mStreamVolumeAlias;
 
@@ -351,6 +356,7 @@
         AppOpsManager.OP_AUDIO_MEDIA_VOLUME,            // STREAM_SYSTEM_ENFORCED
         AppOpsManager.OP_AUDIO_MEDIA_VOLUME,            // STREAM_DTMF
         AppOpsManager.OP_AUDIO_MEDIA_VOLUME,            // STREAM_TTS
+        AppOpsManager.OP_AUDIO_ACCESSIBILITY_VOLUME,    // STREAM_ACCESSIBILITY
     };
 
     private final boolean mUseFixedVolume;
diff --git a/services/core/java/com/android/server/pm/EphemeralResolver.java b/services/core/java/com/android/server/pm/EphemeralResolver.java
index 7ddd058..3ce5007 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolver.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolver.java
@@ -46,10 +46,6 @@
 
 /** @hide */
 public abstract class EphemeralResolver {
-
-    /** TODO b/30204367 remove when the platform fully supports ephemeral applications */
-    public static final boolean USE_DEFAULT_EPHEMERAL_LAUNCHER = false;
-
     public static EphemeralResponse doEphemeralResolutionPhaseOne(Context context,
             EphemeralResolverConnection connection, EphemeralRequest requestObj) {
         final Intent intent = requestObj.origIntent;
@@ -162,16 +158,10 @@
                         new IntentSender(failureIntentTarget));
             } catch (RemoteException ignore) { /* ignore; same process */ }
 
-            final Intent ephemeralIntent;
-            if (EphemeralResolver.USE_DEFAULT_EPHEMERAL_LAUNCHER) {
-                // Force the intent to be directed to the ephemeral package
-                ephemeralIntent = new Intent(origIntent);
-                ephemeralIntent.setPackage(ephemeralPackageName);
-            } else {
-                // Success intent goes back to the installer
-                ephemeralIntent = new Intent(launchIntent);
-            }
-
+            // Success intent goes back to the installer
+            final Intent ephemeralIntent = new Intent(launchIntent)
+                    .setComponent(null)
+                    .setPackage(ephemeralPackageName);
             // Intent that is eventually launched if the ephemeral package was
             // installed successfully. This will actually be launched by a platform
             // broadcast receiver.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ee3f42b..a8db535 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -391,19 +391,17 @@
     static final int SCAN_FORCE_DEX = 1<<2;
     static final int SCAN_UPDATE_SIGNATURE = 1<<3;
     static final int SCAN_NEW_INSTALL = 1<<4;
-    static final int SCAN_NO_PATHS = 1<<5;
-    static final int SCAN_UPDATE_TIME = 1<<6;
-    static final int SCAN_DEFER_DEX = 1<<7;
-    static final int SCAN_BOOTING = 1<<8;
-    static final int SCAN_TRUSTED_OVERLAY = 1<<9;
-    static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<10;
-    static final int SCAN_REPLACING = 1<<11;
-    static final int SCAN_REQUIRE_KNOWN = 1<<12;
-    static final int SCAN_MOVE = 1<<13;
-    static final int SCAN_INITIAL = 1<<14;
-    static final int SCAN_CHECK_ONLY = 1<<15;
-    static final int SCAN_DONT_KILL_APP = 1<<17;
-    static final int SCAN_IGNORE_FROZEN = 1<<18;
+    static final int SCAN_UPDATE_TIME = 1<<5;
+    static final int SCAN_BOOTING = 1<<6;
+    static final int SCAN_TRUSTED_OVERLAY = 1<<7;
+    static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<8;
+    static final int SCAN_REPLACING = 1<<9;
+    static final int SCAN_REQUIRE_KNOWN = 1<<10;
+    static final int SCAN_MOVE = 1<<11;
+    static final int SCAN_INITIAL = 1<<12;
+    static final int SCAN_CHECK_ONLY = 1<<13;
+    static final int SCAN_DONT_KILL_APP = 1<<14;
+    static final int SCAN_IGNORE_FROZEN = 1<<15;
 
     static final int REMOVE_CHATTY = 1<<16;
 
@@ -2219,7 +2217,7 @@
 
             // Set flag to monitor and not change apk file paths when
             // scanning install directories.
-            final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
+            final int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
 
             final String bootClassPath = System.getenv("BOOTCLASSPATH");
             final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
@@ -16223,7 +16221,8 @@
 
         final PackageParser.Package newPkg;
         try {
-            newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0, null);
+            newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, 0 /* scanFlags */,
+                0 /* currentTime */, null);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": "
                     + e.getMessage());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index eef8ce2..e330585 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2470,7 +2470,10 @@
 
         final File file = new File(dir, "appid");
         try {
-            FileUtils.stringToFile(file, Integer.toString(ps.appId));
+            // Note that the use of US_ASCII here is safe, we're only writing a decimal
+            // number to the file.
+            FileUtils.bytesToFile(file.getAbsolutePath(),
+                    Integer.toString(ps.appId).getBytes(StandardCharsets.US_ASCII));
             mKernelMapping.put(ps.name, ps.appId);
         } catch (IOException ignored) {
         }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index e1b598a..c42647e 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -411,7 +411,7 @@
         }
         if (mService.mInputMethodTarget != null
                 && mService.mInputMethodTarget.mAppToken == mAppToken) {
-            mAppToken.getDisplayContent().moveInputMethodWindowsIfNeeded(true);
+            mAppToken.getDisplayContent().computeImeTarget(true /* updateImeTarget */);
         }
 
         if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5d739d1..4569596 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1006,10 +1006,7 @@
             tStartingWindow.mToken = this;
             tStartingWindow.mAppToken = this;
 
-            if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
-                    "Removing starting window: " + tStartingWindow);
-            getDisplayContent().removeFromWindowList(tStartingWindow);
-            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+            if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
                     "Removing starting " + tStartingWindow + " from " + fromToken);
             fromToken.removeChild(tStartingWindow);
             fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
@@ -1259,18 +1256,6 @@
         }
     }
 
-    int rebuildWindowListUnchecked(int addIndex) {
-        return super.rebuildWindowList(addIndex);
-    }
-
-    @Override
-    int rebuildWindowList(int addIndex) {
-        if (mIsExiting && !waitingForReplacement()) {
-            return addIndex;
-        }
-        return rebuildWindowListUnchecked(addIndex);
-    }
-
     @Override
     boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
         // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
@@ -1332,6 +1317,32 @@
         mLastContainsShowWhenLockedWindow = containsShowWhenLocked;
     }
 
+    WindowState getImeTargetBelowWindow(WindowState w) {
+        final int index = mChildren.indexOf(w);
+        if (index > 0) {
+            final WindowState target = mChildren.get(index - 1);
+            if (target.canBeImeTarget()) {
+                return target;
+            }
+        }
+        return null;
+    }
+
+    WindowState getHighestAnimLayerWindow(WindowState currentTarget) {
+        WindowState candidate = null;
+        for (int i = mChildren.indexOf(currentTarget); i >= 0; i--) {
+            final WindowState w = mChildren.get(i);
+            if (w.mRemoved) {
+                continue;
+            }
+            if (candidate == null || w.mWinAnimator.mAnimLayer >
+                    candidate.mWinAnimator.mAnimLayer) {
+                candidate = w;
+            }
+        }
+        return candidate;
+    }
+
     @Override
     void dump(PrintWriter pw, String prefix) {
         super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e73acde..24b9d69 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -33,10 +33,6 @@
 import static android.view.WindowManager.DOCKED_BOTTOM;
 import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_TOP;
-import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
-import static android.view.WindowManager.INPUT_CONSUMER_PIP;
-import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -44,13 +40,10 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE;
 import static android.view.WindowManager.LayoutParams.NEEDS_MENU_UNSET;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -64,7 +57,6 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
@@ -77,10 +69,8 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -94,7 +84,6 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.dipToPixel;
-import static com.android.server.wm.WindowManagerService.localLOGV;
 import static com.android.server.wm.WindowManagerService.logSurface;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
 import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
@@ -120,29 +109,21 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.IWindow;
-import android.view.InputChannel;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManagerPolicy;
 
-import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.view.IInputMethodClient;
-import com.android.server.input.InputWindowHandle;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
 
 /**
  * Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -174,8 +155,7 @@
     private final NonAppWindowContainers mImeWindowsContainers =
             new NonAppWindowContainers("mImeWindowsContainers");
 
-    // Z-ordered (bottom-most first) list of all Window objects.
-    private final WindowList mWindows = new WindowList();
+    private WindowState mTmpWindow;
 
     // Mapping from a token IBinder to a WindowToken object on this display.
     private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
@@ -232,21 +212,19 @@
 
     final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
 
-    /** Used when rebuilding window list to keep track of windows that have been removed. */
-    private WindowState[] mRebuildTmp = new WindowState[20];
-
-    /**
-     * Temporary list for comparison. Always clear this after use so we don't end up with
-     * orphaned windows references
-     */
-    private final ArrayList<WindowState> mTmpWindows = new ArrayList<>();
+    private boolean mHaveBootMsg = false;
+    private boolean mHaveApp = false;
+    private boolean mHaveWallpaper = false;
+    private boolean mHaveKeyguard = true;
 
     private final LinkedList<AppWindowToken> mTmpUpdateAllDrawn = new LinkedList();
 
     private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult =
             new TaskForResizePointSearchResult();
-    private final GetWindowOnDisplaySearchResult mTmpGetWindowOnDisplaySearchResult =
-            new GetWindowOnDisplaySearchResult();
+    private final ApplySurfaceChangesTransactionState mTmpApplySurfaceChangesTransactionState =
+            new ApplySurfaceChangesTransactionState();
+    private final ScreenshotApplicationState mScreenshotApplicationState =
+            new ScreenshotApplicationState();
 
     // True if this display is in the process of being removed. Used to determine if the removal of
     // the display's direct children should be allowed.
@@ -455,17 +433,13 @@
     @Override
     void onAppTransitionDone() {
         super.onAppTransitionDone();
-        rebuildAppWindowList();
+        mService.mWindowsChanged = true;
     }
 
     @Override
     int getOrientation() {
         final WindowManagerPolicy policy = mService.mPolicy;
 
-        // TODO: All the logic before the last return statement in this method should really go in
-        // #NonAppWindowContainer.getOrientation() since it is trying to decide orientation based
-        // on non-app windows. But, we can not do that until the window list is always correct in
-        // terms of z-ordering based on layers.
         if (mService.mDisplayFrozen) {
             if (mService.mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
                 if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
@@ -486,31 +460,9 @@
                 return mService.mLastOrientation;
             }
         } else {
-            for (int pos = mWindows.size() - 1; pos >= 0; --pos) {
-                final WindowState win = mWindows.get(pos);
-                if (win.mAppToken != null) {
-                    // We hit an application window. so the orientation will be determined by the
-                    // app window. No point in continuing further.
-                    break;
-                }
-                if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) {
-                    continue;
-                }
-                int req = win.mAttrs.screenOrientation;
-                if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND) {
-                    continue;
-                }
-
-                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req);
-                if (policy.isKeyguardHostWindow(win.mAttrs)) {
-                    mService.mLastKeyguardForcedOrientation = req;
-                }
-                return (mService.mLastWindowForcedOrientation = req);
-            }
-            mService.mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-
-            if (policy.isKeyguardShowingAndNotOccluded()) {
-                return mService.mLastKeyguardForcedOrientation;
+            final int orientation = mAboveAppWindowsContainers.getOrientation();
+            if (orientation != SCREEN_ORIENTATION_UNSET) {
+                return orientation;
             }
         }
 
@@ -738,22 +690,10 @@
         }
     }
 
+    @Override
     void switchUser() {
-        final int count = mWindows.size();
-        for (int i = 0; i < count; i++) {
-            final WindowState win = mWindows.get(i);
-            if (win.isHiddenFromUserLocked()) {
-                if (DEBUG_VISIBILITY) Slog.w(TAG_WM, "user changing, hiding " + win
-                        + ", attrs=" + win.mAttrs.type + ", belonging to " + win.mOwnerUid);
-                win.hideLw(false);
-            }
-        }
-
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            mTaskStackContainers.get(stackNdx).switchUser();
-        }
-
-        rebuildAppWindowList();
+        super.switchUser();
+        mService.mWindowsChanged = true;
     }
 
     private void resetAnimationBackgroundAnimator() {
@@ -915,19 +855,9 @@
     void setInputMethodAnimLayerAdjustment(int adj) {
         if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj);
         mInputMethodAnimLayerAdjustment = adj;
-        final WindowState imw = mService.mInputMethodWindow;
-        if (imw != null) {
-            imw.adjustAnimLayer(adj);
-        }
-        for (int i = mService.mInputMethodDialogs.size() - 1; i >= 0; i--) {
-            final WindowState dialog = mService.mInputMethodDialogs.get(i);
-            // TODO: This and other places setting mAnimLayer can probably use WS.adjustAnimLayer,
-            // but need to make sure we are not setting things twice for child windows that are
-            // already in the list.
-            dialog.mWinAnimator.mAnimLayer = dialog.mLayer + adj;
-            if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw
-                    + " anim layer: " + dialog.mWinAnimator.mAnimLayer);
-        }
+        mImeWindowsContainers.forAllWindows(w -> {
+            w.adjustAnimLayer(adj);
+        }, true /* traverseTopToBottom */);
     }
 
     /**
@@ -936,11 +866,11 @@
      * suddenly disappear.
      */
     int getLayerForAnimationBackground(WindowStateAnimator winAnimator) {
-        for (int i = mWindows.size() - 1; i >= 0; --i) {
-            final WindowState win = mWindows.get(i);
-            if (win.mIsWallpaper && win.isVisibleNow()) {
-                return win.mWinAnimator.mAnimLayer;
-            }
+        final WindowState visibleWallpaper = mBelowAppWindowsContainers.getWindow(
+                w -> w.mIsWallpaper && w.isVisibleNow());
+
+        if (visibleWallpaper != null) {
+            return visibleWallpaper.mWinAnimator.mAnimLayer;
         }
         return winAnimator.mAnimLayer;
     }
@@ -1088,48 +1018,37 @@
 
     /** Find the visible, touch-deliverable window under the given point */
     WindowState getTouchableWinAtPointLocked(float xf, float yf) {
-        WindowState touchedWin = null;
         final int x = (int) xf;
         final int y = (int) yf;
-
-        for (int i = mWindows.size() - 1; i >= 0; i--) {
-            WindowState window = mWindows.get(i);
-            final int flags = window.mAttrs.flags;
-            if (!window.isVisibleLw()) {
-                continue;
+        final WindowState touchedWin = getWindow(w -> {
+            final int flags = w.mAttrs.flags;
+            if (!w.isVisibleLw()) {
+                return false;
             }
             if ((flags & FLAG_NOT_TOUCHABLE) != 0) {
-                continue;
+                return false;
             }
 
-            window.getVisibleBounds(mTmpRect);
+            w.getVisibleBounds(mTmpRect);
             if (!mTmpRect.contains(x, y)) {
-                continue;
+                return false;
             }
 
-            window.getTouchableRegion(mTmpRegion);
+            w.getTouchableRegion(mTmpRegion);
 
             final int touchFlags = flags & (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL);
-            if (mTmpRegion.contains(x, y) || touchFlags == 0) {
-                touchedWin = window;
-                break;
-            }
-        }
+            return mTmpRegion.contains(x, y) || touchFlags == 0;
+        });
 
         return touchedWin;
     }
 
     boolean canAddToastWindowForUid(int uid) {
         // We allow one toast window per UID being shown at a time.
-        final int windowCount = mWindows.size();
-        for (int i = 0; i < windowCount; i++) {
-            final WindowState window = mWindows.get(i);
-            if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == uid
-                    && !window.mPermanentlyHidden && !window.mWindowRemovalAllowed) {
-                return false;
-            }
-        }
-        return true;
+        final WindowState win = getWindow(w ->
+                w.mAttrs.type == TYPE_TOAST && w.mOwnerUid == uid && !w.mPermanentlyHidden
+                && !w.mWindowRemovalAllowed);
+        return win == null;
     }
 
     void scheduleToastWindowsTimeoutIfNeededLocked(WindowState oldFocus, WindowState newFocus) {
@@ -1137,268 +1056,76 @@
             return;
         }
         final int lostFocusUid = oldFocus.mOwnerUid;
-        final int windowCount = mWindows.size();
         final Handler handler = mService.mH;
-        for (int i = 0; i < windowCount; i++) {
-            final WindowState window = mWindows.get(i);
-            if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == lostFocusUid) {
-                if (!handler.hasMessages(WINDOW_HIDE_TIMEOUT, window)) {
-                    handler.sendMessageDelayed(handler.obtainMessage(WINDOW_HIDE_TIMEOUT, window),
-                            window.mAttrs.hideTimeoutMilliseconds);
+
+        forAllWindows(w -> {
+            if (w.mAttrs.type == TYPE_TOAST && w.mOwnerUid == lostFocusUid) {
+                if (!handler.hasMessages(WINDOW_HIDE_TIMEOUT, w)) {
+                    handler.sendMessageDelayed(handler.obtainMessage(WINDOW_HIDE_TIMEOUT, w),
+                            w.mAttrs.hideTimeoutMilliseconds);
                 }
             }
-        }
+        }, false /* traverseTopToBottom */);
     }
 
     WindowState findFocusedWindow() {
         final AppWindowToken focusedApp = mService.mFocusedApp;
+        mTmpWindow = null;
 
-        for (int i = mWindows.size() - 1; i >= 0; i--) {
-            final WindowState win = mWindows.get(i);
+        forAllWindows(w -> {
+            if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + w
+                    + ", flags=" + w.mAttrs.flags + ", canReceive=" + w.canReceiveKeys());
 
-            if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + i + " = " + win
-                    + ", flags=" + win.mAttrs.flags + ", canReceive=" + win.canReceiveKeys());
-
-            if (!win.canReceiveKeys()) {
-                continue;
+            if (!w.canReceiveKeys()) {
+                return false;
             }
 
-            final AppWindowToken wtoken = win.mAppToken;
+            final AppWindowToken wtoken = w.mAppToken;
 
             // If this window's application has been removed, just skip it.
             if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) {
                 if (DEBUG_FOCUS) Slog.v(TAG_WM, "Skipping " + wtoken + " because "
                         + (wtoken.removed ? "removed" : "sendingToBottom"));
-                continue;
+                return false;
             }
 
             if (focusedApp == null) {
                 if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp=null"
-                        + " using new focus @ " + i + " = " + win);
-                return win;
+                        + " using new focus @ " + w);
+                mTmpWindow = w;
+                return true;
             }
 
             if (!focusedApp.windowsAreFocusable()) {
                 // Current focused app windows aren't focusable...
                 if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp windows not"
-                        + " focusable using new focus @ " + i + " = " + win);
-                return win;
+                        + " focusable using new focus @ " + w);
+                mTmpWindow = w;
+                return true;
             }
 
             // Descend through all of the app tokens and find the first that either matches
             // win.mAppToken (return win) or mFocusedApp (return null).
-            if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING) {
+            if (wtoken != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {
                 if (focusedApp.compareTo(wtoken) > 0) {
                     // App stack below focused app stack. No focus for you!!!
                     if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
                             "findFocusedWindow: Reached focused app=" + focusedApp);
-                    return null;
+                    mTmpWindow = null;
+                    return true;
                 }
             }
 
-            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ "
-                    + i + " = " + win);
-            return win;
+            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ " + w);
+            mTmpWindow = w;
+            return true;
+        }, true /* traverseTopToBottom */);
+
+        if (mTmpWindow == null) {
+            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
+            return null;
         }
-
-        if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
-        return null;
-    }
-
-    void addAppWindowToWindowList(final WindowState win) {
-        final IWindow client = win.mClient;
-
-        WindowList tokenWindowList = getTokenWindowsOnDisplay(win.mToken);
-        if (!tokenWindowList.isEmpty()) {
-            addAppWindowExisting(win, tokenWindowList);
-            return;
-        }
-
-        // No windows from this token on this display
-        if (localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window "
-                + client.asBinder() + " (token=" + this + ")");
-
-        final WindowToken wToken = win.mToken;
-
-        // Figure out where the window should go, based on the order of applications.
-        mTmpGetWindowOnDisplaySearchResult.reset();
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
-            stack.getWindowOnDisplayBeforeToken(this, wToken, mTmpGetWindowOnDisplaySearchResult);
-            if (mTmpGetWindowOnDisplaySearchResult.reachedToken) {
-                // We have reach the token we are interested in. End search.
-                break;
-            }
-        }
-
-        WindowState pos = mTmpGetWindowOnDisplaySearchResult.foundWindow;
-
-        // We now know the index into the apps. If we found an app window above, that gives us the
-        // position; else we need to look some more.
-        if (pos != null) {
-            // Move behind any windows attached to this one.
-            final WindowToken atoken = getWindowToken(pos.mClient.asBinder());
-            if (atoken != null) {
-                tokenWindowList = getTokenWindowsOnDisplay(atoken);
-                final int NC = tokenWindowList.size();
-                if (NC > 0) {
-                    WindowState bottom = tokenWindowList.get(0);
-                    if (bottom.mSubLayer < 0) {
-                        pos = bottom;
-                    }
-                }
-            }
-            addWindowToListBefore(win, pos);
-            return;
-        }
-
-        // Continue looking down until we find the first token that has windows on this display.
-        mTmpGetWindowOnDisplaySearchResult.reset();
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
-            stack.getWindowOnDisplayAfterToken(this, wToken, mTmpGetWindowOnDisplaySearchResult);
-            if (mTmpGetWindowOnDisplaySearchResult.foundWindow != null) {
-                // We have found a window after the token. End search.
-                break;
-            }
-        }
-
-        pos = mTmpGetWindowOnDisplaySearchResult.foundWindow;
-
-        if (pos != null) {
-            // Move in front of any windows attached to this one.
-            final WindowToken atoken = getWindowToken(pos.mClient.asBinder());
-            if (atoken != null) {
-                final WindowState top = atoken.getTopWindow();
-                if (top != null && top.mSubLayer >= 0) {
-                    pos = top;
-                }
-            }
-            addWindowToListAfter(win, pos);
-            return;
-        }
-
-        // Just search for the start of this layer.
-        final int myLayer = win.mBaseLayer;
-        int i;
-        for (i = mWindows.size() - 1; i >= 0; --i) {
-            final WindowState w = mWindows.get(i);
-            // Dock divider shares the base layer with application windows, but we want to always
-            // keep it above the application windows. The sharing of the base layer is intended
-            // for window animations, which need to be above the dock divider for the duration
-            // of the animation.
-            if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) {
-                break;
-            }
-        }
-        if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                "Based on layer: Adding window " + win + " at " + (i + 1) + " of "
-                + mWindows.size());
-        mWindows.add(i + 1, win);
-        mService.mWindowsChanged = true;
-    }
-
-    /** Adds this non-app window to the window list. */
-    void addNonAppWindowToWindowList(WindowState win) {
-        // Figure out where window should go, based on layer.
-        int i;
-        for (i = mWindows.size() - 1; i >= 0; i--) {
-            final WindowState otherWin = mWindows.get(i);
-            if (otherWin.getBaseType() != TYPE_WALLPAPER && otherWin.mBaseLayer <= win.mBaseLayer) {
-                // Wallpaper wanders through the window list, for example to position itself
-                // directly behind keyguard. Because of this it will break the ordering based on
-                // WindowState.mBaseLayer. There might windows with higher mBaseLayer behind it and
-                // we don't want the new window to appear above them. An example of this is adding
-                // of the docked stack divider. Consider a scenario with the following ordering (top
-                // to bottom): keyguard, wallpaper, assist preview, apps. We want the dock divider
-                // to land below the assist preview, so the dock divider must ignore the wallpaper,
-                // with which it shares the base layer.
-                break;
-            }
-        }
-
-        i++;
-        if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                "Free window: Adding window " + this + " at " + i + " of " + mWindows.size());
-        mWindows.add(i, win);
-        mService.mWindowsChanged = true;
-    }
-
-    void addToWindowList(WindowState win, int index) {
-        mService.mWindowsChanged = true;
-        mWindows.add(index, win);
-    }
-
-    boolean removeFromWindowList(WindowState win) {
-        mService.mWindowsChanged = true;
-        return mWindows.remove(win);
-    }
-
-    private int removeWindowAndChildrenFromWindowList(WindowState win, int interestingPos) {
-        int wpos = mWindows.indexOf(win);
-        if (wpos < 0) {
-            return interestingPos;
-        }
-
-        if (wpos < interestingPos) interestingPos--;
-        if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Temp removing at " + wpos + ": " + this);
-        mWindows.remove(wpos);
-        mService.mWindowsChanged = true;
-        int childWinCount = win.mChildren.size();
-        while (childWinCount > 0) {
-            childWinCount--;
-            final WindowState cw = win.mChildren.get(childWinCount);
-            int cpos = mWindows.indexOf(cw);
-            if (cpos >= 0) {
-                if (cpos < interestingPos) interestingPos--;
-                if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
-                        "Temp removing child at " + cpos + ": " + cw);
-                mWindows.remove(cpos);
-            }
-        }
-        return interestingPos;
-    }
-
-    void addChildWindowToWindowList(WindowState win) {
-        final WindowState parentWindow = win.getParentWindow();
-
-        WindowList windowsOnSameDisplay = getTokenWindowsOnDisplay(win.mToken);
-
-        // Figure out this window's ordering relative to the parent window.
-        final int wCount = windowsOnSameDisplay.size();
-        final int sublayer = win.mSubLayer;
-        int largestSublayer = Integer.MIN_VALUE;
-        WindowState windowWithLargestSublayer = null;
-        int i;
-        for (i = 0; i < wCount; i++) {
-            WindowState w = windowsOnSameDisplay.get(i);
-            final int wSublayer = w.mSubLayer;
-            if (wSublayer >= largestSublayer) {
-                largestSublayer = wSublayer;
-                windowWithLargestSublayer = w;
-            }
-            if (sublayer < 0) {
-                // For negative sublayers, we go below all windows in the same sublayer.
-                if (wSublayer >= sublayer) {
-                    addWindowToListBefore(win, wSublayer >= 0 ? parentWindow : w);
-                    break;
-                }
-            } else {
-                // For positive sublayers, we go above all windows in the same sublayer.
-                if (wSublayer > sublayer) {
-                    addWindowToListBefore(win, w);
-                    break;
-                }
-            }
-        }
-        if (i >= wCount) {
-            if (sublayer < 0) {
-                addWindowToListBefore(win, parentWindow);
-            } else {
-                addWindowToListAfter(win,
-                        largestSublayer >= 0 ? windowWithLargestSublayer : parentWindow);
-            }
-        }
+        return mTmpWindow;
     }
 
     /** Updates the layer assignment of windows on this display. */
@@ -1409,136 +1136,9 @@
         }
     }
 
-    void adjustWallpaperWindows() {
-        mWallpaperController.adjustWallpaperWindows(this);
-    }
-
-    /**
-     * Z-orders the display window list so that:
-     * <ul>
-     * <li>Any windows that are currently below the wallpaper window stay below the wallpaper
-     *      window.
-     * <li>Exiting application windows are at the bottom, but above the wallpaper window.
-     * <li>All other application windows are above the exiting application windows and ordered based
-     *      on the ordering of their stacks and tasks on the display.
-     * <li>Non-application windows are at the very top.
-     * </ul>
-     * <p>
-     * NOTE: This isn't a complete picture of what the user see. Further manipulation of the window
-     *       surface layering is done in {@link WindowLayersController}.
-     */
-    void rebuildAppWindowList() {
-        int count = mWindows.size();
-        int i;
-        int lastBelow = -1;
-        int numRemoved = 0;
-
-        if (mRebuildTmp.length < count) {
-            mRebuildTmp = new WindowState[count + 10];
-        }
-
-        // First remove all existing app windows.
-        i = 0;
-        while (i < count) {
-            final WindowState w = mWindows.get(i);
-            if (w.mAppToken != null) {
-                final WindowState win = mWindows.remove(i);
-                win.mRebuilding = true;
-                mRebuildTmp[numRemoved] = win;
-                mService.mWindowsChanged = true;
-                if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Rebuild removing window: " + win);
-                count--;
-                numRemoved++;
-                continue;
-            } else if (lastBelow == i-1) {
-                if (w.mAttrs.type == TYPE_WALLPAPER) {
-                    lastBelow = i;
-                }
-            }
-            i++;
-        }
-
-        // Keep whatever windows were below the app windows still below, by skipping them.
-        lastBelow++;
-        i = lastBelow;
-
-        // First add all of the exiting app tokens...  these are no longer in the main app list,
-        // but still have windows shown. We put them in the back because now that the animation is
-        // over we no longer will care about them.
-        final int numStacks = mTaskStackContainers.size();
-        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-            AppTokenList exitingAppTokens = mTaskStackContainers.get(stackNdx).mExitingAppTokens;
-            int NT = exitingAppTokens.size();
-            for (int j = 0; j < NT; j++) {
-                i = exitingAppTokens.get(j).rebuildWindowListUnchecked(i);
-            }
-        }
-
-        // And add in the still active app tokens in Z order.
-        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-            i = mTaskStackContainers.get(stackNdx).rebuildWindowList(i);
-        }
-
-        i -= lastBelow;
-        if (i != numRemoved) {
-            setLayoutNeeded();
-            Slog.w(TAG_WM, "On display=" + mDisplayId + " Rebuild removed " + numRemoved
-                    + " windows but added " + i + " rebuildAppWindowListLocked() "
-                    + " callers=" + Debug.getCallers(10));
-            for (i = 0; i < numRemoved; i++) {
-                WindowState ws = mRebuildTmp[i];
-                if (ws.mRebuilding) {
-                    StringWriter sw = new StringWriter();
-                    PrintWriter pw = new FastPrintWriter(sw, false, 1024);
-                    ws.dump(pw, "", true);
-                    pw.flush();
-                    Slog.w(TAG_WM, "This window was lost: " + ws);
-                    Slog.w(TAG_WM, sw.toString());
-                    ws.mWinAnimator.destroySurfaceLocked();
-                }
-            }
-            Slog.w(TAG_WM, "Current window hierarchy:");
-            dumpChildrenNames();
-            Slog.w(TAG_WM, "Final window list:");
-            dumpWindows();
-        }
-        Arrays.fill(mRebuildTmp, null);
-    }
-
-    /** Rebuilds the display's window list and does a relayout if something changed. */
-    void rebuildAppWindowsAndLayoutIfNeeded() {
-        mTmpWindows.clear();
-        mTmpWindows.addAll(mWindows);
-
-        rebuildAppWindowList();
-
-        // Set displayContent.mLayoutNeeded if window order changed.
-        final int tmpSize = mTmpWindows.size();
-        final int winSize = mWindows.size();
-        int tmpNdx = 0, winNdx = 0;
-        while (tmpNdx < tmpSize && winNdx < winSize) {
-            // Skip over all exiting windows, they've been moved out of order.
-            WindowState tmp;
-            do {
-                tmp = mTmpWindows.get(tmpNdx++);
-            } while (tmpNdx < tmpSize && tmp.mAppToken != null && tmp.mAppToken.mIsExiting);
-
-            WindowState win;
-            do {
-                win = mWindows.get(winNdx++);
-            } while (winNdx < winSize && win.mAppToken != null && win.mAppToken.mIsExiting);
-
-            if (tmp != win) {
-                // Window order changed.
-                setLayoutNeeded();
-                break;
-            }
-        }
-        if (tmpNdx != winNdx) {
-            // One list was different from the other.
-            setLayoutNeeded();
-        }
-        mTmpWindows.clear();
+    void layoutAndAssignWindowLayersIfNeeded() {
+        mService.mWindowsChanged = true;
+        setLayoutNeeded();
 
         if (!mService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
                 false /*updateInputWindows*/)) {
@@ -1550,321 +1150,69 @@
         mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
     }
 
-    void updateInputWindows(InputMonitor inputMonitor, WindowState inputFocus, boolean inDrag) {
-        final InputConsumerImpl navInputConsumer =
-                mService.mInputMonitor.getInputConsumer(INPUT_CONSUMER_NAVIGATION, mDisplayId);
-        final InputConsumerImpl pipInputConsumer =
-                mService.mInputMonitor.getInputConsumer(INPUT_CONSUMER_PIP, mDisplayId);
-        final InputConsumerImpl wallpaperInputConsumer =
-                mService.mInputMonitor.getInputConsumer(INPUT_CONSUMER_WALLPAPER, mDisplayId);
-        boolean addInputConsumerHandle = navInputConsumer != null;
-        boolean addPipInputConsumerHandle = pipInputConsumer != null;
-        boolean addWallpaperInputConsumerHandle = wallpaperInputConsumer != null;
-        final Rect pipTouchableBounds = addPipInputConsumerHandle ? new Rect() : null;
-        boolean disableWallpaperTouchEvents = false;
-
-        for (int winNdx = mWindows.size() - 1; winNdx >= 0; --winNdx) {
-            final WindowState child = mWindows.get(winNdx);
-            final InputChannel inputChannel = child.mInputChannel;
-            final InputWindowHandle inputWindowHandle = child.mInputWindowHandle;
-            if (inputChannel == null || inputWindowHandle == null || child.mRemoved
-                    || child.isAdjustedForMinimizedDock()) {
-                // Skip this window because it cannot possibly receive input.
-                continue;
-            }
-
-            if (addPipInputConsumerHandle
-                    && child.getStackId() == PINNED_STACK_ID
-                    && inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer) {
-                // Update the bounds of the Pip input consumer to match the Pinned stack
-                child.getStack().getBounds(pipTouchableBounds);
-                pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
-                inputMonitor.addInputWindowHandle(pipInputConsumer.mWindowHandle);
-                addPipInputConsumerHandle = false;
-            }
-
-            if (addInputConsumerHandle
-                    && inputWindowHandle.layer <= navInputConsumer.mWindowHandle.layer) {
-                inputMonitor.addInputWindowHandle(navInputConsumer.mWindowHandle);
-                addInputConsumerHandle = false;
-            }
-
-            if (addWallpaperInputConsumerHandle) {
-                if (child.mAttrs.type == TYPE_WALLPAPER && child.isVisibleLw()) {
-                    // Add the wallpaper input consumer above the first visible wallpaper.
-                    inputMonitor.addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
-                    addWallpaperInputConsumerHandle = false;
-                }
-            }
-
-            final int flags = child.mAttrs.flags;
-            final int privateFlags = child.mAttrs.privateFlags;
-            final int type = child.mAttrs.type;
-
-            final boolean hasFocus = child == inputFocus;
-            final boolean isVisible = child.isVisibleLw();
-            if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
-                disableWallpaperTouchEvents = true;
-            }
-            final boolean hasWallpaper = mWallpaperController.isWallpaperTarget(child)
-                    && (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
-                    && !disableWallpaperTouchEvents;
-
-            // If there's a drag in progress and 'child' is a potential drop target,
-            // make sure it's been told about the drag
-            if (inDrag && isVisible && isDefaultDisplay) {
-                mService.mDragState.sendDragStartedIfNeededLw(child);
-            }
-
-            inputMonitor.addInputWindowHandle(
-                    inputWindowHandle, child, flags, type, isVisible, hasFocus, hasWallpaper);
-        }
-
-        if (addWallpaperInputConsumerHandle) {
-            // No visible wallpaper found, add the wallpaper input consumer at the end.
-            inputMonitor.addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
-        }
-    }
-
     /** Returns true if a leaked surface was destroyed */
     boolean destroyLeakedSurfaces() {
-        boolean leakedSurface = false;
-        final int numWindows = mWindows.size();
-        for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
-            final WindowState ws = mWindows.get(winNdx);
-            final WindowStateAnimator wsa = ws.mWinAnimator;
+        // Used to indicate that a surface was leaked.
+        mTmpWindow = null;
+        forAllWindows(w -> {
+            final WindowStateAnimator wsa = w.mWinAnimator;
             if (wsa.mSurfaceController == null) {
-                continue;
+                return;
             }
             if (!mService.mSessions.contains(wsa.mSession)) {
                 Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): "
-                        + ws + " surface=" + wsa.mSurfaceController
-                        + " token=" + ws.mToken
-                        + " pid=" + ws.mSession.mPid
-                        + " uid=" + ws.mSession.mUid);
+                        + w + " surface=" + wsa.mSurfaceController
+                        + " token=" + w.mToken
+                        + " pid=" + w.mSession.mPid
+                        + " uid=" + w.mSession.mUid);
                 wsa.destroySurface();
-                mService.mForceRemoves.add(ws);
-                leakedSurface = true;
-            } else if (ws.mAppToken != null && ws.mAppToken.clientHidden) {
+                mService.mForceRemoves.add(w);
+                mTmpWindow = w;
+            } else if (w.mAppToken != null && w.mAppToken.clientHidden) {
                 Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): "
-                        + ws + " surface=" + wsa.mSurfaceController
-                        + " token=" + ws.mAppToken
-                        + " saved=" + ws.hasSavedSurface());
-                if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", false);
+                        + w + " surface=" + wsa.mSurfaceController
+                        + " token=" + w.mAppToken
+                        + " saved=" + w.hasSavedSurface());
+                if (SHOW_TRANSACTIONS) logSurface(w, "LEAK DESTROY", false);
                 wsa.destroySurface();
-                leakedSurface = true;
+                mTmpWindow = w;
             }
-        }
+        }, false /* traverseTopToBottom */);
 
-        return leakedSurface;
-    }
-
-    /** Return the list of Windows on this display associated with the input token. */
-    WindowList getTokenWindowsOnDisplay(WindowToken token) {
-        final WindowList windowList = new WindowList();
-        final int count = mWindows.size();
-        for (int i = 0; i < count; i++) {
-            final WindowState win = mWindows.get(i);
-            if (win.mToken == token) {
-                windowList.add(win);
-            }
-        }
-        return windowList;
-    }
-
-    private void reAddToWindowList(WindowState win) {
-        win.mToken.addWindow(win);
-        // This is a hack to get all of the child windows added as well at the right position. Child
-        // windows should be rare and this case should be rare, so it shouldn't be that big a deal.
-        int wpos = mWindows.indexOf(win);
-        if (wpos >= 0) {
-            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "ReAdd removing from " + wpos + ": " + win);
-            mWindows.remove(wpos);
-            mService.mWindowsChanged = true;
-            win.reAddWindow(wpos);
-        }
-    }
-
-    void moveInputMethodDialogs(int pos) {
-        ArrayList<WindowState> dialogs = mService.mInputMethodDialogs;
-
-        final int N = dialogs.size();
-        if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Removing " + N + " dialogs w/pos=" + pos);
-        for (int i = 0; i < N; i++) {
-            pos = removeWindowAndChildrenFromWindowList(dialogs.get(i), pos);
-        }
-        if (DEBUG_INPUT_METHOD) {
-            Slog.v(TAG_WM, "Window list w/pos=" + pos);
-            logWindowList(mWindows, "  ");
-        }
-
-        WindowState ime = mService.mInputMethodWindow;
-        if (pos >= 0) {
-            // Skip windows owned by the input method.
-            if (ime != null) {
-                while (pos < mWindows.size()) {
-                    WindowState wp = mWindows.get(pos);
-                    if (wp == ime || wp.getParentWindow() == ime) {
-                        pos++;
-                        continue;
-                    }
-                    break;
-                }
-            }
-            if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Adding " + N + " dialogs at pos=" + pos);
-            for (int i=0; i<N; i++) {
-                WindowState win = dialogs.get(i);
-                pos = win.reAddWindow(pos);
-            }
-            if (DEBUG_INPUT_METHOD) {
-                Slog.v(TAG_WM, "Final window list:");
-                logWindowList(mWindows, "  ");
-            }
-            return;
-        }
-        for (int i=0; i<N; i++) {
-            WindowState win = dialogs.get(i);
-            reAddToWindowList(win);
-            if (DEBUG_INPUT_METHOD) {
-                Slog.v(TAG_WM, "No IM target, final list:");
-                logWindowList(mWindows, "  ");
-            }
-        }
-    }
-
-    boolean moveInputMethodWindowsIfNeeded(boolean needAssignLayers) {
-        final WindowState imWin = mService.mInputMethodWindow;
-        final int DN = mService.mInputMethodDialogs.size();
-        if (imWin == null && DN == 0) {
-            return false;
-        }
-
-        // TODO(multidisplay): IMEs are only supported on the default display.
-        int imPos = findDesiredInputMethodWindowIndex(true);
-        if (imPos >= 0) {
-            // In this case, the input method windows are to be placed
-            // immediately above the window they are targeting.
-
-            // First check to see if the input method windows are already
-            // located here, and contiguous.
-            final int N = mWindows.size();
-            final WindowState firstImWin = imPos < N ? mWindows.get(imPos) : null;
-
-            // Figure out the actual input method window that should be
-            // at the bottom of their stack.
-            WindowState baseImWin = imWin != null ? imWin : mService.mInputMethodDialogs.get(0);
-            final WindowState cw = baseImWin.getBottomChild();
-            if (cw != null && cw.mSubLayer < 0) {
-                baseImWin = cw;
-            }
-
-            if (firstImWin == baseImWin) {
-                // The windows haven't moved...  but are they still contiguous?
-                // First find the top IM window.
-                int pos = imPos+1;
-                while (pos < N) {
-                    if (!(mWindows.get(pos)).mIsImWindow) {
-                        break;
-                    }
-                    pos++;
-                }
-                pos++;
-                // Now there should be no more input method windows above.
-                while (pos < N) {
-                    if ((mWindows.get(pos)).mIsImWindow) {
-                        break;
-                    }
-                    pos++;
-                }
-                if (pos >= N) {
-                    return false;
-                }
-            }
-
-            if (imWin != null) {
-                if (DEBUG_INPUT_METHOD) {
-                    Slog.v(TAG_WM, "Moving IM from " + imPos);
-                    logWindowList(mWindows, "  ");
-                }
-                imPos = removeWindowAndChildrenFromWindowList(imWin, imPos);
-                if (DEBUG_INPUT_METHOD) {
-                    Slog.v(TAG_WM, "List after removing with new pos " + imPos + ":");
-                    logWindowList(mWindows, "  ");
-                }
-                imWin.reAddWindow(imPos);
-                if (DEBUG_INPUT_METHOD) {
-                    Slog.v(TAG_WM, "List after moving IM to " + imPos + ":");
-                    logWindowList(mWindows, "  ");
-                }
-                if (DN > 0) moveInputMethodDialogs(imPos+1);
-            } else {
-                moveInputMethodDialogs(imPos);
-            }
-
-        } else {
-            // In this case, the input method windows go in a fixed layer,
-            // because they aren't currently associated with a focus window.
-
-            if (imWin != null) {
-                if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Moving IM from " + imPos);
-                removeWindowAndChildrenFromWindowList(imWin, 0);
-                reAddToWindowList(imWin);
-                if (DEBUG_INPUT_METHOD) {
-                    Slog.v(TAG_WM, "List with no IM target:");
-                    logWindowList(mWindows, "  ");
-                }
-                if (DN > 0) moveInputMethodDialogs(-1);
-            } else {
-                moveInputMethodDialogs(-1);
-            }
-
-        }
-
-        if (needAssignLayers) {
-            assignWindowLayers(false /* setLayoutNeeded */);
-        }
-
-        return true;
+        return mTmpWindow != null;
     }
 
     /**
-     * Dig through the WindowStates and find the one that the Input Method will target.
-     * @param willMove
-     * @return The index+1 in mWindows of the discovered target.
+     * Determine and return the window that should be the IME target.
+     * @param updateImeTarget If true the system IME target will be updated to match what we found.
+     * @return The window that should be used as the IME target or null if there isn't any.
      */
-    int findDesiredInputMethodWindowIndex(boolean willMove) {
+    WindowState computeImeTarget(boolean updateImeTarget) {
         // TODO(multidisplay): Needs some serious rethought when the target and IME are not on the
         // same display. Or even when the current IME/target are not on the same screen as the next
         // IME/target. For now only look for input windows on the main screen.
-        WindowState w = null;
-        int i;
-        for (i = mWindows.size() - 1; i >= 0; --i) {
-            final WindowState win = mWindows.get(i);
+        WindowState target = getWindow(w -> {
+            if (DEBUG_INPUT_METHOD && updateImeTarget) Slog.i(TAG_WM, "Checking window @"
+                    + w + " fl=0x" + Integer.toHexString(w.mAttrs.flags));
+            return w.canBeImeTarget();
+        });
 
-            if (DEBUG_INPUT_METHOD && willMove) Slog.i(TAG_WM, "Checking window @" + i
-                    + " " + win + " fl=0x" + Integer.toHexString(win.mAttrs.flags));
-            if (canBeImeTarget(win)) {
-                w = win;
-                //Slog.i(TAG_WM, "Putting input method here!");
 
-                // Yet more tricksyness!  If this window is a "starting" window, we do actually want
-                // to be on top of it, but it is not -really- where input will go.  So if the caller
-                // is not actually looking to move the IME, look down below for a real window to
-                // target...
-                if (!willMove && w.mAttrs.type == TYPE_APPLICATION_STARTING && i > 0) {
-                    final WindowState wb = mWindows.get(i-1);
-                    if (wb.mAppToken == w.mAppToken && canBeImeTarget(wb)) {
-                        i--;
-                        w = wb;
-                    }
+        // Yet more tricksyness!  If this window is a "starting" window, we do actually want
+        // to be on top of it, but it is not -really- where input will go. So look down below
+        // for a real window to target...
+        if (target != null && target.mAttrs.type == TYPE_APPLICATION_STARTING) {
+            final AppWindowToken token = target.mAppToken;
+            if (token != null) {
+                final WindowState betterTarget = token.getImeTargetBelowWindow(target);
+                if (betterTarget != null) {
+                    target = betterTarget;
                 }
-                break;
             }
         }
 
-        // Now w is either mWindows[0] or an IME (or null if mWindows is empty).
-
-        if (DEBUG_INPUT_METHOD && willMove) Slog.v(TAG_WM, "Proposed new IME target: " + w);
+        if (DEBUG_INPUT_METHOD && updateImeTarget) Slog.v(TAG_WM,
+                "Proposed new IME target: " + target);
 
         // Now, a special case -- if the last target's window is in the process of exiting, and is
         // above the new target, keep on the last target to avoid flicker. Consider for example a
@@ -1872,18 +1220,28 @@
         // until it is completely gone so it doesn't drop behind the dialog or its full-screen
         // scrim.
         final WindowState curTarget = mService.mInputMethodTarget;
-        if (curTarget != null
-                && curTarget.isDisplayedLw()
-                && curTarget.isClosing()
-                && (w == null || curTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer)) {
+        if (curTarget != null && curTarget.isDisplayedLw() && curTarget.isClosing()
+                && (target == null
+                    || curTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer)) {
             if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Current target higher, not changing");
-            return mWindows.indexOf(curTarget) + 1;
+            return curTarget;
         }
 
-        if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Desired input method target="
-                + w + " willMove=" + willMove);
+        if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Desired input method target=" + target
+                + " updateImeTarget=" + updateImeTarget);
 
-        if (willMove && w != null) {
+        if (target == null) {
+            if (updateImeTarget) {
+                if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
+                        + " to null." + (SHOW_STACK_CRAWLS ? " Callers="
+                        + Debug.getCallers(4) : ""));
+                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+            }
+
+            return null;
+        }
+
+        if (updateImeTarget) {
             AppWindowToken token = curTarget == null ? null : curTarget.mAppToken;
             if (token != null) {
 
@@ -1891,24 +1249,8 @@
                 // to look at all windows below the current target that are in this app, finding the
                 // highest visible one in layering.
                 WindowState highestTarget = null;
-                int highestPos = 0;
                 if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
-                    WindowList curWindows = token.getDisplayContent().mWindows;
-                    int pos = curWindows.indexOf(curTarget);
-                    while (pos >= 0) {
-                        WindowState win = curWindows.get(pos);
-                        if (win.mAppToken != token) {
-                            break;
-                        }
-                        if (!win.mRemoved) {
-                            if (highestTarget == null || win.mWinAnimator.mAnimLayer >
-                                    highestTarget.mWinAnimator.mAnimLayer) {
-                                highestTarget = win;
-                                highestPos = pos;
-                            }
-                        }
-                        pos--;
-                    }
+                    highestTarget = token.getHighestAnimLayerWindow(curTarget);
                 }
 
                 if (highestTarget != null) {
@@ -1916,121 +1258,76 @@
                     if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, appTransition + " " + highestTarget
                             + " animating=" + highestTarget.mWinAnimator.isAnimationSet()
                             + " layer=" + highestTarget.mWinAnimator.mAnimLayer
-                            + " new layer=" + w.mWinAnimator.mAnimLayer);
+                            + " new layer=" + target.mWinAnimator.mAnimLayer);
 
                     if (appTransition.isTransitionSet()) {
                         // If we are currently setting up for an animation, hold everything until we
                         // can find out what will happen.
-                        mService.mInputMethodTargetWaitingAnim = true;
-                        mService.mInputMethodTarget = highestTarget;
-                        return highestPos + 1;
+                        setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+                        return highestTarget;
                     } else if (highestTarget.mWinAnimator.isAnimationSet() &&
-                            highestTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer) {
+                            highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) {
                         // If the window we are currently targeting is involved with an animation,
                         // and it is on top of the next target we will be over, then hold off on
                         // moving until that is done.
-                        mService.mInputMethodTargetWaitingAnim = true;
-                        mService.mInputMethodTarget = highestTarget;
-                        return highestPos + 1;
+                        setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+                        return highestTarget;
                     }
                 }
             }
+
+            if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
+                    + target + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
+            setInputMethodTarget(target, false, target.mAppToken != null
+                    ? target.mAppToken.mAppAnimator.animLayerAdjustment : 0);
         }
 
-        //Slog.i(TAG_WM, "Placing input method @" + (i+1));
-        if (w != null) {
-            if (willMove) {
-                if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
-                        + w + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
-                mService.mInputMethodTarget = w;
-                mService.mInputMethodTargetWaitingAnim = false;
-                if (w.mAppToken != null) {
-                    setInputMethodAnimLayerAdjustment(
-                            w.mAppToken.mAppAnimator.animLayerAdjustment);
-                } else {
-                    setInputMethodAnimLayerAdjustment(0);
-                }
-            }
-
-            // If the docked divider is visible, we still need to go through this whole excercise to
-            // find the appropriate input method target (used for animations and dialog
-            // adjustments), but for purposes of Z ordering we simply wish to place it above the
-            // docked divider. Unless it is already above the divider.
-            final WindowState dockedDivider = mDividerControllerLocked.getWindow();
-            if (dockedDivider != null && dockedDivider.isVisibleLw()) {
-                int dividerIndex = mWindows.indexOf(dockedDivider);
-                if (dividerIndex > 0 && dividerIndex > i) {
-                    return dividerIndex + 1;
-                }
-            }
-            return i+1;
-        }
-        if (willMove) {
-            if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
-                    + " to null." + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
-            mService.mInputMethodTarget = null;
-            setInputMethodAnimLayerAdjustment(0);
-        }
-        return -1;
+        return target;
     }
 
-    private static boolean canBeImeTarget(WindowState w) {
-        final int fl = w.mAttrs.flags & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
-        final int type = w.mAttrs.type;
-
-        if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
-                && type != TYPE_APPLICATION_STARTING) {
-            return false;
+    private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim, int layerAdj) {
+        if (target == mService.mInputMethodTarget
+                && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim
+                && mInputMethodAnimLayerAdjustment == layerAdj) {
+            return;
         }
 
-        if (DEBUG_INPUT_METHOD) {
-            Slog.i(TAG_WM, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
-            if (!w.isVisibleOrAdding()) {
-                Slog.i(TAG_WM, "  mSurfaceController=" + w.mWinAnimator.mSurfaceController
-                        + " relayoutCalled=" + w.mRelayoutCalled
-                        + " viewVis=" + w.mViewVisibility
-                        + " policyVis=" + w.mPolicyVisibility
-                        + " policyVisAfterAnim=" + w.mPolicyVisibilityAfterAnim
-                        + " parentHidden=" + w.isParentWindowHidden()
-                        + " exiting=" + w.mAnimatingExit + " destroying=" + w.mDestroying);
-                if (w.mAppToken != null) {
-                    Slog.i(TAG_WM, "  mAppToken.hiddenRequested=" + w.mAppToken.hiddenRequested);
-                }
+        mService.mInputMethodTarget = target;
+        mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
+        setInputMethodAnimLayerAdjustment(layerAdj);
+        assignWindowLayers(false /* setLayoutNeeded */);
+    }
+
+    boolean getNeedsMenu(WindowState top, WindowManagerPolicy.WindowState bottom) {
+        if (top.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
+            return top.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
+        }
+
+        // Used to indicate we have reached the first window in the range we are interested in.
+        mTmpWindow = null;
+
+        // TODO: Figure-out a more efficient way to do this.
+        final WindowState candidate = getWindow(w -> {
+            if (w == top) {
+                // Reached the first window in the range we are interested in.
+                mTmpWindow = w;
             }
-        }
-        return w.isVisibleOrAdding();
-    }
+            if (mTmpWindow == null) {
+                return false;
+            }
 
-    private void logWindowList(final WindowList windows, String prefix) {
-        int N = windows.size();
-        while (N > 0) {
-            N--;
-            Slog.v(TAG_WM, prefix + "#" + N + ": " + windows.get(N));
-        }
-    }
-
-    boolean getNeedsMenu(WindowState win, WindowManagerPolicy.WindowState bottom) {
-        int index = -1;
-        while (true) {
-            if (win.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
-                return win.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
+            if (w.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
+                return true;
             }
             // If we reached the bottom of the range of windows we are considering,
             // assume no menu is needed.
-            if (win == bottom) {
-                return false;
+            if (w == bottom) {
+                return true;
             }
-            // The current window hasn't specified whether menu key is needed; look behind it.
-            // First, we may need to determine the starting position.
-            if (index < 0) {
-                index = mWindows.indexOf(win);
-            }
-            index--;
-            if (index < 0) {
-                return false;
-            }
-            win = mWindows.get(index);
-        }
+            return false;
+        });
+
+        return candidate != null && candidate.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
     }
 
     void setLayoutNeeded() {
@@ -2047,85 +1344,6 @@
         return mLayoutNeeded;
     }
 
-    private void addAppWindowExisting(WindowState win, WindowList tokenWindowList) {
-
-        // If this application has existing windows, we simply place the new window on top of
-        // them... but keep the starting window on top.
-        if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
-            // Base windows go behind everything else.
-            final WindowState lowestWindow = tokenWindowList.get(0);
-            addWindowToListBefore(win, lowestWindow);
-        } else {
-            final AppWindowToken atoken = win.mAppToken;
-            final int windowListPos = tokenWindowList.size();
-            final WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
-            if (atoken != null && lastWindow == atoken.startingWindow) {
-                addWindowToListBefore(win, lastWindow);
-            } else {
-                int newIdx = findIdxBasedOnAppTokens(win);
-                // There is a window above this one associated with the same apptoken note that the
-                // window could be a floating window that was created later or a window at the top
-                // of the list of windows associated with this token.
-                if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                        "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of "
-                                + mWindows.size());
-                mWindows.add(newIdx + 1, win);
-                mService.mWindowsChanged = true;
-            }
-        }
-    }
-
-    /** Places the first input window after the second input window in the window list. */
-    private void addWindowToListAfter(WindowState first, WindowState second) {
-        final int i = mWindows.indexOf(second);
-        if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                "Adding window " + this + " at " + (i + 1) + " of " + mWindows.size()
-                + " (after " + second + ")");
-        mWindows.add(i + 1, first);
-        mService.mWindowsChanged = true;
-    }
-
-    /** Places the first input window before the second input window in the window list. */
-    private void addWindowToListBefore(WindowState first, WindowState second) {
-        int i = mWindows.indexOf(second);
-        if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                "Adding window " + this + " at " + i + " of " + mWindows.size()
-                + " (before " + second + ")");
-        if (i < 0) {
-            Slog.w(TAG_WM, "addWindowToListBefore: Unable to find " + second + " in " + mWindows);
-            i = 0;
-        }
-        mWindows.add(i, first);
-        mService.mWindowsChanged = true;
-    }
-
-    /**
-     * This method finds out the index of a window that has the same app token as win. used for z
-     * ordering the windows in mWindows
-     */
-    private int findIdxBasedOnAppTokens(WindowState win) {
-        for(int j = mWindows.size() - 1; j >= 0; j--) {
-            final WindowState wentry = mWindows.get(j);
-            if(wentry.mAppToken == win.mAppToken) {
-                return j;
-            }
-        }
-        return -1;
-    }
-
-    private void dumpChildrenNames() {
-        StringBuilder output = new StringBuilder();
-        dumpChildrenNames(output, " ");
-        Slog.v(TAG_WM, output.toString());
-    }
-
-    private void dumpWindows() {
-        Slog.v(TAG_WM, " Display #" + mDisplayId);
-        for (int winNdx = mWindows.size() - 1; winNdx >= 0; --winNdx) {
-            Slog.v(TAG_WM, "  #" + winNdx + ": " + mWindows.get(winNdx));
-        }
-    }
-
     void dumpTokens(PrintWriter pw, boolean dumpAll) {
         if (mTokenMap.isEmpty()) {
             return;
@@ -2146,25 +1364,24 @@
     }
 
     void dumpWindowAnimators(PrintWriter pw, String subPrefix) {
-        final int count = mWindows.size();
-        for (int j = 0; j < count; j++) {
-            final WindowStateAnimator wAnim = mWindows.get(j).mWinAnimator;
-            pw.println(subPrefix + "Window #" + j + ": " + wAnim);
-        }
+        final int[] index = new int[1];
+        forAllWindows(w -> {
+            final WindowStateAnimator wAnim = w.mWinAnimator;
+            pw.println(subPrefix + "Window #" + index[0] + ": " + wAnim);
+            index[0] = index[0] + 1;
+        }, false /* traverseTopToBottom */);
     }
 
     void enableSurfaceTrace(FileDescriptor fd) {
-        for (int i = mWindows.size() - 1; i >= 0; i--) {
-            final WindowState win = mWindows.get(i);
-            win.mWinAnimator.enableSurfaceTrace(fd);
-        }
+        forAllWindows(w -> {
+            w.mWinAnimator.enableSurfaceTrace(fd);
+        }, true /* traverseTopToBottom */);
     }
 
     void disableSurfaceTrace() {
-        for (int i = mWindows.size() - 1; i >= 0; i--) {
-            final WindowState win = mWindows.get(i);
-            win.mWinAnimator.disableSurfaceTrace();
-        }
+        forAllWindows(w -> {
+            w.mWinAnimator.disableSurfaceTrace();
+        }, true /* traverseTopToBottom */);
     }
 
     /**
@@ -2172,63 +1389,68 @@
      */
     void startKeyguardExitOnNonAppWindows(boolean onWallpaper, boolean goingToShade) {
         final WindowManagerPolicy policy = mService.mPolicy;
-        for (int i = mWindows.size() - 1; i >= 0; i--) {
-            final WindowState window = mWindows.get(i);
-            if (window.mAppToken == null && policy.canBeHiddenByKeyguardLw(window)) {
-                window.mWinAnimator.setAnimation(
+        forAllWindows(w -> {
+            if (w.mAppToken == null && policy.canBeHiddenByKeyguardLw(w)) {
+                w.mWinAnimator.setAnimation(
                         policy.createHiddenByKeyguardExit(onWallpaper, goingToShade));
             }
-        }
+        }, true /* traverseTopToBottom */);
     }
 
     boolean checkWaitingForWindows() {
 
-        boolean haveBootMsg = false;
-        boolean haveApp = false;
-        // if the wallpaper service is disabled on the device, we're never going to have
-        // wallpaper, don't bother waiting for it
-        boolean haveWallpaper = false;
-        boolean wallpaperEnabled = mService.mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableWallpaperService)
-                && !mService.mOnlyCore;
-        boolean haveKeyguard = true;
-        final int count = mWindows.size();
-        for (int i = 0; i < count; i++) {
-            final WindowState w = mWindows.get(i);
+        mHaveBootMsg = false;
+        mHaveApp = false;
+        mHaveWallpaper = false;
+        mHaveKeyguard = true;
+
+        final WindowState visibleWindow = getWindow(w -> {
             if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
                 return true;
             }
             if (w.isDrawnLw()) {
                 if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
-                    haveBootMsg = true;
+                    mHaveBootMsg = true;
                 } else if (w.mAttrs.type == TYPE_APPLICATION
                         || w.mAttrs.type == TYPE_DRAWN_APPLICATION) {
-                    haveApp = true;
+                    mHaveApp = true;
                 } else if (w.mAttrs.type == TYPE_WALLPAPER) {
-                    haveWallpaper = true;
+                    mHaveWallpaper = true;
                 } else if (w.mAttrs.type == TYPE_STATUS_BAR) {
-                    haveKeyguard = mService.mPolicy.isKeyguardDrawnLw();
+                    mHaveKeyguard = mService.mPolicy.isKeyguardDrawnLw();
                 }
             }
+            return false;
+        });
+
+        if (visibleWindow != null) {
+            // We have a visible window.
+            return true;
         }
 
+        // if the wallpaper service is disabled on the device, we're never going to have
+        // wallpaper, don't bother waiting for it
+        boolean wallpaperEnabled = mService.mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableWallpaperService)
+                && !mService.mOnlyCore;
+
         if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM,
                 "******** booted=" + mService.mSystemBooted
                 + " msg=" + mService.mShowingBootMessages
-                + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
-                + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
-                + " haveKeyguard=" + haveKeyguard);
+                + " haveBoot=" + mHaveBootMsg + " haveApp=" + mHaveApp
+                + " haveWall=" + mHaveWallpaper + " wallEnabled=" + wallpaperEnabled
+                + " haveKeyguard=" + mHaveKeyguard);
 
         // If we are turning on the screen to show the boot message, don't do it until the boot
         // message is actually displayed.
-        if (!mService.mSystemBooted && !haveBootMsg) {
+        if (!mService.mSystemBooted && !mHaveBootMsg) {
             return true;
         }
 
         // If we are turning on the screen after the boot is completed normally, don't do so until
         // we have the application and wallpaper.
-        if (mService.mSystemBooted && ((!haveApp && !haveKeyguard) ||
-                (wallpaperEnabled && !haveWallpaper))) {
+        if (mService.mSystemBooted
+                && ((!mHaveApp && !mHaveKeyguard) || (wallpaperEnabled && !mHaveWallpaper))) {
             return true;
         }
 
@@ -2236,10 +1458,8 @@
     }
 
     void updateWindowsForAnimator(WindowAnimator animator) {
-        final WallpaperController wallpaperController = mWallpaperController;
-        for (int i = mWindows.size() - 1; i >= 0; i--) {
-            WindowState win = mWindows.get(i);
-            WindowStateAnimator winAnimator = win.mWinAnimator;
+        forAllWindows(w -> {
+            WindowStateAnimator winAnimator = w.mWinAnimator;
             if (winAnimator.hasSurface()) {
                 final boolean wasAnimating = winAnimator.mWasAnimating;
                 final boolean nowAnimating = winAnimator.stepAnimationLocked(animator.mCurrentTime);
@@ -2247,10 +1467,10 @@
                 animator.orAnimating(nowAnimating);
 
                 if (DEBUG_WALLPAPER) Slog.v(TAG,
-                        win + ": wasAnimating=" + wasAnimating + ", nowAnimating=" + nowAnimating);
+                        w + ": wasAnimating=" + wasAnimating + ", nowAnimating=" + nowAnimating);
 
                 if (wasAnimating && !winAnimator.mAnimating
-                        && wallpaperController.isWallpaperTarget(win)) {
+                        && mWallpaperController.isWallpaperTarget(w)) {
                     animator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
                     pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                     if (DEBUG_LAYOUT_REPEATS) {
@@ -2260,10 +1480,10 @@
                 }
             }
 
-            final AppWindowToken atoken = win.mAppToken;
+            final AppWindowToken atoken = w.mAppToken;
             if (winAnimator.mDrawState == READY_TO_SHOW) {
                 if (atoken == null || atoken.allDrawn) {
-                    if (win.performShowLocked()) {
+                    if (w.performShowLocked()) {
                         pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
                         if (DEBUG_LAYOUT_REPEATS) {
                             mService.mWindowPlacerLocked.debugLayoutRepeats(
@@ -2282,23 +1502,22 @@
                     appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
                 }
             }
-        } // end forall windows
+        }, true /* traverseTopToBottom */);
     }
 
     void updateWallpaperForAnimator(WindowAnimator animator) {
         resetAnimationBackgroundAnimator();
 
-        final WindowList windows = mWindows;
-        WindowState detachedWallpaper = null;
+        // Used to indicate a detached wallpaper.
+        mTmpWindow = null;
 
-        for (int i = windows.size() - 1; i >= 0; i--) {
-            final WindowState win = windows.get(i);
-            final WindowStateAnimator winAnimator = win.mWinAnimator;
+        forAllWindows(w -> {
+            final WindowStateAnimator winAnimator = w.mWinAnimator;
             if (winAnimator.mSurfaceController == null || !winAnimator.hasSurface()) {
-                continue;
+                return;
             }
 
-            final int flags = win.mAttrs.flags;
+            final int flags = w.mAttrs.flags;
 
             // If this window is animating, make a note that we have an animating window and take
             // care of a request to run a detached wallpaper animation.
@@ -2306,11 +1525,11 @@
                 if (winAnimator.mAnimation != null) {
                     if ((flags & FLAG_SHOW_WALLPAPER) != 0
                             && winAnimator.mAnimation.getDetachWallpaper()) {
-                        detachedWallpaper = win;
+                        mTmpWindow = w;
                     }
                     final int color = winAnimator.mAnimation.getBackgroundColor();
                     if (color != 0) {
-                        final TaskStack stack = win.getStack();
+                        final TaskStack stack = w.getStack();
                         if (stack != null) {
                             stack.setAnimationBackground(winAnimator, color);
                         }
@@ -2326,62 +1545,43 @@
                     && appAnimator.animating) {
                 if ((flags & FLAG_SHOW_WALLPAPER) != 0
                         && appAnimator.animation.getDetachWallpaper()) {
-                    detachedWallpaper = win;
+                    mTmpWindow = w;
                 }
 
                 final int color = appAnimator.animation.getBackgroundColor();
                 if (color != 0) {
-                    final TaskStack stack = win.getStack();
+                    final TaskStack stack = w.getStack();
                     if (stack != null) {
                         stack.setAnimationBackground(winAnimator, color);
                     }
                 }
             }
-        } // end forall windows
+        }, true /* traverseTopToBottom */);
 
-        if (animator.mWindowDetachedWallpaper != detachedWallpaper) {
+        if (animator.mWindowDetachedWallpaper != mTmpWindow) {
             if (DEBUG_WALLPAPER) Slog.v(TAG, "Detached wallpaper changed from "
-                    + animator.mWindowDetachedWallpaper + " to " + detachedWallpaper);
-            animator.mWindowDetachedWallpaper = detachedWallpaper;
+                    + animator.mWindowDetachedWallpaper + " to " + mTmpWindow);
+            animator.mWindowDetachedWallpaper = mTmpWindow;
             animator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
         }
     }
 
     void prepareWindowSurfaces() {
-        final int count = mWindows.size();
-        for (int j = 0; j < count; j++) {
-            mWindows.get(j).mWinAnimator.prepareSurfaceLocked(true);
-        }
+        forAllWindows(w -> {
+            w.mWinAnimator.prepareSurfaceLocked(true);
+        }, false /* traverseTopToBottom */);
     }
 
     boolean inputMethodClientHasFocus(IInputMethodClient client) {
-        // The focus for the client is the window immediately below where we would place the input
-        // method window.
-        int idx = findDesiredInputMethodWindowIndex(false);
-        if (idx <= 0) {
-            return false;
-        }
-
-        WindowState imFocus = mWindows.get(idx - 1);
-        if (DEBUG_INPUT_METHOD) {
-            Slog.i(TAG_WM, "Desired input method target: " + imFocus);
-            Slog.i(TAG_WM, "Current focus: " + mService.mCurrentFocus);
-            Slog.i(TAG_WM, "Last focus: " + mService.mLastFocus);
-        }
-
+        final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
         if (imFocus == null) {
             return false;
         }
 
-        // This may be a starting window, in which case we still want to count it as okay.
-        if (imFocus.mAttrs.type == TYPE_APPLICATION_STARTING && imFocus.mAppToken != null) {
-            // The client has definitely started, so it really should have a window in this app
-            // token. Let's look for it.
-            final WindowState w = imFocus.mAppToken.getFirstNonStartingWindow();
-            if (w != null) {
-                if (DEBUG_INPUT_METHOD) Slog.i(TAG_WM, "Switching to real app window: " + w);
-                imFocus = w;
-            }
+        if (DEBUG_INPUT_METHOD) {
+            Slog.i(TAG_WM, "Desired input method target: " + imFocus);
+            Slog.i(TAG_WM, "Current focus: " + mService.mCurrentFocus);
+            Slog.i(TAG_WM, "Last focus: " + mService.mLastFocus);
         }
 
         final IInputMethodClient imeClient = imFocus.mSession.mClient;
@@ -2398,75 +1598,63 @@
     }
 
     boolean hasSecureWindowOnScreen() {
-        for (int i = mWindows.size() - 1; i >= 0; --i) {
-            final WindowState ws = mWindows.get(i);
-            if (ws.isOnScreen() && (ws.mAttrs.flags & FLAG_SECURE) != 0) {
-                return true;
-            }
-        }
-        return false;
+        final WindowState win = getWindow(
+                w -> w.isOnScreen() && (w.mAttrs.flags & FLAG_SECURE) != 0);
+        return win != null;
     }
 
     void updateSystemUiVisibility(int visibility, int globalDiff) {
-        for (int i = mWindows.size() - 1; i >= 0; --i) {
-            final WindowState ws = mWindows.get(i);
+        forAllWindows(w -> {
             try {
-                int curValue = ws.mSystemUiVisibility;
-                int diff = (curValue ^ visibility) & globalDiff;
-                int newValue = (curValue & ~diff) | (visibility & diff);
+                final int curValue = w.mSystemUiVisibility;
+                final int diff = (curValue ^ visibility) & globalDiff;
+                final int newValue = (curValue & ~diff) | (visibility & diff);
                 if (newValue != curValue) {
-                    ws.mSeq++;
-                    ws.mSystemUiVisibility = newValue;
+                    w.mSeq++;
+                    w.mSystemUiVisibility = newValue;
                 }
-                if (newValue != curValue || ws.mAttrs.hasSystemUiListeners) {
-                    ws.mClient.dispatchSystemUiVisibilityChanged(ws.mSeq,
+                if (newValue != curValue || w.mAttrs.hasSystemUiListeners) {
+                    w.mClient.dispatchSystemUiVisibilityChanged(w.mSeq,
                             visibility, newValue, diff);
                 }
             } catch (RemoteException e) {
                 // so sorry
             }
-        }
+        }, true /* traverseTopToBottom */);
     }
 
     void onWindowFreezeTimeout() {
         Slog.w(TAG_WM, "Window freeze timeout expired.");
         mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
-        for (int i = mWindows.size() - 1; i >= 0; --i) {
-            final WindowState w = mWindows.get(i);
+
+        forAllWindows(w -> {
             if (!w.mOrientationChanging) {
-                continue;
+                return;
             }
             w.mOrientationChanging = false;
             w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
                     - mService.mDisplayFreezeTime);
             Slog.w(TAG_WM, "Force clearing orientation change: " + w);
-        }
+        }, true /* traverseTopToBottom */);
         mService.mWindowPlacerLocked.performSurfacePlacement();
     }
 
     void waitForAllWindowsDrawn() {
         final WindowManagerPolicy policy = mService.mPolicy;
-        for (int winNdx = mWindows.size() - 1; winNdx >= 0; --winNdx) {
-            final WindowState win = mWindows.get(winNdx);
-            final boolean keyguard = policy.isKeyguardHostWindow(win.mAttrs);
-            if (win.isVisibleLw() && (win.mAppToken != null || keyguard)) {
-                win.mWinAnimator.mDrawState = DRAW_PENDING;
+        forAllWindows(w -> {
+            final boolean keyguard = policy.isKeyguardHostWindow(w.mAttrs);
+            if (w.isVisibleLw() && (w.mAppToken != null || keyguard)) {
+                w.mWinAnimator.mDrawState = DRAW_PENDING;
                 // Force add to mResizingWindows.
-                win.mLastContentInsets.set(-1, -1, -1, -1);
-                mService.mWaitingForDrawn.add(win);
+                w.mLastContentInsets.set(-1, -1, -1, -1);
+                mService.mWaitingForDrawn.add(w);
             }
-        }
+        }, true /* traverseTopToBottom */);
     }
 
     // TODO: Super crazy long method that should be broken down...
     boolean applySurfaceChangesTransaction(boolean recoveringMemory) {
 
-        boolean focusDisplayed = false;
-        boolean displayHasContent = false;
-        float preferredRefreshRate = 0;
-        int preferredModeId = 0;
-
-
         final int dw = mDisplayInfo.logicalWidth;
         final int dh = mDisplayInfo.logicalHeight;
         final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
@@ -2490,7 +1678,7 @@
             // Remove check for default display when there will be support for multiple wallpaper
             // targets (on different displays).
             if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
-                adjustWallpaperWindows();
+                mWallpaperController.adjustWallpaperWindows(this);
             }
 
             if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
@@ -2517,55 +1705,59 @@
 
             if (isDefaultDisplay) {
                 mService.mPolicy.beginPostLayoutPolicyLw(dw, dh);
-                for (int i = mWindows.size() - 1; i >= 0; i--) {
-                    final WindowState w = mWindows.get(i);
+                forAllWindows(w -> {
                     mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.getParentWindow(),
                             mService.mInputMethodTarget);
-                }
+                }, true /* traverseTopToBottom */);
                 pendingLayoutChanges |= mService.mPolicy.finishPostLayoutPolicyLw();
                 if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats(
                         "after finishPostLayoutPolicyLw", pendingLayoutChanges);
             }
         } while (pendingLayoutChanges != 0);
 
-        RootWindowContainer root = mService.mRoot;
-        boolean obscured = false;
-        boolean syswin = false;
+        final RootWindowContainer root = mService.mRoot;
+        mTmpApplySurfaceChangesTransactionState.reset();
         resetDimming();
 
         // Only used if default window
         final boolean someoneLosingFocus = !mService.mLosingFocus.isEmpty();
 
-        for (int i = mWindows.size() - 1; i >= 0; i--) {
-            final WindowState w = mWindows.get(i);
-            final Task task = w.getTask();
-            final boolean obscuredChanged = w.mObscured != obscured;
+        forAllWindows(w -> {
+            final boolean obscuredChanged = w.mObscured !=
+                    mTmpApplySurfaceChangesTransactionState.obscured;
 
             // Update effect.
-            w.mObscured = obscured;
-            if (!obscured) {
+            w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured;
+            if (!mTmpApplySurfaceChangesTransactionState.obscured) {
                 final boolean isDisplayed = w.isDisplayedLw();
 
                 if (isDisplayed && w.isObscuringFullscreen(mDisplayInfo)) {
                     // This window completely covers everything behind it, so we want to leave all
                     // of them as undimmed (for performance reasons).
                     root.mObscuringWindow = w;
-                    obscured = true;
+                    mTmpApplySurfaceChangesTransactionState.obscured = true;
                 }
 
-                displayHasContent |= root.handleNotObscuredLocked(w, obscured, syswin);
+                mTmpApplySurfaceChangesTransactionState.displayHasContent |=
+                        root.handleNotObscuredLocked(w,
+                                mTmpApplySurfaceChangesTransactionState.obscured,
+                                mTmpApplySurfaceChangesTransactionState.syswin);
 
                 if (w.mHasSurface && isDisplayed) {
                     final int type = w.mAttrs.type;
                     if (type == TYPE_SYSTEM_DIALOG || type == TYPE_SYSTEM_ERROR
                             || (w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
-                        syswin = true;
+                        mTmpApplySurfaceChangesTransactionState.syswin = true;
                     }
-                    if (preferredRefreshRate == 0 && w.mAttrs.preferredRefreshRate != 0) {
-                        preferredRefreshRate = w.mAttrs.preferredRefreshRate;
+                    if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0
+                            && w.mAttrs.preferredRefreshRate != 0) {
+                        mTmpApplySurfaceChangesTransactionState.preferredRefreshRate
+                                = w.mAttrs.preferredRefreshRate;
                     }
-                    if (preferredModeId == 0 && w.mAttrs.preferredDisplayModeId != 0) {
-                        preferredModeId = w.mAttrs.preferredDisplayModeId;
+                    if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
+                            && w.mAttrs.preferredDisplayModeId != 0) {
+                        mTmpApplySurfaceChangesTransactionState.preferredModeId
+                                = w.mAttrs.preferredDisplayModeId;
                     }
                 }
             }
@@ -2638,16 +1830,16 @@
 
             if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus
                     && w.isDisplayedLw()) {
-                focusDisplayed = true;
+                mTmpApplySurfaceChangesTransactionState.focusDisplayed = true;
             }
 
             w.updateResizingWindowIfNeeded();
-        }
+        }, true /* traverseTopToBottom */);
 
         mService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
-                displayHasContent,
-                preferredRefreshRate,
-                preferredModeId,
+                mTmpApplySurfaceChangesTransactionState.displayHasContent,
+                mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
+                mTmpApplySurfaceChangesTransactionState.preferredModeId,
                 true /* inTraversal, must call performTraversalInTrans... below */);
 
         stopDimmingIfNeeded();
@@ -2659,7 +1851,7 @@
             atoken.updateAllDrawn(this);
         }
 
-        return focusDisplayed;
+        return mTmpApplySurfaceChangesTransactionState.focusDisplayed;
     }
 
     void performLayout(boolean initial, boolean updateInputWindows) {
@@ -2692,110 +1884,110 @@
         if (seq < 0) seq = 0;
         mService.mLayoutSeq = seq;
 
-        boolean behindDream = false;
+        // Used to indicate that we have processed the dream window and all additional windows are
+        // behind it.
+        mTmpWindow = null;
 
         // First perform layout of any root windows (not attached to another window).
-        int topAttached = -1;
-        for (i = mWindows.size() - 1; i >= 0; i--) {
-            final WindowState win = mWindows.get(i);
-
+        forAllWindows(w -> {
             // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
             // wasting time and funky changes while a window is animating away.
-            final boolean gone = (behindDream && mService.mPolicy.canBeHiddenByKeyguardLw(win))
-                    || win.isGoneForLayoutLw();
+            final boolean gone = (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w))
+                    || w.isGoneForLayoutLw();
 
-            if (DEBUG_LAYOUT && !win.mLayoutAttached) {
-                Slog.v(TAG, "1ST PASS " + win + ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame
-                        + " mLayoutAttached=" + win.mLayoutAttached
-                        + " screen changed=" + win.isConfigChanged());
-                final AppWindowToken atoken = win.mAppToken;
-                if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + win.mViewVisibility
-                        + " mRelayoutCalled=" + win.mRelayoutCalled + " hidden=" + win.mToken.hidden
+            if (DEBUG_LAYOUT && !w.mLayoutAttached) {
+                Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame
+                        + " mLayoutAttached=" + w.mLayoutAttached
+                        + " screen changed=" + w.isConfigChanged());
+                final AppWindowToken atoken = w.mAppToken;
+                if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
+                        + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
                         + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
-                        + " parentHidden=" + win.isParentWindowHidden());
-                else Slog.v(TAG, "  VIS: mViewVisibility=" + win.mViewVisibility
-                        + " mRelayoutCalled=" + win.mRelayoutCalled + " hidden=" + win.mToken.hidden
+                        + " parentHidden=" + w.isParentWindowHidden());
+                else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
+                        + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
                         + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
-                        + " parentHidden=" + win.isParentWindowHidden());
+                        + " parentHidden=" + w.isParentWindowHidden());
             }
 
             // If this view is GONE, then skip it -- keep the current frame, and let the caller know
             // so they can ignore it if they want.  (We do the normal layout for INVISIBLE windows,
             // since that means "perform layout as normal, just don't display").
-            if (!gone || !win.mHaveFrame || win.mLayoutNeeded
-                    || ((win.isConfigChanged() || win.setReportResizeHints())
-                    && !win.isGoneForLayoutLw() &&
-                    ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
-                            (win.mHasSurface && win.mAppToken != null &&
-                                    win.mAppToken.layoutConfigChanges)))) {
-                if (!win.mLayoutAttached) {
+            if (!gone || !w.mHaveFrame || w.mLayoutNeeded
+                    || ((w.isConfigChanged() || w.setReportResizeHints())
+                    && !w.isGoneForLayoutLw() &&
+                    ((w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
+                            (w.mHasSurface && w.mAppToken != null &&
+                                    w.mAppToken.layoutConfigChanges)))) {
+                if (!w.mLayoutAttached) {
                     if (initial) {
                         //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
-                        win.mContentChanged = false;
+                        w.mContentChanged = false;
                     }
-                    if (win.mAttrs.type == TYPE_DREAM) {
+                    if (w.mAttrs.type == TYPE_DREAM) {
                         // Don't layout windows behind a dream, so that if it does stuff like hide
                         // the status bar we won't get a bad transition when it goes away.
-                        behindDream = true;
+                        mTmpWindow = w;
                     }
-                    win.mLayoutNeeded = false;
-                    win.prelayout();
-                    mService.mPolicy.layoutWindowLw(win, null);
-                    win.mLayoutSeq = seq;
+                    w.mLayoutNeeded = false;
+                    w.prelayout();
+                    mService.mPolicy.layoutWindowLw(w, null);
+                    w.mLayoutSeq = mService.mLayoutSeq;
 
                     // Window frames may have changed. Update dim layer with the new bounds.
-                    final Task task = win.getTask();
+                    final Task task = w.getTask();
                     if (task != null) {
                         mDimLayerController.updateDimLayer(task);
                     }
 
-                    if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + win.mFrame
-                            + " mContainingFrame=" + win.mContainingFrame
-                            + " mDisplayFrame=" + win.mDisplayFrame);
-                } else {
-                    if (topAttached < 0) topAttached = i;
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.mFrame
+                            + " mContainingFrame=" + w.mContainingFrame
+                            + " mDisplayFrame=" + w.mDisplayFrame);
                 }
             }
-        }
+        }, true /* traverseTopToBottom */);
 
-        boolean attachedBehindDream = false;
+        // Used to indicate that we have processed the dream window and all additional attached
+        // windows are behind it.
+        final WindowState dreamWin = mTmpWindow;
+        mTmpWindow = null;
 
         // Now perform layout of attached windows, which usually depend on the position of the
         // window they are attached to. XXX does not deal with windows that are attached to windows
         // that are themselves attached.
-        for (i = topAttached; i >= 0; i--) {
-            final WindowState win = mWindows.get(i);
-
-            if (win.mLayoutAttached) {
-                if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + win + " mHaveFrame=" + win.mHaveFrame
-                        + " mViewVisibility=" + win.mViewVisibility
-                        + " mRelayoutCalled=" + win.mRelayoutCalled);
-                // If this view is GONE, then skip it -- keep the current frame, and let the caller
-                // know so they can ignore it if they want.  (We do the normal layout for INVISIBLE
-                // windows, since that means "perform layout as normal, just don't display").
-                if (attachedBehindDream && mService.mPolicy.canBeHiddenByKeyguardLw(win)) {
-                    continue;
+        forAllWindows(w -> {
+            if (w.mLayoutAttached) {
+                if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
+                        + " mViewVisibility=" + w.mViewVisibility
+                        + " mRelayoutCalled=" + w.mRelayoutCalled);
+                // If this view is GONE, then skip it -- keep the current frame, and let the
+                // caller know so they can ignore it if they want.  (We do the normal layout for
+                // INVISIBLE windows, since that means "perform layout as normal, just don't
+                // display").
+                if (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w)) {
+                    return;
                 }
-                if ((win.mViewVisibility != GONE && win.mRelayoutCalled) || !win.mHaveFrame
-                        || win.mLayoutNeeded) {
+                if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
+                        || w.mLayoutNeeded) {
                     if (initial) {
                         //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
-                        win.mContentChanged = false;
+                        w.mContentChanged = false;
                     }
-                    win.mLayoutNeeded = false;
-                    win.prelayout();
-                    mService.mPolicy.layoutWindowLw(win, win.getParentWindow());
-                    win.mLayoutSeq = seq;
-                    if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + win.mFrame
-                            + " mContainingFrame=" + win.mContainingFrame
-                            + " mDisplayFrame=" + win.mDisplayFrame);
+                    w.mLayoutNeeded = false;
+                    w.prelayout();
+                    mService.mPolicy.layoutWindowLw(w, w.getParentWindow());
+                    w.mLayoutSeq = mService.mLayoutSeq;
+                    if (DEBUG_LAYOUT)
+                        Slog.v(TAG, "  LAYOUT: mFrame=" + w.mFrame
+                                + " mContainingFrame=" + w.mContainingFrame
+                                + " mDisplayFrame=" + w.mDisplayFrame);
                 }
-            } else if (win.mAttrs.type == TYPE_DREAM) {
+            } else if (w.mAttrs.type == TYPE_DREAM) {
                 // Don't layout windows behind a dream, so that if it does stuff like hide the
                 // status bar we won't get a bad transition when it goes away.
-                attachedBehindDream = behindDream;
+                mTmpWindow = dreamWin;
             }
-        }
+        }, true /* traverseTopToBottom */);
 
         // Window frames may have changed. Tell the input dispatcher about it.
         mService.mInputMonitor.layoutInputConsumers(dw, dh);
@@ -2819,7 +2011,7 @@
      * @param config of the output bitmap
      * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
      */
-    Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height,
+    Bitmap screenshotApplications(IBinder appToken, int width, int height,
             boolean includeFullDisplay, float frameScale, Bitmap.Config config,
             boolean wallpaperOnly) {
         int dw = mDisplayInfo.logicalWidth;
@@ -2832,22 +2024,10 @@
 
         Bitmap bm = null;
 
-        int maxLayer = 0;
+        mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly);
         final Rect frame = new Rect();
         final Rect stackBounds = new Rect();
 
-        boolean screenshotReady;
-        int minLayer;
-        if (appToken == null && !wallpaperOnly) {
-            screenshotReady = true;
-            minLayer = 0;
-        } else {
-            screenshotReady = false;
-            minLayer = Integer.MAX_VALUE;
-        }
-
-        WindowState appWin = null;
-
         boolean includeImeInScreenshot;
         synchronized(mService.mWindowMap) {
             final AppWindowToken imeTargetAppToken = mService.mInputMethodTarget != null
@@ -2868,70 +2048,69 @@
 
         synchronized(mService.mWindowMap) {
             // Figure out the part of the screen that is actually the app.
-            appWin = null;
-            for (int i = mWindows.size() - 1; i >= 0; i--) {
-                final WindowState ws = mWindows.get(i);
-                if (!ws.mHasSurface) {
-                    continue;
+            mScreenshotApplicationState.appWin = null;
+            forAllWindows(w -> {
+                if (!w.mHasSurface) {
+                    return false;
                 }
-                if (ws.mLayer >= aboveAppLayer) {
-                    continue;
+                if (w.mLayer >= aboveAppLayer) {
+                    return false;
                 }
-                if (wallpaperOnly && !ws.mIsWallpaper) {
-                    continue;
+                if (wallpaperOnly && !w.mIsWallpaper) {
+                    return false;
                 }
-                if (ws.mIsImWindow) {
+                if (w.mIsImWindow) {
                     if (!includeImeInScreenshot) {
-                        continue;
+                        return false;
                     }
-                } else if (ws.mIsWallpaper) {
+                } else if (w.mIsWallpaper) {
                     // If this is the wallpaper layer and we're only looking for the wallpaper layer
                     // then the target window state is this one.
                     if (wallpaperOnly) {
-                        appWin = ws;
+                        mScreenshotApplicationState.appWin = w;
                     }
 
-                    if (appWin == null) {
+                    if (mScreenshotApplicationState.appWin == null) {
                         // We have not ran across the target window yet, so it is probably behind
                         // the wallpaper. This can happen when the keyguard is up and all windows
                         // are moved behind the wallpaper. We don't want to include the wallpaper
                         // layer in the screenshot as it will cover-up the layer of the target
                         // window.
-                        continue;
+                        return false;
                     }
                     // Fall through. The target window is in front of the wallpaper. For this
                     // case we want to include the wallpaper layer in the screenshot because
                     // the target window might have some transparent areas.
                 } else if (appToken != null) {
-                    if (ws.mAppToken == null || ws.mAppToken.token != appToken) {
+                    if (w.mAppToken == null || w.mAppToken.token != appToken) {
                         // This app window is of no interest if it is not associated with the
                         // screenshot app.
-                        continue;
+                        return false;
                     }
-                    appWin = ws;
+                    mScreenshotApplicationState.appWin = w;
                 }
 
                 // Include this window.
 
-                final WindowStateAnimator winAnim = ws.mWinAnimator;
+                final WindowStateAnimator winAnim = w.mWinAnimator;
                 int layer = winAnim.mSurfaceController.getLayer();
-                if (maxLayer < layer) {
-                    maxLayer = layer;
+                if (mScreenshotApplicationState.maxLayer < layer) {
+                    mScreenshotApplicationState.maxLayer = layer;
                 }
-                if (minLayer > layer) {
-                    minLayer = layer;
+                if (mScreenshotApplicationState.minLayer > layer) {
+                    mScreenshotApplicationState.minLayer = layer;
                 }
 
                 // Don't include wallpaper in bounds calculation
-                if (!includeFullDisplay && !ws.mIsWallpaper) {
-                    final Rect wf = ws.mFrame;
-                    final Rect cr = ws.mContentInsets;
+                if (!includeFullDisplay && !w.mIsWallpaper) {
+                    final Rect wf = w.mFrame;
+                    final Rect cr = w.mContentInsets;
                     int left = wf.left + cr.left;
                     int top = wf.top + cr.top;
                     int right = wf.right - cr.right;
                     int bottom = wf.bottom - cr.bottom;
                     frame.union(left, top, right, bottom);
-                    ws.getVisibleBounds(stackBounds);
+                    w.getVisibleBounds(stackBounds);
                     if (!Rect.intersects(frame, stackBounds)) {
                         // Set frame empty if there's no intersection.
                         frame.setEmpty();
@@ -2939,16 +2118,22 @@
                 }
 
                 final boolean foundTargetWs =
-                        (ws.mAppToken != null && ws.mAppToken.token == appToken)
-                                || (appWin != null && wallpaperOnly);
-                if (foundTargetWs && ws.isDisplayedLw() && winAnim.getShown()) {
-                    screenshotReady = true;
+                        (w.mAppToken != null && w.mAppToken.token == appToken)
+                                || (mScreenshotApplicationState.appWin != null && wallpaperOnly);
+                if (foundTargetWs && w.isDisplayedLw() && winAnim.getShown()) {
+                    mScreenshotApplicationState.screenshotReady = true;
                 }
 
-                if (ws.isObscuringFullscreen(mDisplayInfo)){
-                    break;
+                if (w.isObscuringFullscreen(mDisplayInfo)){
+                    return true;
                 }
-            }
+                return false;
+            }, true /* traverseTopToBottom */);
+
+            final WindowState appWin = mScreenshotApplicationState.appWin;
+            final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
+            final int maxLayer = mScreenshotApplicationState.maxLayer;
+            final int minLayer = mScreenshotApplicationState.minLayer;
 
             if (appToken != null && appWin == null) {
                 // Can't find a window to snapshot.
@@ -3020,14 +2205,13 @@
             if (DEBUG_SCREENSHOT) {
                 Slog.i(TAG_WM, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
                         + maxLayer + " appToken=" + appToken);
-                for (int i = 0; i < mWindows.size(); i++) {
-                    final WindowState win = mWindows.get(i);
-                    final WindowSurfaceController controller = win.mWinAnimator.mSurfaceController;
-                    Slog.i(TAG_WM, win + ": " + win.mLayer
-                            + " animLayer=" + win.mWinAnimator.mAnimLayer
+                forAllWindows(w -> {
+                    final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
+                    Slog.i(TAG_WM, w + ": " + w.mLayer
+                            + " animLayer=" + w.mWinAnimator.mAnimLayer
                             + " surfaceLayer=" + ((controller == null)
                             ? "null" : controller.getLayer()));
-                }
+                }, false /* traverseTopToBottom */);
             }
 
             final ScreenRotationAnimation screenRotationAnimation =
@@ -3064,6 +2248,9 @@
                 }
             }
             if (allBlack) {
+                final WindowState appWin = mScreenshotApplicationState.appWin;
+                final int maxLayer = mScreenshotApplicationState.maxLayer;
+                final int minLayer = mScreenshotApplicationState.minLayer;
                 Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
                         Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
                         (appWin != null ?
@@ -3104,32 +2291,23 @@
     }
 
     void onSeamlessRotationTimeout() {
-        boolean layoutNeeded = false;
-        for (int i = mWindows.size() - 1; i >= 0; i--) {
-            final WindowState w = mWindows.get(i);
+        // Used to indicate the layout is needed.
+        mTmpWindow = null;
+
+        forAllWindows(w -> {
             if (!w.mSeamlesslyRotated) {
-                continue;
+                return;
             }
-            layoutNeeded = true;
+            mTmpWindow = w;
             w.setDisplayLayoutNeeded();
             mService.markForSeamlessRotation(w, false);
-        }
+        }, true /* traverseTopToBottom */);
 
-        if (layoutNeeded) {
+        if (mTmpWindow != null) {
             mService.mWindowPlacerLocked.performSurfacePlacement();
         }
     }
 
-    static final class GetWindowOnDisplaySearchResult {
-        boolean reachedToken;
-        WindowState foundWindow;
-
-        void reset() {
-            reachedToken = false;
-            foundWindow = null;
-        }
-    }
-
     static final class TaskForResizePointSearchResult {
         boolean searchDone;
         Task taskForResize;
@@ -3140,6 +2318,39 @@
         }
     }
 
+    private static final class ApplySurfaceChangesTransactionState {
+        boolean displayHasContent;
+        boolean obscured;
+        boolean syswin;
+        boolean focusDisplayed;
+        float preferredRefreshRate;
+        int preferredModeId;
+
+        void reset() {
+            displayHasContent = false;
+            obscured = false;
+            syswin = false;
+            focusDisplayed = false;
+            preferredRefreshRate = 0;
+            preferredModeId = 0;
+        }
+    }
+
+    private static final class ScreenshotApplicationState {
+        WindowState appWin;
+        int maxLayer;
+        int minLayer;
+        boolean screenshotReady;
+
+        void reset(boolean screenshotReady) {
+            appWin = null;
+            maxLayer = 0;
+            minLayer = 0;
+            this.screenshotReady = screenshotReady;
+            minLayer = (screenshotReady) ? 0 : Integer.MAX_VALUE;
+        }
+    }
+
     /**
      * Base class for any direct child window container of {@link #DisplayContent} need to inherit
      * from. This is mainly a pass through class that allows {@link #DisplayContent} to have
@@ -3192,15 +2403,6 @@
         void removeStackFromDisplay(TaskStack stack) {
             removeChild(stack);
             stack.onRemovedFromDisplay();
-            // TODO: remove when window list will be gone.
-            // Manually remove records from window list and tap excluded windows list.
-            for (int i = mWindows.size() - 1; i >= 0; --i) {
-                final WindowState windowState = mWindows.get(i);
-                if (stack == windowState.getStack()) {
-                    mWindows.remove(i);
-                    mTapExcludedWindows.remove(windowState);
-                }
-            }
         }
 
         void moveStack(TaskStack stack, boolean toTop) {
@@ -3345,6 +2547,40 @@
         }
 
         @Override
+        int getOrientation() {
+            final WindowManagerPolicy policy = mService.mPolicy;
+            // Find a window requesting orientation.
+            final WindowState win = getWindow((w) -> {
+                if (!w.isVisibleLw() || !w.mPolicyVisibilityAfterAnim) {
+                    return false;
+                }
+                final int req = w.mAttrs.screenOrientation;
+                if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND
+                        || req == SCREEN_ORIENTATION_UNSET) {
+                    return false;
+                }
+                return true;
+            });
+
+            if (win != null) {
+                final int req = win.mAttrs.screenOrientation;
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req);
+                if (policy.isKeyguardHostWindow(win.mAttrs)) {
+                    mService.mLastKeyguardForcedOrientation = req;
+                }
+                return (mService.mLastWindowForcedOrientation = req);
+            }
+
+            mService.mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+            if (policy.isKeyguardShowingAndNotOccluded()) {
+                return mService.mLastKeyguardForcedOrientation;
+            }
+
+            return SCREEN_ORIENTATION_UNSET;
+        }
+
+        @Override
         String getName() {
             return mName;
         }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 12c72e9..01d1c30 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -16,10 +16,15 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
@@ -70,6 +75,10 @@
     // Array of window handles to provide to the input dispatcher.
     private InputWindowHandle[] mInputWindowHandles;
     private int mInputWindowHandleCount;
+    private boolean mAddInputConsumerHandle;
+    private boolean mAddPipInputConsumerHandle;
+    private boolean mAddWallpaperInputConsumerHandle;
+    private boolean mDisableWallpaperTouchEvents;
 
     // Set to true when the first input device configuration change notification
     // is received to indicate that the input devices are ready.
@@ -323,12 +332,12 @@
         }
     }
 
-    public void setUpdateInputWindowsNeededLw() {
+    void setUpdateInputWindowsNeededLw() {
         mUpdateInputWindowsNeeded = true;
     }
 
     /* Updates the cached window information provided to the input dispatcher. */
-    public void updateInputWindowsLw(boolean force) {
+    void updateInputWindowsLw(boolean force) {
         if (!force && !mUpdateInputWindowsNeeded) {
             return;
         }
@@ -372,15 +381,92 @@
         }
 
         // Add all windows on the default display.
-        mService.mRoot.updateInputWindows(this, mInputFocus, inDrag);
+        updateInputWindows(inDrag);
+
+        if (false) Slog.d(TAG_WM, "<<<<<<< EXITED updateInputWindowsLw");
+    }
+
+    private void updateInputWindows(boolean inDrag) {
+
+        clearInputWindowHandlesLw();
+
+        // TODO: multi-display
+        final InputConsumerImpl navInputConsumer =
+                getInputConsumer(INPUT_CONSUMER_NAVIGATION, DEFAULT_DISPLAY);
+        final InputConsumerImpl pipInputConsumer =
+                getInputConsumer(INPUT_CONSUMER_PIP, DEFAULT_DISPLAY);
+        final InputConsumerImpl wallpaperInputConsumer =
+                getInputConsumer(INPUT_CONSUMER_WALLPAPER, DEFAULT_DISPLAY);
+        mAddInputConsumerHandle = navInputConsumer != null;
+        mAddPipInputConsumerHandle = pipInputConsumer != null;
+        mAddWallpaperInputConsumerHandle = wallpaperInputConsumer != null;
+        final Rect pipTouchableBounds = mAddPipInputConsumerHandle ? new Rect() : null;
+        mDisableWallpaperTouchEvents = false;
+
+        final WallpaperController wallpaperController = mService.mRoot.mWallpaperController;
+        mService.mRoot.forAllWindows(w -> {
+            final InputChannel inputChannel = w.mInputChannel;
+            final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
+            if (inputChannel == null || inputWindowHandle == null || w.mRemoved
+                    || w.isAdjustedForMinimizedDock()) {
+                // Skip this window because it cannot possibly receive input.
+                return;
+            }
+
+            if (mAddPipInputConsumerHandle
+                    && w.getStackId() == PINNED_STACK_ID
+                    && inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer) {
+                // Update the bounds of the Pip input consumer to match the Pinned stack
+                w.getStack().getBounds(pipTouchableBounds);
+                pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
+                addInputWindowHandle(pipInputConsumer.mWindowHandle);
+                mAddPipInputConsumerHandle = false;
+            }
+
+            if (mAddInputConsumerHandle
+                    && inputWindowHandle.layer <= navInputConsumer.mWindowHandle.layer) {
+                addInputWindowHandle(navInputConsumer.mWindowHandle);
+                mAddInputConsumerHandle = false;
+            }
+
+            if (mAddWallpaperInputConsumerHandle) {
+                if (w.mAttrs.type == TYPE_WALLPAPER && w.isVisibleLw()) {
+                    // Add the wallpaper input consumer above the first visible wallpaper.
+                    addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
+                    mAddWallpaperInputConsumerHandle = false;
+                }
+            }
+
+            final int flags = w.mAttrs.flags;
+            final int privateFlags = w.mAttrs.privateFlags;
+            final int type = w.mAttrs.type;
+
+            final boolean hasFocus = w == mInputFocus;
+            final boolean isVisible = w.isVisibleLw();
+            if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
+                mDisableWallpaperTouchEvents = true;
+            }
+            final boolean hasWallpaper = wallpaperController.isWallpaperTarget(w)
+                    && (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
+                    && !mDisableWallpaperTouchEvents;
+
+            // If there's a drag in progress and 'child' is a potential drop target,
+            // make sure it's been told about the drag
+            if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
+                mService.mDragState.sendDragStartedIfNeededLw(w);
+            }
+
+            addInputWindowHandle(
+                    inputWindowHandle, w, flags, type, isVisible, hasFocus, hasWallpaper);
+        }, true /* traverseTopToBottom */);
+
+        if (mAddWallpaperInputConsumerHandle) {
+            // No visible wallpaper found, add the wallpaper input consumer at the end.
+            addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
+        }
 
         // Send windows to native code.
         mService.mInputManager.setInputWindows(mInputWindowHandles);
-
-        // Clear the list in preparation for the next round.
-        clearInputWindowHandlesLw();
-
-        if (false) Slog.d(TAG_WM, "<<<<<<< EXITED updateInputWindowsLw");
     }
 
     /* Notifies that the input device configuration has changed. */
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 88986e3..1962ca7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -219,7 +219,7 @@
         return false;
     }
 
-    void getWindowsByName(WindowList output, String name) {
+    void getWindowsByName(ArrayList<WindowState> output, String name) {
         int objectId = 0;
         // See if this is an object ID.
         try {
@@ -231,7 +231,7 @@
         getWindowsByName(output, name, objectId);
     }
 
-    private void getWindowsByName(WindowList output, String name, int objectId) {
+    private void getWindowsByName(ArrayList<WindowState> output, String name, int objectId) {
         forAllWindows((w) -> {
             if (name != null) {
                 if (w.mAttrs.getTitle().toString().contains(name)) {
@@ -276,15 +276,6 @@
         return null;
     }
 
-    // TODO: Users would have their own window containers under the display container?
-    void switchUser() {
-        final int count = mChildren.size();
-        for (int i = 0; i < count; ++i) {
-            final DisplayContent dc = mChildren.get(i);
-            dc.switchUser();
-        }
-    }
-
     /**
      * Set new display override config and return array of ids of stacks that were changed during
      * update. If called for the default display, global configuration will also be updated.
@@ -429,14 +420,6 @@
         return hasChanges;
     }
 
-    void updateInputWindows(InputMonitor inputMonitor, WindowState inputFocus, boolean inDrag) {
-        final int count = mChildren.size();
-        for (int i = 0; i < count; ++i) {
-            final DisplayContent dc = mChildren.get(i);
-            dc.updateInputWindows(inputMonitor, inputFocus, inDrag);
-        }
-    }
-
     boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
             boolean secure) {
         final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b889db2..772833c8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -589,46 +589,6 @@
         }
     }
 
-    void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
-            DisplayContent.GetWindowOnDisplaySearchResult result) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final AppWindowToken current = mChildren.get(i);
-            if (current == token) {
-                // We have reach the token we are interested in. End search.
-                result.reachedToken = true;
-                return;
-            }
-
-            // We haven't reached the token yet; if this token is not going to the bottom and
-            // has windows on this display, then it is a candidate for what we are looking for.
-            final WindowList tokenWindowList = dc.getTokenWindowsOnDisplay(current);
-            if (!current.sendingToBottom && tokenWindowList.size() > 0) {
-                result.foundWindow = tokenWindowList.get(0);
-            }
-        }
-    }
-
-    void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
-            DisplayContent.GetWindowOnDisplaySearchResult result) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final AppWindowToken current = mChildren.get(i);
-            if (!result.reachedToken) {
-                if (current == token) {
-                    // We have reached the token we are interested in. Get whichever window occurs
-                    // after it that is on the same display.
-                    result.reachedToken = true;
-                }
-                continue;
-            }
-
-            final WindowList tokenWindowList = dc.getTokenWindowsOnDisplay(current);
-            if (tokenWindowList.size() > 0) {
-                result.foundWindow = tokenWindowList.get(tokenWindowList.size() - 1);
-                return;
-            }
-        }
-    }
-
     @Override
     boolean fillsParent() {
         return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 203ba72..f2c74df 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -859,7 +859,9 @@
     }
 
     // TODO: Should each user have there own stacks?
+    @Override
     void switchUser() {
+        super.switchUser();
         int top = mChildren.size();
         for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
             Task task = mChildren.get(taskNdx);
@@ -1488,30 +1490,6 @@
         }
     }
 
-    void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
-            DisplayContent.GetWindowOnDisplaySearchResult result) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final Task task = mChildren.get(i);
-            task.getWindowOnDisplayBeforeToken(dc, token, result);
-            if (result.reachedToken) {
-                // We have reach the token we are interested in. End search.
-                return;
-            }
-        }
-    }
-
-    void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
-            DisplayContent.GetWindowOnDisplaySearchResult result) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final Task task = mChildren.get(i);
-            task.getWindowOnDisplayAfterToken(dc, token, result);
-            if (result.foundWindow != null) {
-                // We have found a window after the token. End search.
-                return;
-            }
-        }
-    }
-
     @Override
     int getOrientation() {
         return (StackId.canSpecifyOrientation(mStackId))
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 8dbf2b3..7fd8028 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -683,24 +683,6 @@
         }
     }
 
-    void dumpTokens(PrintWriter pw, String prefix, boolean dumpAll) {
-        if (!mWallpaperTokens.isEmpty()) {
-            pw.println();
-            pw.print(prefix); pw.println("Wallpaper tokens:");
-            for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
-                WindowToken token = mWallpaperTokens.get(i);
-                pw.print(prefix); pw.print("Wallpaper #"); pw.print(i);
-                pw.print(' '); pw.print(token);
-                if (dumpAll) {
-                    pw.println(':');
-                    token.dump(pw, "    ");
-                } else {
-                    pw.println();
-                }
-            }
-        }
-    }
-
     /** Helper class for storing the results of a wallpaper target find operation. */
     final private static class FindWallpaperTargetResult {
         WindowState topWallpaper = null;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a6a907c..f5db0b6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -18,7 +18,6 @@
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
-import android.view.animation.Animation;
 import com.android.internal.util.ToBooleanFunction;
 
 import java.util.Comparator;
@@ -483,19 +482,11 @@
         return false;
     }
 
-    /**
-     * Rebuilds the WindowList for the input display content.
-     * @param addIndex The index in the window list to add the next entry to.
-     * @return The next index in the window list to.
-     */
-    // TODO: Hoping we can get rid of WindowList so this method wouldn't be needed.
-    int rebuildWindowList(int addIndex) {
-        final int count = mChildren.size();
-        for (int i = 0; i < count; i++) {
-            final WindowContainer wc = mChildren.get(i);
-            addIndex = wc.rebuildWindowList(addIndex);
+    // TODO: Users would have their own window containers under the display container?
+    void switchUser() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).switchUser();
         }
-        return addIndex;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 00d8fba..507679b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -606,17 +606,6 @@
     boolean mInputMethodTargetWaitingAnim;
 
     WindowState mInputMethodWindow = null;
-    // TODO: Remove with extreme prejudice! This list is maintained so that we can keep track of
-    // dialogs that should go on top of the IME so they can be re-arranged in the window list any
-    // time the IME changes position in the window list. Normally you would have a dialog be a child
-    // window of the IME, but they don't share the same token since they are added by different
-    // clients. This doesn't really affect what the user sees on screen since this dialogs have an
-    // higher base layer than the IME window, but it will affect users of the window list that
-    // expect the list to represent the order of things on-screen (e.g input service). This makes
-    // the code for managing the window list hard to follow (see all the places it is used).
-    // We can remove the use of this field when we automatically assign layers and z-order the
-    // window list before it is used whenever window container order changes.
-    final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<>();
 
     boolean mHardKeyboardAvailable;
     WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
@@ -1377,19 +1366,16 @@
 
             boolean imMayMove = true;
 
+            win.mToken.addWindow(win);
             if (type == TYPE_INPUT_METHOD) {
                 win.mGivenInsetsPending = true;
                 mInputMethodWindow = win;
-                win.mToken.addImeWindow(win);
+                displayContent.computeImeTarget(true /* updateImeTarget */);
                 imMayMove = false;
             } else if (type == TYPE_INPUT_METHOD_DIALOG) {
-                mInputMethodDialogs.add(win);
-                win.mToken.addWindow(win);
-                displayContent.moveInputMethodDialogs(
-                        displayContent.findDesiredInputMethodWindowIndex(true));
+                displayContent.computeImeTarget(true /* updateImeTarget */);
                 imMayMove = false;
             } else {
-                win.mToken.addWindow(win);
                 if (type == TYPE_WALLPAPER) {
                     displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
                     displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
@@ -1462,7 +1448,7 @@
             }
 
             if (imMayMove) {
-                displayContent.moveInputMethodWindowsIfNeeded(false);
+                displayContent.computeImeTarget(true /* updateImeTarget */);
             }
 
             // Don't do layout here, the window must call
@@ -1646,8 +1632,6 @@
 
         if (mInputMethodWindow == win) {
             mInputMethodWindow = null;
-        } else if (win.mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
-            mInputMethodDialogs.remove(win);
         }
 
         final WindowToken token = win.mToken;
@@ -1677,13 +1661,11 @@
             dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
         }
 
-        if (dc != null && dc.removeFromWindowList(win)) {
-            if (!mWindowPlacerLocked.isInLayout()) {
-                dc.assignWindowLayers(true /* setLayoutNeeded */);
-                mWindowPlacerLocked.performSurfacePlacement();
-                if (win.mAppToken != null) {
-                    win.mAppToken.updateReportedVisibilityLocked();
-                }
+        if (dc != null && !mWindowPlacerLocked.isInLayout()) {
+            dc.assignWindowLayers(true /* setLayoutNeeded */);
+            mWindowPlacerLocked.performSurfacePlacement();
+            if (win.mAppToken != null) {
+                win.mAppToken.updateReportedVisibilityLocked();
             }
         }
 
@@ -2064,14 +2046,15 @@
             // reassign them at this point if the IM window state gets shuffled
             boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
             final DisplayContent dc = win.getDisplayContent();
-            if (imMayMove && (dc.moveInputMethodWindowsIfNeeded(false) || toBeDisplayed)) {
-                // Little hack here -- we -should- be able to rely on the function to return true if
-                // the IME has moved and needs its layer recomputed.  However, if the IME was hidden
-                // and isn't actually moved in the list, its layer may be out of data so we make
-                // sure to recompute it.
-                // TODO: Probably not needed once the window list always has the right z-ordering
-                // when the window hierarchy is updated.
-                dc.assignWindowLayers(false /* setLayoutNeeded */);
+            if (imMayMove) {
+                dc.computeImeTarget(true /* updateImeTarget */);
+                if (toBeDisplayed) {
+                    // Little hack here -- we -should- be able to rely on the function to return
+                    // true if the IME has moved and needs its layer recomputed. However, if the IME
+                    // was hidden and isn't actually moved in the list, its layer may be out of data
+                    // so we make sure to recompute it.
+                    dc.assignWindowLayers(false /* setLayoutNeeded */);
+                }
             }
 
             if (wallpaperMayMove) {
@@ -3351,7 +3334,7 @@
                 if (mAppTransition.isTransitionSet()) {
                     task.setSendingToBottom(false);
                 }
-                displayContent.rebuildAppWindowsAndLayoutIfNeeded();
+                displayContent.layoutAndAssignWindowLayersIfNeeded();
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -3373,7 +3356,7 @@
                 if (mAppTransition.isTransitionSet()) {
                     task.setSendingToBottom(true);
                 }
-                stack.getDisplayContent().rebuildAppWindowsAndLayoutIfNeeded();
+                stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4619,7 +4602,7 @@
                 return null;
             }
         }
-        return displayContent.screenshotApplications(appToken, displayId, width, height,
+        return displayContent.screenshotApplications(appToken, width, height,
                 includeFullDisplay, frameScale, config, wallpaperOnly);
     }
 
@@ -5181,7 +5164,7 @@
 
         boolean result = true;
 
-        final WindowList windows = new WindowList();
+        final ArrayList<WindowState> windows = new ArrayList();
         synchronized (mWindowMap) {
             mRoot.forAllWindows(w -> {
                 windows.add(w);
@@ -7312,7 +7295,7 @@
         changes |= FINISH_LAYOUT_REDO_LAYOUT;
         if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG_WM,
                 "Wallpaper layer changed: assigning layers + relayout");
-        dc.moveInputMethodWindowsIfNeeded(true);
+        dc.computeImeTarget(true /* updateImeTarget */);
         mRoot.mWallpaperMayChange = true;
         // Since the window list has been rebuilt, focus might have to be recomputed since the
         // actual order of windows might have changed again.
@@ -7405,10 +7388,25 @@
             mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
             // TODO(multidisplay): Focused windows on default display only.
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
-            final boolean imWindowChanged = displayContent.moveInputMethodWindowsIfNeeded(
-                    mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
-                            && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES);
+            boolean imWindowChanged = false;
+            if (mInputMethodWindow != null) {
+                final WindowState prevTarget = mInputMethodTarget;
+                final WindowState newTarget =
+                        displayContent.computeImeTarget(true /* updateImeTarget*/);
+
+                imWindowChanged = prevTarget != newTarget;
+
+                if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
+                        && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
+                    final int prevImeAnimLayer = mInputMethodWindow.mWinAnimator.mAnimLayer;
+                    displayContent.assignWindowLayers(false /* setLayoutNeeded */);
+                    imWindowChanged |=
+                            prevImeAnimLayer != mInputMethodWindow.mWinAnimator.mAnimLayer;
+                }
+            }
+
             if (imWindowChanged) {
+                mWindowsChanged = true;
                 displayContent.setLayoutNeeded();
                 newFocus = mRoot.computeFocusedWindow();
             }
@@ -7883,7 +7881,6 @@
     private void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
         pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)");
         mRoot.dumpTokens(pw, dumpAll);
-        mRoot.mWallpaperController.dumpTokens(pw, "  ", dumpAll);
         if (!mFinishedStarting.isEmpty()) {
             pw.println();
             pw.println("  Finishing start of application tokens:");
@@ -7929,16 +7926,6 @@
             ArrayList<WindowState> windows) {
         mRoot.dumpWindowsNoHeader(pw, dumpAll, windows);
 
-        if (mInputMethodDialogs.size() > 0) {
-            pw.println();
-            pw.println("  Input method dialogs:");
-            for (int i=mInputMethodDialogs.size()-1; i>=0; i--) {
-                WindowState w = mInputMethodDialogs.get(i);
-                if (windows == null || windows.contains(w)) {
-                    pw.print("  IM Dialog #"); pw.print(i); pw.print(": "); pw.println(w);
-                }
-            }
-        }
         if (mPendingRemove.size() > 0) {
             pw.println();
             pw.println("  Remove pending for:");
@@ -8097,7 +8084,7 @@
 
     private boolean dumpWindows(PrintWriter pw, String name, String[] args, int opti,
             boolean dumpAll) {
-        final WindowList windows = new WindowList();
+        final ArrayList<WindowState> windows = new ArrayList();
         if ("apps".equals(name) || "visible".equals(name) || "visible-apps".equals(name)) {
             final boolean appsOnly = name.contains("apps");
             final boolean visibleOnly = name.contains("visible");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 661df9cd..d959d8c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -76,6 +76,7 @@
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 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_LAYOUT_NO_LIMITS;
@@ -115,6 +116,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -140,9 +142,6 @@
 import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
 import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
 
-class WindowList extends ArrayList<WindowState> {
-}
-
 /** A window in the window manager. */
 class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
     static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
@@ -448,12 +447,6 @@
      */
     boolean mWindowRemovalAllowed;
 
-    /**
-     * Temp for keeping track of windows that have been removed when
-     * rebuilding window list.
-     */
-    boolean mRebuilding;
-
     // Input channel and input window handle used by the input dispatcher.
     final InputWindowHandle mInputWindowHandle;
     InputChannel mInputChannel;
@@ -1721,7 +1714,7 @@
 
         final DisplayContent dc = getDisplayContent();
         if (mService.mInputMethodTarget == this) {
-            dc.moveInputMethodWindowsIfNeeded(false);
+            dc.computeImeTarget(true /* updateImeTarget */);
         }
 
         final int type = mAttrs.type;
@@ -1931,6 +1924,33 @@
         return mLayer + specialAdjustment;
     }
 
+    boolean canBeImeTarget() {
+        final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
+        final int type = mAttrs.type;
+
+        if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
+                && type != TYPE_APPLICATION_STARTING) {
+            return false;
+        }
+
+        if (DEBUG_INPUT_METHOD) {
+            Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding());
+            if (!isVisibleOrAdding()) {
+                Slog.i(TAG_WM, "  mSurfaceController=" + mWinAnimator.mSurfaceController
+                        + " relayoutCalled=" + mRelayoutCalled
+                        + " viewVis=" + mViewVisibility
+                        + " policyVis=" + mPolicyVisibility
+                        + " policyVisAfterAnim=" + mPolicyVisibilityAfterAnim
+                        + " parentHidden=" + isParentWindowHidden()
+                        + " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
+                if (mAppToken != null) {
+                    Slog.i(TAG_WM, "  mAppToken.hiddenRequested=" + mAppToken.hiddenRequested);
+                }
+            }
+        }
+        return isVisibleOrAdding();
+    }
+
     void scheduleAnimationIfDimming() {
         final DisplayContent dc = getDisplayContent();
         if (dc == null) {
@@ -2085,7 +2105,7 @@
         return false;
     }
 
-    void removeReplacedWindow() {
+    private void removeReplacedWindow() {
         if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this);
         if (isDimming()) {
             transferDimToReplacement();
@@ -2138,6 +2158,16 @@
         }
     }
 
+    @Override
+    void switchUser() {
+        super.switchUser();
+        if (isHiddenFromUserLocked()) {
+            if (DEBUG_VISIBILITY) Slog.w(TAG_WM, "user changing, hiding " + this
+                    + ", attrs=" + mAttrs.type + ", belonging to " + mOwnerUid);
+            hideLw(false);
+        }
+    }
+
     int getTouchableRegion(Region region, int flags) {
         final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
         if (modal && mAppToken != null) {
@@ -3499,16 +3529,6 @@
         return mIsChildWindow;
     }
 
-    /**
-     * Returns the bottom child window in regards to z-order of this window or null if no children.
-     */
-    WindowState getBottomChild() {
-        // Child windows are z-ordered based on sub-layer using {@link #sWindowSubLayerComparator}
-        // and the child with the lowest z-order will be at the head of the list.
-        WindowState c = mChildren.peekFirst();
-        return c == null ? null : c;
-    }
-
     boolean layoutInParentFrame() {
         return mIsChildWindow
                 && (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0;
@@ -3795,44 +3815,6 @@
     }
 
     @Override
-    int rebuildWindowList(int addIndex) {
-        return reAddWindow(addIndex);
-    }
-
-    // TODO: come-up with a better name for this method that represents what it does.
-    // Or, it is probably not going to matter anyways if we are successful in getting rid of
-    // the WindowList concept.
-    int reAddWindow(int index) {
-        final DisplayContent dc = getDisplayContent();
-        // Adding child windows relies on child windows being ordered by mSubLayer using
-        // {@link #sWindowSubLayerComparator}.
-        final int childCount = mChildren.size();
-        boolean winAdded = false;
-        for (int j = 0; j < childCount; j++) {
-            final WindowState child = mChildren.get(j);
-            if (!winAdded && child.mSubLayer >= 0) {
-                if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
-                        "Re-adding child window at " + index + ": " + child);
-                mRebuilding = false;
-                dc.addToWindowList(this, index);
-                index++;
-                winAdded = true;
-            }
-            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + child);
-            child.mRebuilding = false;
-            dc.addToWindowList(child, index);
-            index++;
-        }
-        if (!winAdded) {
-            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + this);
-            mRebuilding = false;
-            dc.addToWindowList(this, index);
-            index++;
-        }
-        return index;
-    }
-
-    @Override
     boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
         if (mChildren.isEmpty()) {
             // The window has no children so we just return it.
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 7e1880f..de80837 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -239,7 +239,8 @@
         mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
 
         final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
-        displayContent.rebuildAppWindowList();
+        // TODO: Don't believe this is really needed...
+        //mService.mWindowsChanged = true;
 
         mService.mRoot.mWallpaperMayChange = false;
 
@@ -356,12 +357,8 @@
         displayContent.setLayoutNeeded();
 
         // TODO(multidisplay): IMEs are only supported on the default display.
-        // TODO: Probably not needed once the window list always has the right z-ordering
-        // when the window hierarchy is updated.
         final DisplayContent dc = mService.getDefaultDisplayContentLocked();
-        if (!dc.moveInputMethodWindowsIfNeeded(true)) {
-            dc.assignWindowLayers(false /*setLayoutNeeded*/);
-        }
+        dc.computeImeTarget(true /* updateImeTarget */);
         mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
                 true /*updateInputWindows*/);
         mService.mFocusMayChange = false;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index a2eebc3..40bd3fb 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -168,13 +168,6 @@
         return highestAnimLayer;
     }
 
-    WindowState getTopWindow() {
-        if (mChildren.isEmpty()) {
-            return null;
-        }
-        return (WindowState) mChildren.get(mChildren.size() - 1).getTop();
-    }
-
     /**
      * Returns true if the new window is considered greater than the existing window in terms of
      * z-order.
@@ -189,51 +182,16 @@
         if (DEBUG_FOCUS) Slog.d(TAG_WM,
                 "addWindow: win=" + win + " Callers=" + Debug.getCallers(5));
 
-        if (!win.isChildWindow()) {
-            if (asAppWindowToken() != null) {
-                mDisplayContent.addAppWindowToWindowList(win);
-            } else {
-                mDisplayContent.addNonAppWindowToWindowList(win);
-            }
-
-            if (!mChildren.contains(win)) {
-                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
-                addChild(win, mWindowComparator);
-            }
-        } else {
-            mDisplayContent.addChildWindowToWindowList(win);
-        }
-    }
-
-    void addImeWindow(WindowState win) {
-        int pos = mDisplayContent.findDesiredInputMethodWindowIndex(true);
-
-        if (pos < 0) {
-            addWindow(win);
-            mDisplayContent.moveInputMethodDialogs(pos);
+        if (win.isChildWindow()) {
+            // Child windows are added to their parent windows.
             return;
         }
-
-        if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                "Adding input method window " + win + " at " + pos);
-        mDisplayContent.addToWindowList(win, pos);
         if (!mChildren.contains(win)) {
-            addChild(win, null);
+            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
+            addChild(win, mWindowComparator);
+            mService.mWindowsChanged = true;
+            // TODO: Should we also be setting layout needed here and other places?
         }
-        mDisplayContent.moveInputMethodDialogs(pos + 1);
-    }
-
-    /** Return the first window in the token window list that isn't a starting window or null. */
-    WindowState getFirstNonStartingWindow() {
-        final int count = mChildren.size();
-        // We only care about parent windows so no need to loop through child windows.
-        for (int i = 0; i < count; i++) {
-            final WindowState w = mChildren.get(i);
-            if (w.mAttrs.type != TYPE_APPLICATION_STARTING) {
-                return w;
-            }
-        }
-        return null;
     }
 
     /** Returns true if the token windows list is empty. */
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ba23f21..1fc4378 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -41,12 +41,10 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.storage.IStorageManager;
-import android.provider.Settings;
+import android.util.BootTimingsTraceLog;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
-import android.util.Pair;
 import android.util.Slog;
 import android.view.WindowManager;
 
@@ -55,7 +53,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.SamplingProfilerIntegration;
-import com.android.internal.os.ZygoteInit;
 import com.android.internal.policy.EmergencyAffordanceManager;
 import com.android.internal.widget.ILockSettings;
 import com.android.server.accessibility.AccessibilityManagerService;
@@ -102,8 +99,8 @@
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
 import com.android.server.trust.TrustManagerService;
-import com.android.server.tv.TvRemoteService;
 import com.android.server.tv.TvInputManagerService;
+import com.android.server.tv.TvRemoteService;
 import com.android.server.twilight.TwilightService;
 import com.android.server.usage.UsageStatsService;
 import com.android.server.vr.VrManagerService;
@@ -112,8 +109,6 @@
 
 import dalvik.system.VMRuntime;
 
-import java.util.ArrayDeque;
-import java.util.Deque;
 import java.io.File;
 import java.io.IOException;
 import java.util.Locale;
@@ -125,11 +120,8 @@
 public final class SystemServer {
     private static final String TAG = "SystemServer";
 
-    private static final boolean LOG_BOOT_TIME = true;
-    // Debug boot time for every step if it's non-user build.
-    private static final boolean DEBUG_BOOT_TIME = LOG_BOOT_TIME && !"user".equals(Build.TYPE);
-    private static final String TAG_BOOT_TIME = "SystemServerTiming";
-    private static final Deque<Pair<String, Long>> START_TIMES = new ArrayDeque<>();
+    private static final BootTimingsTraceLog BOOT_TIMINGS_TRACE_LOG
+            = new BootTimingsTraceLog("SystemServerTiming", Trace.TRACE_TAG_SYSTEM_SERVER);
 
     private static final String ENCRYPTING_STATE = "trigger_restart_min_framework";
     private static final String ENCRYPTED_STATE = "1";
@@ -279,6 +271,11 @@
             int uptimeMillis = (int) SystemClock.uptimeMillis();
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
             MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
+            // Also report when first stage of init has started
+            long initStartNs = SystemProperties.getLong("init.start", -1);
+            if (initStartNs >= 0) {
+                MetricsLogger.histogram(null, "boot_android_init", (int)(initStartNs / 1000000));
+            }
 
             // In case the runtime switched since last boot (such as when
             // the old runtime was removed in an OTA), set the system
@@ -1643,24 +1640,11 @@
     }
 
     private static void traceBeginAndSlog(String name) {
-        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
         Slog.i(TAG, name);
-        if (DEBUG_BOOT_TIME) {
-            START_TIMES.push(Pair.create(name, Long.valueOf(SystemClock.elapsedRealtime())));
-        }
+        BOOT_TIMINGS_TRACE_LOG.traceBegin(name);
     }
 
     private static void traceEnd() {
-        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
-        if (!DEBUG_BOOT_TIME) {
-            return;
-        }
-        Pair<String, Long> event = START_TIMES.pollFirst();
-        if (event == null) {
-            Slog.w(TAG, "traceEnd called more times than traceBeginAndSlog");
-            return;
-        }
-        Slog.d(TAG_BOOT_TIME, event.first + " took to complete: "
-                + (SystemClock.elapsedRealtime() - event.second) + "ms");
+        BOOT_TIMINGS_TRACE_LOG.traceEnd();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index 5b565a7..8591dae 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -37,6 +37,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -338,4 +339,65 @@
         actualId = mAccountsDb.findDeAccountId(account);
         assertEquals(-1, actualId);
     }
+
+    @Test
+    public void testFindDeAccountByAccountId() {
+        long accId = 10;
+        Account account = new Account("name", "example.com");
+        assertNull(mAccountsDb.findDeAccountByAccountId(accId));
+
+        mAccountsDb.insertDeAccount(account, accId);
+
+        Account foundAccount = mAccountsDb.findDeAccountByAccountId(accId);
+        assertEquals(account, foundAccount);
+    }
+
+    @Test
+    public void testVisibilityFindSetDelete() {
+        long accId = 10;
+        int uid1 = 100500;
+        int uid2 = 100501;
+        Account account = new Account("name", "example.com");
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+
+        mAccountsDb.insertDeAccount(account, accId);
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+        assertNull(mAccountsDb.findAccountVisibility(accId, uid1));
+
+        mAccountsDb.setAccountVisibility(accId, uid1, 1);
+        assertEquals(mAccountsDb.findAccountVisibility(account, uid1), Integer.valueOf(1));
+        assertEquals(mAccountsDb.findAccountVisibility(accId, uid1), Integer.valueOf(1));
+
+        mAccountsDb.setAccountVisibility(accId, uid2, 2);
+        assertEquals(mAccountsDb.findAccountVisibility(accId, uid2), Integer.valueOf(2));
+
+        mAccountsDb.setAccountVisibility(accId, uid2, 3);
+        assertEquals(mAccountsDb.findAccountVisibility(accId, uid2), Integer.valueOf(3));
+
+        Map<Integer, Integer> vis = mAccountsDb.findAccountVisibilityForAccountId(accId);
+        assertEquals(vis.size(), 2);
+        assertEquals(vis.get(uid1), Integer.valueOf(1));
+        assertEquals(vis.get(uid2), Integer.valueOf(3));
+
+        assertTrue(mAccountsDb.deleteAccountVisibilityForUid(uid1));
+        assertNull(mAccountsDb.findAccountVisibility(accId, uid1));
+        assertFalse(mAccountsDb.deleteAccountVisibilityForUid(uid1)); // Already deleted.
+    }
+
+    @Test
+    public void testVisibilityCleanupTrigger() {
+        long accId = 10;
+        int uid1 = 100500;
+        Account account = new Account("name", "example.com");
+
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+        mAccountsDb.insertDeAccount(account, accId);
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+
+        mAccountsDb.setAccountVisibility(accId, uid1, 1);
+        assertEquals(mAccountsDb.findAccountVisibility(accId, uid1), Integer.valueOf(1));
+
+        assertTrue(mAccountsDb.deleteDeAccount(accId)); // Trigger should remove visibility.
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index 50e5a22..3f47d5c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -117,35 +117,6 @@
     }
 
     @Test
-    public void testGetBottomChild() throws Exception {
-        final WindowState parentWindow =
-                createWindow(null, TYPE_APPLICATION, mWindowToken, "parentWindow");
-        assertNull(parentWindow.getBottomChild());
-
-        final WindowState child1 =
-                createWindow(parentWindow, TYPE_APPLICATION_PANEL, mWindowToken, "child1");
-        assertEquals(child1, parentWindow.getBottomChild());
-
-        final WindowState child2 =
-                createWindow(parentWindow, TYPE_APPLICATION_PANEL, mWindowToken, "child2");
-        // Since child1 and child2 are at the same layer, then child2 is expect to be added on top
-        // on child1
-        assertEquals(child1, parentWindow.getBottomChild());
-
-        final WindowState child3 =
-                createWindow(parentWindow, TYPE_APPLICATION_MEDIA_OVERLAY, mWindowToken, "child3");
-        // Since child3 is a negative layer, we would expect it to be added below current children
-        // with positive layers.
-        assertEquals(child3, parentWindow.getBottomChild());
-
-        final WindowState child4 =
-                createWindow(parentWindow, TYPE_APPLICATION_MEDIA_OVERLAY, mWindowToken, "child4");
-        // We would also expect additional negative layers to be added below existing negative
-        // layers.
-        assertEquals(child4, parentWindow.getBottomChild());
-    }
-
-    @Test
     public void testGetParentWindow() throws Exception {
         final WindowState parentWindow =
                 createWindow(null, TYPE_APPLICATION, mWindowToken, "parentWindow");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 20dd012..457fd88 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -686,42 +686,49 @@
     public static final String EXTRA_DATA_FAILURE_CAUSE = PhoneConstants.DATA_FAILURE_CAUSE_KEY;
 
     /**
-     * Broadcast intent action for letting custom component know to show voicemail notification.
-     * @hide
+     * Broadcast intent action for letting the default dialer to know to show voicemail
+     * notification.
+     *
+     * <p>
+     * The {@link #EXTRA_NOTIFICATION_COUNT} extra indicates the total numbers of unheard
+     * voicemails.
+     * The {@link #EXTRA_VOICEMAIL_NUMBER} extra indicates the voicemail number if available.
+     * The {@link #EXTRA_CALL_VOICEMAIL_INTENT} extra is a {@link android.app.PendingIntent} that
+     * will call the voicemail number when sent. This extra will be empty if the voicemail number
+     * is not set, and {@link #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT} will be set instead.
+     * The {@link #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT} extra is a
+     * {@link android.app.PendingIntent} that will launch the voicemail settings. This extra is only
+     * available when the voicemail number is not set.
+     *
+     * @see #EXTRA_NOTIFICATION_COUNT
+     * @see #EXTRA_VOICEMAIL_NUMBER
+     * @see #EXTRA_CALL_VOICEMAIL_INTENT
+     * @see #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT
      */
-    @SystemApi
     public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION =
             "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
 
     /**
      * The number of voice messages associated with the notification.
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_NOTIFICATION_COUNT =
             "android.telephony.extra.NOTIFICATION_COUNT";
 
     /**
      * The voicemail number.
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_VOICEMAIL_NUMBER =
             "android.telephony.extra.VOICEMAIL_NUMBER";
 
     /**
      * The intent to call voicemail.
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_CALL_VOICEMAIL_INTENT =
             "android.telephony.extra.CALL_VOICEMAIL_INTENT";
 
     /**
      * The intent to launch voicemail settings.
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT =
             "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
 
@@ -4850,10 +4857,19 @@
         return false;
     }
 
-    /** @hide */
-    @SystemApi
+    /**
+     * Turns mobile data on or off.
+     *
+     * <p>Requires Permission:
+     *     {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
+     *     calling app has carrier privileges.
+     *
+     * @param enable Whether to enable mobile data.
+     *
+     * @see #hasCarrierPrivileges
+     */
     public void setDataEnabled(boolean enable) {
-        setDataEnabled(SubscriptionManager.getDefaultDataSubscriptionId(), enable);
+        setDataEnabled(getSubId(), enable);
     }
 
     /** @hide */
@@ -4869,10 +4885,20 @@
         }
     }
 
-    /** @hide */
-    @SystemApi
+    /**
+     * Returns whether mobile data is enabled or not.
+     *
+     * <p>Requires Permission:
+     *     {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
+     *     {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
+     *     calling app has carrier privileges.
+     *
+     * @return true if mobile data is enabled.
+     *
+     * @see #hasCarrierPrivileges
+     */
     public boolean getDataEnabled() {
-        return getDataEnabled(SubscriptionManager.getDefaultDataSubscriptionId());
+        return getDataEnabled(getSubId());
     }
 
     /** @hide */
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 4d0e6f0..306dec5 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -728,10 +728,7 @@
     static private void waitFor(Criteria criteria) {
         int delays = 0;
         while (!criteria.get()) {
-            try {
-                Thread.sleep(50);
-            } catch (InterruptedException e) {
-            }
+            sleepFor(50);
             if (++delays == 10) fail();
         }
     }
@@ -2333,10 +2330,30 @@
         networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
 
         // pass timeout and validate that UNAVAILABLE is not called
-        try {
-            Thread.sleep(15);
-        } catch (InterruptedException e) {
-        }
+        sleepFor(15);
+        networkCallback.assertNoCallback();
+    }
+
+    /**
+     * Validate that a satisfied network request followed by a disconnected (lost) network does
+     * not trigger onUnavailable() once the time-out period expires.
+     */
+    @SmallTest
+    public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(nr, networkCallback, 500);
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        sleepFor(20);
+        mWiFiNetworkAgent.disconnect();
+        networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // pass timeout and validate that UNAVAILABLE is not called
+        sleepFor(600);
         networkCallback.assertNoCallback();
     }
 
@@ -2378,10 +2395,7 @@
         // pass timeout and validate that no callbacks
         // Note: doesn't validate that nothing called from CS since even if called the CM already
         // unregisters the callback and won't pass it through!
-        try {
-            Thread.sleep(15);
-        } catch (InterruptedException e) {
-        }
+        sleepFor(15);
         networkCallback.assertNoCallback();
 
         // create a network satisfying request - validate that request not triggered
@@ -2795,4 +2809,13 @@
             mCm.unregisterNetworkCallback(pendingIntent);
         }
     }
+
+    /* test utilities */
+    static private void sleepFor(int ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+
+    }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index a62a0fb..643753a 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -36,6 +36,27 @@
     public HomeSP homeSp = null;
     public Credential credential = null;
 
+    /**
+     * Constructor for creating PasspointConfiguration with default values.
+     */
+    public PasspointConfiguration() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public PasspointConfiguration(PasspointConfiguration source) {
+        if (source != null) {
+            if (source.homeSp != null) {
+                homeSp = new HomeSP(source.homeSp);
+            }
+            if (source.credential != null) {
+                credential = new Credential(source.credential);
+            }
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 57e65eb..790dfaf 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -109,6 +109,25 @@
          */
         public String nonEapInnerMethod = null;
 
+        /**
+         * Constructor for creating UserCredential with default values.
+         */
+        public UserCredential() {}
+
+        /**
+         * Copy constructor.
+         *
+         * @param source The source to copy from
+         */
+        public UserCredential(UserCredential source) {
+            if (source != null) {
+                username = source.username;
+                password = source.password;
+                eapType = source.eapType;
+                nonEapInnerMethod = source.nonEapInnerMethod;
+            }
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -221,6 +240,26 @@
          */
         public byte[] certSha256FingerPrint = null;
 
+        /**
+         * Constructor for creating CertificateCredential with default values.
+         */
+        public CertificateCredential() {}
+
+        /**
+         * Copy constructor.
+         *
+         * @param source The source to copy from
+         */
+        public CertificateCredential(CertificateCredential source) {
+            if (source != null) {
+                certType = source.certType;
+                if (source.certSha256FingerPrint != null) {
+                    certSha256FingerPrint = Arrays.copyOf(source.certSha256FingerPrint,
+                                                          source.certSha256FingerPrint.length);
+                }
+            }
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -307,6 +346,23 @@
          */
         public int eapType = Integer.MIN_VALUE;
 
+        /**
+         * Constructor for creating SimCredential with default values.
+         */
+        public SimCredential() {}
+
+        /**
+         * Copy constructor
+         *
+         * @param source The source to copy from
+         */
+        public SimCredential(SimCredential source) {
+            if (source != null) {
+                imsi = source.imsi;
+                eapType = source.eapType;
+            }
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -422,6 +478,37 @@
      */
     public PrivateKey clientPrivateKey = null;
 
+    /**
+     * Constructor for creating Credential with default values.
+     */
+    public Credential() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public Credential(Credential source) {
+        if (source != null) {
+            realm = source.realm;
+            if (source.userCredential != null) {
+                userCredential = new UserCredential(source.userCredential);
+            }
+            if (source.certCredential != null) {
+                certCredential = new CertificateCredential(source.certCredential);
+            }
+            if (source.simCredential != null) {
+                simCredential = new SimCredential(source.simCredential);
+            }
+            if (source.clientCertificateChain != null) {
+                clientCertificateChain = Arrays.copyOf(source.clientCertificateChain,
+                                                       source.clientCertificateChain.length);
+            }
+            caCertificate = source.caCertificate;
+            clientPrivateKey = source.clientPrivateKey;
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
index 5837c06..d4a5792 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
@@ -53,6 +53,27 @@
      */
     public long[] roamingConsortiumOIs = null;
 
+    /**
+     * Constructor for creating HomeSP with default values.
+     */
+    public HomeSP() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public HomeSP(HomeSP source) {
+        if (source != null) {
+            fqdn = source.fqdn;
+            friendlyName = source.friendlyName;
+            if (source.roamingConsortiumOIs != null) {
+                roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
+                                                     source.roamingConsortiumOIs.length);
+            }
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index b4a3acf..2350d32 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -33,6 +33,11 @@
 @SmallTest
 public class PasspointConfigurationTest {
 
+    /**
+     * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
+     *
+     * @return {@link android.net.wifi.hotspot2.pps.HomeSP}
+     */
     private static HomeSP createHomeSp() {
         HomeSP homeSp = new HomeSP();
         homeSp.fqdn = "fqdn";
@@ -41,6 +46,11 @@
         return homeSp;
     }
 
+    /**
+     * Utility function for creating a {@link android.net.wifi.hotspot2.pps.Credential}.
+     *
+     * @return {@link android.net.wifi.hotspot2.pps.Credential}
+     */
     private static Credential createCredential() {
         Credential cred = new Credential();
         cred.realm = "realm";
@@ -55,6 +65,12 @@
         return cred;
     }
 
+    /**
+     * Verify parcel write and read consistency for the given configuration.
+     *
+     * @param writeConfig The configuration to verify
+     * @throws Exception
+     */
     private static void verifyParcel(PasspointConfiguration writeConfig) throws Exception {
         Parcel parcel = Parcel.obtain();
         writeConfig.writeToParcel(parcel, 0);
@@ -77,6 +93,7 @@
 
     /**
      * Verify parcel read/write for a configuration that contained both HomeSP and Credential.
+     *
      * @throws Exception
      */
     @Test
@@ -158,4 +175,30 @@
         config.credential = createCredential();
         assertTrue(config.validate());
     }
-}
\ No newline at end of file
+
+    /**
+     * Verify that copy constructor works when pass in a null source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithNullSource() throws Exception {
+        PasspointConfiguration copyConfig = new PasspointConfiguration(null);
+        PasspointConfiguration defaultConfig = new PasspointConfiguration();
+        assertTrue(copyConfig.equals(defaultConfig));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a valid source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithValidSource() throws Exception {
+        PasspointConfiguration sourceConfig = new PasspointConfiguration();
+        sourceConfig.homeSp = createHomeSp();
+        sourceConfig.credential = createCredential();
+        PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
+        assertTrue(copyConfig.equals(sourceConfig));
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 223aa52..9c8b749 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -470,4 +470,52 @@
         cred.simCredential.eapType = EAPConstants.EAP_SIM;
         assertFalse(cred.validate());
     }
+
+    /**
+     * Verify that copy constructor works when pass in a null source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithNullSource() throws Exception {
+        Credential copyCred = new Credential(null);
+        Credential defaultCred = new Credential();
+        assertTrue(copyCred.equals(defaultCred));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a source with user credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithSourceWithUserCred() throws Exception {
+        Credential sourceCred = createCredentialWithUserCredential();
+        Credential copyCred = new Credential(sourceCred);
+        assertTrue(copyCred.equals(sourceCred));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a source with certificate credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithSourceWithCertCred() throws Exception {
+        Credential sourceCred = createCredentialWithCertificateCredential();
+        Credential copyCred = new Credential(sourceCred);
+        assertTrue(copyCred.equals(sourceCred));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a source with SIM credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithSourceWithSimCred() throws Exception {
+        Credential sourceCred = createCredentialWithSimCredential();
+        Credential copyCred = new Credential(sourceCred);
+        assertTrue(copyCred.equals(sourceCred));
+    }
 }
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
index fff1477..c707993 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
@@ -118,4 +118,31 @@
         homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
         assertTrue(homeSp.validate());
     }
+
+    /**
+     * Verify that copy constructor works when pass in a null source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorFromNullSource() throws Exception {
+        HomeSP copySp = new HomeSP(null);
+        HomeSP defaultSp = new HomeSP();
+        assertTrue(copySp.equals(defaultSp));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a valid source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorFromValidSource() throws Exception {
+        HomeSP sourceSp = new HomeSP();
+        sourceSp.fqdn = "fqdn";
+        sourceSp.friendlyName = "friendlyName";
+        sourceSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
+        HomeSP copySp = new HomeSP(sourceSp);
+        assertTrue(copySp.equals(sourceSp));
+    }
 }