Merge "SensorManager: attach background thread to JavaVM" into oc-dev
diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java
index 3665d1b..4ee38fe 100644
--- a/core/java/android/companion/BluetoothDeviceFilterUtils.java
+++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java
@@ -58,7 +58,7 @@
 
     static boolean matchesAddress(String deviceAddress, BluetoothDevice device) {
         final boolean result = deviceAddress == null
-                || (device == null || !deviceAddress.equals(device.getAddress()));
+                || (device != null && deviceAddress.equals(device.getAddress()));
         if (DEBUG) debugLogMatchResult(result, device, deviceAddress);
         return result;
     }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9b0bab4..fdb0f2ba 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6852,7 +6852,7 @@
             ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
         }
         ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
-        ai.resourceDirs = state.resourceDirs;
+        ai.resourceDirs = state.overlayPaths;
     }
 
     public static ApplicationInfo generateApplicationInfo(Package p, int flags,
@@ -7000,6 +7000,7 @@
             return null;
         }
         if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) {
+            updateApplicationInfo(a.info.applicationInfo, flags, state);
             return a.info;
         }
         // Make shallow copies so we can store the metadata safely
@@ -7088,6 +7089,7 @@
             return null;
         }
         if (!copyNeeded(flags, s.owner, state, s.metaData, userId)) {
+            updateApplicationInfo(s.info.applicationInfo, flags, state);
             return s.info;
         }
         // Make shallow copies so we can store the metadata safely
@@ -7183,6 +7185,7 @@
         if (!copyNeeded(flags, p.owner, state, p.metaData, userId)
                 && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
                         || p.info.uriPermissionPatterns == null)) {
+            updateApplicationInfo(p.info.applicationInfo, flags, state);
             return p.info;
         }
         // Make shallow copies so we can store the metadata safely
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 4e53914..470336c 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -55,7 +55,7 @@
     public ArraySet<String> disabledComponents;
     public ArraySet<String> enabledComponents;
 
-    public String[] resourceDirs;
+    public String[] overlayPaths;
 
     public PackageUserState() {
         installed = true;
@@ -83,8 +83,8 @@
         installReason = o.installReason;
         disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
         enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
-        resourceDirs =
-            o.resourceDirs == null ? null : Arrays.copyOf(o.resourceDirs, o.resourceDirs.length);
+        overlayPaths =
+            o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
     }
 
     /**
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 96b443d..1f84061 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -25,6 +25,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Stream;
 
@@ -101,7 +102,7 @@
     /**
      * Returns the given list, or an immutable empty list if the provided list is null
      *
-     * This can be used to guaranty null-safety without paying the price of extra allocations
+     * This can be used to guarantee null-safety without paying the price of extra allocations
      *
      * @see Collections#emptyList
      */
@@ -110,6 +111,17 @@
     }
 
     /**
+     * Returns the given set, or an immutable empty set if the provided set is null
+     *
+     * This can be used to guarantee null-safety without paying the price of extra allocations
+     *
+     * @see Collections#emptySet
+     */
+    public static @NonNull <T> Set<T> emptyIfNull(@Nullable Set<T> cur) {
+        return cur == null ? Collections.emptySet() : cur;
+    }
+
+    /**
      * Returns the size of the given list, or 0 if the list is null
      */
     public static int size(@Nullable Collection<?> cur) {
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index de67c50..26b0034 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -29,6 +29,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <android-base/stringprintf.h>
 #include <binder/IInterface.h>
 #include <binder/IServiceManager.h>
 #include <binder/IPCThreadState.h>
@@ -194,10 +195,34 @@
         /*
          * It's an Error: Reraise the exception and ask the runtime to abort.
          */
+
+        // Try to get the exception string. Sometimes logcat isn't available,
+        // so try to add it to the abort message.
+        std::string exc_msg = "(Unknown exception message)";
+        {
+            ScopedLocalRef<jclass> exc_class(env, env->GetObjectClass(excep));
+            jmethodID method_id = env->GetMethodID(exc_class.get(),
+                                                   "toString",
+                                                   "()Ljava/lang/String;");
+            ScopedLocalRef<jstring> jstr(
+                    env,
+                    reinterpret_cast<jstring>(
+                            env->CallObjectMethod(excep, method_id)));
+            env->ExceptionClear();  // Just for good measure.
+            if (jstr.get() != nullptr) {
+                ScopedUtfChars jstr_utf(env, jstr.get());
+                exc_msg = jstr_utf.c_str();
+            }
+        }
+
         env->Throw(excep);
         ALOGE("java.lang.Error thrown during binder transaction (stack trace follows) : ");
         env->ExceptionDescribe();
-        env->FatalError("java.lang.Error thrown during binder transaction.");
+
+        std::string error_msg = base::StringPrintf(
+                "java.lang.Error thrown during binder transaction: %s",
+                exc_msg.c_str());
+        env->FatalError(error_msg.c_str());
     }
 
 bail:
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f8044e2..794d4f8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -453,6 +453,7 @@
     <protected-broadcast android:name="com.android.server.NetworkTimeUpdateService.action.POLL" />
     <protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
     <protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
+    <protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
 
     <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
     <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index 2a4ab0f..3b29a6c 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -20,6 +20,8 @@
 import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
 
 import static com.android.internal.util.ArrayUtils.isEmpty;
+import static com.android.internal.util.CollectionUtils.emptyIfNull;
+import static com.android.internal.util.CollectionUtils.size;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -154,6 +156,25 @@
             onReadyToShowUI();
         }
 
+        // If filtering to get single device by mac address, also search in the set of already
+        // bonded devices to allow linking those directly
+        String singleMacAddressFilter = null;
+        if (mRequest.isSingleDevice()) {
+            int numFilters = size(mBluetoothFilters);
+            for (int i = 0; i < numFilters; i++) {
+                BluetoothDeviceFilter filter = mBluetoothFilters.get(i);
+                if (!TextUtils.isEmpty(filter.getAddress())) {
+                    singleMacAddressFilter = filter.getAddress();
+                    break;
+                }
+            }
+        }
+        if (singleMacAddressFilter != null) {
+            for (BluetoothDevice dev : emptyIfNull(mBluetoothAdapter.getBondedDevices())) {
+                onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
+            }
+        }
+
         if (shouldScan(mBluetoothFilters)) {
             final IntentFilter intentFilter = new IntentFilter();
             intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
@@ -211,6 +232,8 @@
     }
 
     private void onDeviceFound(@Nullable DeviceFilterPair device) {
+        if (device == null) return;
+
         if (mDevicesFound.contains(device)) {
             return;
         }
@@ -444,12 +467,9 @@
                 }
 
                 for (int i = 0; i < scanResults.size(); i++) {
-                    DeviceFilterPair<android.net.wifi.ScanResult> deviceFilterPair =
-                            DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters);
-                    if (deviceFilterPair != null) onDeviceFound(deviceFilterPair);
+                    onDeviceFound(DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters));
                 }
             }
-
         }
     }
 }
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 6df02eb..526a8d0 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -329,7 +329,7 @@
     <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> حتى يكتمل الشحن"</string>
     <string name="power_charging_duration_short" msgid="1098603958472207920">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"غير معروف"</string>
