Merge "VolumeShaper: Enable xOffset from Java" into oc-dev
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 97da588..17f00c2 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -167,4 +167,12 @@
      * Remove user's running state
      */
     public abstract void removeUserState(int userId);
+
+    /**
+     * Returns an array of user ids. This array is cached in UserManagerService and passed as a
+     * reference, so do not modify the returned array.
+     *
+     * @return the array of user ids.
+     */
+    public abstract int[] getUserIds();
 }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 9cea4ed..2c33b60 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -856,7 +856,7 @@
      */
     @Deprecated
     public void setWindowType(int type) {
-        if (getContext().getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.N_MR1) {
+        if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
             throw new UnsupportedOperationException(
                     "SurfaceView#setWindowType() has never been a public API.");
         }
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index e4d4e7b..0973d0a 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -334,7 +334,7 @@
      */
     ViewTreeObserver(Context context) {
         sIllegalOnDrawModificationIsFatal =
-                context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.N_MR1;
+                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O;
     }
 
     /**
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index f53b5d6..8920089 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -83,8 +83,7 @@
      * supported by the current framework version.
      */
     public static boolean hasCorrectTargetSdkVersion(PackageInfo packageInfo) {
-        // TODO(gsennton) use Build.VERSION_CODES.O when that has been updated.
-        return packageInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1;
+        return packageInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O;
     }
 
     public UserInfo getUserInfo() {
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index be0967f..cfd1445 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -16,7 +16,7 @@
 
 package android.widget;
 
-import static android.os.Build.VERSION_CODES.N_MR1;
+import static android.os.Build.VERSION_CODES.O;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -302,7 +302,7 @@
         getTempCalendarForTime(timeInMillis);
 
         final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
-        if (targetSdkVersion > N_MR1) {
+        if (targetSdkVersion >= O) {
             if (mTempCalendar.before(mMinDate) || mTempCalendar.after(mMaxDate)) {
                 throw new IllegalArgumentException("timeInMillis must be between the values of "
                         + "getMinDate() and getMaxDate()");
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index c96e280..c1d26e3 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -17,8 +17,7 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:detachWallpaper="true"
-        android:shareInterpolator="false"
-        android:startOffset="100">
+        android:shareInterpolator="false">
 
     <translate android:fromYDelta="110%p" android:toYDelta="0"
             android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
diff --git a/core/res/res/anim/lock_screen_behind_enter_fade_in.xml b/core/res/res/anim/lock_screen_behind_enter_fade_in.xml
index 94e40a8..e9475f5 100644
--- a/core/res/res/anim/lock_screen_behind_enter_fade_in.xml
+++ b/core/res/res/anim/lock_screen_behind_enter_fade_in.xml
@@ -22,6 +22,5 @@
     android:interpolator="@interpolator/linear"
     android:fromAlpha="0" android:toAlpha="1"
     android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-    android:duration="250"
-    android:startOffset="100">
+    android:duration="250">
 </alpha>
\ No newline at end of file
diff --git a/core/res/res/anim/lock_screen_behind_enter_wallpaper.xml b/core/res/res/anim/lock_screen_behind_enter_wallpaper.xml
index 660b662..50156fb 100644
--- a/core/res/res/anim/lock_screen_behind_enter_wallpaper.xml
+++ b/core/res/res/anim/lock_screen_behind_enter_wallpaper.xml
@@ -16,12 +16,12 @@
   -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="100">
+    android:detachWallpaper="true" android:shareInterpolator="false" >
     <alpha
         android:fromAlpha="0.0" android:toAlpha="1.0"
         android:fillEnabled="true" android:fillBefore="true"
         android:interpolator="@interpolator/decelerate_quint"
-        android:duration="400"/>
+        android:duration="300"/>
 
     <translate android:fromYDelta="11%p" android:toYDelta="0"
         android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
diff --git a/core/res/res/anim/lock_screen_wallpaper_exit.xml b/core/res/res/anim/lock_screen_wallpaper_exit.xml
index 49d0327..ba8741a 100644
--- a/core/res/res/anim/lock_screen_wallpaper_exit.xml
+++ b/core/res/res/anim/lock_screen_wallpaper_exit.xml
@@ -16,12 +16,12 @@
   -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false" android:startOffset="100">
+        android:shareInterpolator="false">
     <alpha
         android:fromAlpha="1.0" android:toAlpha="0.0"
         android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
         android:interpolator="@interpolator/fast_out_linear_in"
-        android:duration="150"/>
+        android:duration="200"/>
 
     <!-- Empty animation so the animation has same duration as lock_screen_behind_enter animation
          -->
diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml
index 8b95f9f..863efef 100644
--- a/core/res/res/layout-land/time_picker_material.xml
+++ b/core/res/res/layout-land/time_picker_material.xml
@@ -20,11 +20,10 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
 
-    <LinearLayout
+    <RelativeLayout
         android:id="@+id/time_header"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:orientation="vertical"
         android:gravity="center"
         android:paddingStart="?attr/dialogPreferredPadding"
         android:paddingEnd="?attr/dialogPreferredPadding">
@@ -112,7 +111,7 @@
                 android:includeFontPadding="false"
                 android:button="@null" />
         </RadioGroup>
-    </LinearLayout>
+    </RelativeLayout>
 
     <TextView
         android:visibility="gone"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6640102..8e6c402 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2849,6 +2849,11 @@
     <!-- Default data warning level in mb -->
     <integer name="default_data_warning_level_mb">2048</integer>
 
+    <!-- When true, indicates that the vendor's IMS implementation requires a workaround when
+     sending a request to enable or disable the camera while the video session is also
+     paused. -->
+    <bool name="config_useVideoPauseWorkaround">false</bool>
+
     <!-- Whether to send a custom package name with the PSD.-->
     <bool name="config_sendPackageName">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 51d5b1e..8d2666e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3005,7 +3005,7 @@
   <java-symbol type="bool" name="enable_pbap_pce_profile" />
 
   <java-symbol type="integer" name="default_data_warning_level_mb" />
-
+  <java-symbol type="bool" name="config_useVideoPauseWorkaround" />
   <java-symbol type="bool" name="config_sendPackageName" />
   <java-symbol type="string" name="config_helpPackageNameKey" />
   <java-symbol type="string" name="config_helpPackageNameValue" />
diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java
index b1dc3ad..9841815 100644
--- a/media/java/android/media/AudioFocusRequest.java
+++ b/media/java/android/media/AudioFocusRequest.java
@@ -40,7 +40,7 @@
  * <p>When an application requests audio focus, it expresses its intention to “own” audio focus to
  * play audio. Let’s review the different types of focus requests, the return value after a request,
  * and the responses to a loss.
- * <br><b>Note:<b> applications should not play anything until granted focus.
+ * <p class="note">Note: applications should not play anything until granted focus.</p>
  *
  * <h3>The different types of focus requests</h3>
  * <p>There are four focus request types. A successful focus request with each will yield different
@@ -77,9 +77,10 @@
  *
  * <p>An {@code AudioFocusRequest} instance always contains one of the four types of requests
  * explained above. It is passed when building an {@code AudioFocusRequest} instance with its
- * builder in the {@link Builder} constructor {@link Builder#Builder(int)}, or with
- * {@link Builder#setFocusGain(int)} after copying an existing instance with
- * {@link Builder#Builder(AudioFocusRequest)}.
+ * builder in the {@link Builder} constructor
+ * {@link AudioFocusRequest.Builder#AudioFocusRequest.Builder(int)}, or
+ * with {@link AudioFocusRequest.Builder#setFocusGain(int)} after copying an existing instance with
+ * {@link AudioFocusRequest.Builder#AudioFocusRequest.Builder(AudioFocusRequest)}.
  *
  * <h3>Qualifying your focus request</h3>
  * <h4>Use case requiring a focus request</h4>
@@ -105,10 +106,11 @@
  * <h4>Pausing vs ducking</h4>
  * <p>When an application requested audio focus with
  * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, the system will duck the current focus
- * owner. Note that this behavior is <b>new for Android O<b>, whereas applications targeting SDK
- * up to API 25, applications had to implement the ducking themselves when they received a focus
+ * owner.
+ * <p class="note">Note: this behavior is <b>new for Android O</b>, whereas applications targeting
+ * SDK level up to API 25 had to implement the ducking themselves when they received a focus
  * loss of {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
- * <br>But ducking is not always the behavior expected by the user. A typical example is when the
+ * <p>But ducking is not always the behavior expected by the user. A typical example is when the
  * device plays driving directions while the user is listening to an audio book or podcast, and
  * expects the audio playback to pause, instead of duck, as it is hard to understand a navigation
  * prompt and spoken content at the same time. Therefore the system will not automatically duck
@@ -126,7 +128,92 @@
  * speech, you can also declare so with {@link Builder#setWillPauseWhenDucked(boolean)}, which will
  * cause the system to call your focus listener instead of automatically ducking.
  *
+ * <h4>Example</h4>
+ * <p>The example below covers the following steps to be found in any application that would play
+ * audio, and use audio focus. Here we play an audio book, and our application is intended to pause
+ * rather than duck when it loses focus. These steps consist in:
+ * <ul>
+ * <li>Creating {@code AudioAttributes} to be used for the playback and the focus request.</li>
+ * <li>Configuring and creating the {@code AudioFocusRequest} instance that defines the intended
+ *     focus behaviors.</li>
+ * <li>Requesting audio focus and checking the return code to see if playback can happen right
+ *     away, or is delayed.</li>
+ * <li>Implementing a focus change listener to respond to focus gains and losses.</li>
+ * </ul>
+ * <p>
+ * <pre class="prettyprint">
+ * // initialization of the audio attributes and focus request
+ * mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
+ * mPlaybackAttributes = new AudioAttributes.Builder()
+ *         .setUsage(AudioAttributes.USAGE_MEDIA)
+ *         .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+ *         .build();
+ * mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ *         .setAudioAttributes(mPlaybackAttributes)
+ *         .setAcceptsDelayedFocusGain(true)
+ *         .setWillPauseWhenDucked(true)
+ *         .setOnAudioFocusChangeListener(this, mMyHandler)
+ *         .build();
+ * mMediaPlayer = new MediaPlayer();
+ * mMediaPlayer.setAudioAttributes(mPlaybackAttributes);
+ * final Object mFocusLock = new Object();
+ *
+ * boolean mPlaybackDelayed = false;
+ *
+ * // requesting audio focus
+ * int res = mAudioManager.requestAudioFocus(mFocusRequest);
+ * synchronized (mFocusLock) {
+ *     if (res == AUDIOFOCUS_REQUEST_FAILED) {
+ *         mPlaybackDelayed = false;
+ *     } else if (res == AUDIOFOCUS_REQUEST_GRANTED) {
+ *         mPlaybackDelayed = false;
+ *         playbackNow();
+ *     } else if (res == AUDIOFOCUS_REQUEST_DELAYED) {
+ *        mPlaybackDelayed = true;
+ *     }
+ * }
+ *
+ * // implementation of the OnAudioFocusChangeListener
+ * &#64;Override
+ * public void onAudioFocusChange(int focusChange) {
+ *     switch (focusChange) {
+ *         case AudioManager.AUDIOFOCUS_GAIN:
+ *             if (mPlaybackDelayed || mResumeOnFocusGain) {
+ *                 synchronized (mFocusLock) {
+ *                     mPlaybackDelayed = false;
+ *                     mResumeOnFocusGain = false;
+ *                 }
+ *                 playbackNow();
+ *             }
+ *             break;
+ *         case AudioManager.AUDIOFOCUS_LOSS:
+ *             synchronized (mFocusLock) {
+ *                 // this is not a transient loss, we shouldn't automatically resume for now
+ *                 mResumeOnFocusGain = false;
+ *                 mPlaybackDelayed = false;
+ *             }
+ *             pausePlayback();
+ *             break;
+ *         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ *         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ *             // we handle all transient losses the same way because we never duck audio books
+ *             synchronized (mFocusLock) {
+ *                 // we should only resume if playback was interrupted
+ *                 mResumeOnFocusGain = mMediaPlayer.isPlaying();
+ *                 mPlaybackDelayed = false;
+ *             }
+ *             pausePlayback();
+ *             break;
+ *     }
+ * }
+ *
+ * // Important:
+ * // Also set "mResumeOnFocusGain" to false when the user pauses or stops playback: this way your
+ * // application doesn't automatically restart when it gains focus, even though the user had
+ * // stopped it.
+ * </pre>
  */
+
 public final class AudioFocusRequest {
 
     // default attributes for the request when not specified
@@ -244,36 +331,15 @@
 
     /**
      * Builder class for {@link AudioFocusRequest} objects.
-     * <p> Here is an example where {@code Builder} is used to define the
-     * {@link AudioFocusRequest} for requesting audio focus:
-     *
-     * <pre class="prettyprint">
-     * mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
-     * mPlaybackAttributes = new AudioAttributes.Builder()
-     *         .setUsage(AudioAttributes.USAGE_GAME)
-     *         .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-     *         .build();
-     * mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
-     *         .setAudioAttributes(mPlaybackAttributes)
-     *         .setAcceptsDelayedFocusGain(true)
-     *         .setOnAudioFocusChangeListener(mMyFocusListener, mMyHandler)
-     *         .build();
-     * mMediaPlayer = new MediaPlayer();
-     *  ...
-     * mMediaPlayer.setAudioAttributes(mPlaybackAttributes);
-     *  ...
-     * boolean mPlaybackAuthorized = true;;
-     * int res = mAudioManager.requestAudioFocus(mFocusRequest);
-     * if (res == AUDIOFOCUS_REQUEST_FAILED) {
-     *     mPlaybackAuthorized = false;
-     *     cancelPlayback();
-     * } else if (res == AUDIOFOCUS_REQUEST_DELAYED) {
-     *     playbackDelayed();
-     * } else { // res == AUDIOFOCUS_REQUEST_GRANTED
-     *     playbackNow();
-     * }
-     * </pre>
-     *
+     * <p>See {@link AudioFocusRequest} for an example of building an instance with this builder.
+     * <br>The default values for the instance to be built are:
+     * <table>
+     * <tr><td>focus listener and handler</td><td>none</td></tr>
+     * <tr><td>{@code AudioAttributes}</td><td>attributes with usage set to
+     *     {@link AudioAttributes#USAGE_MEDIA}</td></tr>
+     * <tr><td>pauses on duck</td><td>false</td></tr>
+     * <tr><td>supports delayed focus grant</td><td>false</td></tr>
+     * </table>
      */
     public static final class Builder {
         private OnAudioFocusChangeListener mFocusListener;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 24ede16..db9f7b8 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -19,6 +19,8 @@
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
@@ -26,16 +28,22 @@
 import android.os.ParcelUuid;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
-public final class A2dpProfile implements LocalBluetoothProfile {
+public class A2dpProfile implements LocalBluetoothProfile {
     private static final String TAG = "A2dpProfile";
     private static boolean V = false;
 
+    private Context mContext;
+
     private BluetoothA2dp mService;
+    BluetoothA2dpWrapper.Factory mWrapperFactory;
+    private BluetoothA2dpWrapper mServiceWrapper;
     private boolean mIsProfileReady;
 
     private final LocalBluetoothAdapter mLocalAdapter;
@@ -59,6 +67,7 @@
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             if (V) Log.d(TAG,"Bluetooth service connected");
             mService = (BluetoothA2dp) proxy;
+            mServiceWrapper = mWrapperFactory.getInstance(mService);
             // We just bound to the service, so refresh the UI for any connected A2DP devices.
             List<BluetoothDevice> deviceList = mService.getConnectedDevices();
             while (!deviceList.isEmpty()) {
@@ -88,11 +97,18 @@
     A2dpProfile(Context context, LocalBluetoothAdapter adapter,
             CachedBluetoothDeviceManager deviceManager,
             LocalBluetoothProfileManager profileManager) {
+        mContext = context;
         mLocalAdapter = adapter;
         mDeviceManager = deviceManager;
         mProfileManager = profileManager;
         mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
                 BluetoothProfile.A2DP);
+        mWrapperFactory = new BluetoothA2dpWrapperImpl.Factory();
+    }
+
+    @VisibleForTesting
+    void setWrapperFactory(BluetoothA2dpWrapper.Factory factory) {
+        mWrapperFactory = factory;
     }
 
     public boolean isConnectable() {
@@ -173,6 +189,72 @@
         return false;
     }
 
+    public boolean supportsHighQualityAudio(BluetoothDevice device) {
+        int support = mServiceWrapper.supportsOptionalCodecs(device);
+        return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED;
+    }
+
+    public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
+        int enabled = mServiceWrapper.getOptionalCodecsEnabled(device);
+        if (enabled != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN) {
+            return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED;
+        } else if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED &&
+                supportsHighQualityAudio(device)) {
+            // Since we don't have a stored preference and the device isn't connected, just return
+            // true since the default behavior when the device gets connected in the future would be
+            // to have optional codecs enabled.
+            return true;
+        }
+        BluetoothCodecConfig codecConfig = null;
+        if (mServiceWrapper.getCodecStatus() != null) {
+            codecConfig = mServiceWrapper.getCodecStatus().getCodecConfig();
+        }
+        if (codecConfig != null)  {
+            return !codecConfig.isMandatoryCodec();
+        } else {
+            return false;
+        }
+    }
+
+    public void setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled) {
+        int prefValue = enabled
+                ? BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED
+                : BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED;
+        mServiceWrapper.setOptionalCodecsEnabled(device, prefValue);
+        if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
+            return;
+        }
+        if (enabled) {
+            mService.enableOptionalCodecs();
+        } else {
+            mService.disableOptionalCodecs();
+        }
+    }
+
+    public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
+        int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
+        if (!supportsHighQualityAudio(device) ||
+                getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
+            return mContext.getString(unknownCodecId);
+        }
+        // We want to get the highest priority codec, since that's the one that will be used with
+        // this device, and see if it is high-quality (ie non-mandatory).
+        BluetoothCodecConfig[] selectable = null;
+        if (mServiceWrapper.getCodecStatus() != null) {
+            selectable = mServiceWrapper.getCodecStatus().getCodecsSelectableCapabilities();
+            // To get the highest priority, we sort in reverse.
+            Arrays.sort(selectable,
+                    (a, b) -> {
+                        return b.getCodecPriority() - a.getCodecPriority();
+                    });
+        }
+        if (selectable == null || selectable.length < 1 || selectable[0].isMandatoryCodec()) {
+            return mContext.getString(unknownCodecId);
+        }
+        return mContext.getString(R.string.bluetooth_profile_a2dp_high_quality,
+                selectable[0].getCodecName());
+    }
+
     public String toString() {
         return NAME;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
new file mode 100644
index 0000000..aa3e835
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * This interface replicates some methods of android.bluetooth.BluetoothA2dp that are new and not
+ * yet available in our current version of  Robolectric. It provides a thin wrapper to call the real
+ * methods in production and a mock in tests.
+ */
+public interface BluetoothA2dpWrapper {
+
+    static interface Factory {
+        BluetoothA2dpWrapper getInstance(BluetoothA2dp service);
+    }
+
+    /**
+     * @return the real {@code BluetoothA2dp} object
+     */
+    BluetoothA2dp getService();
+
+    /**
+     * Wraps {@code BluetoothA2dp.getCodecStatus}
+     */
+    public BluetoothCodecStatus getCodecStatus();
+
+    /**
+     * Wraps {@code BluetoothA2dp.supportsOptionalCodecs}
+     */
+    int supportsOptionalCodecs(BluetoothDevice device);
+
+    /**
+     * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled}
+     */
+    int getOptionalCodecsEnabled(BluetoothDevice device);
+
+    /**
+     * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled}
+     */
+    void setOptionalCodecsEnabled(BluetoothDevice device, int value);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
new file mode 100644
index 0000000..14fa796
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+
+public class BluetoothA2dpWrapperImpl implements BluetoothA2dpWrapper {
+
+    public static class Factory implements BluetoothA2dpWrapper.Factory {
+        @Override
+        public BluetoothA2dpWrapper getInstance(BluetoothA2dp service) {
+            return new BluetoothA2dpWrapperImpl(service);
+        }
+    }
+
+    private BluetoothA2dp mService;
+
+    public BluetoothA2dpWrapperImpl(BluetoothA2dp service) {
+        mService = service;
+    }
+
+    @Override
+    public BluetoothA2dp getService() {
+        return mService;
+    }
+
+    @Override
+    public BluetoothCodecStatus getCodecStatus() {
+        return mService.getCodecStatus();
+    }
+
+    @Override
+    public int supportsOptionalCodecs(BluetoothDevice device) {
+        return mService.supportsOptionalCodecs(device);
+    }
+
+    @Override
+    public int getOptionalCodecsEnabled(BluetoothDevice device) {
+        return mService.getOptionalCodecsEnabled(device);
+    }
+
+    @Override
+    public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
+        mService.setOptionalCodecsEnabled(device, value);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 5a9a749..0750dc7 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -42,7 +42,7 @@
  * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
  * objects for the available Bluetooth profiles.
  */
-public final class LocalBluetoothProfileManager {
+public class LocalBluetoothProfileManager {
     private static final String TAG = "LocalBluetoothProfileManager";
     private static final boolean DEBUG = Utils.D;
     /** Singleton instance. */
diff --git a/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecConfig.java b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecConfig.java
new file mode 100644
index 0000000..656ab86
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecConfig.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+/**
+ * A placeholder class to prevent ClassNotFound exceptions caused by lack of visibility.
+ */
+public class BluetoothCodecConfig {
+    public boolean isMandatoryCodec() { return true; }
+    public String getCodecName() { return null;}
+}
diff --git a/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecStatus.java b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecStatus.java
new file mode 100644
index 0000000..919ec3f
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecStatus.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+/**
+ * A placeholder class to prevent ClassNotFound exceptions caused by lack of visibility.
+ */
+public class BluetoothCodecStatus {
+    public BluetoothCodecConfig getCodecConfig() { return null; }
+    public BluetoothCodecConfig[] getCodecsSelectableCapabilities() { return null; }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
new file mode 100644
index 0000000..07a0e11
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.R;
+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.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class A2dpProfileTest {
+
+    @Mock Context mContext;
+    @Mock LocalBluetoothAdapter mAdapter;
+    @Mock CachedBluetoothDeviceManager mDeviceManager;
+    @Mock LocalBluetoothProfileManager mProfileManager;
+    @Mock BluetoothDevice mDevice;
+    @Mock BluetoothA2dp mBluetoothA2dp;
+    @Mock BluetoothA2dpWrapper mBluetoothA2dpWrapper;
+    BluetoothProfile.ServiceListener mServiceListener;
+
+    A2dpProfile mProfile;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // Capture the A2dpServiceListener our A2dpProfile will pass during its constructor, so that
+        // we can call its onServiceConnected method and get it to use our mock BluetoothA2dp
+        // object.
+        doAnswer((invocation) -> {
+            mServiceListener = (BluetoothProfile.ServiceListener) invocation.getArguments()[1];
+            return null;
+        }).when(mAdapter).getProfileProxy(any(Context.class), any(), eq(BluetoothProfile.A2DP));
+
+        mProfile = new A2dpProfile(mContext, mAdapter, mDeviceManager, mProfileManager);
+        mProfile.setWrapperFactory((service) -> { return mBluetoothA2dpWrapper; });
+        mServiceListener.onServiceConnected(BluetoothProfile.A2DP, mBluetoothA2dp);
+    }
+
+    @Test
+    public void supportsHighQualityAudio() {
+        when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+        assertThat(mProfile.supportsHighQualityAudio(mDevice)).isTrue();
+
+        when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+        assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
+
+        when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
+        assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
+    }
+
+    @Test
+    public void isHighQualityAudioEnabled() {
+        when(mBluetoothA2dpWrapper.getOptionalCodecsEnabled(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+        assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
+
+        when(mBluetoothA2dpWrapper.getOptionalCodecsEnabled(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED);
+        assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
+
+        // If we don't have a stored pref for whether optional codecs should be enabled or not,
+        // then isHighQualityAudioEnabled() should return true or false based on whether optional
+        // codecs are supported. If the device is connected then we should ask it directly, but if
+        // the device isn't connected then rely on the stored pref about such support.
+        when(mBluetoothA2dpWrapper.getOptionalCodecsEnabled(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+        when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+
+        when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+        assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
+
+        when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+        assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
+
+        when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
+        when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+        BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
+        when(status.getCodecConfig()).thenReturn(config);
+        when(config.isMandatoryCodec()).thenReturn(false);
+        assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
+        when(config.isMandatoryCodec()).thenReturn(true);
+        assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
+    }
+
+    // Strings to use in fake resource lookups.
+    private static String KNOWN_CODEC_LABEL = "Use high quality audio: %1$s";
+    private static String UNKNOWN_CODEC_LABEL = "Use high quality audio";
+
+    /**
+     * Helper for setting up several tests of getHighQualityAudioOptionLabel
+     */
+    private void setupLabelTest() {
+        // SettingsLib doesn't have string resource lookup working for robotests, so fake our own
+        // string loading.
+        when(mContext.getString(eq(R.string.bluetooth_profile_a2dp_high_quality),
+                any(String.class))).thenAnswer((invocation) -> {
+            return String.format(KNOWN_CODEC_LABEL, invocation.getArguments()[1]);
+        });
+        when(mContext.getString(eq(R.string.bluetooth_profile_a2dp_high_quality_unknown_codec)))
+                .thenReturn(UNKNOWN_CODEC_LABEL);
+
+        // Most tests want to simulate optional codecs being supported by the device, so do that
+        // by default here.
+        when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
+    }
+
+    @Test
+    public void getLableCodecsNotSupported() {
+        setupLabelTest();
+        when(mBluetoothA2dpWrapper.supportsOptionalCodecs(any())).thenReturn(
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
+        assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
+    }
+
+    @Test
+    public void getLabelDeviceDisconnected() {
+        setupLabelTest();
+        when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
+    }
+
+    @Test
+    public void getLabelDeviceConnectedButNotHighQualityCodec() {
+        setupLabelTest();
+        when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
+        BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
+        BluetoothCodecConfig[] configs = {config};
+        when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+        when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
+
+        when(config.isMandatoryCodec()).thenReturn(true);
+        assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
+    }
+
+    @Test
+    public void getLabelDeviceConnectedWithHighQualityCodec() {
+        setupLabelTest();
+        when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
+        BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
+        BluetoothCodecConfig[] configs = {config};
+        when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+        when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
+
+        when(config.isMandatoryCodec()).thenReturn(false);
+        when(config.getCodecName()).thenReturn("PiedPiper");
+        assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(
+                String.format(KNOWN_CODEC_LABEL, config.getCodecName()));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index f4d4a9f..0cf8ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -264,6 +264,8 @@
         mProviders.put(AccessibilityManagerWrapper.class,
                 () -> new AccessibilityManagerWrapper(mContext));
 
+        mProviders.put(UiOffloadThread.class, UiOffloadThread::new);
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
new file mode 100644
index 0000000..82fd9b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Thread that offloads work from the UI thread but that is still perceptible to the user, so the
+ * priority is the same as the main thread.
+ */
+public class UiOffloadThread {
+
+    private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
+
+    public Future<?> submit(Runnable runnable) {
+        return mExecutorService.submit(runnable);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index 1abea37..a129ace 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -30,7 +30,10 @@
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.systemui.Dependency;
+import com.android.systemui.UiOffloadThread;
 import com.android.systemui.analytics.DataCollector;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.statusbar.StatusBarState;
 
 import java.io.PrintWriter;
@@ -63,6 +66,7 @@
     private final DataCollector mDataCollector;
     private final HumanInteractionClassifier mHumanInteractionClassifier;
     private final AccessibilityManager mAccessibilityManager;
+    private final UiOffloadThread mUiOffloadThread;
 
     private static FalsingManager sInstance = null;
 
@@ -86,6 +90,7 @@
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mDataCollector = DataCollector.getInstance(mContext);
         mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
+        mUiOffloadThread = Dependency.get(UiOffloadThread.class);
         mScreenOn = context.getSystemService(PowerManager.class).isInteractive();
 
         mContext.getContentResolver().registerContentObserver(
@@ -130,7 +135,11 @@
     private void sessionExitpoint(boolean force) {
         if (mSessionActive && (force || !shouldSessionBeActive())) {
             mSessionActive = false;
-            mSensorManager.unregisterListener(this);
+
+            // This can be expensive, and doesn't need to happen on the main thread.
+            mUiOffloadThread.submit(() -> {
+                mSensorManager.unregisterListener(this);
+            });
         }
     }
 
@@ -154,7 +163,11 @@
         for (int sensorType : sensors) {
             Sensor s = mSensorManager.getDefaultSensor(sensorType);
             if (s != null) {
-                mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME);
+
+                // This can be expensive, and doesn't need to happen on the main thread.
+                mUiOffloadThread.submit(() -> {
+                    mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME);
+                });
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
index 262d29d..745f312 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
@@ -17,6 +17,10 @@
 package com.android.systemui.keyguard;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.systemui.Dependency;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import java.util.ArrayList;
 
@@ -26,6 +30,7 @@
 public class DismissCallbackRegistry {
 
     private final ArrayList<DismissCallbackWrapper> mDismissCallbacks = new ArrayList<>();
+    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
 
     public void addCallback(IKeyguardDismissCallback callback) {
         mDismissCallbacks.add(new DismissCallbackWrapper(callback));
@@ -33,14 +38,16 @@
 
     public void notifyDismissCancelled() {
         for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
-            mDismissCallbacks.get(i).notifyDismissCancelled();
+            DismissCallbackWrapper callback = mDismissCallbacks.get(i);
+            mUiOffloadThread.submit(callback::notifyDismissCancelled);
         }
         mDismissCallbacks.clear();
     }
 
     public void notifyDismissSucceeded() {
         for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
-            mDismissCallbacks.get(i).notifyDismissSucceeded();
+            DismissCallbackWrapper callback = mDismissCallbacks.get(i);
+            mUiOffloadThread.submit(callback::notifyDismissSucceeded);
         }
         mDismissCallbacks.clear();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f618a2c..2d7521d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -51,6 +51,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.provider.Settings.System;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.EventLog;
@@ -76,9 +77,13 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.LatencyTracker;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.UiOffloadThread;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.statusbar.phone.FingerprintUnlockController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -195,6 +200,7 @@
     private AlarmManager mAlarmManager;
     private AudioManager mAudioManager;
     private StatusBarManager mStatusBarManager;
+    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
 
     private boolean mSystemReady;
     private boolean mBootCompleted;
@@ -1199,18 +1205,20 @@
             updateInputRestrictedLocked();
         }
     }
+
     private void updateInputRestrictedLocked() {
         boolean inputRestricted = isInputRestricted();
         if (mInputRestricted != inputRestricted) {
             mInputRestricted = inputRestricted;
             int size = mKeyguardStateCallbacks.size();
             for (int i = size - 1; i >= 0; i--) {
+                final IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
                 try {
-                    mKeyguardStateCallbacks.get(i).onInputRestrictedStateChanged(inputRestricted);
+                    callback.onInputRestrictedStateChanged(inputRestricted);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to call onDeviceProvisioned", e);
                     if (e instanceof DeadObjectException) {
-                        mKeyguardStateCallbacks.remove(i);
+                        mKeyguardStateCallbacks.remove(callback);
                     }
                 }
             }
@@ -1569,9 +1577,11 @@
     private void handleKeyguardDone() {
         Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");
         final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
-        if (mLockPatternUtils.isSecure(currentUser)) {
-            mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
-        }
+        mUiOffloadThread.submit(() -> {
+            if (mLockPatternUtils.isSecure(currentUser)) {
+                mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
+            }
+        });
         if (DEBUG) Log.d(TAG, "handleKeyguardDone");
         synchronized (this) {
             resetKeyguardDonePendingLocked();
@@ -1611,10 +1621,12 @@
                 final UserHandle currentUser = new UserHandle(currentUserId);
                 final UserManager um = (UserManager) mContext.getSystemService(
                         Context.USER_SERVICE);
-                for (int profileId : um.getProfileIdsWithDisabled(currentUser.getIdentifier())) {
-                    mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, UserHandle.of(profileId));
-                }
-                getLockPatternUtils().userPresent(currentUserId);
+                mUiOffloadThread.submit(() -> {
+                    for (int profileId : um.getProfileIdsWithDisabled(currentUser.getIdentifier())) {
+                        mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, UserHandle.of(profileId));
+                    }
+                    getLockPatternUtils().userPresent(currentUserId);
+                });
             } else {
                 mBootSendUserPresent = true;
             }
@@ -1659,11 +1671,18 @@
                 if (mAudioManager == null) return;
                 mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType();
             }
-            // If the stream is muted, don't play the sound
-            if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
 
-            mLockSoundStreamId = mLockSounds.play(soundId,
-                    mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+            mUiOffloadThread.submit(() -> {
+                // If the stream is muted, don't play the sound
+                if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
+
+                int id = mLockSounds.play(soundId,
+                        mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+                synchronized (this) {
+                    mLockSoundStreamId = id;
+                }
+            });
+
         }
     }
 
@@ -1671,13 +1690,13 @@
         playSound(mTrustedSoundId);
     }
 
-    private void updateActivityLockScreenState() {
-        Trace.beginSection("KeyguardViewMediator#updateActivityLockScreenState");
-        try {
-            ActivityManager.getService().setLockScreenShown(mShowing);
-        } catch (RemoteException e) {
-        }
-        Trace.endSection();
+    private void updateActivityLockScreenState(boolean showing) {
+        mUiOffloadThread.submit(() -> {
+            try {
+                ActivityManager.getService().setLockScreenShown(showing);
+            } catch (RemoteException e) {
+            }
+        });
     }
 
     /**
@@ -1846,7 +1865,10 @@
             }
 
             if (!(mContext instanceof Activity)) {
-                mStatusBarManager.disable(flags);
+                final int finalFlags = flags;
+                mUiOffloadThread.submit(() -> {
+                    mStatusBarManager.disable(finalFlags);
+                });
             }
         }
     }
@@ -2044,18 +2066,21 @@
             mShowing = showing;
             int size = mKeyguardStateCallbacks.size();
             for (int i = size - 1; i >= 0; i--) {
+                IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
                 try {
-                    mKeyguardStateCallbacks.get(i).onShowingStateChanged(showing);
+                    callback.onShowingStateChanged(showing);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to call onShowingStateChanged", e);
                     if (e instanceof DeadObjectException) {
-                        mKeyguardStateCallbacks.remove(i);
+                        mKeyguardStateCallbacks.remove(callback);
                     }
                 }
             }
             updateInputRestrictedLocked();
-            mTrustManager.reportKeyguardShowingChanged();
-            updateActivityLockScreenState();
+            mUiOffloadThread.submit(() -> {
+                mTrustManager.reportKeyguardShowingChanged();
+            });
+            updateActivityLockScreenState(showing);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 4eb0748..dbf0724 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -204,8 +204,8 @@
                                 Recents.getSystemServices().removeTask(task.persistentId);
                             }
                         }
-                        Settings.Secure.putLongForUser(RecentsActivity.this.getContentResolver(),
-                                Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, currentTime, currentUser);
+                        Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
+                                currentTime, currentUser);
 
                         // Clear the last PiP task time, it's an edge case and we'd rather it
                         // not relaunch the PiP task if the user double taps
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index cde865e..e229c90 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -35,15 +35,18 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.util.Log;
 import android.util.MutableBoolean;
+import android.util.Pair;
 import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
 import android.widget.Toast;
+
+import com.google.android.collect.Lists;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.policy.DockedDividerUtils;
 import com.android.systemui.R;
@@ -74,6 +77,8 @@
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskGrouping;
 import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.RecentsTransitionHelper;
+import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
 import com.android.systemui.recents.views.TaskStackView;
@@ -671,7 +676,7 @@
             windowRect.bottom -= systemInsets.bottom;
             systemInsets.bottom = 0;
         }
-        calculateWindowStableInsets(systemInsets, windowRect);
+        calculateWindowStableInsets(systemInsets, windowRect, displayRect);
         windowRect.offsetTo(0, 0);
 
         synchronized (mDummyStackView) {
@@ -761,8 +766,7 @@
      * Given the stable insets and the rect for our window, calculates the insets that affect our
      * window.
      */
-    private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
-        Rect displayRect = Recents.getSystemServices().getDisplayRect();
+    private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect, Rect displayRect) {
 
         // Display rect without insets - available app space
         Rect appRect = new Rect(displayRect);
@@ -812,8 +816,9 @@
     /**
      * Creates the activity options for an app->recents transition.
      */
-    private ActivityOptions getThumbnailTransitionActivityOptions(
-            ActivityManager.RunningTaskInfo runningTask, Rect windowOverrideRect) {
+    private Pair<ActivityOptions, AppTransitionAnimationSpecsFuture>
+            getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
+                    Rect windowOverrideRect) {
         if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
             ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
             ArrayList<Task> tasks;
@@ -844,23 +849,27 @@
             }
             AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
             specs.toArray(specsArray);
-            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    specsArray, mHandler, null, this);
+            return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+                    specsArray, mHandler, null, this), null);
         } else {
             // Update the destination rect
             Task toTask = new Task();
             TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
                     windowOverrideRect);
-            Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
-                            mThumbTransitionBitmapCache);
-            if (thumbnail != null) {
-                RectF toTaskRect = toTransform.rect;
-                return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                        thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
-                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
-            }
-            // If both the screenshot and thumbnail fails, then just fall back to the default transition
-            return getUnknownTransitionActivityOptions();
+
+            RectF toTaskRect = toTransform.rect;
+            AppTransitionAnimationSpecsFuture future =
+                    new RecentsTransitionHelper(mContext).getAppTransitionFuture(
+                            () -> {
+                        Rect rect = new Rect();
+                        toTaskRect.round(rect);
+                        Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
+                                mThumbTransitionBitmapCache);
+                        return Lists.newArrayList(new AppTransitionAnimationSpec(
+                                toTask.key.id, thumbnail, rect));
+                    });
+            return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
+                    mHandler, future.getFuture(), null, false /* scaleUp */), future);
         }
     }
 
@@ -986,30 +995,31 @@
         launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
 
         if (!animate) {
-            startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
+            startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1),
+                    null /* future */);
             return;
         }
 
-        ActivityOptions opts;
+        Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair;
         if (isBlacklisted) {
-            opts = getUnknownTransitionActivityOptions();
+            pair = new Pair<>(getUnknownTransitionActivityOptions(), null);
         } else if (useThumbnailTransition) {
             // Try starting with a thumbnail transition
-            opts = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
+            pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
         } else {
             // If there is no thumbnail transition, but is launching from home into recents, then
             // use a quick home transition
-            opts = hasRecentTasks
-                ? getHomeTransitionActivityOptions()
-                : getUnknownTransitionActivityOptions();
+            pair = new Pair<>(hasRecentTasks
+                    ? getHomeTransitionActivityOptions()
+                    : getUnknownTransitionActivityOptions(), null);
         }
-        startRecentsActivity(opts);
+        startRecentsActivity(pair.first, pair.second);
         mLastToggleTime = SystemClock.elapsedRealtime();
     }
 
     private Rect getWindowRectOverride(int growTarget) {
         if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
-            return null;
+            return SystemServicesProxy.getInstance(mContext).getWindowRect();
         }
         Rect result = new Rect();
         Rect displayRect = Recents.getSystemServices().getDisplayRect();
@@ -1022,21 +1032,21 @@
     /**
      * Starts the recents activity.
      */
-    private void startRecentsActivity(ActivityOptions opts) {
+    private void startRecentsActivity(ActivityOptions opts,
+            final AppTransitionAnimationSpecsFuture future) {
         Intent intent = new Intent();
         intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-
+        Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
         HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent();
         hideMenuEvent.addPostAnimationCallback(() -> {
-            if (opts != null) {
-                mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
-            } else {
-                mContext.startActivityAsUser(intent, UserHandle.CURRENT);
-            }
+            Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
             EventBus.getDefault().send(new RecentsActivityStartingEvent());
+            if (future != null) {
+                future.precacheSpecs();
+            }
         });
         EventBus.getDefault().send(hideMenuEvent);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index f85fb34..a155a71 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -58,20 +58,22 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.IRemoteCallback;
-import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
 import android.util.ArraySet;
 import android.util.IconDrawableFactory;
 import android.util.Log;
 import android.util.MutableBoolean;
-import android.view.AppTransitionAnimationSpec;
 import android.view.Display;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDockedStackListener;
@@ -84,14 +86,15 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.UiOffloadThread;
 import com.android.systemui.pip.tv.PipMenuActivity;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.RecentsImpl;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.ThumbnailData;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -130,6 +133,8 @@
     PackageManager mPm;
     IconDrawableFactory mDrawableFactory;
     IPackageManager mIpm;
+    private final IDreamManager mDreamManager;
+    private final Context mContext;
     AssistUtils mAssistUtils;
     WindowManager mWm;
     IWindowManager mIwm;
@@ -149,7 +154,8 @@
     Canvas mBgProtectionCanvas;
 
     private final Handler mHandler = new H();
-    private final ExecutorService mOnewayExecutor = Executors.newSingleThreadExecutor();
+
+    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
 
     /**
      * An abstract class to track task stack changes.
@@ -285,6 +291,7 @@
 
     /** Private constructor */
     private SystemServicesProxy(Context context) {
+        mContext = context.getApplicationContext();
         mAccm = AccessibilityManager.getInstance(context);
         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         mIam = ActivityManager.getService();
@@ -296,6 +303,8 @@
         mIwm = WindowManagerGlobal.getWindowManagerService();
         mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
         mUm = UserManager.get(context);
+        mDreamManager = IDreamManager.Stub.asInterface(
+                ServiceManager.checkService(DreamService.DREAM_SERVICE));
         mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
         mHasFreeformWorkspaceSupport =
@@ -778,7 +787,7 @@
      * Sends a message to close other system windows.
      */
     public void sendCloseSystemWindows(String reason) {
-        mOnewayExecutor.submit(() -> {
+        mUiOffloadThread.submit(() -> {
             try {
                 mIam.closeSystemDialogs(reason);
             } catch (RemoteException e) {
@@ -1023,7 +1032,12 @@
      * Returns the current user id.
      */
     public int getCurrentUser() {
-        return KeyguardUpdateMonitor.getCurrentUser();
+        if (mAm == null) return 0;
+
+        // This must call through ActivityManager, as the SystemServicesProxy can be called in a
+        // secondary user's SystemUI process, and KeyguardUpdateMonitor is only updated in the
+        // primary user's SystemUI process
+        return mAm.getCurrentUser();
     }
 
     /**
@@ -1127,6 +1141,11 @@
         }
     }
 
+    public void startActivityAsUserAsync(Intent intent, ActivityOptions opts) {
+        mUiOffloadThread.submit(() -> mContext.startActivityAsUser(intent,
+                opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
+    }
+
     /** Starts an activity from recents. */
     public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
             ActivityOptions options, int stackId,
@@ -1151,7 +1170,7 @@
 
         // Execute this from another thread such that we can do other things (like caching the
         // bitmap for the thumbnail) while AM is busy starting our activity.
-        mOnewayExecutor.submit(() -> {
+        mUiOffloadThread.submit(() -> {
             try {
                 mIam.startActivityFromRecents(
                         taskKey.id, finalOptions == null ? null : finalOptions.toBundle());
@@ -1283,6 +1302,33 @@
         }
     }
 
+    public boolean isDreaming() {
+        try {
+            return mDreamManager.isDreaming();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query dream manager.", e);
+        }
+        return false;
+    }
+
+    public void awakenDreamsAsync() {
+        mUiOffloadThread.submit(() -> {
+            try {
+                mDreamManager.awaken();
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        });
+    }
+
+    public void updateOverviewLastStackActiveTimeAsync(long newLastStackActiveTime,
+            int currentUserId) {
+        mUiOffloadThread.submit(() -> {
+            Settings.Secure.putLongForUser(mContext.getContentResolver(),
+                    Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
+        });
+    }
+
     public interface StartActivityFromRecentsResultListener {
         void onStartActivityResult(boolean succeeded);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 7ee0906..ed09640 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -87,7 +87,7 @@
         mCurrentQuietProfiles.clear();
 
         if (currentUserId == UserHandle.USER_CURRENT) {
-            currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+            currentUserId = SystemServicesProxy.getInstance(mContext).getCurrentUser();
         }
         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         List<UserInfo> profiles = userManager.getProfiles(currentUserId);
@@ -218,8 +218,8 @@
             affiliatedTasks.put(taskKey.id, taskKey);
         }
         if (newLastStackActiveTime != -1) {
-            Settings.Secure.putLongForUser(mContext.getContentResolver(),
-                    Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
+            Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
+                    newLastStackActiveTime, currentUserId);
         }
 
         // Initialize the stacks
@@ -316,9 +316,8 @@
             for (int i = 0; i < users.size(); i++) {
                 int userId = users.get(i).id;
                 if (userId != currentUserId) {
-                    Settings.Secure.putLongForUser(mContext.getContentResolver(),
-                            Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime,
-                            userId);
+                    Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
+                            legacyLastStackActiveTime, userId);
                 }
             }
             return legacyLastStackActiveTime;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 97a9659..c5a292f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -28,6 +28,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.Trace;
 import android.util.Log;
 import android.util.LruCache;
 
@@ -351,7 +352,12 @@
     /** Preloads recents tasks using the specified plan to store the output. */
     public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
             boolean includeFrontMostExcludedTask) {
-        plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
+        try {
+            Trace.beginSection("preloadPlan");
+            plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
+        } finally {
+            Trace.endSection();
+        }
     }
 
     /** Begins loading the heavy task data according to the specified options. */
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 24a2927..0c77036 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1169,7 +1169,7 @@
     }
 
     public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
-        if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP
+        if (mGrowRecents && mDockSide == WindowManager.DOCKED_TOP
                 && getSnapAlgorithm().getMiddleTarget() != getSnapAlgorithm().getLastSplitTarget()
                 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
             mState.growAfterRecentsDrawn = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index be221bb..bf89b01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -279,7 +279,9 @@
     public void toggleRecentApps() {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_TOGGLE_RECENT_APPS);
-            mHandler.obtainMessage(MSG_TOGGLE_RECENT_APPS, 0, 0, null).sendToTarget();
+            Message msg = mHandler.obtainMessage(MSG_TOGGLE_RECENT_APPS, 0, 0, null);
+            msg.setAsynchronous(true);
+            msg.sendToTarget();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0924089..1691e135 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -270,7 +270,7 @@
                 return;
             }
 
-            if (!mUserManager.isUserUnlocked(ActivityManager.getCurrentUser())) {
+            if (!mUserManager.isUserUnlocked(KeyguardUpdateMonitor.getCurrentUser())) {
                 mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
                 mTextView.setTextColor(Color.WHITE);
 
@@ -365,6 +365,9 @@
     };
 
     public void setDozing(boolean dozing) {
+        if (mDozing == dozing) {
+            return;
+        }
         mDozing = dozing;
         updateIndication();
         updateDisclosure();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 2124011..95f32bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
+import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
@@ -55,6 +56,7 @@
     protected final ViewGroup mContainer;
     private final FalsingManager mFalsingManager;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
+    private final Handler mHandler;
     protected KeyguardHostView mKeyguardView;
     protected ViewGroup mRoot;
     private boolean mShowingSoon;
@@ -66,6 +68,7 @@
                     mBouncerPromptReason = mCallback.getBouncerPromptReason();
                 }
             };
+    private final Runnable mRemoveViewRunnable = this::removeView;
 
     public KeyguardBouncer(Context context, ViewMediatorCallback callback,
             LockPatternUtils lockPatternUtils, ViewGroup container,
@@ -77,6 +80,7 @@
         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
         mFalsingManager = FalsingManager.getInstance(mContext);
         mDismissCallbackRegistry = dismissCallbackRegistry;
+        mHandler = new Handler();
     }
 
     public void show(boolean resetSecuritySelection) {
@@ -179,10 +183,14 @@
             mKeyguardView.cancelDismissAction();
             mKeyguardView.cleanUp();
         }
-        if (destroyView) {
-            removeView();
-        } else if (mRoot != null) {
+        if (mRoot != null) {
             mRoot.setVisibility(View.INVISIBLE);
+            if (destroyView) {
+
+                // We have a ViewFlipper that unregisters a broadcast when being detached, which may
+                // be slow because of AM lock contention during unlocking. We can delay it a bit.
+                mHandler.postDelayed(mRemoveViewRunnable, 50);
+            }
         }
     }
 
@@ -226,6 +234,7 @@
     }
 
     protected void ensureView() {
+        mHandler.removeCallbacks(mRemoveViewRunnable);
         if (mRoot == null) {
             inflateView();
         }
@@ -233,6 +242,7 @@
 
     protected void inflateView() {
         removeView();
+        mHandler.removeCallbacks(mRemoveViewRunnable);
         mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
         mKeyguardView = (KeyguardHostView) mRoot.findViewById(R.id.keyguard_host_view);
         mKeyguardView.setLockPatternUtils(mLockPatternUtils);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 4581204..52838b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -128,8 +128,7 @@
 
     protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
             NotificationData notificationData, boolean showAmbient) {
-        if (notificationData.isAmbient(entry.key) && !showAmbient
-                && !NotificationData.showNotificationEvenIfUnprovisioned(entry.notification)) {
+        if (notificationData.isAmbient(entry.key) && !showAmbient) {
             return false;
         }
         if (!StatusBar.isTopLevelChild(entry)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 41fb5f7..f3ba5aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -58,6 +58,7 @@
 import com.android.systemui.DockedStackExistsListener;
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.UiOffloadThread;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -128,6 +129,7 @@
     private final KeyguardMonitor mKeyguardMonitor;
     private final LocationController mLocationController;
     private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
+    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
 
     // Assume it's all good unless we hear otherwise.  We don't always seem
     // to get broadcasts that it *is* there.
@@ -472,30 +474,38 @@
     }
 
     private void updateManagedProfile() {
-        try {
-            final boolean showIcon;
-            final int userId = ActivityManager.getService().getLastResumedActivityUserId();
-            if (mUserManager.isManagedProfile(userId) && !mKeyguardMonitor.isShowing()) {
-                showIcon = true;
-                mIconController.setIcon(mSlotManagedProfile,
-                        R.drawable.stat_sys_managed_profile_status,
-                        mContext.getString(R.string.accessibility_managed_profile));
-            } else if (mManagedProfileInQuietMode) {
-                showIcon = true;
-                mIconController.setIcon(mSlotManagedProfile,
-                        R.drawable.stat_sys_managed_profile_status_off,
-                        mContext.getString(R.string.accessibility_managed_profile));
-            } else {
-                showIcon = false;
+        // getLastResumedActivityUserId needds to acquire the AM lock, which may be contended in
+        // some cases. Since it doesn't really matter here whether it's updated in this frame
+        // or in the next one, we call this method from our UI offload thread.
+        mUiOffloadThread.submit(() -> {
+            final int userId;
+            try {
+                userId = ActivityManager.getService().getLastResumedActivityUserId();
+                boolean isManagedProfile = mUserManager.isManagedProfile(userId);
+                mHandler.post(() -> {
+                    final boolean showIcon;
+                    if (isManagedProfile && !mKeyguardMonitor.isShowing()) {
+                        showIcon = true;
+                        mIconController.setIcon(mSlotManagedProfile,
+                                R.drawable.stat_sys_managed_profile_status,
+                                mContext.getString(R.string.accessibility_managed_profile));
+                    } else if (mManagedProfileInQuietMode) {
+                        showIcon = true;
+                        mIconController.setIcon(mSlotManagedProfile,
+                                R.drawable.stat_sys_managed_profile_status_off,
+                                mContext.getString(R.string.accessibility_managed_profile));
+                    } else {
+                        showIcon = false;
+                    }
+                    if (mManagedProfileIconVisible != showIcon) {
+                        mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
+                        mManagedProfileIconVisible = showIcon;
+                    }
+                });
+            } catch (RemoteException e) {
+                Log.w(TAG, "updateManagedProfile: ", e);
             }
-            if (mManagedProfileIconVisible != showIcon) {
-                mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
-                mManagedProfileIconVisible = showIcon;
-            }
-        } catch (RemoteException ex) {
-            Log.w(TAG, "updateManagedProfile: ", ex);
-            // ignore
-        }
+        });
     }
 
     private void updateForegroundInstantApps() {
@@ -503,26 +513,22 @@
         ArraySet<Pair<String, Integer>> notifs = new ArraySet<>(mCurrentNotifs);
         IPackageManager pm = AppGlobals.getPackageManager();
         mCurrentNotifs.clear();
-        try {
-            ArraySet<Integer> stacksToCheck = new ArraySet<>();
-            int[] STACKS_TO_CHECK = new int[]{
-                    StackId.FULLSCREEN_WORKSPACE_STACK_ID,
-                    StackId.DOCKED_STACK_ID,
-            };
-            int focusedId = ActivityManager.getService().getFocusedStackId();
-            if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID
-                    || focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) {
-                checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm);
+        mUiOffloadThread.submit(() -> {
+            try {
+                int focusedId = ActivityManager.getService().getFocusedStackId();
+                if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) {
+                    checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm);
+                }
+                if (mDockedStackExists) {
+                    checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm);
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
             }
-            if (mDockedStackExists) {
-                checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm);
-            }
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-        // Cancel all the leftover notifications that don't have a foreground process anymore.
-        notifs.forEach(v -> noMan.cancelAsUser(v.first, SystemMessage.NOTE_INSTANT_APPS,
-                new UserHandle(v.second)));
+            // Cancel all the leftover notifications that don't have a foreground process anymore.
+            notifs.forEach(v -> noMan.cancelAsUser(v.first, SystemMessage.NOTE_INSTANT_APPS,
+                    new UserHandle(v.second)));
+        });
     }
 
     private void checkStack(int stackId, ArraySet<Pair<String, Integer>> notifs,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index fc73c0f..d798fbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -83,6 +83,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.SystemService;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -131,6 +132,7 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
+import com.android.systemui.UiOffloadThread;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.classifier.FalsingManager;
@@ -149,6 +151,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
 import com.android.systemui.recents.events.activity.UndockingTaskEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.stackdivider.WindowManagerProxy;
 import com.android.systemui.statusbar.ActivatableNotificationView;
@@ -220,8 +223,6 @@
 import android.content.pm.UserInfo;
 import android.os.Build;
 import android.os.Handler;
-import android.service.dreams.DreamService;
-import android.service.dreams.IDreamManager;
 import android.service.notification.NotificationListenerService;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
@@ -538,6 +539,7 @@
     private ViewMediatorCallback mKeyguardViewMediatorCallback;
     protected ScrimController mScrimController;
     protected DozeScrimController mDozeScrimController;
+    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
 
     private final Runnable mAutohide = new Runnable() {
         @Override
@@ -778,8 +780,6 @@
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
-        mDreamManager = IDreamManager.Stub.asInterface(
-                ServiceManager.checkService(DreamService.DREAM_SERVICE));
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
@@ -1532,13 +1532,7 @@
     }
 
     void awakenDreams() {
-        if (mDreamManager != null) {
-            try {
-                mDreamManager.awaken();
-            } catch (RemoteException e) {
-                // fine, stay asleep then
-            }
-        }
+        SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
     }
 
     public UserHandle getCurrentUserHandle() {
@@ -4348,7 +4342,7 @@
         checkBarModes();
         updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
         mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
-                mStatusBarKeyguardViewManager.isSecure(),
+                mUnlockMethodCache.isMethodSecure(),
                 mStatusBarKeyguardViewManager.isOccluded());
         Trace.endSection();
     }
@@ -5173,7 +5167,6 @@
     protected boolean mDisableNotificationAlerts = false;
 
     protected DevicePolicyManager mDevicePolicyManager;
-    protected IDreamManager mDreamManager;
     protected PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
@@ -6816,12 +6809,8 @@
             return false;
         }
 
-        boolean inUse = mPowerManager.isScreenOn();
-        try {
-            inUse = inUse && !mDreamManager.isDreaming();
-        } catch (RemoteException e) {
-            Log.d(TAG, "failed to query dream manager", e);
-        }
+        boolean inUse = mPowerManager.isScreenOn()
+                && !SystemServicesProxy.getInstance(mContext).isDreaming();
 
         if (!inUse && !isDozing()) {
             if (DEBUG) {
@@ -6898,11 +6887,13 @@
 
     @Override
     public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        try {
-            mBarService.onNotificationExpansionChanged(key, userAction, expanded);
-        } catch (RemoteException e) {
-            // Ignore.
-        }
+        mUiOffloadThread.submit(() -> {
+            try {
+                mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+            } catch (RemoteException e) {
+                // Ignore.
+            }
+        });
     }
 
     public boolean isKeyguardSecure() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
index d6c080a..7bce33a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -124,13 +124,11 @@
         mKeyguardFadingAway = true;
         mKeyguardFadingAwayDelay = delay;
         mKeyguardFadingAwayDuration = fadeoutDuration;
-        notifyKeyguardChanged();
     }
 
     public void notifyKeyguardDoneFading() {
         mKeyguardFadingAway = false;
         mKeyguardGoingAway = false;
-        notifyKeyguardChanged();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 53671a1..e1acc9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -872,7 +872,14 @@
     private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
         @Override
         public void onKeyguardShowingChanged() {
-            notifyAdapters();
+
+            // When Keyguard is going away, we don't need to update our items immediately which
+            // helps making the transition faster.
+            if (!mKeyguardMonitor.isShowing()) {
+                mHandler.post(UserSwitcherController.this::notifyAdapters);
+            } else {
+                notifyAdapters();
+            }
         }
     };
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index aadae0f..0aac1c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -21,15 +21,21 @@
 import android.os.MessageQueue;
 import android.support.test.InstrumentationRegistry;
 import android.testing.LeakCheck;
+import android.util.Log;
 
 import org.junit.Before;
 import org.junit.Rule;
 
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
 /**
  * Base class that does System UI specific setup.
  */
 public abstract class SysuiTestCase {
 
+    private static final String TAG = "SysuiTestCase";
+
     private Handler mHandler;
     @Rule
     public SysuiTestableContext mContext = new SysuiTestableContext(
@@ -57,6 +63,15 @@
         waitForIdleSync(mHandler);
     }
 
+    protected void waitForUiOffloadThread() {
+        Future<?> future = Dependency.get(UiOffloadThread.class).submit(() -> {});
+        try {
+            future.get();
+        } catch (InterruptedException | ExecutionException e) {
+            Log.e(TAG, "Failed to wait for ui offload thread.", e);
+        }
+    }
+
     public static void waitForIdleSync(Handler h) {
         validateThread(h.getLooper());
         Idler idler = new Idler(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
index 131a70b..308670d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
@@ -23,6 +23,8 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -35,7 +37,7 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class DismissCallbackRegistryTest {
+public class DismissCallbackRegistryTest extends SysuiTestCase {
 
     private final DismissCallbackRegistry mDismissCallbackRegistry = new DismissCallbackRegistry();
     private @Mock IKeyguardDismissCallback mMockCallback;
@@ -50,6 +52,7 @@
     public void testCancelled() throws Exception {
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.notifyDismissCancelled();
+        waitForUiOffloadThread();
         verify(mMockCallback).onDismissCancelled();
     }
 
@@ -58,6 +61,7 @@
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.addCallback(mMockCallback2);
         mDismissCallbackRegistry.notifyDismissCancelled();
+        waitForUiOffloadThread();
         verify(mMockCallback).onDismissCancelled();
         verify(mMockCallback2).onDismissCancelled();
     }
@@ -66,6 +70,7 @@
     public void testSucceeded() throws Exception {
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.notifyDismissSucceeded();
+        waitForUiOffloadThread();
         verify(mMockCallback).onDismissSucceeded();
     }
 
@@ -74,6 +79,7 @@
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.addCallback(mMockCallback2);
         mDismissCallbackRegistry.notifyDismissSucceeded();
+        waitForUiOffloadThread();
         verify(mMockCallback).onDismissSucceeded();
         verify(mMockCallback2).onDismissSucceeded();
     }
@@ -83,6 +89,7 @@
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.notifyDismissSucceeded();
         mDismissCallbackRegistry.notifyDismissSucceeded();
+        waitForUiOffloadThread();
         verify(mMockCallback, times(1)).onDismissSucceeded();
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index 0e30fb2..de8a518 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -26,6 +26,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -96,7 +97,7 @@
                 mNodeFromOriginalWindow = info;
             } else {
                 Slog.e(LOG_TAG, "Callback with unexpected interactionId");
-                throw new RuntimeException("Callback with unexpected interactionId"); // Remove
+                return;
             }
 
             mSingleNodeCallbackHappened = true;
@@ -119,7 +120,7 @@
                 mNodesWithReplacementActions = infos;
             } else {
                 Slog.e(LOG_TAG, "Callback with unexpected interactionId");
-                throw new RuntimeException("Callback with unexpected interactionId"); // Remove
+                return;
             }
             callbackForSingleNode = mSingleNodeCallbackHappened;
             callbackForMultipleNodes = mMultiNodeCallbackHappened;
@@ -147,7 +148,7 @@
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Extra callback");
                 }
-                throw new RuntimeException("Extra callback"); // Replace with return before submit
+                return;
             }
             if (mNodeFromOriginalWindow != null) {
                 replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
@@ -172,7 +173,7 @@
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Extra callback");
                 }
-                throw new RuntimeException("Extra callback"); // Replace with return before submit
+                return;
             }
             if (mNodesFromOriginalWindow != null) {
                 for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) {
@@ -180,7 +181,8 @@
                 }
             }
             recycleReplaceActionNodesLocked();
-            nodesToReturn = mNodesFromOriginalWindow;
+            nodesToReturn = (mNodesFromOriginalWindow == null)
+                    ? null : new ArrayList<>(mNodesFromOriginalWindow);
             mDone = true;
         }
         try {
@@ -195,6 +197,12 @@
     @GuardedBy("mLock")
     private void replaceActionsOnInfoLocked(AccessibilityNodeInfo info) {
         info.removeAllActions();
+        info.setClickable(false);
+        info.setFocusable(false);
+        info.setContextClickable(false);
+        info.setScrollable(false);
+        info.setLongClickable(false);
+        info.setDismissable(false);
         // We currently only replace actions for the root node
         if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID)
                 && mNodesWithReplacementActions != null) {
@@ -213,6 +221,12 @@
                         info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
                         info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
                     }
+                    info.setClickable(nodeWithReplacementActions.isClickable());
+                    info.setFocusable(nodeWithReplacementActions.isFocusable());
+                    info.setContextClickable(nodeWithReplacementActions.isContextClickable());
+                    info.setScrollable(nodeWithReplacementActions.isScrollable());
+                    info.setLongClickable(nodeWithReplacementActions.isLongClickable());
+                    info.setDismissable(nodeWithReplacementActions.isDismissable());
                 }
             }
         }
@@ -220,6 +234,7 @@
 
     @GuardedBy("mLock")
     private void recycleReplaceActionNodesLocked() {
+        if (mNodesWithReplacementActions == null) return;
         for (int i = mNodesWithReplacementActions.size() - 1; i >= 0; i--) {
             AccessibilityNodeInfo nodeWithReplacementAction = mNodesWithReplacementActions.get(i);
             nodeWithReplacementAction.recycle();
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 6e87f88..e380f2c 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -938,7 +938,12 @@
         if (pointerIdBits == ALL_POINTER_ID_BITS) {
             event = prototype;
         } else {
-            event = prototype.split(pointerIdBits);
+            try {
+                event = prototype.split(pointerIdBits);
+            } catch (IllegalArgumentException e) {
+                Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
+                return;
+            }
         }
         if (action == MotionEvent.ACTION_DOWN) {
             event.setDownTime(event.getEventTime());
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index b65f54e..61057dd 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1697,7 +1697,7 @@
                     mHandler.removeMessages(MESSAGE_USER_SWITCHED);
 
                     /* disable and enable BT when detect a user switch */
-                    if (mEnable && mBluetooth != null) {
+                    if (mBluetooth != null && isEnabled()) {
                         try {
                             mBluetoothLock.readLock().lock();
                             if (mBluetooth != null) {
@@ -1766,6 +1766,8 @@
                         mState = BluetoothAdapter.STATE_OFF;
                         // enable
                         addActiveLog(REASON_USER_SWITCH, true);
+                        // mEnable flag could have been reset on disableBLE. Reenable it.
+                        mEnable = true;
                         handleEnable(mQuietEnable);
                     } else if (mBinding || mBluetooth != null) {
                         Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED);
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 96f6f2d..11296ab 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -960,14 +960,18 @@
                 return result;
             }
         }
-        // Otherwise wait until https probe completes and use its result.
+        // Otherwise wait until http and https probes completes and use their results.
         try {
+            httpProbe.join();
+            if (httpProbe.result().isPortal()) {
+                return httpProbe.result();
+            }
             httpsProbe.join();
+            return httpsProbe.result();
         } catch (InterruptedException e) {
-            validationLog("Error: https probe wait interrupted!");
+            validationLog("Error: http or https probe wait interrupted!");
             return CaptivePortalProbeResult.FAILED;
         }
-        return httpsProbe.result();
     }
 
     private URL makeURL(String url) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 0e55c3f..b0d76e8 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -63,6 +63,7 @@
 import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManagerInternal;
 import android.provider.Settings;
 import android.util.KeyValueListParser;
 import android.util.Slog;
@@ -751,6 +752,13 @@
         }
     }
 
+    private void cancelJobsForNonExistentUsers() {
+        UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+        synchronized (mLock) {
+            mJobs.removeJobsOfNonUsers(umi.getUserIds());
+        }
+    }
+
     void cancelJobsForPackageAndUid(String pkgName, int uid) {
         synchronized (mLock) {
             final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
@@ -941,6 +949,8 @@
             } catch (RemoteException e) {
                 // ignored; both services live in system_server
             }
+            // Remove any jobs that are not associated with any of the current users.
+            cancelJobsForNonExistentUsers();
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             synchronized (mLock) {
                 // Let's go!
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 497d36f..22eed3b 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -35,6 +35,7 @@
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.IoThread;
 import com.android.server.job.controllers.JobStatus;
@@ -171,6 +172,14 @@
         return removed;
     }
 
+    /**
+     * Remove the jobs of users not specified in the whitelist.
+     * @param whitelist Array of User IDs whose jobs are not to be removed.
+     */
+    public void removeJobsOfNonUsers(int[] whitelist) {
+        mJobSet.removeJobsOfNonUsers(whitelist);
+    }
+
     @VisibleForTesting
     public void clear() {
         mJobSet.clear();
@@ -839,6 +848,17 @@
             return didRemove;
         }
 
+        // Remove the jobs all users not specified by the whitelist of user ids
+        public void removeJobsOfNonUsers(int[] whitelist) {
+            for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) {
+                int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex));
+                // check if job's user id is not in the whitelist
+                if (!ArrayUtils.contains(whitelist, jobUserId)) {
+                    mJobs.removeAt(jobIndex);
+                }
+            }
+        }
+
         public boolean contains(JobStatus job) {
             final int uid = job.getUid();
             ArraySet<JobStatus> jobs = mJobs.get(uid);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 5a5e658..803b0dc 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -153,7 +153,7 @@
                   final ApplicationInfo applicationInfo =
                         mContext.getPackageManager().getApplicationInfoAsUser(sbn.getPackageName(),
                                 0, UserHandle.getUserId(sbn.getUid()));
-                if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+                if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index e184f83..f00ef38 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -286,7 +286,7 @@
     private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
         final int userId = UserHandle.getUserId(r.uid);
         final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
-        if (applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
+        if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
             // O apps should not have the default channel.
             return false;
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 30c4009..b115422 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3684,6 +3684,11 @@
         }
 
         @Override
+        public int[] getUserIds() {
+            return UserManagerService.this.getUserIds();
+        }
+
+        @Override
         public boolean isUserUnlockingOrUnlocked(int userId) {
             synchronized (mUserStates) {
                 int state = mUserStates.get(userId, -1);
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index fb7ccc7..4044fdb 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -368,7 +368,7 @@
 
             // There is an active service, update it if needed
             updateCurrentVrServiceLocked(mVrModeEnabled, mCurrentVrService.getComponent(),
-                    mCurrentVrService.getUserId(), null);
+                    mCurrentVrService.getUserId(), mCurrentVrModeComponent);
         }
     }
 
@@ -675,7 +675,7 @@
      * @param enabled new state for VR mode.
      * @param component new component to be bound as a VR listener.
      * @param userId user owning the component to be bound.
-     * @param calling the component currently using VR mode, or null to leave unchanged.
+     * @param calling the component currently using VR mode.
      *
      * @return {@code true} if the component/user combination specified is valid.
      */
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 5545e2b..c982f08 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -19,6 +19,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -98,18 +99,27 @@
     private final Runnable mRemoveStartingWindow = () -> {
         StartingSurface surface = null;
         synchronized (mWindowMap) {
+            if (mContainer == null) {
+                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "mContainer was null while trying to"
+                        + " remove starting window");
+                return;
+            }
             if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Remove starting " + mContainer
                     + ": startingWindow=" + mContainer.startingWindow
                     + " startingView=" + mContainer.startingSurface);
-            if (mContainer == null) {
-                return;
-            }
             if (mContainer.startingWindow != null) {
                 surface = mContainer.startingSurface;
                 mContainer.startingData = null;
                 mContainer.startingSurface = null;
                 mContainer.startingWindow = null;
                 mContainer.startingDisplayed = false;
+                if (surface == null && DEBUG_STARTING_WINDOW) {
+                    Slog.v(TAG_WM, "startingWindow was set but startingSurface==null, couldn't "
+                            + "remove");
+                }
+            } else if (DEBUG_STARTING_WINDOW) {
+                Slog.v(TAG_WM, "Tried to remove starting window but startingWindow was null:"
+                        + mContainer);
             }
         }
         if (surface != null) {
@@ -127,6 +137,8 @@
 
         synchronized (mWindowMap) {
             if (mContainer == null) {
+                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "mContainer was null while trying to"
+                        + " add starting window");
                 return;
             }
             startingData = mContainer.startingData;
@@ -135,6 +147,8 @@
 
         if (startingData == null) {
             // Animation has been canceled... do nothing.
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "startingData was nulled out before handling"
+                    + " mAddStartingWindow: " + mContainer);
             return;
         }
 
@@ -174,6 +188,8 @@
             if (abort) {
                 surface.remove();
             }
+        } else if (DEBUG_STARTING_WINDOW) {
+            Slog.v(TAG_WM, "Surface returned was null: " + mContainer);
         }
     };
 
@@ -450,7 +466,9 @@
             boolean allowTaskSnapshot, boolean activityCreated) {
         synchronized(mWindowMap) {
             if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
-                    + " pkg=" + pkg + " transferFrom=" + transferFrom);
+                    + " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask
+                    + " taskSwitch=" + taskSwitch + " processRunning=" + processRunning
+                    + " allowTaskSnapshot=" + allowTaskSnapshot);
 
             if (mContainer == null) {
                 Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + mToken);
@@ -536,7 +554,7 @@
                 return false;
             }
 
-            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating StartingData");
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SplashScreenStartingData");
             mContainer.startingData = new SplashScreenStartingData(mService, pkg, theme,
                     compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                     mContainer.getMergedOverrideConfiguration());
@@ -574,6 +592,7 @@
             return false;
         }
 
+        if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SnapshotStartingData");
         mContainer.startingData = new SnapshotStartingData(mService, snapshot);
         scheduleAddStartingWindow();
         return true;
@@ -583,6 +602,8 @@
         synchronized (mWindowMap) {
             if (mHandler.hasCallbacks(mRemoveStartingWindow)) {
                 // Already scheduled.
+                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Trying to remove starting window but "
+                        + "already scheduled");
                 return;
             }
 
@@ -597,8 +618,7 @@
                 return;
             }
 
-            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, Debug.getCallers(1)
-                    + ": Schedule remove starting " + mContainer
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Schedule remove starting " + mContainer
                     + " startingWindow=" + mContainer.startingWindow);
             mHandler.post(mRemoveStartingWindow);
         }
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 57b0fe2..17db253 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -38,6 +38,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
@@ -53,7 +54,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.SystemClock;
@@ -155,6 +155,9 @@
     StartingSurface startingSurface;
     boolean startingDisplayed;
     boolean startingMoved;
+    // True if the hidden state of this token was forced to false due to a transferred starting
+    // window.
+    private boolean mHiddenSetFromTransferredStartingWindow;
     boolean firstWindowDrawn;
     private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
             new WindowState.UpdateReportedVisibilityResults();
@@ -185,7 +188,7 @@
     ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
     ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
 
-    private boolean mDisbalePreviewScreenshots;
+    private boolean mDisablePreviewScreenshots;
 
     Task mLastParent;
 
@@ -790,14 +793,22 @@
             if (getController() != null) {
                 getController().removeStartingWindow();
             }
-        } else if (mChildren.size() == 0 && startingData != null) {
+        } else if (mChildren.size() == 0) {
             // If this is the last window and we had requested a starting transition window,
             // well there is no point now.
             if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Nulling last startingData");
             startingData = null;
+            if (mHiddenSetFromTransferredStartingWindow) {
+                // We set the hidden state to false for the token from a transferred starting window.
+                // We now reset it back to true since the starting window was the last window in the
+                // token.
+                hidden = true;
+            }
         } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
             // If this is the last window except for a starting transition window,
             // we need to get rid of the starting transition.
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Last window, removing starting window "
+                    + win);
             if (getController() != null) {
                 getController().removeStartingWindow();
             }
@@ -1171,6 +1182,7 @@
                     "Removing starting " + tStartingWindow + " from " + fromToken);
             fromToken.removeChild(tStartingWindow);
             fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
+            fromToken.mHiddenSetFromTransferredStartingWindow = false;
             addWindow(tStartingWindow);
 
             // Propagate other interesting state between the tokens. If the old token is displayed,
@@ -1186,6 +1198,7 @@
             if (!fromToken.hidden) {
                 hidden = false;
                 hiddenRequested = false;
+                mHiddenSetFromTransferredStartingWindow = true;
             }
             setClientHidden(fromToken.mClientHidden);
             fromToken.mAppAnimator.transferCurrentAnimation(
@@ -1362,7 +1375,7 @@
      *         windows in this app token where not considered drawn as of the last pass.
      */
     boolean updateDrawnWindowStates(WindowState w) {
-        if (DEBUG_STARTING_WINDOW && w == startingWindow) {
+        if (DEBUG_STARTING_WINDOW_VERBOSE && w == startingWindow) {
             Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
                     + " allDrawn=" + allDrawn + " freezingScreen=" + mAppAnimator.freezingScreen);
         }
@@ -1544,7 +1557,7 @@
      * See {@link Activity#setDisablePreviewScreenshots}.
      */
     void setDisablePreviewScreenshots(boolean disable) {
-        mDisbalePreviewScreenshots = disable;
+        mDisablePreviewScreenshots = disable;
     }
 
     /**
@@ -1556,7 +1569,7 @@
      *         screenshot.
      */
     boolean shouldUseAppThemeSnapshot() {
-        return mDisbalePreviewScreenshots || forAllWindows(w -> (w.mAttrs.flags & FLAG_SECURE) != 0,
+        return mDisablePreviewScreenshots || forAllWindows(w -> (w.mAttrs.flags & FLAG_SECURE) != 0,
                 true /* topToBottom */);
     }
 
@@ -1604,11 +1617,13 @@
                     pw.print(" mIsExiting="); pw.println(mIsExiting);
         }
         if (startingWindow != null || startingSurface != null
-                || startingDisplayed || startingMoved) {
+                || startingDisplayed || startingMoved || mHiddenSetFromTransferredStartingWindow) {
             pw.print(prefix); pw.print("startingWindow="); pw.print(startingWindow);
                     pw.print(" startingSurface="); pw.print(startingSurface);
                     pw.print(" startingDisplayed="); pw.print(startingDisplayed);
-                    pw.print(" startingMoved="); pw.println(startingMoved);
+                    pw.print(" startingMoved="); pw.print(startingMoved);
+                    pw.println(" mHiddenSetFromTransferredStartingWindow="
+                            + mHiddenSetFromTransferredStartingWindow);
         }
         if (!mFrozenBounds.isEmpty()) {
             pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bbf7d9f..4941610 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1899,6 +1899,11 @@
         return true;
     }
 
+    /** @return 'true' if removal of this display content is deferred due to active animation. */
+    boolean isRemovalDeferred() {
+        return mDeferredRemoval;
+    }
+
     boolean animateForIme(float interpolatedValue, float animationTarget,
             float dividerAnimationTarget) {
         boolean updated = false;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index be3558b..233e75b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -181,7 +181,12 @@
 
         final int size = mChildren.size();
         for (int i = 0; i < size; ++i) {
-            displaysInFocusOrder.put(i, mChildren.get(i).getDisplayId());
+            final DisplayContent displayContent = mChildren.get(i);
+            if (displayContent.isRemovalDeferred()) {
+                // Don't report displays that are going to be removed soon.
+                continue;
+            }
+            displaysInFocusOrder.put(i, displayContent.getDisplayId());
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 5e7b910..3a116bb 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -39,6 +39,7 @@
 import static com.android.internal.policy.DecorView.getColorViewLeftInset;
 import static com.android.internal.policy.DecorView.getColorViewTopInset;
 import static com.android.internal.policy.DecorView.getNavigationBarRect;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -113,7 +114,7 @@
     private final Rect mStableInsets = new Rect();
     private final Rect mContentInsets = new Rect();
     private final Rect mFrame = new Rect();
-    private final TaskSnapshot mSnapshot;
+    private TaskSnapshot mSnapshot;
     private final CharSequence mTitle;
     private boolean mHasDrawn;
     private long mShownTime;
@@ -232,10 +233,14 @@
             final long now = SystemClock.uptimeMillis();
             if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS) {
                 mHandler.postAtTime(this::remove, mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS);
+                if (DEBUG_STARTING_WINDOW) {
+                    Slog.v(TAG, "Defer removing snapshot surface in "  + (now - mShownTime) + "ms");
+                }
                 return;
             }
         }
         try {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Removing snapshot surface");
             mSession.remove(mWindow);
         } catch (RemoteException e) {
             // Local call.
@@ -254,6 +259,8 @@
 
     private void drawSnapshot() {
         final GraphicBuffer buffer = mSnapshot.getSnapshot();
+        if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Drawing snapshot surface sizeMismatch="
+                + mSizeMismatch);
         if (mSizeMismatch) {
             // The dimensions of the buffer and the window don't match, so attaching the buffer
             // will fail. Better create a child window with the exact dimensions and fill the parent
@@ -267,6 +274,9 @@
             mHasDrawn = true;
         }
         reportDrawn();
+
+        // In case window manager leaks us, make sure we don't retain the snapshot.
+        mSnapshot = null;
     }
 
     private void drawSizeMatchSnapshot(GraphicBuffer buffer) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ca8c484..6d3e0ed 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -655,39 +655,44 @@
 
         final LinkedList<WindowContainer> thisParentChain = mTmpChain1;
         final LinkedList<WindowContainer> otherParentChain = mTmpChain2;
-        getParents(thisParentChain);
-        other.getParents(otherParentChain);
+        try {
+            getParents(thisParentChain);
+            other.getParents(otherParentChain);
 
-        // Find the common ancestor of both containers.
-        WindowContainer commonAncestor = null;
-        WindowContainer thisTop = thisParentChain.peekLast();
-        WindowContainer otherTop = otherParentChain.peekLast();
-        while (thisTop != null && otherTop != null && thisTop == otherTop) {
-            commonAncestor = thisParentChain.removeLast();
-            otherParentChain.removeLast();
-            thisTop = thisParentChain.peekLast();
-            otherTop = otherParentChain.peekLast();
+            // Find the common ancestor of both containers.
+            WindowContainer commonAncestor = null;
+            WindowContainer thisTop = thisParentChain.peekLast();
+            WindowContainer otherTop = otherParentChain.peekLast();
+            while (thisTop != null && otherTop != null && thisTop == otherTop) {
+                commonAncestor = thisParentChain.removeLast();
+                otherParentChain.removeLast();
+                thisTop = thisParentChain.peekLast();
+                otherTop = otherParentChain.peekLast();
+            }
+
+            // Containers don't belong to the same hierarchy???
+            if (commonAncestor == null) {
+                throw new IllegalArgumentException("No in the same hierarchy this="
+                        + thisParentChain + " other=" + otherParentChain);
+            }
+
+            // Children are always considered greater than their parents, so if one of the containers
+            // we are comparing it the parent of the other then whichever is the child is greater.
+            if (commonAncestor == this) {
+                return -1;
+            } else if (commonAncestor == other) {
+                return 1;
+            }
+
+            // The position of the first non-common ancestor in the common ancestor list determines
+            // which is greater the which.
+            final WindowList<WindowContainer> list = commonAncestor.mChildren;
+            return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
+                    ? 1 : -1;
+        } finally {
+            mTmpChain1.clear();
+            mTmpChain2.clear();
         }
-
-        // Containers don't belong to the same hierarchy???
-        if (commonAncestor == null) {
-            throw new IllegalArgumentException("No in the same hierarchy this="
-                    + thisParentChain + " other=" + otherParentChain);
-        }
-
-        // Children are always considered greater than their parents, so if one of the containers
-        // we are comparing it the parent of the other then whichever is the child is greater.
-        if (commonAncestor == this) {
-            return -1;
-        } else if (commonAncestor == other) {
-            return 1;
-        }
-
-        // The position of the first non-common ancestor in the common ancestor list determines
-        // which is greater the which.
-        final WindowList<WindowContainer> list = commonAncestor.mChildren;
-        return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
-                ? 1 : -1;
     }
 
     private void getParents(LinkedList<WindowContainer> parents) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 1b61fca..8279b51 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -51,7 +51,8 @@
     static final boolean DEBUG_APP_ORIENTATION = false;
     static final boolean DEBUG_CONFIGURATION = false;
     static final boolean DEBUG_APP_TRANSITIONS = false;
-    static final boolean DEBUG_STARTING_WINDOW = false;
+    static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false;
+    static final boolean DEBUG_STARTING_WINDOW = DEBUG_STARTING_WINDOW_VERBOSE || true;
     static final boolean DEBUG_WALLPAPER = false;
     static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
     static final boolean DEBUG_DRAG = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6be7900..c8eb98c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1521,7 +1521,7 @@
         // Try using the target SDK of the root window
         if (attachedWindow != null) {
             return attachedWindow.mAppToken != null
-                    && attachedWindow.mAppToken.mTargetSdk > Build.VERSION_CODES.N_MR1;
+                    && attachedWindow.mAppToken.mTargetSdk >= Build.VERSION_CODES.O;
         } else {
             // Otherwise, look at the package
             try {
@@ -1532,7 +1532,7 @@
                     throw new SecurityException("Package " + packageName + " not in UID "
                             + callingUid);
                 }
-                if (appInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
+                if (appInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
                     return true;
                 }
             } catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 344c616..2ffa152 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -80,6 +80,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
@@ -138,6 +139,7 @@
 import com.android.server.input.InputWindowHandle;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.LinkedList;
@@ -170,7 +172,7 @@
     final int mOwnerUid;
     /** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
     final boolean mOwnerCanAddInternalSystemWindow;
-    final IWindowId mWindowId;
+    final WindowId mWindowId;
     WindowToken mToken;
     // The same object as mToken if this is an app window and null for non-app windows.
     AppWindowToken mAppToken;
@@ -587,20 +589,7 @@
         mAppToken = mToken.asAppWindowToken();
         mOwnerUid = ownerId;
         mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
-        mWindowId = new IWindowId.Stub() {
-            @Override
-            public void registerFocusObserver(IWindowFocusObserver observer) {
-                WindowState.this.registerFocusObserver(observer);
-            }
-            @Override
-            public void unregisterFocusObserver(IWindowFocusObserver observer) {
-                WindowState.this.unregisterFocusObserver(observer);
-            }
-            @Override
-            public boolean isFocused() {
-                return WindowState.this.isFocused();
-            }
-        };
+        mWindowId = new WindowId(this);
         mAttrs.copyFrom(a);
         mViewVisibility = viewVisibility;
         mPolicy = mService.mPolicy;
@@ -1306,7 +1295,7 @@
      *         otherwise.
      */
     boolean wouldBeVisibleIfPolicyIgnored() {
-        return mHasSurface && mPolicyVisibility && !isParentWindowHidden()
+        return mHasSurface && !isParentWindowHidden()
                 && !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
     }
 
@@ -3797,7 +3786,7 @@
 
     private void logPerformShow(String prefix) {
         if (DEBUG_VISIBILITY
-                || (DEBUG_STARTING_WINDOW && mAttrs.type == TYPE_APPLICATION_STARTING)) {
+                || (DEBUG_STARTING_WINDOW_VERBOSE && mAttrs.type == TYPE_APPLICATION_STARTING)) {
             Slog.v(TAG, prefix + this
                     + ": mDrawState=" + mWinAnimator.drawStateToString()
                     + " readyForDisplay=" + isReadyForDisplay()
@@ -4435,6 +4424,40 @@
         }
     }
 
+    private static final class WindowId extends IWindowId.Stub {
+        private final WeakReference<WindowState> mOuter;
+
+        private WindowId(WindowState outer) {
+
+            // Use a weak reference for the outer class. This is important to prevent the following
+            // leak: Since we send this class to the client process, binder will keep it alive as
+            // long as the client keeps it alive. Now, if the window is removed, we need to clear
+            // out our reference so even though this class is kept alive we don't leak WindowState,
+            // which can keep a whole lot of classes alive.
+            mOuter = new WeakReference<>(outer);
+        }
+
+        @Override
+        public void registerFocusObserver(IWindowFocusObserver observer) {
+            final WindowState outer = mOuter.get();
+            if (outer != null) {
+                outer.registerFocusObserver(observer);
+            }
+        }
+        @Override
+        public void unregisterFocusObserver(IWindowFocusObserver observer) {
+            final WindowState outer = mOuter.get();
+            if (outer != null) {
+                outer.unregisterFocusObserver(observer);
+            }
+        }
+        @Override
+        public boolean isFocused() {
+            final WindowState outer = mOuter.get();
+            return outer != null && outer.isFocused();
+        }
+    }
+
     boolean usesRelativeZOrdering() {
         if (!isChildWindow()) {
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e1c6bc3..d75afcf 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -30,6 +30,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
@@ -519,7 +520,7 @@
 
     // This must be called while inside a transaction.
     boolean commitFinishDrawingLocked() {
-        if (DEBUG_STARTING_WINDOW &&
+        if (DEBUG_STARTING_WINDOW_VERBOSE &&
                 mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
             Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState="
                     + drawStateToString());
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index 267d2a6..0812783 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -99,7 +99,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mPm);
 
         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
-        upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+        upgrade.targetSdkVersion = Build.VERSION_CODES.O;
         try {
             when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(legacy);
             when(mPm.getApplicationInfoAsUser(eq(pkg2), anyInt(), anyInt())).thenReturn(upgrade);
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index cb0ee25..2c9c114 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -119,7 +119,7 @@
         final ApplicationInfo legacy = new ApplicationInfo();
         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
         final ApplicationInfo upgrade = new ApplicationInfo();
-        upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+        upgrade.targetSdkVersion = Build.VERSION_CODES.O;
         when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
         when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
         when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java
index 8afe853..72820f1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ActionReplacingCallbackTest.java
@@ -25,6 +25,9 @@
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -45,12 +48,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
 /**
@@ -63,15 +67,50 @@
     private static final int NON_ROOT_NODE_ID = 0xAAAA5555;
     private static final long INTERROGATING_TID = 0x1234FACE;
 
-    private static final AccessibilityAction[] ACTIONS_FROM_REPLACER =
-            {ACTION_CLICK, ACTION_EXPAND};
-    private static final AccessibilityAction[] A11Y_FOCUS_ACTIONS =
-            {ACTION_ACCESSIBILITY_FOCUS, ACTION_CLEAR_ACCESSIBILITY_FOCUS};
     // We expect both the replacer actions and a11y focus actions to appear
     private static final AccessibilityAction[] REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE =
             {ACTION_CLICK, ACTION_EXPAND, ACTION_ACCESSIBILITY_FOCUS,
                     ACTION_CLEAR_ACCESSIBILITY_FOCUS};
 
+    private static final Matcher<AccessibilityNodeInfo> HAS_NO_ACTIONS =
+            new BaseMatcher<AccessibilityNodeInfo>() {
+        @Override
+        public boolean matches(Object o) {
+            AccessibilityNodeInfo node = (AccessibilityNodeInfo) o;
+            if (!node.getActionList().isEmpty()) return false;
+            return (!node.isScrollable() && !node.isLongClickable() && !node.isClickable()
+                    && !node.isContextClickable() && !node.isDismissable() && !node.isFocusable());
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Has no actions");
+        }
+    };
+
+    private static final Matcher<AccessibilityNodeInfo> HAS_EXPECTED_ACTIONS_ON_ROOT =
+            new BaseMatcher<AccessibilityNodeInfo>() {
+                @Override
+                public boolean matches(Object o) {
+                    AccessibilityNodeInfo node = (AccessibilityNodeInfo) o;
+                    List<AccessibilityAction> actions = node.getActionList();
+                    if ((actions.size() != 4) || !actions.contains(ACTION_CLICK)
+                            || !actions.contains(ACTION_EXPAND)
+                            || !actions.contains(ACTION_ACCESSIBILITY_FOCUS)) {
+                        return false;
+                    }
+                    return (!node.isScrollable() && !node.isLongClickable()
+                            && !node.isLongClickable() && node.isClickable()
+                            && !node.isContextClickable() && !node.isDismissable()
+                            && !node.isFocusable());
+                }
+
+                @Override
+                public void describeTo(Description description) {
+                    description.appendText("Has only 4 actions expected on root");
+                }
+            };
+
     @Mock IAccessibilityInteractionConnectionCallback mMockServiceCallback;
     @Mock IAccessibilityInteractionConnection mMockReplacerConnection;
 
@@ -118,9 +157,10 @@
                 eq(INTERACTION_ID));
         AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
         assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId());
-        assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
+        assertThat(infoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
     }
 
+    @Test
     public void testCallbacks_singleNonrootNodeThenReplacer_returnsNodeWithNoActions()
             throws RemoteException {
         AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain();
@@ -136,9 +176,10 @@
                 eq(INTERACTION_ID));
         AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
         assertEquals(NON_ROOT_NODE_ID, infoSentToService.getSourceNodeId());
-        assertTrue(infoSentToService.getActionList().isEmpty());
+        assertThat(infoSentToService, HAS_NO_ACTIONS);
     }
 
+    @Test
     public void testCallbacks_replacerThenSingleRootNode_returnsNodeWithReplacedActions()
             throws RemoteException {
         mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(),
@@ -154,9 +195,10 @@
                 eq(INTERACTION_ID));
         AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
         assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId());
-        assertInfoHasExactlyTheseActions(infoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
+        assertThat(infoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
     }
 
+    @Test
     public void testCallbacks_multipleNodesThenReplacer_clearsActionsAndAddsSomeToRoot()
             throws RemoteException {
         mActionReplacingCallback
@@ -173,11 +215,11 @@
                 mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID);
         AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList(
                 mInfoListCaptor.getValue(), NON_ROOT_NODE_ID);
-        assertInfoHasExactlyTheseActions(
-                rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
-        assertTrue(otherInfoSentToService.getActionList().isEmpty());
+        assertThat(rootInfoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
+        assertThat(otherInfoSentToService, HAS_NO_ACTIONS);
     }
 
+    @Test
     public void testCallbacks_replacerThenMultipleNodes_clearsActionsAndAddsSomeToRoot()
             throws RemoteException {
         mActionReplacingCallback.setFindAccessibilityNodeInfosResult(getReplacerNodes(),
@@ -194,18 +236,18 @@
                 mInfoListCaptor.getValue(), AccessibilityNodeInfo.ROOT_NODE_ID);
         AccessibilityNodeInfo otherInfoSentToService = getNodeWithIdFromList(
                 mInfoListCaptor.getValue(), NON_ROOT_NODE_ID);
-        assertInfoHasExactlyTheseActions(
-                rootInfoSentToService, REQUIRED_ACTIONS_ON_ROOT_TO_SERVICE);
-        assertTrue(otherInfoSentToService.getActionList().isEmpty());
+        assertThat(rootInfoSentToService, HAS_EXPECTED_ACTIONS_ON_ROOT);
+        assertThat(otherInfoSentToService, HAS_NO_ACTIONS);
     }
 
+    @Test
     public void testConstructor_actionReplacerThrowsException_passesDataToService()
             throws RemoteException {
         doThrow(RemoteException.class).when(mMockReplacerConnection)
                 .findAccessibilityNodeInfoByAccessibilityId(eq(AccessibilityNodeInfo.ROOT_NODE_ID),
-                        (Region) anyObject(), mInteractionIdCaptor.capture(),
-                        eq(mActionReplacingCallback), eq(0), eq(INTERROGATING_PID),
-                        eq(INTERROGATING_TID), (MagnificationSpec) anyObject(), eq(null));
+                        (Region) anyObject(), anyInt(), (ActionReplacingCallback) anyObject(),
+                        eq(0),  eq(INTERROGATING_PID), eq(INTERROGATING_TID),
+                        (MagnificationSpec) anyObject(), eq(null));
         ActionReplacingCallback actionReplacingCallback = new ActionReplacingCallback(
                 mMockServiceCallback, mMockReplacerConnection, INTERACTION_ID, INTERROGATING_PID,
                 INTERROGATING_TID);
@@ -214,16 +256,17 @@
         AccessibilityNodeInfo infoFromApp = AccessibilityNodeInfo.obtain();
         infoFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, APP_WINDOW_ID);
         infoFromApp.addAction(ACTION_CONTEXT_CLICK);
+        infoFromApp.setContextClickable(true);
         actionReplacingCallback.setFindAccessibilityNodeInfoResult(infoFromApp, INTERACTION_ID);
 
         verify(mMockServiceCallback).setFindAccessibilityNodeInfoResult(mInfoCaptor.capture(),
                 eq(INTERACTION_ID));
         AccessibilityNodeInfo infoSentToService = mInfoCaptor.getValue();
         assertEquals(AccessibilityNodeInfo.ROOT_NODE_ID, infoSentToService.getSourceNodeId());
-        assertEquals(1, infoSentToService.getActionList().size());
-        assertEquals(ACTION_CONTEXT_CLICK, infoSentToService.getActionList().get(0));
+        assertThat(infoSentToService, HAS_NO_ACTIONS);
     }
 
+    @Test
     public void testSetPerformAccessibilityActionResult_actsAsPassThrough() throws RemoteException {
         mActionReplacingCallback.setPerformAccessibilityActionResult(true, INTERACTION_ID);
         verify(mMockServiceCallback).setPerformAccessibilityActionResult(true, INTERACTION_ID);
@@ -236,9 +279,9 @@
         AccessibilityNodeInfo root = AccessibilityNodeInfo.obtain();
         root.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID,
                 AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
-        for (AccessibilityAction action : ACTIONS_FROM_REPLACER) {
-            root.addAction(action);
-        }
+        root.addAction(ACTION_CLICK);
+        root.addAction(ACTION_EXPAND);
+        root.setClickable(true);
 
         // Second node should have no effect
         AccessibilityNodeInfo other = AccessibilityNodeInfo.obtain();
@@ -249,13 +292,6 @@
         return Arrays.asList(root, other);
     }
 
-    private void assertInfoHasExactlyTheseActions(
-            AccessibilityNodeInfo info, AccessibilityAction[] actions) {
-        List<AccessibilityAction> nodeActions = info.getActionList();
-        assertEquals(new HashSet<AccessibilityAction>(nodeActions),
-                new HashSet<AccessibilityAction>(Arrays.asList(actions)));
-    }
-
     private AccessibilityNodeInfo getNodeWithIdFromList(
             List<AccessibilityNodeInfo> infos, long id) {
         for (AccessibilityNodeInfo info : infos) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index f9254ad..856e940 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -34,6 +34,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseIntArray;
 
 import java.util.Arrays;
 import java.util.LinkedList;
@@ -239,7 +240,7 @@
     @Test
     @Ignore
     public void testFocusedWindowMultipleDisplays() throws Exception {
-        // Create a focusable window and check that focus is calcualted correctly
+        // Create a focusable window and check that focus is calculated correctly
         final WindowState window1 =
                 createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1");
         assertEquals(window1, sWm.mRoot.computeFocusedWindow());
@@ -310,6 +311,24 @@
         assertEquals(afterStackCount - 1, mDisplayContent.getStaskPosById(PINNED_STACK_ID));
     }
 
+    /**
+     * Test that WM does not report displays to AM that are pending to be removed.
+     */
+    @Test
+    public void testDontReportDeferredRemoval() {
+        // Create a display and add an animating window to it.
+        final DisplayContent dc = createNewDisplay();
+        final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
+        window.mAnimatingExit = true;
+        // Request display removal, it should be deferred.
+        dc.removeIfPossible();
+        // Request ordered display ids from WM.
+        final SparseIntArray orderedDisplayIds = new SparseIntArray();
+        sWm.getDisplaysInFocusOrder(orderedDisplayIds);
+        // Make sure that display that is marked for removal is not reported.
+        assertEquals(-1, orderedDisplayIds.indexOfValue(dc.getDisplayId()));
+    }
+
     private static void verifySizes(DisplayContent displayContent, int expectedBaseWidth,
                              int expectedBaseHeight, int expectedBaseDensity) {
         assertEquals(displayContent.mBaseDisplayWidth, expectedBaseWidth);
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 90f713b..0d1850f 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -34,42 +34,43 @@
 
 namespace aapt {
 
-constexpr const char* sXliffNamespaceUri =
-    "urn:oasis:names:tc:xliff:document:1.2";
+constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
 
-/**
- * Returns true if the element is <skip> or <eat-comment> and can be safely
- * ignored.
- */
-static bool ShouldIgnoreElement(const StringPiece& ns,
-                                const StringPiece& name) {
+// Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
+static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
   return ns.empty() && (name == "skip" || name == "eat-comment");
 }
 
-static uint32_t ParseFormatType(const StringPiece& piece) {
-  if (piece == "reference")
+static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) {
+  if (piece == "reference") {
     return android::ResTable_map::TYPE_REFERENCE;
-  else if (piece == "string")
+  } else if (piece == "string") {
     return android::ResTable_map::TYPE_STRING;
-  else if (piece == "integer")
+  } else if (piece == "integer") {
     return android::ResTable_map::TYPE_INTEGER;
-  else if (piece == "boolean")
+  } else if (piece == "boolean") {
     return android::ResTable_map::TYPE_BOOLEAN;
-  else if (piece == "color")
+  } else if (piece == "color") {
     return android::ResTable_map::TYPE_COLOR;
-  else if (piece == "float")
+  } else if (piece == "float") {
     return android::ResTable_map::TYPE_FLOAT;
-  else if (piece == "dimension")
+  } else if (piece == "dimension") {
     return android::ResTable_map::TYPE_DIMENSION;
-  else if (piece == "fraction")
+  } else if (piece == "fraction") {
     return android::ResTable_map::TYPE_FRACTION;
-  else if (piece == "enum")
-    return android::ResTable_map::TYPE_ENUM;
-  else if (piece == "flags")
-    return android::ResTable_map::TYPE_FLAGS;
+  }
   return 0;
 }
 
+static uint32_t ParseFormatType(const StringPiece& piece) {
+  if (piece == "enum") {
+    return android::ResTable_map::TYPE_ENUM;
+  } else if (piece == "flags") {
+    return android::ResTable_map::TYPE_FLAGS;
+  }
+  return ParseFormatTypeNoEnumsOrFlags(piece);
+}
+
 static uint32_t ParseFormatAttribute(const StringPiece& str) {
   uint32_t mask = 0;
   for (StringPiece part : util::Tokenize(str, '|')) {
@@ -83,9 +84,7 @@
   return mask;
 }
 
-/**
- * A parsed resource ready to be added to the ResourceTable.
- */
+// A parsed resource ready to be added to the ResourceTable.
 struct ParsedResource {
   ResourceName name;
   ConfigDescription config;
@@ -416,8 +415,7 @@
     can_be_bag = false;
 
     // Items have their type encoded in the type attribute.
-    if (Maybe<StringPiece> maybe_type =
-            xml::FindNonEmptyAttribute(parser, "type")) {
+    if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
       resource_type = maybe_type.value().to_string();
     } else {
       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
@@ -425,13 +423,11 @@
       return false;
     }
 
-    if (Maybe<StringPiece> maybe_format =
-            xml::FindNonEmptyAttribute(parser, "format")) {
+    if (Maybe<StringPiece> maybe_format = xml::FindNonEmptyAttribute(parser, "format")) {
       // An explicit format for this resource was specified. The resource will
-      // retain
-      // its type in its name, but the accepted value for this type is
+      // retain its type in its name, but the accepted value for this type is
       // overridden.
-      resource_format = ParseFormatType(maybe_format.value());
+      resource_format = ParseFormatTypeNoEnumsOrFlags(maybe_format.value());
       if (!resource_format) {
         diag_->Error(DiagMessage(out_resource->source)
                      << "'" << maybe_format.value()
@@ -1157,21 +1153,25 @@
   return true;
 }
 
-bool ResourceParser::ParseArray(xml::XmlPullParser* parser,
-                                ParsedResource* out_resource) {
-  return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_ANY);
+bool ResourceParser::ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+  uint32_t resource_format = android::ResTable_map::TYPE_ANY;
+  if (Maybe<StringPiece> format_attr = xml::FindNonEmptyAttribute(parser, "format")) {
+    resource_format = ParseFormatTypeNoEnumsOrFlags(format_attr.value());
+    if (resource_format == 0u) {
+      diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
+                   << "'" << format_attr.value() << "' is an invalid format");
+      return false;
+    }
+  }
+  return ParseArrayImpl(parser, out_resource, resource_format);
 }
 
-bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser,
-                                       ParsedResource* out_resource) {
-  return ParseArrayImpl(parser, out_resource,
-                        android::ResTable_map::TYPE_INTEGER);
+bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+  return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_INTEGER);
 }
 
-bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser,
-                                      ParsedResource* out_resource) {
-  return ParseArrayImpl(parser, out_resource,
-                        android::ResTable_map::TYPE_STRING);
+bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+  return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_STRING);
 }
 
 bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser,
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 8062c2e6..faa6607 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -571,14 +571,39 @@
 }
 
 TEST_F(ResourceParserTest, ParseStringArray) {
-  std::string input =
-      "<string-array name=\"foo\">\n"
-      "  <item>\"Werk\"</item>\n"
-      "</string-array>\n";
+  std::string input = R"EOF(
+      <string-array name="foo">
+        <item>"Werk"</item>"
+      </string-array>)EOF";
   ASSERT_TRUE(TestParse(input));
   EXPECT_NE(nullptr, test::GetValue<Array>(&table_, "array/foo"));
 }
 
+TEST_F(ResourceParserTest, ParseArrayWithFormat) {
+  std::string input = R"EOF(
+      <array name="foo" format="string">
+        <item>100</item>
+      </array>)EOF";
+  ASSERT_TRUE(TestParse(input));
+
+  Array* array = test::GetValue<Array>(&table_, "array/foo");
+  ASSERT_NE(nullptr, array);
+
+  ASSERT_EQ(1u, array->items.size());
+
+  String* str = ValueCast<String>(array->items[0].get());
+  ASSERT_NE(nullptr, str);
+  EXPECT_EQ(std::string("100"), *str->value);
+}
+
+TEST_F(ResourceParserTest, ParseArrayWithBadFormat) {
+  std::string input = R"EOF(
+      <array name="foo" format="integer">
+        <item>Hi</item>
+      </array>)EOF";
+  ASSERT_FALSE(TestParse(input));
+}
+
 TEST_F(ResourceParserTest, ParsePlural) {
   std::string input =
       "<plurals name=\"foo\">\n"
diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp
index 66aff82..5fbe77e 100644
--- a/tools/aapt2/filter/ConfigFilter.cpp
+++ b/tools/aapt2/filter/ConfigFilter.cpp
@@ -38,6 +38,43 @@
   config_mask_ |= diff_mask;
 }
 
+// Returns true if the locale script of the config should be considered matching
+// the locale script of entry.
+//
+// If both the scripts are empty, the scripts are considered matching for
+// backward compatibility reasons.
+//
+// If only one script is empty, we try to compute it based on the provided
+// language and country. If we could not compute it, we assume it's either a
+// new language we don't know about, or a private use language. We return true
+// since we don't know any better and they might as well be a match.
+//
+// Finally, when we have two scripts (one of which could be computed), we return
+// true if and only if they are an exact match.
+static bool ScriptsMatch(const ConfigDescription& config, const ConfigDescription& entry) {
+  const char* config_script = config.localeScript;
+  const char* entry_script = entry.localeScript;
+  if (config_script[0] == '\0' && entry_script[0] == '\0') {
+    return true;  // both scripts are empty. We match for backward compatibility reasons.
+  }
+
+  char script_buffer[sizeof(config.localeScript)];
+  if (config_script[0] == '\0') {
+    android::localeDataComputeScript(script_buffer, config.language, config.country);
+    if (script_buffer[0] == '\0') {  // We can't compute the script, so we match.
+      return true;
+    }
+    config_script = script_buffer;
+  } else if (entry_script[0] == '\0') {
+    android::localeDataComputeScript(script_buffer, entry.language, entry.country);
+    if (script_buffer[0] == '\0') {  // We can't compute the script, so we match.
+      return true;
+    }
+    entry_script = script_buffer;
+  }
+  return memcmp(config_script, entry_script, sizeof(config.localeScript)) == 0;
+}
+
 bool AxisConfigFilter::Match(const ConfigDescription& config) const {
   const uint32_t mask = ConfigDescription::DefaultConfig().diff(config);
   if ((config_mask_ & mask) == 0) {
@@ -57,12 +94,16 @@
       // If the locales differ, but the languages are the same and
       // the locale we are matching only has a language specified,
       // we match.
-      if (config.language[0] &&
-          memcmp(config.language, target.language, sizeof(config.language)) ==
-              0) {
-        if (config.country[0] == 0) {
-          matched_axis |= android::ResTable_config::CONFIG_LOCALE;
-        }
+      //
+      // Exception: we won't match if a script is specified for at least
+      // one of the locales and it's different from the other locale's
+      // script. (We will compute the other script if at least one of the
+      // scripts were explicitly set. In cases we can't compute an script,
+      // we match.)
+      if (config.language[0] != '\0' && config.country[0] == '\0' &&
+          config.localeVariant[0] == '\0' && config.language[0] == entry.first.language[0] &&
+          config.language[1] == entry.first.language[1] && ScriptsMatch(config, entry.first)) {
+        matched_axis |= android::ResTable_config::CONFIG_LOCALE;
       }
     } else if ((diff & diff_mask) ==
                android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp
index 586dd5f..4a31080 100644
--- a/tools/aapt2/filter/ConfigFilter_test.cpp
+++ b/tools/aapt2/filter/ConfigFilter_test.cpp
@@ -109,4 +109,20 @@
   EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("kok-rIN")));
 }
 
+TEST(ConfigFilterTest, MatchesScripts) {
+  AxisConfigFilter filter;
+
+  // "sr" gets automatically computed the script "Cyrl"
+  filter.AddConfig(test::ParseConfigOrDie("sr"));
+
+  // "sr" -> "b+sr+Cyrl"
+  EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("b+sr+Cyrl")));
+
+  // The incoming "sr" is also auto-computed to "b+sr+Cyrl".
+  EXPECT_TRUE(filter.Match(test::ParseConfigOrDie("sr")));
+
+  // "sr" -> "b+sr+Cyrl", which doesn't match "Latn".
+  EXPECT_FALSE(filter.Match(test::ParseConfigOrDie("b+sr+Latn")));
+}
+
 }  // namespace aapt
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e59b74f0..cda078a 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -452,6 +452,15 @@
     public static final int SAP_START_FAILURE_NO_CHANNEL = 1;
 
     /**
+     * Interface IP mode unspecified.
+     *
+     * @see updateInterfaceIpState(String, int)
+     *
+     * @hide
+     */
+    public static final int IFACE_IP_MODE_UNSPECIFIED = -1;
+
+    /**
      * Interface IP mode for configuration error.
      *
      * @see updateInterfaceIpState(String, int)
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
index 92c7e36..ebf5c2a 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
@@ -139,7 +139,7 @@
 
     public String getDeviceInfoHex() {
         return String.format(
-                Locale.US, "%04x%04x%04x%04x", 6, mDeviceInfo, mCtrlPort, mMaxThroughput);
+                Locale.US, "%04x%04x%04x", mDeviceInfo, mCtrlPort, mMaxThroughput);
     }
 
     public String toString() {