-    <string name="battery_info_status_charging" msgid="1705179948350365604">"جاري الشحن"</string>
+    <string name="battery_info_status_charging" msgid="1705179948350365604">"جارٍ الشحن"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"جارٍ الشحن"</string>
     <string name="battery_info_status_discharging" msgid="310932812698268588">"لا يتم الشحن"</string>
     <string name="battery_info_status_not_charging" msgid="2820070506621483576">"لا يتم الشحن"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 35a341a..48bf180 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -272,7 +272,7 @@
     <string name="app_process_limit_title" msgid="4280600650253107163">"Límita processos en segon pla"</string>
     <string name="show_all_anrs" msgid="28462979638729082">"Tots els errors sense resposta"</string>
     <string name="show_all_anrs_summary" msgid="641908614413544127">"Informa que una aplicació en segon pla no respon"</string>
-    <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Avisos del canal de notificacions"</string>
+    <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostra avisos del canal de notificacions"</string>
     <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra un avís a la pantalla quan una app publica una notificació sense canal vàlid"</string>
     <string name="force_allow_on_external" msgid="3215759785081916381">"Força permís d\'aplicacions a l\'emmagatzem. extern"</string>
     <string name="force_allow_on_external_summary" msgid="3640752408258034689">"Permet que qualsevol aplicació es pugui escriure en un dispositiu d’emmagatzematge extern, independentment dels valors definits"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9b20a7a..79fb5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -179,12 +179,6 @@
 
         @Override
         public void onBluetoothDevicesChanged() {
-            mUiHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mDetailAdapter.updateItems();
-                }
-            });
             refreshState();
             if (isShowingDetail()) {
                 mDetailAdapter.updateItems();
@@ -198,6 +192,9 @@
     }
 
     protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
+        // We probably won't ever have space in the UI for more than 20 devices, so don't
+        // get info for them.
+        private static final int MAX_DEVICES = 20;
         private QSDetailItems mItems;
 
         @Override
@@ -260,13 +257,14 @@
             final Collection<CachedBluetoothDevice> devices = mController.getDevices();
             if (devices != null) {
                 int connectedDevices = 0;
+                int count = 0;
                 for (CachedBluetoothDevice device : devices) {
-                    if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
+                    if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
                     final Item item = new Item();
                     item.icon = R.drawable.ic_qs_bluetooth_on;
                     item.line1 = device.getName();
                     item.tag = device;
-                    int state = device.getMaxConnectionState();
+                    int state = mController.getMaxConnectionState(device);
                     if (state == BluetoothProfile.STATE_CONNECTED) {
                         item.icon = R.drawable.ic_qs_bluetooth_connected;
                         item.line2 = mContext.getString(R.string.quick_settings_connected);
@@ -280,6 +278,9 @@
                     } else {
                         items.add(item);
                     }
+                    if (++count == MAX_DEVICES) {
+                        break;
+                    }
                 }
             }
             mItems.setItems(items.toArray(new Item[items.size()]));
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 3b37437..0232911 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -25,6 +25,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -56,6 +58,7 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         ssp.registerDockedStackListener(mDockDividerVisibilityListener);
         mForcedResizableController = new ForcedResizableInfoActivityController(mContext);
+        EventBus.getDefault().register(this);
     }
 
     @Override
@@ -153,6 +156,18 @@
         mWindowManager.setTouchable((mHomeStackResizable || !mMinimized) && !mAdjustedForIme);
     }
 
+    /**
+     * Workaround for b/62528361, at the time RecentsDrawnEvent is sent, it may happen before a
+     * configuration change to the Divider, and internally, the event will be posted to the
+     * subscriber, or DividerView, which has been removed and prevented from resizing. Instead,
+     * register the event handler here and proxy the event to the current DividerView.
+     */
+    public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
+        if (mView != null) {
+            mView.onRecentsDrawn();
+        }
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.print("  mVisible="); pw.println(mVisible);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 6dc7870..7691652 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1222,7 +1222,7 @@
                 mSnapAlgorithm.getMiddleTarget());
     }
 
-    public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
+    public void onRecentsDrawn() {
         if (mState.animateAfterRecentsDrawn) {
             mState.animateAfterRecentsDrawn = false;
             updateDockSide();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index df30e20..9daa199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -37,6 +37,9 @@
     void disconnect(CachedBluetoothDevice device);
     boolean canConfigBluetooth();
 
+    int getMaxConnectionState(CachedBluetoothDevice device);
+    int getBondState(CachedBluetoothDevice device);
+
     public interface Callback {
         void onBluetoothStateChange(boolean enabled);
         void onBluetoothDevicesChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 36d24b3..dc4b115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -18,6 +18,8 @@
 
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
@@ -33,8 +35,10 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.WeakHashMap;
 
 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
         CachedBluetoothDevice.Callback {
@@ -44,18 +48,22 @@
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final UserManager mUserManager;
     private final int mCurrentUser;
+    private final WeakHashMap<CachedBluetoothDevice, ActuallyCachedState> mCachedState =
+            new WeakHashMap<>();
+    private final Handler mBgHandler;
 
     private boolean mEnabled;
     private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
     private CachedBluetoothDevice mLastDevice;
 
-    private final H mHandler = new H();
+    private final H mHandler = new H(Looper.getMainLooper());
     private int mState;
 
     public BluetoothControllerImpl(Context context, Looper bgLooper) {
         mLocalBluetoothManager = Dependency.get(LocalBluetoothManager.class);
+        mBgHandler = new Handler(bgLooper);
         if (mLocalBluetoothManager != null) {
-            mLocalBluetoothManager.getEventManager().setReceiverHandler(new Handler(bgLooper));
+            mLocalBluetoothManager.getEventManager().setReceiverHandler(mBgHandler);
             mLocalBluetoothManager.getEventManager().registerCallback(this);
             onBluetoothStateChanged(
                     mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
@@ -106,6 +114,16 @@
     }
 
     @Override
+    public int getBondState(CachedBluetoothDevice device) {
+        return getCachedState(device).mBondState;
+    }
+
+    @Override
+    public int getMaxConnectionState(CachedBluetoothDevice device) {
+        return getCachedState(device).mMaxConnectionState;
+    }
+
+    @Override
     public void addCallback(Callback cb) {
         mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
@@ -225,12 +243,14 @@
 
     @Override
     public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+        mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
     }
 
     @Override
     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+        mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
     }
@@ -243,11 +263,44 @@
 
     @Override
     public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        mCachedState.remove(cachedDevice);
         mLastDevice = cachedDevice;
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
+    private ActuallyCachedState getCachedState(CachedBluetoothDevice device) {
+        ActuallyCachedState state = mCachedState.get(device);
+        if (state == null) {
+            state = new ActuallyCachedState(device, mHandler);
+            mBgHandler.post(state);
+            mCachedState.put(device, state);
+            return state;
+        }
+        return state;
+    }
+
+    private static class ActuallyCachedState implements Runnable {
+
+        private final WeakReference<CachedBluetoothDevice> mDevice;
+        private final Handler mUiHandler;
+        private int mBondState = BluetoothDevice.BOND_NONE;
+        private int mMaxConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+
+        private ActuallyCachedState(CachedBluetoothDevice device, Handler uiHandler) {
+            mDevice = new WeakReference<>(device);
+            mUiHandler = uiHandler;
+        }
+
+        @Override
+        public void run() {
+            mBondState = mDevice.get().getBondState();
+            mMaxConnectionState = mDevice.get().getMaxConnectionState();
+            mUiHandler.removeMessages(H.MSG_PAIRED_DEVICES_CHANGED);
+            mUiHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
+        }
+    }
+
     private final class H extends Handler {
         private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>();
 
@@ -256,6 +309,10 @@
         private static final int MSG_ADD_CALLBACK = 3;
         private static final int MSG_REMOVE_CALLBACK = 4;
 
+        public H(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 2eb9560..4cc8bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -14,16 +14,21 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.os.Looper;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.Log;
 
 import com.android.settingslib.bluetooth.BluetoothEventManager;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -80,4 +85,56 @@
                 BluetoothAdapter.STATE_DISCONNECTED);
         assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
     }
+
+    @Test
+    public void testDefaultConnectionState() {
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mBluetoothControllerImpl.getMaxConnectionState(device));
+    }
+
+    @Test
+    public void testAsyncBondState() throws Exception {
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        when(device.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+        mBluetoothControllerImpl.addCallback(callback);
+
+        // Grab the main looper, we'll need it later.
+        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+
+        // Trigger the state getting.
+        assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
+
+        mTestableLooper.processMessages(1);
+        mainLooper.processAllMessages();
+
+        assertEquals(BluetoothDevice.BOND_BONDED, mBluetoothControllerImpl.getBondState(device));
+        verify(callback).onBluetoothDevicesChanged();
+        mainLooper.destroy();
+    }
+
+    @Test
+    public void testAsyncConnectionState() throws Exception {
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+        BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+        mBluetoothControllerImpl.addCallback(callback);
+
+        // Grab the main looper, we'll need it later.
+        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+
+        // Trigger the state getting.
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mBluetoothControllerImpl.getMaxConnectionState(device));
+
+        mTestableLooper.processMessages(1);
+        mainLooper.processAllMessages();
+
+        assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mBluetoothControllerImpl.getMaxConnectionState(device));
+        verify(callback).onBluetoothDevicesChanged();
+        mainLooper.destroy();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index 0ba0319..9ec096a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -83,4 +83,14 @@
     public boolean canConfigBluetooth() {
         return false;
     }
+
+    @Override
+    public int getMaxConnectionState(CachedBluetoothDevice device) {
+        return 0;
+    }
+
+    @Override
+    public int getBondState(CachedBluetoothDevice device) {
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 30df978..ac68a9e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1808,7 +1808,7 @@
                     }
                     AppErrorResult res = (AppErrorResult) data.get("result");
                     if (mShowDialogs && !mSleeping && !mShuttingDown) {
-                        Dialog d = new StrictModeViolationDialog(mContext,
+                        Dialog d = new StrictModeViolationDialog(mUiContext,
                                 ActivityManagerService.this, res, proc);
                         d.show();
                         proc.crashDialog = d;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 6a310f2..eeab605 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -599,6 +599,7 @@
     void finishUserStopping(final int userId, final UserState uss) {
         // On to the next.
         final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
+        shutdownIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         // This is the result receiver for the final shutdown broadcast.
         final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
             @Override
diff --git a/services/core/java/com/android/server/job/GrantedUriPermissions.java b/services/core/java/com/android/server/job/GrantedUriPermissions.java
index e413d8d..c23b109 100644
--- a/services/core/java/com/android/server/job/GrantedUriPermissions.java
+++ b/services/core/java/com/android/server/job/GrantedUriPermissions.java
@@ -29,7 +29,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-public class GrantedUriPermissions {
+public final class GrantedUriPermissions {
     private final int mGrantFlags;
     private final int mSourceUserId;
     private final String mTag;
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
index 2d2f61f..a53c088 100644
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -26,7 +26,7 @@
 
 import java.io.PrintWriter;
 
-public class JobSchedulerShellCommand extends ShellCommand {
+public final class JobSchedulerShellCommand extends ShellCommand {
     public static final int CMD_ERR_NO_PACKAGE = -1000;
     public static final int CMD_ERR_NO_JOB = -1001;
     public static final int CMD_ERR_CONSTRAINTS = -1002;
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 637db11..5d3f6f7 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -55,13 +55,13 @@
  * job lands, and again when it is complete.
  * - Cancelling is trickier, because there are also interactions from the client. It's possible
  * the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
- * {@link #doCancelLocked(int)} after the client has already finished. This is handled by having
+ * {@link #doCancelLocked} after the client has already finished. This is handled by having
  * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether
  * the context is still valid.
  * To mitigate this, we avoid sending duplicate onStopJob()
  * calls to the client after they've specified jobFinished().
  */
-public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
+public final class JobServiceContext implements ServiceConnection {
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
     private static final String TAG = "JobServiceContext";
     /** Amount of time a job is allowed to execute for before being considered timed-out. */
@@ -112,6 +112,7 @@
      * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
      */
     private JobStatus mRunningJob;
+    private JobCallback mRunningCallback;
     /** Used to store next job to run when current job is to be preempted. */
     private int mPreferredUid;
     IJobService service;
@@ -133,6 +134,36 @@
     // Debugging: time this job was last stopped.
     public long mStoppedTime;
 
+    final class JobCallback extends IJobCallback.Stub {
+        public String mStoppedReason;
+        public long mStoppedTime;
+
+        @Override
+        public void acknowledgeStartMessage(int jobId, boolean ongoing) {
+            doAcknowledgeStartMessage(this, jobId, ongoing);
+        }
+
+        @Override
+        public void acknowledgeStopMessage(int jobId, boolean reschedule) {
+            doAcknowledgeStopMessage(this, jobId, reschedule);
+        }
+
+        @Override
+        public JobWorkItem dequeueWork(int jobId) {
+            return doDequeueWork(this, jobId);
+        }
+
+        @Override
+        public boolean completeWork(int jobId, int workId) {
+            return doCompleteWork(this, jobId, workId);
+        }
+
+        @Override
+        public void jobFinished(int jobId, boolean reschedule) {
+            doJobFinished(this, jobId, reschedule);
+        }
+    }
+
     JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
             JobPackageTracker tracker, Looper looper) {
         this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
@@ -168,6 +199,7 @@
             mPreferredUid = NO_PREFERRED_UID;
 
             mRunningJob = job;
+            mRunningCallback = new JobCallback();
             final boolean isDeadlineExpired =
                     job.hasDeadlineConstraint() &&
                             (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
@@ -182,7 +214,7 @@
                 job.changedAuthorities.toArray(triggeredAuthorities);
             }
             final JobInfo ji = job.getJob();
-            mParams = new JobParameters(this, job.getJobId(), ji.getExtras(),
+            mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
                     isDeadlineExpired, triggeredUris, triggeredAuthorities);
             mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
@@ -198,6 +230,7 @@
                     Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                 }
                 mRunningJob = null;
+                mRunningCallback = null;
                 mParams = null;
                 mExecutionStartTimeElapsed = 0L;
                 mVerb = VERB_FINISHED;
@@ -263,28 +296,29 @@
         return false;
     }
 
-    @Override
-    public void jobFinished(int jobId, boolean reschedule) {
-        doCallback(reschedule, "app called jobFinished");
+    void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
+        doCallback(cb, reschedule, "app called jobFinished");
     }
 
-    @Override
-    public void acknowledgeStopMessage(int jobId, boolean reschedule) {
-        doCallback(reschedule, null);
+    void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
+        doCallback(cb, reschedule, null);
     }
 
-    @Override
-    public void acknowledgeStartMessage(int jobId, boolean ongoing) {
-        doCallback(ongoing, "finished start");
+    void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) {
+        doCallback(cb, ongoing, "finished start");
     }
 
-    @Override
-    public JobWorkItem dequeueWork(int jobId) {
-        final int callingUid = Binder.getCallingUid();
+    JobWorkItem doDequeueWork(JobCallback cb, int jobId) {
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                assertCallingUidLocked(callingUid);
+                assertCallerLocked(cb);
+                if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) {
+                    // This job is either all done, or on its way out.  Either way, it
+                    // should not dispatch any more work.  We will pick up any remaining
+                    // work the next time we start the job again.
+                    return null;
+                }
                 final JobWorkItem work = mRunningJob.dequeueWorkLocked();
                 if (work == null && !mRunningJob.hasExecutingWorkLocked()) {
                     // This will finish the job.
@@ -297,13 +331,11 @@
         }
     }
 
-    @Override
-    public boolean completeWork(int jobId, int workId) {
-        final int callingUid = Binder.getCallingUid();
+    boolean doCompleteWork(JobCallback cb, int jobId, int workId) {
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                assertCallingUidLocked(callingUid);
+                assertCallerLocked(cb);
                 return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId);
             }
         } finally {
@@ -369,8 +401,8 @@
      * whether the client exercising the callback is the client we expect.
      * @return True if the binder calling is coming from the client we expect.
      */
-    private boolean verifyCallingUidLocked(final int callingUid) {
-        if (mRunningJob == null || callingUid != mRunningJob.getUid()) {
+    private boolean verifyCallerLocked(JobCallback cb) {
+        if (mRunningCallback != cb) {
             if (DEBUG) {
                 Slog.d(TAG, "Stale callback received, ignoring.");
             }
@@ -379,16 +411,15 @@
         return true;
     }
 
-    private void assertCallingUidLocked(final int callingUid) {
-        if (!verifyCallingUidLocked(callingUid)) {
+    private void assertCallerLocked(JobCallback cb) {
+        if (!verifyCallerLocked(cb)) {
             StringBuilder sb = new StringBuilder(128);
-            sb.append("Bad calling uid ");
-            sb.append(callingUid);
-            if (mStoppedReason != null) {
+            sb.append("Caller no longer running");
+            if (cb.mStoppedReason != null) {
                 sb.append(", last stopped ");
-                TimeUtils.formatDuration(SystemClock.elapsedRealtime() - mStoppedTime, sb);
+                TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb);
                 sb.append(" because: ");
-                sb.append(mStoppedReason);
+                sb.append(cb.mStoppedReason);
             }
             throw new SecurityException(sb.toString());
         }
@@ -421,12 +452,11 @@
         handleServiceBoundLocked();
     }
 
-    void doCallback(boolean reschedule, String reason) {
-        final int callingUid = Binder.getCallingUid();
+    void doCallback(JobCallback cb, boolean reschedule, String reason) {
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                if (!verifyCallingUidLocked(callingUid)) {
+                if (!verifyCallerLocked(cb)) {
                     return;
                 }
                 doCallbackLocked(reschedule, reason);
@@ -559,7 +589,7 @@
      * VERB_BINDING    -> Cancelled before bind completed. Mark as cancelled and wait for
      *                    {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
      *     _STARTING   -> Mark as cancelled and wait for
-     *                    {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
+     *                    {@link JobServiceContext#doAcknowledgeStartMessage}
      *     _EXECUTING  -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks
      *                      in the message queue.
      *     _ENDING     -> No point in doing anything here, so we ignore.
@@ -671,6 +701,7 @@
         mContext.unbindService(JobServiceContext.this);
         mWakeLock = null;
         mRunningJob = null;
+        mRunningCallback = null;
         mParams = null;
         mVerb = VERB_FINISHED;
         mCancelled = false;
@@ -684,6 +715,10 @@
         if (reason != null && mStoppedReason == null) {
             mStoppedReason = reason;
             mStoppedTime = SystemClock.elapsedRealtime();
+            if (mRunningCallback != null) {
+                mRunningCallback.mStoppedReason = mStoppedReason;
+                mRunningCallback.mStoppedTime = mStoppedTime;
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 22eed3b..f0cd8a8 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -66,7 +66,7 @@
  *      and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
  *      object.
  */
-public class JobStore {
+public final class JobStore {
     private static final String TAG = "JobStore";
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
 
@@ -263,7 +263,7 @@
      * Runnable that writes {@link #mJobSet} out to xml.
      * NOTE: This Runnable locks on mLock
      */
-    private class WriteJobsMapToDiskRunnable implements Runnable {
+    private final class WriteJobsMapToDiskRunnable implements Runnable {
         @Override
         public void run() {
             final long startElapsed = SystemClock.elapsedRealtime();
@@ -444,7 +444,7 @@
      * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
      * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
      */
-    private class ReadJobMapFromDiskRunnable implements Runnable {
+    private final class ReadJobMapFromDiskRunnable implements Runnable {
         private final JobSet jobSet;
 
         /**
@@ -796,7 +796,7 @@
         }
     }
 
-    static class JobSet {
+    static final class JobSet {
         // Key is the getUid() originator of the jobs in each sheaf
         private SparseArray<ArraySet<JobStatus>> mJobs;
 
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 68dd00f..39f2a96 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -33,7 +33,7 @@
  * for a certain amount of time (maybe hours or days) are considered idle. When the app comes
  * out of idle state, it will be allowed to run scheduled jobs.
  */
-public class AppIdleController extends StateController {
+public final class AppIdleController extends StateController {
 
     private static final String LOG_TAG = "AppIdleController";
     private static final boolean DEBUG = false;
@@ -171,7 +171,7 @@
         }
     }
 
-    private class AppIdleStateChangeListener
+    private final class AppIdleStateChangeListener
             extends UsageStatsManagerInternal.AppIdleStateChangeListener {
         @Override
         public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index d275bd9..9111969 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -39,7 +39,7 @@
  * be charging when it's been plugged in for more than two minutes, and the system has broadcast
  * ACTION_BATTERY_OK.
  */
-public class BatteryController extends StateController {
+public final class BatteryController extends StateController {
     private static final String TAG = "JobScheduler.Batt";
 
     private static final Object sCreationLock = new Object();
@@ -121,7 +121,7 @@
         }
     }
 
-    public class ChargingTracker extends BroadcastReceiver {
+    public final class ChargingTracker extends BroadcastReceiver {
         /**
          * Track whether we're "charging", where charging means that we're ready to commit to
          * doing work.
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index f426818..17c8928 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -43,7 +43,7 @@
  * status due to user-requested network policies, so we need to check
  * constraints on a per-UID basis.
  */
-public class ConnectivityController extends StateController implements
+public final class ConnectivityController extends StateController implements
         ConnectivityManager.OnNetworkActiveListener {
     private static final String TAG = "JobScheduler.Conn";
     private static final boolean DEBUG = false;
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index cfafc38..ff807ec 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -39,7 +39,7 @@
 /**
  * Controller for monitoring changes to content URIs through a ContentObserver.
  */
-public class ContentObserverController extends StateController {
+public final class ContentObserverController extends StateController {
     private static final String TAG = "JobScheduler.Content";
     private static final boolean DEBUG = false;
 
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 5ccf812..85993b9 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -37,7 +37,7 @@
  * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
  * When device is not dozing, set constraint for all jobs as satisfied.
  */
-public class DeviceIdleJobsController extends StateController {
+public final class DeviceIdleJobsController extends StateController {
 
     private static final String LOG_TAG = "DeviceIdleJobsController";
     private static final boolean LOG_DEBUG = false;
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 7e92293..9eda046 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -33,7 +33,7 @@
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 
-public class IdleController extends StateController {
+public final class IdleController extends StateController {
     private static final String TAG = "IdleController";
 
     // Policy: we decide that we're "idle" if the device has been unused /
@@ -107,7 +107,7 @@
         mIdleTracker.startTracking();
     }
 
-    class IdlenessTracker extends BroadcastReceiver {
+    final class IdlenessTracker extends BroadcastReceiver {
         private AlarmManager mAlarm;
         private PendingIntent mIdleTriggerIntent;
         boolean mIdle;
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 53bf402..446b0d6 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -698,7 +698,8 @@
     static final int CONSTRAINTS_OF_INTEREST =
             CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
             CONSTRAINT_TIMING_DELAY |
-            CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | CONSTRAINT_NOT_ROAMING |
+            CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
+            CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED |
             CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
 
     // Soft override covers all non-"functional" constraints
@@ -865,6 +866,9 @@
         if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) {
             pw.print(" NOT_ROAMING");
         }
+        if ((constraints&CONSTRAINT_METERED) != 0) {
+            pw.print(" METERED");
+        }
         if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) {
             pw.print(" APP_NOT_IDLE");
         }
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
index 4fe8eca..c24e563 100644
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -35,7 +35,7 @@
 /**
  * Simple controller that tracks the status of the device's storage.
  */
-public class StorageController extends StateController {
+public final class StorageController extends StateController {
     private static final String TAG = "JobScheduler.Stor";
 
     private static final Object sCreationLock = new Object();
@@ -112,7 +112,7 @@
         }
     }
 
-    public class StorageTracker extends BroadcastReceiver {
+    public final class StorageTracker extends BroadcastReceiver {
         /**
          * Track whether storage is low.
          */
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index 01c841e..d90699a 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -38,7 +38,7 @@
  * This class sets an alarm for the next expiring job, and determines whether a job's minimum
  * delay has been satisfied.
  */
-public class TimeController extends StateController {
+public final class TimeController extends StateController {
     private static final String TAG = "JobScheduler.Time";
 
     /** Deadline alarm tag for logging purposes */
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 75e7a1e..62b563a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -81,8 +81,10 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.util.Xml;
 
+import java.io.CharArrayWriter;
 import libcore.io.IoUtils;
 
 import com.android.internal.R;
@@ -195,7 +197,10 @@
 
     /** Historical sessions kept around for debugging purposes */
     @GuardedBy("mSessions")
-    private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>();
+    private final List<String> mHistoricalSessions = new ArrayList<>();
+
+    @GuardedBy("mSessions")
+    private final SparseIntArray mHistoricalSessionsByInstaller = new SparseIntArray();
 
     /** Sessions allocated to legacy users */
     @GuardedBy("mSessions")
@@ -371,7 +376,7 @@
                             // Since this is early during boot we don't send
                             // any observer events about the session, but we
                             // keep details around for dumpsys.
-                            mHistoricalSessions.put(session.sessionId, session);
+                            addHistoricalSessionLocked(session);
                         }
                         mAllocatedSessions.put(session.sessionId, true);
                     }
@@ -386,6 +391,18 @@
         }
     }
 
+    private void addHistoricalSessionLocked(PackageInstallerSession session) {
+        CharArrayWriter writer = new CharArrayWriter();
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "    ");
+        session.dump(pw);
+        mHistoricalSessions.add(writer.toString());
+
+        // Increment the number of sessions by this installerUid.
+        mHistoricalSessionsByInstaller.put(
+                session.installerUid,
+                mHistoricalSessionsByInstaller.get(session.installerUid) + 1);
+    }
+
     private PackageInstallerSession readSessionLocked(XmlPullParser in) throws IOException,
             XmlPullParserException {
         final int sessionId = readIntAttribute(in, ATTR_SESSION_ID);
@@ -676,7 +693,7 @@
                 throw new IllegalStateException(
                         "Too many active sessions for UID " + callingUid);
             }
-            final int historicalCount = getSessionCount(mHistoricalSessions, callingUid);
+            final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
             if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
                 throw new IllegalStateException(
                         "Too many historical sessions for UID " + callingUid);
@@ -1228,8 +1245,7 @@
             pw.increaseIndent();
             N = mHistoricalSessions.size();
             for (int i = 0; i < N; i++) {
-                final PackageInstallerSession session = mHistoricalSessions.valueAt(i);
-                session.dump(pw);
+                pw.print(mHistoricalSessions.get(i));
                 pw.println();
             }
             pw.println();
@@ -1264,7 +1280,7 @@
                 public void run() {
                     synchronized (mSessions) {
                         mSessions.remove(session.sessionId);
-                        mHistoricalSessions.put(session.sessionId, session);
+                        addHistoricalSessionLocked(session);
 
                         final File appIconFile = buildAppIconFile(session.sessionId);
                         if (appIconFile.exists()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e62b107..07d548d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -48,6 +48,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
@@ -675,13 +676,6 @@
     @GuardedBy("mPackages")
     final SparseIntArray mIsolatedOwners = new SparseIntArray();
 
-    // List of APK paths to load for each user and package. This data is never
-    // persisted by the package manager. Instead, the overlay manager will
-    // ensure the data is up-to-date in runtime.
-    @GuardedBy("mPackages")
-    final SparseArray<ArrayMap<String, ArrayList<String>>> mEnabledOverlayPaths =
-        new SparseArray<ArrayMap<String, ArrayList<String>>>();
-
     /**
      * Tracks new system packages [received in an OTA] that we expect to
      * find updated user-installed versions. Keys are package name, values
@@ -3583,8 +3577,6 @@
             return null;
         }
 
-        rebaseEnabledOverlays(packageInfo.applicationInfo, userId);
-
         packageInfo.packageName = packageInfo.applicationInfo.packageName =
                 resolveExternalPackageNameLPr(p);
 
@@ -4096,7 +4088,6 @@
             ApplicationInfo ai = PackageParser.generateApplicationInfo(ps.pkg, flags,
                     ps.readUserState(userId), userId);
             if (ai != null) {
-                rebaseEnabledOverlays(ai, userId);
                 ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
             }
             return ai;
@@ -4145,7 +4136,6 @@
                 ApplicationInfo ai = PackageParser.generateApplicationInfo(
                         p, flags, ps.readUserState(userId), userId);
                 if (ai != null) {
-                    rebaseEnabledOverlays(ai, userId);
                     ai.packageName = resolveExternalPackageNameLPr(p);
                 }
                 return ai;
@@ -4162,26 +4152,6 @@
         return null;
     }
 
-    private void rebaseEnabledOverlays(@NonNull ApplicationInfo ai, int userId) {
-        List<String> paths = new ArrayList<>();
-        ArrayMap<String, ArrayList<String>> userSpecificOverlays =
-            mEnabledOverlayPaths.get(userId);
-        if (userSpecificOverlays != null) {
-            if (!"android".equals(ai.packageName)) {
-                ArrayList<String> frameworkOverlays = userSpecificOverlays.get("android");
-                if (frameworkOverlays != null) {
-                    paths.addAll(frameworkOverlays);
-                }
-            }
-
-            ArrayList<String> appOverlays = userSpecificOverlays.get(ai.packageName);
-            if (appOverlays != null) {
-                paths.addAll(appOverlays);
-            }
-        }
-        ai.resourceDirs = paths.size() > 0 ? paths.toArray(new String[paths.size()]) : null;
-    }
-
     private String normalizePackageNameLPr(String packageName) {
         String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
         return normalizedPackageName != null ? normalizedPackageName : packageName;
@@ -4566,24 +4536,6 @@
         return updateFlagsForComponent(flags, userId, intent /*cookie*/);
     }
 
-    private ActivityInfo generateActivityInfo(ActivityInfo ai, int flags, PackageUserState state,
-            int userId) {
-        ActivityInfo ret = PackageParser.generateActivityInfo(ai, flags, state, userId);
-        if (ret != null) {
-            rebaseEnabledOverlays(ret.applicationInfo, userId);
-        }
-        return ret;
-    }
-
-    private ActivityInfo generateActivityInfo(PackageParser.Activity a, int flags,
-            PackageUserState state, int userId) {
-        ActivityInfo ai = PackageParser.generateActivityInfo(a, flags, state, userId);
-        if (ai != null) {
-            rebaseEnabledOverlays(ai.applicationInfo, userId);
-        }
-        return ai;
-    }
-
     @Override
     public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
         return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
@@ -4611,11 +4563,12 @@
                 if (filterAppAccessLPr(ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
                     return null;
                 }
-                return generateActivityInfo(a, flags, ps.readUserState(userId), userId);
+                return PackageParser.generateActivityInfo(
+                        a, flags, ps.readUserState(userId), userId);
             }
             if (mResolveComponentName.equals(component)) {
-                return generateActivityInfo(mResolveActivity, flags, new PackageUserState(),
-                        userId);
+                return PackageParser.generateActivityInfo(
+                        mResolveActivity, flags, new PackageUserState(), userId);
             }
         }
         return null;
@@ -4669,7 +4622,8 @@
                 if (filterAppAccessLPr(ps, callingUid, component, TYPE_RECEIVER, userId)) {
                     return null;
                 }
-                return generateActivityInfo(a, flags, ps.readUserState(userId), userId);
+                return PackageParser.generateActivityInfo(
+                        a, flags, ps.readUserState(userId), userId);
             }
         }
         return null;
@@ -4805,12 +4759,8 @@
                 if (filterAppAccessLPr(ps, callingUid, component, TYPE_SERVICE, userId)) {
                     return null;
                 }
-                ServiceInfo si = PackageParser.generateServiceInfo(s, flags,
-                        ps.readUserState(userId), userId);
-                if (si != null) {
-                    rebaseEnabledOverlays(si.applicationInfo, userId);
-                }
-                return si;
+                return PackageParser.generateServiceInfo(
+                        s, flags, ps.readUserState(userId), userId);
             }
         }
         return null;
@@ -4833,12 +4783,8 @@
                 if (filterAppAccessLPr(ps, callingUid, component, TYPE_PROVIDER, userId)) {
                     return null;
                 }
-                ProviderInfo pi = PackageParser.generateProviderInfo(p, flags,
-                        ps.readUserState(userId), userId);
-                if (pi != null) {
-                    rebaseEnabledOverlays(pi.applicationInfo, userId);
-                }
-                return pi;
+                return PackageParser.generateProviderInfo(
+                        p, flags, ps.readUserState(userId), userId);
             }
         }
         return null;
@@ -8264,7 +8210,6 @@
                         ai = PackageParser.generateApplicationInfo(ps.pkg, effectiveFlags,
                                 ps.readUserState(userId), userId);
                         if (ai != null) {
-                            rebaseEnabledOverlays(ai, userId);
                             ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
                         }
                     } else {
@@ -8291,7 +8236,6 @@
                         ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
                                 ps.readUserState(userId), userId);
                         if (ai != null) {
-                            rebaseEnabledOverlays(ai, userId);
                             ai.packageName = resolveExternalPackageNameLPr(p);
                             list.add(ai);
                         }
@@ -8445,7 +8389,6 @@
                         ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
                                 ps.readUserState(userId), userId);
                         if (ai != null) {
-                            rebaseEnabledOverlays(ai, userId);
                             finalList.add(ai);
                         }
                     }
@@ -13308,7 +13251,8 @@
                 return null;
             }
             final PackageUserState userState = ps.readUserState(userId);
-            ActivityInfo ai = generateActivityInfo(activity, mFlags, userState, userId);
+            ActivityInfo ai =
+                    PackageParser.generateActivityInfo(activity, mFlags, userState, userId);
             if (ai == null) {
                 return null;
             }
@@ -17837,16 +17781,17 @@
 
         // Instant apps must have target SDK >= O and have targetSanboxVersion >= 2
         if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
-            Slog.w(TAG, "Instant app package " + pkg.packageName
-                    + " does not target O, this will be a fatal error.");
-            // STOPSHIP: Make this a fatal error
-            pkg.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+            Slog.w(TAG, "Instant app package " + pkg.packageName + " does not target O");
+            res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
+                    "Instant app package must target O");
+            return;
         }
         if (instantApp && pkg.applicationInfo.targetSandboxVersion != 2) {
             Slog.w(TAG, "Instant app package " + pkg.packageName
-                    + " does not target targetSandboxVersion 2, this will be a fatal error.");
-            // STOPSHIP: Make this a fatal error
-            pkg.applicationInfo.targetSandboxVersion = 2;
+                    + " does not target targetSandboxVersion 2");
+            res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
+                    "Instant app package must use targetSanboxVersion 2");
+            return;
         }
 
         if (pkg.applicationInfo.isStaticSharedLibrary()) {
@@ -22277,11 +22222,6 @@
                 dumpCompilerStatsLPr(pw, packageName);
             }
 
-            if (!checkin && dumpState.isDumping(DumpState.DUMP_ENABLED_OVERLAYS)) {
-                if (dumpState.onTitlePrinted()) pw.println();
-                dumpEnabledOverlaysLPr(pw);
-            }
-
             if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
                 if (dumpState.onTitlePrinted()) pw.println();
                 mSettings.dumpReadMessagesLPr(pw, dumpState);
@@ -22478,23 +22418,6 @@
         }
     }
 
-    private void dumpEnabledOverlaysLPr(PrintWriter pw) {
-        pw.println("Enabled overlay paths:");
-        final int N = mEnabledOverlayPaths.size();
-        for (int i = 0; i < N; i++) {
-            final int userId = mEnabledOverlayPaths.keyAt(i);
-            pw.println(String.format("    User %d:", userId));
-            final ArrayMap<String, ArrayList<String>> userSpecificOverlays =
-                mEnabledOverlayPaths.valueAt(i);
-            final int M = userSpecificOverlays.size();
-            for (int j = 0; j < M; j++) {
-                final String targetPackageName = userSpecificOverlays.keyAt(j);
-                final ArrayList<String> overlayPackagePaths = userSpecificOverlays.valueAt(j);
-                pw.println(String.format("        %s: %s", targetPackageName, overlayPackagePaths));
-            }
-        }
-    }
-
     private String dumpDomainString(String packageName) {
         List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName)
                 .getList();
@@ -24669,11 +24592,10 @@
                     Slog.e(TAG, "failed to find package " + targetPackageName);
                     return false;
                 }
-
-                ArrayList<String> paths = null;
-                if (overlayPackageNames != null) {
+                ArrayList<String> overlayPaths = null;
+                if (overlayPackageNames != null && overlayPackageNames.size() > 0) {
                     final int N = overlayPackageNames.size();
-                    paths = new ArrayList<>(N);
+                    overlayPaths = new ArrayList<>(N);
                     for (int i = 0; i < N; i++) {
                         final String packageName = overlayPackageNames.get(i);
                         final PackageParser.Package pkg = mPackages.get(packageName);
@@ -24681,22 +24603,17 @@
                             Slog.e(TAG, "failed to find package " + packageName);
                             return false;
                         }
-                        paths.add(pkg.baseCodePath);
+                        overlayPaths.add(pkg.baseCodePath);
                     }
                 }
 
-                ArrayMap<String, ArrayList<String>> userSpecificOverlays =
-                    mEnabledOverlayPaths.get(userId);
-                if (userSpecificOverlays == null) {
-                    userSpecificOverlays = new ArrayMap<>();
-                    mEnabledOverlayPaths.put(userId, userSpecificOverlays);
+                final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
+                String[] frameworkOverlayPaths = null;
+                if (!"android".equals(targetPackageName)) {
+                    frameworkOverlayPaths =
+                            mSettings.mPackages.get("android").getOverlayPaths(userId);
                 }
-
-                if (paths != null && paths.size() > 0) {
-                    userSpecificOverlays.put(targetPackageName, paths);
-                } else {
-                    userSpecificOverlays.remove(targetPackageName);
-                }
+                ps.setOverlayPaths(overlayPaths, frameworkOverlayPaths, userId);
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 14f65eb..d17267f 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -31,6 +31,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.google.android.collect.Lists;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -329,6 +330,27 @@
         modifyUserState(userId).installReason = installReason;
     }
 
+    void setOverlayPaths(List<String> overlayPaths, String[] frameworkOverlayPaths, int userId) {
+        if (overlayPaths == null && frameworkOverlayPaths == null) {
+            modifyUserState(userId).overlayPaths = null;
+            return;
+        }
+        final List<String> paths;
+        if (frameworkOverlayPaths == null) {
+            paths = overlayPaths;
+        } else {
+            paths = Lists.newArrayList(frameworkOverlayPaths);
+            if (overlayPaths != null) {
+                paths.addAll(overlayPaths);
+            }
+        }
+        modifyUserState(userId).overlayPaths = paths.toArray(new String[paths.size()]);
+    }
+
+    String[] getOverlayPaths(int userId) {
+        return readUserState(userId).overlayPaths;
+    }
+
     /** Only use for testing. Do NOT use in production code. */
     @VisibleForTesting
     SparseArray<PackageUserState> getUserState() {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 24cbdbf..b006c2d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4858,6 +4858,15 @@
             pw.print(ps.getEnabled(user.id));
             pw.print(" instant=");
             pw.println(ps.getInstantApp(user.id));
+
+            String[] overlayPaths = ps.getOverlayPaths(user.id);
+            if (overlayPaths != null && overlayPaths.length > 0) {
+                pw.println("Overlay paths:");
+                for (String path : overlayPaths) {
+                    pw.println(path);
+                }
+            }
+
             String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
             if (lastDisabledAppCaller != null) {
                 pw.print(prefix); pw.print("    lastDisabledCaller: ");
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 28ffc94..02f2afc 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -391,7 +391,8 @@
         // First send the high-level shut down broadcast.
         mActionDone = false;
         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mContext.sendOrderedBroadcastAsUser(intent,
                 UserHandle.ALL, null, br, mHandler, 0, null, null);
 
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 7d13889..cff2fad 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -206,6 +206,10 @@
             mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
                     mFrom.top + mFrozenTaskHeight);
 
+            // Boost the thread priority of the animation thread while the bounds animation is
+            // running
+            updateBooster();
+
             // Ensure that we have prepared the target for animation before
             // we trigger any size changes, so it can swap surfaces
             // in to appropriate modes, or do as it wishes otherwise.
@@ -316,6 +320,9 @@
             removeListener(this);
             removeUpdateListener(this);
             mRunningAnimations.remove(mTarget);
+
+            // Reset the thread priority of the animation thread after the bounds animation is done
+            updateBooster();
         }
 
         @Override
@@ -446,4 +453,9 @@
             b.resume();
         }
     }
+
+    private void updateBooster() {
+        WindowManagerService.sThreadPriorityBooster.setBoundsAnimationRunning(
+                !mRunningAnimations.isEmpty());
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ccc8f63..54983c8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -314,6 +314,9 @@
     // the display's direct children should be allowed.
     private boolean mRemovingDisplay = false;
 
+    // {@code false} if this display is in the processing of being created.
+    private boolean mDisplayReady = false;
+
     private final WindowLayersController mLayersController;
     WallpaperController mWallpaperController;
     int mInputMethodAnimLayerAdjustment;
@@ -720,7 +723,6 @@
      */
     DisplayContent(Display display, WindowManagerService service,
             WindowLayersController layersController, WallpaperController wallpaperController) {
-
         if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
             throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
                     + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -748,6 +750,15 @@
 
         // Add itself as a child to the root container.
         mService.mRoot.addChild(this, null);
+
+        // TODO(b/62541591): evaluate whether this is the best spot to declare the
+        // {@link DisplayContent} ready for use.
+        mDisplayReady = true;
+    }
+
+    boolean isReady() {
+        // The display is ready when the system and the individual display are both ready.
+        return mService.mDisplayReady && mDisplayReady;
     }
 
     int getDisplayId() {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 03b5b827..aabf2be 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -108,7 +108,8 @@
     }
 
     void addDisplayLocked(final int displayId) {
-        // Create the DisplayContentsAnimator object by retrieving it.
+        // Create the DisplayContentsAnimator object by retrieving it if the associated
+        // {@link DisplayContent} exists.
         getDisplayContentsAnimatorLocked(displayId);
         if (displayId == DEFAULT_DISPLAY) {
             mInitialized = true;
@@ -356,8 +357,16 @@
     }
 
     private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
+        if (displayId < 0) {
+            return null;
+        }
+
         DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
-        if (displayAnimator == null) {
+
+        // It is possible that this underlying {@link DisplayContent} has been removed. In this
+        // case, we do not want to create an animator associated with it as {link #animate} will
+        // fail.
+        if (displayAnimator == null && mService.mRoot.getDisplayContent(displayId) != null) {
             displayAnimator = new DisplayContentsAnimator();
             mDisplayContentsAnimators.put(displayId, displayAnimator);
         }
@@ -365,8 +374,10 @@
     }
 
     void setScreenRotationAnimationLocked(int displayId, ScreenRotationAnimation animation) {
-        if (displayId >= 0) {
-            getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation = animation;
+        final DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
+
+        if (animator != null) {
+            animator.mScreenRotationAnimation = animation;
         }
     }
 
@@ -374,7 +385,9 @@
         if (displayId < 0) {
             return null;
         }
-        return getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation;
+
+        DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
+        return animator != null? animator.mScreenRotationAnimation : null;
     }
 
     void requestRemovalOfReplacedWindows(WindowState win) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 77e2f71..9e3edef 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5890,8 +5890,8 @@
             return;
         }
 
-        if (!mDisplayReady || !mPolicy.isScreenOn()) {
-            // No need to freeze the screen before the system is ready or if
+        if (!displayContent.isReady() || !mPolicy.isScreenOn()) {
+            // No need to freeze the screen before the display is ready, system is ready, or if
             // the screen is off.
             return;
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 6a244a2..1b2eb46 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -23,6 +23,7 @@
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.AnimationThread;
 import com.android.server.ThreadPriorityBooster;
 
@@ -32,12 +33,18 @@
  */
 class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster {
 
-    private final AnimationThread mAnimationThread;
+    private final Object mLock = new Object();
+
+    private final int mAnimationThreadId;
+
+    @GuardedBy("mLock")
     private boolean mAppTransitionRunning;
+    @GuardedBy("mLock")
+    private boolean mBoundsAnimationRunning;
 
     WindowManagerThreadPriorityBooster() {
         super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW);
-        mAnimationThread = AnimationThread.get();
+        mAnimationThreadId = AnimationThread.get().getThreadId();
     }
 
     @Override
@@ -45,7 +52,7 @@
 
         // Do not boost the animation thread. As the animation thread is changing priorities,
         // boosting it might mess up the priority because we reset it the the previous priority.
-        if (myTid() == mAnimationThread.getThreadId()) {
+        if (myTid() == mAnimationThreadId) {
             return;
         }
         super.boost();
@@ -55,24 +62,35 @@
     public void reset() {
 
         // See comment in boost().
-        if (myTid() == mAnimationThread.getThreadId()) {
+        if (myTid() == mAnimationThreadId) {
             return;
         }
         super.reset();
     }
 
     void setAppTransitionRunning(boolean running) {
-        if (mAppTransitionRunning == running) {
-            return;
+        synchronized (mLock) {
+            if (mAppTransitionRunning != running) {
+                mAppTransitionRunning = running;
+                updatePriorityLocked();
+            }
         }
-
-        final int priority = calculatePriority(running);
-        setBoostToPriority(priority);
-        setThreadPriority(mAnimationThread.getThreadId(), priority);
-        mAppTransitionRunning = running;
     }
 
-    private int calculatePriority(boolean appTransitionRunning) {
-        return appTransitionRunning ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
+    void setBoundsAnimationRunning(boolean running) {
+        synchronized (mLock) {
+            if (mBoundsAnimationRunning != running) {
+                mBoundsAnimationRunning = running;
+                updatePriorityLocked();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updatePriorityLocked() {
+        int priority = (mAppTransitionRunning || mBoundsAnimationRunning)
+                ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
+        setBoostToPriority(priority);
+        setThreadPriority(mAnimationThreadId, priority);
     }
 }
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 8a5fd4b..272653f 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -111,7 +111,6 @@
 
 
     private Map<String, Intent> mNameToIntent;
-    private Map<String, String> mNameToProcess;
     private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>();
     private Map<String, String> mNameToResultKey;
     private Map<String, List<Long>> mNameToLaunchTime;
@@ -431,7 +430,6 @@
 
     private void createMappings() {
         mNameToIntent = new LinkedHashMap<String, Intent>();
-        mNameToProcess = new LinkedHashMap<String, String>();
 
         PackageManager pm = getInstrumentation().getContext()
                 .getPackageManager();
@@ -459,8 +457,9 @@
                         ri.activityInfo.name);
                 String appName = ri.loadLabel(pm).toString();
                 if (appName != null) {
+                    // Support launching intent using package name or app name
+                    mNameToIntent.put(ri.activityInfo.packageName, startIntent);
                     mNameToIntent.put(appName, startIntent);
-                    mNameToProcess.put(appName, ri.activityInfo.processName);
                 }
             }
         }
diff --git a/tests/JobSchedulerTestApp/res/layout/activity_main.xml b/tests/JobSchedulerTestApp/res/layout/activity_main.xml
index 96e1641..41f9777 100644
--- a/tests/JobSchedulerTestApp/res/layout/activity_main.xml
+++ b/tests/JobSchedulerTestApp/res/layout/activity_main.xml
@@ -73,10 +73,18 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:orientation="horizontal">
+                    <RadioButton android:id="@+id/checkbox_none"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/none"/>
                     <RadioButton android:id="@+id/checkbox_any"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:text="@string/any"/>
+                    <RadioButton android:id="@+id/checkbox_metered"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/metered"/>
                     <RadioButton android:id="@+id/checkbox_unmetered"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
diff --git a/tests/JobSchedulerTestApp/res/values/strings.xml b/tests/JobSchedulerTestApp/res/values/strings.xml
index 90dd2b6..866b61e 100644
--- a/tests/JobSchedulerTestApp/res/values/strings.xml
+++ b/tests/JobSchedulerTestApp/res/values/strings.xml
@@ -30,7 +30,9 @@
     <string name="persisted_caption">Persisted:</string>
     <string name="constraints">Constraints</string>
     <string name="connectivity">Connectivity:</string>
+    <string name="none">None</string>
     <string name="any">Any</string>
+    <string name="metered">Metered</string>
     <string name="unmetered">WiFi</string>
     <string name="timing">Timing:</string>
     <string name="delay">Delay:</string>
diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
index 51cdbb5..3dfdba7 100644
--- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
+++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
@@ -63,6 +63,7 @@
         mDeadlineEditText = findViewById(R.id.deadline_time);
         mWiFiConnectivityRadioButton = findViewById(R.id.checkbox_unmetered);
         mAnyConnectivityRadioButton = findViewById(R.id.checkbox_any);
+        mCellConnectivityRadioButton = findViewById(R.id.checkbox_metered);
         mRequiresChargingCheckBox = findViewById(R.id.checkbox_charging);
         mRequiresIdleCheckbox = findViewById(R.id.checkbox_idle);
         mIsPersistedCheckbox = findViewById(R.id.checkbox_persisted);
@@ -85,6 +86,7 @@
     EditText mDeadlineEditText;
     RadioButton mWiFiConnectivityRadioButton;
     RadioButton mAnyConnectivityRadioButton;
+    RadioButton mCellConnectivityRadioButton;
     CheckBox mRequiresChargingCheckBox;
     CheckBox mRequiresIdleCheckbox;
     CheckBox mIsPersistedCheckbox;
@@ -141,9 +143,12 @@
             builder.setOverrideDeadline(Long.parseLong(deadline) * 1000);
         }
         boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
+        boolean requiresMetered = mCellConnectivityRadioButton.isChecked();
         boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
         if (requiresUnmetered) {
             builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+        } else if (requiresMetered) {
+            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
         } else if (requiresAnyConnectivity) {
             builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
         }
diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java
index 9df11fe..b698a3a 100644
--- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java
+++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java
@@ -81,7 +81,8 @@
 
     @Override
     public boolean onStartJob(JobParameters params) {
-        Log.i(TAG, "on start job: " + params.getJobId());
+        Log.i(TAG, "on start job: " + params.getJobId()
+                + " deadline?=" + params.isOverrideDeadlineExpired());
         currentId++;
         jobParamsMap.put(currentId, params);
         final int currId = this.currentId;