Merge "[LSC] Add LOCAL_LICENSE_KINDS to frameworks/base" am: 7226ad3729 am: a087245e03

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1661581

Change-Id: I70994b32bd4a68bb3d3062d349cc833ba2edf5a0
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4c16072..2c44c8d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5183,12 +5183,6 @@
      * #checkSelfPermission(String)}.
      * </p>
      * <p>
-     * Calling this API for permissions already granted to your app would show UI
-     * to the user to decide whether the app can still hold these permissions. This
-     * can be useful if the way your app uses data guarded by the permissions
-     * changes significantly.
-     * </p>
-     * <p>
      * You cannot request a permission if your activity sets {@link
      * android.R.styleable#AndroidManifestActivity_noHistory noHistory} to
      * <code>true</code> because in this case the activity would not receive
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index a5965bc..eb2a40f 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -440,4 +440,11 @@
      * @return true if exists, false otherwise.
      */
     public abstract boolean isPendingTopUid(int uid);
+
+    public abstract void tempAllowWhileInUsePermissionInFgs(int uid, long duration);
+
+    public abstract boolean isTempAllowlistedForFgsWhileInUse(int uid);
+
+    public abstract boolean canAllowWhileInUsePermissionInFgs(int pid, int uid,
+            @NonNull String packageName);
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 65f2c02..67207cb 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4602,6 +4602,10 @@
         }
 
         if (r.isTopResumedActivity == onTop) {
+            if (!Build.IS_DEBUGGABLE) {
+                Slog.w(TAG, "Activity top position already set to onTop=" + onTop);
+                return;
+            }
             throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
         }
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 694c519..c4cdbbc 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7636,8 +7636,8 @@
                 } else if (collectionMode == COLLECT_SYNC
                         // Only collect app-ops when the proxy is trusted
                         && (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1,
-                        myUid) == PackageManager.PERMISSION_GRANTED
-                        || isTrustedVoiceServiceProxy(mContext, mContext.getOpPackageName(), op))) {
+                        myUid) == PackageManager.PERMISSION_GRANTED || isTrustedVoiceServiceProxy(
+                        mContext, mContext.getOpPackageName(), op, mContext.getUserId()))) {
                     collectNotedOpSync(op, proxiedAttributionTag);
                 }
             }
@@ -7655,7 +7655,7 @@
      * @hide
      */
     public static boolean isTrustedVoiceServiceProxy(Context context, String packageName,
-            int code) {
+            int code, int userId) {
         // This is a workaround for R QPR, new API change is not allowed. We only allow the current
         // voice recognizer is also the voice interactor to noteproxy op.
         if (code != OP_RECORD_AUDIO) {
@@ -7667,7 +7667,7 @@
         final String voiceRecognitionServicePackageName =
                 getComponentPackageNameFromString(voiceRecognitionComponent);
         return (Objects.equals(packageName, voiceRecognitionServicePackageName))
-                && isPackagePreInstalled(context, packageName);
+                && isPackagePreInstalled(context, packageName, userId);
     }
 
     private static String getComponentPackageNameFromString(String from) {
@@ -7675,10 +7675,10 @@
         return componentName != null ? componentName.getPackageName() : "";
     }
 
-    private static boolean isPackagePreInstalled(Context context, String packageName) {
+    private static boolean isPackagePreInstalled(Context context, String packageName, int userId) {
         try {
             final PackageManager pm = context.getPackageManager();
-            final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+            final ApplicationInfo info = pm.getApplicationInfoAsUser(packageName, 0, userId);
             return ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
         } catch (PackageManager.NameNotFoundException e) {
             return false;
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index ec17e44..aca74ce 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.app.admin;
 
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -241,6 +242,7 @@
     /**
      * Returns the profile owner component for the given user, or {@code null} if there is not one.
      */
+    @Nullable
     public abstract ComponentName getProfileOwnerAsUser(int userHandle);
 
     /**
@@ -254,4 +256,9 @@
      * {@link #supportsResetOp(int)} is true.
      */
     public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
+
+    /**
+     * Returns whether the given package is a device owner or a profile owner in the calling user.
+     */
+    public abstract boolean isDeviceOrProfileOwnerInCallingUser(String packageName);
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 9271d0e..d83b432 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -62,6 +62,9 @@
      * {@link #EXTRA_WIFI_DISPLAY_STATUS} extra.
      * </p><p>
      * This broadcast is only sent to registered receivers and can only be sent by the system.
+     * </p><p>
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission is required to
+     * receive this broadcast.
      * </p>
      * @hide
      */
@@ -870,12 +873,52 @@
     public interface DeviceConfig {
 
         /**
-         * Key for refresh rate in the zone defined by thresholds.
+         * Key for refresh rate in the low zone defined by thresholds.
          *
+         * Note that the name and value don't match because they were added before we had a high
+         * zone to consider.
          * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
          * @see android.R.integer#config_defaultZoneBehavior
          */
-        String KEY_REFRESH_RATE_IN_ZONE = "refresh_rate_in_zone";
+        String KEY_REFRESH_RATE_IN_LOW_ZONE = "refresh_rate_in_zone";
+
+        /**
+         * Key for accessing the low display brightness thresholds for the configured refresh
+         * rate zone.
+         * The value will be a pair of comma separated integers representing the minimum and maximum
+         * thresholds of the zone, respectively, in display backlight units (i.e. [0, 255]).
+         *
+         * Note that the name and value don't match because they were added before we had a high
+         * zone to consider.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.array#config_brightnessThresholdsOfPeakRefreshRate
+         * @hide
+         */
+        String KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS =
+                "peak_refresh_rate_brightness_thresholds";
+
+        /**
+         * Key for accessing the low ambient brightness thresholds for the configured refresh
+         * rate zone. The value will be a pair of comma separated integers representing the minimum
+         * and maximum thresholds of the zone, respectively, in lux.
+         *
+         * Note that the name and value don't match because they were added before we had a high
+         * zone to consider.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.array#config_ambientThresholdsOfPeakRefreshRate
+         * @hide
+         */
+        String KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS =
+                "peak_refresh_rate_ambient_thresholds";
+        /**
+         * Key for refresh rate in the high zone defined by thresholds.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.integer#config_fixedRefreshRateInHighZone
+         */
+        String KEY_REFRESH_RATE_IN_HIGH_ZONE = "refresh_rate_in_high_zone";
 
         /**
          * Key for accessing the display brightness thresholds for the configured refresh rate zone.
@@ -883,11 +926,11 @@
          * thresholds of the zone, respectively, in display backlight units (i.e. [0, 255]).
          *
          * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
-         * @see android.R.array#config_brightnessThresholdsOfPeakRefreshRate
+         * @see android.R.array#config_brightnessHighThresholdsOfFixedRefreshRate
          * @hide
          */
-        String KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS =
-                "peak_refresh_rate_brightness_thresholds";
+        String KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS =
+                "fixed_refresh_rate_high_display_brightness_thresholds";
 
         /**
          * Key for accessing the ambient brightness thresholds for the configured refresh rate zone.
@@ -895,12 +938,11 @@
          * thresholds of the zone, respectively, in lux.
          *
          * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
-         * @see android.R.array#config_ambientThresholdsOfPeakRefreshRate
+         * @see android.R.array#config_ambientHighThresholdsOfFixedRefreshRate
          * @hide
          */
-        String KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS =
-                "peak_refresh_rate_ambient_thresholds";
-
+        String KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS =
+                "fixed_refresh_rate_high_ambient_brightness_thresholds";
         /**
          * Key for default peak refresh rate
          *
diff --git a/core/java/android/hardware/usb/AccessoryFilter.java b/core/java/android/hardware/usb/AccessoryFilter.java
index f22dad4..f4c73d5 100644
--- a/core/java/android/hardware/usb/AccessoryFilter.java
+++ b/core/java/android/hardware/usb/AccessoryFilter.java
@@ -101,7 +101,7 @@
     public boolean matches(UsbAccessory acc) {
         if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
         if (mModel != null && !acc.getModel().equals(mModel)) return false;
-        return !(mVersion != null && !acc.getVersion().equals(mVersion));
+        return !(mVersion != null && !mVersion.equals(acc.getVersion()));
     }
 
     /**
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 9b29fb1..46140a4 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -34,8 +34,12 @@
 
 import libcore.io.IoUtils;
 
+import java.io.BufferedReader;
 import java.io.FileDescriptor;
+import java.io.FileReader;
+import java.io.IOException;
 import java.util.Map;
+import java.util.StringTokenizer;
 import java.util.concurrent.TimeoutException;
 
 /**
@@ -208,6 +212,12 @@
     public static final int SE_UID = 1068;
 
     /**
+     * Defines the UID/GID for the iorapd.
+     * @hide
+     */
+    public static final int IORAPD_UID = 1071;
+
+    /**
      * Defines the UID/GID for the NetworkStack app.
      * @hide
      */
@@ -1397,4 +1407,43 @@
     }
 
     private static native int nativePidFdOpen(int pid, int flags) throws ErrnoException;
+
+    /**
+     * Checks if a process corresponding to a specific pid owns any file locks.
+     * @param pid The process ID for which we want to know the existence of file locks.
+     * @return true If the process holds any file locks, false otherwise.
+     * @throws IOException if /proc/locks can't be accessed.
+     *
+     * @hide
+     */
+    public static boolean hasFileLocks(int pid) throws Exception {
+        BufferedReader br = null;
+
+        try {
+            br = new BufferedReader(new FileReader("/proc/locks"));
+            String line;
+
+            while ((line = br.readLine()) != null) {
+                StringTokenizer st = new StringTokenizer(line);
+
+                for (int i = 0; i < 5 && st.hasMoreTokens(); i++) {
+                    String str = st.nextToken();
+                    try {
+                        if (i == 4 && Integer.parseInt(str) == pid) {
+                            return true;
+                        }
+                    } catch (NumberFormatException nfe) {
+                        throw new Exception("Exception parsing /proc/locks at \" "
+                                + line +  " \", token #" + i);
+                    }
+                }
+            }
+
+            return false;
+        } finally {
+            if (br != null) {
+                br.close();
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 1236044..09e4557 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -108,6 +108,9 @@
         mControl = new InsetsAnimationControlImpl(controls, frame, state, listener,
                 types, mCallbacks, durationMs, interpolator, animationType);
         InsetsAnimationThread.getHandler().post(() -> {
+            if (mControl.isCancelled()) {
+                return;
+            }
             Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW,
                     "InsetsAsyncAnimation: " + WindowInsets.Type.toString(types), types);
             listener.onReady(mControl, types);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 700dc66..ba40459 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -113,13 +113,20 @@
                     InsetsState.typeToString(control.getType()),
                     mController.getHost().getRootViewTitle()));
         }
-        // We are loosing control
         if (mSourceControl == null) {
+            // We are loosing control
             mController.notifyControlRevoked(this);
 
-            // Restore server visibility.
-            mState.getSource(getType()).setVisible(
-                    mController.getLastDispatchedState().getSource(getType()).isVisible());
+            // Check if we need to restore server visibility.
+            final InsetsSource source = mState.getSource(mType);
+            final boolean serverVisibility =
+                    mController.getLastDispatchedState().getSourceOrDefaultVisibility(mType);
+            if (source.isVisible() != serverVisibility) {
+                source.setVisible(serverVisibility);
+                mController.notifyVisibilityChanged();
+            }
+
+            // For updateCompatSysUiVisibility
             applyLocalVisibilityOverride();
         } else {
             // We are gaining control, and need to run an animation since previous state
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 1af4c36..f0006d9 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -156,7 +156,10 @@
             mStateForWindow.put(window.asBinder(), state);
         }
 
-        return WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
+        final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
+
+        // Include whether the window is in touch mode.
+        return isInTouchMode() ? res | WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE : res;
     }
 
     /**
@@ -207,6 +210,15 @@
         return !PixelFormat.formatHasAlpha(attrs.format);
     }
 
+    private boolean isInTouchMode() {
+        try {
+            return WindowManagerGlobal.getWindowSession().getInTouchMode();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to check if the window is in touch mode", e);
+        }
+        return false;
+    }
+
     /** @hide */
     protected SurfaceControl getSurfaceControl(View rootView) {
         final ViewRootImpl root = rootView.getViewRootImpl();
@@ -268,7 +280,8 @@
             }
         }
 
-        return 0;
+        // Include whether the window is in touch mode.
+        return isInTouchMode() ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0;
     }
 
     @Override
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 99cd270..76267d8 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -593,6 +593,14 @@
         public String getPackageName() {
             return mContextForResources.getPackageName();
         }
+
+        @Override
+        public boolean isRestricted() {
+            // Override isRestricted and direct to resource's implementation. The isRestricted is
+            // used for determining the risky resources loading, e.g. fonts, thus direct to context
+            // for resource.
+            return mContextForResources.isRestricted();
+        }
     }
 
     private class SetEmptyView extends Action {
diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java
index 56ec87c..375e503 100644
--- a/core/java/com/android/internal/app/NetInitiatedActivity.java
+++ b/core/java/com/android/internal/app/NetInitiatedActivity.java
@@ -17,18 +17,14 @@
 package com.android.internal.app;
 
 import android.app.AlertDialog;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.location.LocationManagerInternal;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.util.Log;
-import android.widget.Toast;
 
 import com.android.internal.R;
 import com.android.internal.location.GpsNetInitiatedHandler;
@@ -43,7 +39,6 @@
     private static final String TAG = "NetInitiatedActivity";
 
     private static final boolean DEBUG = true;
-    private static final boolean VERBOSE = false;
 
     private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE;
     private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON_NEGATIVE;
@@ -55,17 +50,6 @@
     private int default_response = -1;
     private int default_response_timeout = 6;
 
-    /** Used to detect when NI request is received */
-    private BroadcastReceiver mNetInitiatedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) Log.d(TAG, "NetInitiatedReceiver onReceive: " + intent.getAction());
-            if (intent.getAction() == GpsNetInitiatedHandler.ACTION_NI_VERIFY) {
-                handleNIVerify(intent);
-            }
-        }
-    };
-
     private final Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -109,14 +93,12 @@
     protected void onResume() {
         super.onResume();
         if (DEBUG) Log.d(TAG, "onResume");
-        registerReceiver(mNetInitiatedReceiver, new IntentFilter(GpsNetInitiatedHandler.ACTION_NI_VERIFY));
     }
 
     @Override
     protected void onPause() {
         super.onPause();
         if (DEBUG) Log.d(TAG, "onPause");
-        unregisterReceiver(mNetInitiatedReceiver);
     }
 
     /**
@@ -141,17 +123,4 @@
         LocationManagerInternal lm = LocalServices.getService(LocationManagerInternal.class);
         lm.sendNiResponse(notificationId, response);
     }
-
-    @UnsupportedAppUsage
-    private void handleNIVerify(Intent intent) {
-        int notifId = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_NOTIF_ID, -1);
-        notificationId = notifId;
-
-        if (DEBUG) Log.d(TAG, "handleNIVerify action: " + intent.getAction());
-    }
-
-    private void showNIError() {
-        Toast.makeText(this, "NI error" /* com.android.internal.R.string.usb_storage_error_message */,
-                Toast.LENGTH_LONG).show();
-    }
 }
diff --git a/core/java/com/android/internal/app/ProcessMap.java b/core/java/com/android/internal/app/ProcessMap.java
index 81036f7..4917a47 100644
--- a/core/java/com/android/internal/app/ProcessMap.java
+++ b/core/java/com/android/internal/app/ProcessMap.java
@@ -22,7 +22,7 @@
 public class ProcessMap<E> {
     final ArrayMap<String, SparseArray<E>> mMap
             = new ArrayMap<String, SparseArray<E>>();
-    
+
     public E get(String name, int uid) {
         SparseArray<E> uids = mMap.get(name);
         if (uids == null) return null;
@@ -58,4 +58,6 @@
     public int size() {
         return mMap.size();
     }
+
+    public void putAll(ProcessMap<E> other) { mMap.putAll(other.mMap); }
 }
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index a23fc4b..7ee846e 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,12 +1,15 @@
 package com.android.internal.util;
 
+import static android.content.Intent.ACTION_USER_SWITCHED;
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -161,8 +164,21 @@
     private ServiceConnection mScreenshotConnection = null;
     private final Context mContext;
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mScreenshotLock) {
+                if (ACTION_USER_SWITCHED.equals(intent.getAction())) {
+                    resetConnection();
+                }
+            }
+        }
+    };
+
     public ScreenshotHelper(Context context) {
         mContext = context;
+        IntentFilter filter = new IntentFilter(ACTION_USER_SWITCHED);
+        mContext.registerReceiver(mBroadcastReceiver, filter);
     }
 
     /**
@@ -279,9 +295,8 @@
             final Runnable mScreenshotTimeout = () -> {
                 synchronized (mScreenshotLock) {
                     if (mScreenshotConnection != null) {
-                        mContext.unbindService(mScreenshotConnection);
-                        mScreenshotConnection = null;
-                        mScreenshotService = null;
+                        Log.e(TAG, "Timed out before getting screenshot capture response");
+                        resetConnection();
                         notifyScreenshotError();
                     }
                 }
@@ -304,11 +319,7 @@
                             break;
                         case SCREENSHOT_MSG_PROCESS_COMPLETE:
                             synchronized (mScreenshotLock) {
-                                if (mScreenshotConnection != null) {
-                                    mContext.unbindService(mScreenshotConnection);
-                                    mScreenshotConnection = null;
-                                    mScreenshotService = null;
-                                }
+                                resetConnection();
                             }
                             break;
                     }
@@ -348,9 +359,7 @@
                     public void onServiceDisconnected(ComponentName name) {
                         synchronized (mScreenshotLock) {
                             if (mScreenshotConnection != null) {
-                                mContext.unbindService(mScreenshotConnection);
-                                mScreenshotConnection = null;
-                                mScreenshotService = null;
+                                resetConnection();
                                 // only log an error if we're still within the timeout period
                                 if (handler.hasCallbacks(mScreenshotTimeout)) {
                                     handler.removeCallbacks(mScreenshotTimeout);
@@ -383,6 +392,17 @@
     }
 
     /**
+     * Unbinds the current screenshot connection (if any).
+     */
+    private void resetConnection() {
+        if (mScreenshotConnection != null) {
+            mContext.unbindService(mScreenshotConnection);
+            mScreenshotConnection = null;
+            mScreenshotService = null;
+        }
+    }
+
+    /**
      * Notifies the screenshot service to show an error.
      */
     private void notifyScreenshotError() {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1f7695e..1dc9cb8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -115,6 +115,12 @@
     <protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
     <protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
 
+    <protected-broadcast android:name="android.app.action.USER_ADDED" />
+    <protected-broadcast android:name="android.app.action.USER_REMOVED" />
+    <protected-broadcast android:name="android.app.action.USER_STARTED" />
+    <protected-broadcast android:name="android.app.action.USER_STOPPED" />
+    <protected-broadcast android:name="android.app.action.USER_SWITCHED" />
+
     <protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
     <protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
     <protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
@@ -491,6 +497,8 @@
     <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_FAILED" />
     <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_SUCCEEDED" />
     <protected-broadcast android:name="com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION" />
+    <protected-broadcast android:name="com.android.server.ACTION_PROFILE_OFF_DEADLINE" />
+    <protected-broadcast android:name="com.android.server.ACTION_TURN_PROFILE_ON_NOTIFICATION" />
 
     <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
     <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_UNLOCKED" />
diff --git a/core/res/res/color-car/car_borderless_button_text_color.xml b/core/res/res/color-car/car_borderless_button_text_color.xml
index 1cdd6cd..0a86e40 100644
--- a/core/res/res/color-car/car_borderless_button_text_color.xml
+++ b/core/res/res/color-car/car_borderless_button_text_color.xml
@@ -16,5 +16,6 @@
 <!-- Default text colors for car buttons when enabled/disabled. -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:color="@*android:color/car_grey_700" android:state_enabled="false"/>
+    <item android:color="@*android:color/car_grey_700" android:state_ux_restricted="true"/>
     <item android:color="?android:attr/colorButtonNormal"/>
 </selector>
diff --git a/core/res/res/color-car/car_switch_track.xml b/core/res/res/color-car/car_switch_track.xml
new file mode 100644
index 0000000..8ca67dd
--- /dev/null
+++ b/core/res/res/color-car/car_switch_track.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- copy of switch_track_material, but with a ux restricted state -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="?attr/colorForeground"
+          android:alpha="?attr/disabledAlpha" />
+    <item android:state_ux_restricted="true"
+          android:color="?attr/colorForeground"
+          android:alpha="?attr/disabledAlpha" />
+    <item android:state_checked="true"
+          android:color="?attr/colorControlActivated" />
+    <item android:color="?attr/colorForeground" />
+</selector>
diff --git a/core/res/res/drawable-car/car_button_background.xml b/core/res/res/drawable-car/car_button_background.xml
index e568aeb..13b0ec1 100644
--- a/core/res/res/drawable-car/car_button_background.xml
+++ b/core/res/res/drawable-car/car_button_background.xml
@@ -25,6 +25,22 @@
                     android:color="#0059B3"/>
         </shape>
     </item>
+    <item android:state_focused="true" android:state_pressed="true" android:state_ux_restricted="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="@*android:dimen/car_button_radius"/>
+            <solid android:color="@*android:color/car_grey_300"/>
+            <stroke android:width="4dp"
+                    android:color="#0059B3"/>
+        </shape>
+    </item>
+    <item android:state_focused="true" android:state_ux_restricted="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="@*android:dimen/car_button_radius"/>
+            <solid android:color="@*android:color/car_grey_300"/>
+            <stroke android:width="8dp"
+                    android:color="#0059B3"/>
+        </shape>
+    </item>
     <item android:state_focused="true" android:state_pressed="true">
         <shape android:shape="rectangle">
             <corners android:radius="@*android:dimen/car_button_radius"/>
@@ -47,6 +63,12 @@
             <solid android:color="@*android:color/car_grey_300"/>
         </shape>
     </item>
+    <item android:state_ux_restricted="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="@*android:dimen/car_button_radius"/>
+            <solid android:color="@*android:color/car_grey_300"/>
+        </shape>
+    </item>
     <item>
         <ripple android:color="?android:attr/colorControlHighlight">
             <item>
diff --git a/core/res/res/drawable-car/car_switch_track.xml b/core/res/res/drawable-car/car_switch_track.xml
index cb0b9be..51e9f7e 100644
--- a/core/res/res/drawable-car/car_switch_track.xml
+++ b/core/res/res/drawable-car/car_switch_track.xml
@@ -41,7 +41,7 @@
       android:right="@dimen/car_switch_track_margin_size">
     <shape
         android:shape="rectangle"
-        android:tint="@color/switch_track_material">
+        android:tint="@color/car_switch_track">
       <corners android:radius="7dp" />
       <solid android:color="@color/white_disabled_material" />
       <size android:height="14dp" />
diff --git a/core/res/res/values/attrs_car.xml b/core/res/res/values/attrs_car.xml
new file mode 100644
index 0000000..6bfea97
--- /dev/null
+++ b/core/res/res/values/attrs_car.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
+     output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+     <attr name="state_ux_restricted" format="boolean"/>
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bfe7802..2d55ac3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4154,6 +4154,35 @@
          If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
     <integer name="config_defaultRefreshRateInZone">0</integer>
 
+    <!-- The display uses different gamma curves for different refresh rates. It's hard for panel
+         vendor to tune the curves to have exact same brightness for different refresh rate. So
+         flicker could be observed at switch time. The issue can be observed on the screen with
+         even full white content at the high brightness. To prevent flickering, we support fixed
+         refresh rates if the display and ambient brightness are equal to or above the provided
+         thresholds. You can define multiple threshold levels as higher brightness environments
+         may have lower display brightness requirements for the flickering is visible. And the
+         high brightness environment could have higher threshold.
+         For example, fixed refresh rate if
+             display brightness >= disp0 && ambient brightness >= amb0
+             || display brightness >= disp1 && ambient brightness >= amb1 -->
+    <integer-array translatable="false" name="config_highDisplayBrightnessThresholdsOfFixedRefreshRate">
+         <!--
+           <item>disp0</item>
+           <item>disp1</item>
+        -->
+    </integer-array>
+
+    <integer-array translatable="false" name="config_highAmbientBrightnessThresholdsOfFixedRefreshRate">
+         <!--
+           <item>amb0</item>
+           <item>amb1</item>
+        -->
+    </integer-array>
+
+    <!-- Default refresh rate in the high zone defined by brightness and ambient thresholds.
+         If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
+    <integer name="config_fixedRefreshRateInHighZone">0</integer>
+
     <!-- The type of the light sensor to be used by the display framework for things like
          auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. -->
     <string name="config_displayLightSensorType" translatable="false" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index cbc08ba..6bdfe28 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -632,6 +632,13 @@
     <!-- The default minimal size of a PiP task, in both dimensions. -->
     <dimen name="default_minimal_size_pip_resizable_task">108dp</dimen>
 
+    <!--
+      The overridable minimal size of a PiP task, in both dimensions.
+      Different from default_minimal_size_pip_resizable_task, this is to limit the dimension
+      when the pinned stack size is overridden by app via minWidth/minHeight.
+    -->
+    <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen>
+
     <!-- Height of a task when in minimized mode from the top when launcher is resizable. -->
     <dimen name="task_height_of_minimized_mode">80dp</dimen>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2901de5..3a149dd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1929,6 +1929,7 @@
   <java-symbol type="fraction" name="config_dimBehindFadeDuration" />
   <java-symbol type="dimen" name="default_minimal_size_resizable_task" />
   <java-symbol type="dimen" name="default_minimal_size_pip_resizable_task" />
+  <java-symbol type="dimen" name="overridable_minimal_size_pip_resizable_task" />
   <java-symbol type="dimen" name="task_height_of_minimized_mode" />
   <java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" />
   <java-symbol type="bool" name="config_allowPriorityVibrationsInLowPowerMode" />
@@ -3788,6 +3789,11 @@
   <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
   <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
 
+  <!-- For fixed refresh rate displays in high brightness-->
+  <java-symbol type="integer" name="config_fixedRefreshRateInHighZone" />
+  <java-symbol type="array" name="config_highDisplayBrightnessThresholdsOfFixedRefreshRate" />
+  <java-symbol type="array" name="config_highAmbientBrightnessThresholdsOfFixedRefreshRate" />
+
   <!-- For Auto-Brightness -->
   <java-symbol type="string" name="config_displayLightSensorType" />
 
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 4181163..1f0d7ac 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -138,13 +138,6 @@
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.companiondevicesupport",
-    sub_dir: "permissions",
-    src: "com.android.car.companiondevicesupport.xml",
-    filename_from_src: true,
-}
-
-prebuilt_etc {
     name: "privapp_whitelist_com.google.android.car.kitchensink",
     sub_dir: "permissions",
     src: "com.google.android.car.kitchensink.xml",
@@ -160,13 +153,6 @@
 }
 
 prebuilt_etc {
-    name: "privapp_whitelist_com.android.car.floatingcardslauncher",
-    sub_dir: "permissions",
-    src: "com.android.car.floatingcardslauncher.xml",
-    filename_from_src: true,
-}
-
-prebuilt_etc {
     name: "privapp_allowlist_com.google.android.car.networking.preferenceupdater",
     sub_dir: "permissions",
     src: "com.google.android.car.networking.preferenceupdater.xml",
@@ -186,3 +172,17 @@
     src: "com.android.car.shell.xml",
     filename_from_src: true,
 }
+
+prebuilt_etc {
+    name: "allowed_privapp_com.android.car.activityresolver",
+    sub_dir: "permissions",
+    src: "com.android.car.activityresolver.xml",
+    filename_from_src: true,
+}
+
+prebuilt_etc {
+    name: "allowed_privapp_com.android.car.rotary",
+    sub_dir: "permissions",
+    src: "com.android.car.rotary.xml",
+    filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.activityresolver.xml b/data/etc/car/com.android.car.activityresolver.xml
new file mode 100644
index 0000000..63f83b4
--- /dev/null
+++ b/data/etc/car/com.android.car.activityresolver.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<permissions>
+    <privapp-permissions package="com.android.car.activityresolver">
+        <permission name="android.permission.MANAGE_USERS"/>
+      </privapp-permissions>
+</permissions>
diff --git a/data/etc/car/com.android.car.companiondevicesupport.xml b/data/etc/car/com.android.car.companiondevicesupport.xml
deleted file mode 100644
index 2067bab..0000000
--- a/data/etc/car/com.android.car.companiondevicesupport.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 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
-  -->
-<permissions>
-    <privapp-permissions package="com.android.car.companiondevicesupport">
-      <permission name="android.permission.INTERACT_ACROSS_USERS"/>
-      <permission name="android.permission.MANAGE_USERS"/>
-      <permission name="android.permission.PROVIDE_TRUST_AGENT"/>
-      <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
-    </privapp-permissions>
-</permissions>
diff --git a/data/etc/car/com.android.car.floatingcardslauncher.xml b/data/etc/car/com.android.car.floatingcardslauncher.xml
deleted file mode 100644
index 2755fee..0000000
--- a/data/etc/car/com.android.car.floatingcardslauncher.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 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
-  -->
-<permissions>
-    <privapp-permissions package="com.android.car.floatingcardslauncher">
-        <permission name="android.permission.ACTIVITY_EMBEDDING"/>
-        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
-        <permission name="android.permission.MANAGE_USERS"/>
-        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
-        <permission name="android.permission.MODIFY_PHONE_STATE"/>
-    </privapp-permissions>
-</permissions>
diff --git a/data/etc/car/com.android.car.rotary.xml b/data/etc/car/com.android.car.rotary.xml
new file mode 100644
index 0000000..5752755
--- /dev/null
+++ b/data/etc/car/com.android.car.rotary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<permissions>
+    <privapp-permissions package="com.android.car.rotary">
+        <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/car/com.android.car.shell.xml b/data/etc/car/com.android.car.shell.xml
index 32666c8..6132d53 100644
--- a/data/etc/car/com.android.car.shell.xml
+++ b/data/etc/car/com.android.car.shell.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License
   -->
 <permissions>
-    <privapp-permissions package="com.android.car.shell">
+    <!-- CarShell now overrides the shell package and adding permission here
+         is ok. -->
+    <privapp-permissions package="com.android.shell">
         <permission name="android.permission.INSTALL_PACKAGES" />
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
     </privapp-permissions>
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index 139474c..bbc0f543 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -50,9 +50,6 @@
 
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    // NI verify activity for bringing up UI (not used yet)
-    public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY";
-
     // string constants for defining data fields in NI Intent
     public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id";
     public static final String NI_INTENT_KEY_TITLE = "title";
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 1a367d9..f9785e9 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -381,7 +381,12 @@
         }
 
         public Display[] getAllPresentationDisplays() {
-            return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
+            try {
+                return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
+            } catch (RuntimeException ex) {
+                Log.e(TAG, "Unable to get displays.", ex);
+                return null;
+            }
         }
 
         private void updatePresentationDisplays(int changedDisplayId) {
@@ -2085,6 +2090,9 @@
         private Display choosePresentationDisplay() {
             if ((mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
                 Display[] displays = sStatic.getAllPresentationDisplays();
+                if (displays == null || displays.length == 0) {
+                    return null;
+                }
 
                 // Ensure that the specified display is valid for presentations.
                 // This check will normally disallow the default display unless it was
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 9fe700a..0dcb75d 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -19,8 +19,7 @@
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.ContentProviderClient;
-import android.content.ContentUris;
-import android.content.ContentValues;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -32,7 +31,6 @@
 import android.media.ThumbnailUtils;
 import android.net.Uri;
 import android.os.BatteryManager;
-import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.storage.StorageVolume;
 import android.provider.MediaStore;
@@ -103,8 +101,6 @@
     private MtpStorageManager mManager;
 
     private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
-    private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
-    private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
     private static final String NO_MEDIA = ".nomedia";
 
     static {
@@ -431,7 +427,7 @@
         }
         // Add the new file to MediaProvider
         if (succeeded) {
-            MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
+            updateMediaStore(mContext, obj.getPath().toFile());
         }
     }
 
@@ -580,32 +576,8 @@
             return MtpConstants.RESPONSE_GENERAL_ERROR;
         }
 
-        // finally update MediaProvider
-        ContentValues values = new ContentValues();
-        values.put(Files.FileColumns.DATA, newPath.toString());
-        String[] whereArgs = new String[]{oldPath.toString()};
-        try {
-            // note - we are relying on a special case in MediaProvider.update() to update
-            // the paths for all children in the case where this is a directory.
-            final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
-            mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
-        }
-
-        // check if nomedia status changed
-        if (obj.isDir()) {
-            // for directories, check if renamed from something hidden to something non-hidden
-            if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
-                MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
-            }
-        } else {
-            // for files, check if renamed from .nomedia to something else
-            if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
-                    && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
-                MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
-            }
-        }
+        updateMediaStore(mContext, oldPath.toFile());
+        updateMediaStore(mContext, newPath.toFile());
         return MtpConstants.RESPONSE_OK;
     }
 
@@ -635,48 +607,15 @@
             Log.e(TAG, "Failed to end move object");
             return;
         }
-
         obj = mManager.getObject(objId);
         if (!success || obj == null)
             return;
-        // Get parent info from MediaProvider, since the id is different from MTP's
-        ContentValues values = new ContentValues();
+
         Path path = newParentObj.getPath().resolve(name);
         Path oldPath = oldParentObj.getPath().resolve(name);
-        values.put(Files.FileColumns.DATA, path.toString());
-        if (obj.getParent().isRoot()) {
-            values.put(Files.FileColumns.PARENT, 0);
-        } else {
-            int parentId = findInMedia(newParentObj, path.getParent());
-            if (parentId != -1) {
-                values.put(Files.FileColumns.PARENT, parentId);
-            } else {
-                // The new parent isn't in MediaProvider, so delete the object instead
-                deleteFromMedia(obj, oldPath, obj.isDir());
-                return;
-            }
-        }
-        // update MediaProvider
-        Cursor c = null;
-        String[] whereArgs = new String[]{oldPath.toString()};
-        try {
-            int parentId = -1;
-            if (!oldParentObj.isRoot()) {
-                parentId = findInMedia(oldParentObj, oldPath.getParent());
-            }
-            if (oldParentObj.isRoot() || parentId != -1) {
-                // Old parent exists in MediaProvider - perform a move
-                // note - we are relying on a special case in MediaProvider.update() to update
-                // the paths for all children in the case where this is a directory.
-                final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
-                mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
-            } else {
-                // Old parent doesn't exist - add the object
-                MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
-        }
+
+        updateMediaStore(mContext, oldPath.toFile());
+        updateMediaStore(mContext, path.toFile());
     }
 
     @VisibleForNative
@@ -699,7 +638,19 @@
         if (!success) {
             return;
         }
-        MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
+
+        updateMediaStore(mContext, obj.getPath().toFile());
+    }
+
+    private static void updateMediaStore(@NonNull Context context, @NonNull File file) {
+        final ContentResolver resolver = context.getContentResolver();
+        // For file, check whether the file name is .nomedia or not.
+        // If yes, scan the parent directory to update all files in the directory.
+        if (!file.isDirectory() && file.getName().toLowerCase(Locale.ROOT).endsWith(NO_MEDIA)) {
+            MediaStore.scanFile(resolver, file.getParentFile());
+        } else {
+            MediaStore.scanFile(resolver, file);
+        }
     }
 
     @VisibleForNative
@@ -940,26 +891,6 @@
             deleteFromMedia(obj, obj.getPath(), obj.isDir());
     }
 
-    private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
-        final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
-
-        int ret = -1;
-        Cursor c = null;
-        try {
-            c = mMediaProvider.query(objectsUri, ID_PROJECTION, PATH_WHERE,
-                    new String[]{path.toString()}, null, null);
-            if (c != null && c.moveToNext()) {
-                ret = c.getInt(0);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error finding " + path + " in MediaProvider");
-        } finally {
-            if (c != null)
-                c.close();
-        }
-        return ret;
-    }
-
     private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
         final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
         try {
@@ -975,13 +906,10 @@
             }
 
             String[] whereArgs = new String[]{path.toString()};
-            if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
-                if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
-                    MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
-                }
-            } else {
-                Log.i(TAG, "Mediaprovider didn't delete " + path);
+            if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) == 0) {
+                Log.i(TAG, "MediaProvider didn't delete " + path);
             }
+            updateMediaStore(mContext, path.toFile());
         } catch (Exception e) {
             Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
         }
diff --git a/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
index 5746102..189f331 100644
--- a/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
+++ b/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
@@ -55,7 +55,7 @@
         android:gravity="center"
         android:orientation="vertical">
 
-        <com.android.keyguard.PasswordTextView
+        <com.android.systemui.car.keyguard.UnfocusablePasswordTextView
             android:id="@+id/pinEntry"
             android:layout_width="@dimen/keyguard_security_width"
             android:layout_height="@dimen/pin_entry_height"
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml
index 3e35df9..f617ec0 100644
--- a/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml
@@ -14,10 +14,7 @@
   ~ limitations under the License.
   -->
 
-<!-- Car customizations
-     Car has solid black background instead of a transparent one
--->
-<LinearLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/keyguard_container"
     android:layout_width="match_parent"
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 815e67d..51b42ab 100644
--- a/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -42,7 +42,7 @@
             android:gravity="center"
             android:orientation="vertical">
 
-            <com.android.keyguard.PasswordTextView
+            <com.android.systemui.car.keyguard.UnfocusablePasswordTextView
                 android:id="@+id/pinEntry"
                 android:layout_width="@dimen/keyguard_security_width"
                 android:layout_height="@dimen/pin_entry_height"
diff --git a/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml b/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
index 8306cb4..c5974e3 100644
--- a/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
@@ -66,7 +66,6 @@
         android:src="@drawable/ic_backspace"
         android:clickable="true"
         android:tint="@android:color/white"
-        android:background="@drawable/ripple_drawable"
         android:contentDescription="@string/keyboardview_keycode_delete" />
     <com.android.keyguard.NumPadKey
         android:id="@+id/key0"
@@ -77,7 +76,6 @@
         style="@style/NumPadKeyButton.LastRow"
         android:src="@drawable/ic_done"
         android:tint="@android:color/white"
-        android:background="@drawable/ripple_drawable"
         android:contentDescription="@string/keyboardview_keycode_enter" />
 </merge>
 
diff --git a/packages/CarSystemUI/res-keyguard/values/dimens.xml b/packages/CarSystemUI/res-keyguard/values/dimens.xml
index 8dfe171..3c13958 100644
--- a/packages/CarSystemUI/res-keyguard/values/dimens.xml
+++ b/packages/CarSystemUI/res-keyguard/values/dimens.xml
@@ -17,10 +17,8 @@
 <resources>
     <dimen name="num_pad_margin_left">112dp</dimen>
     <dimen name="num_pad_margin_right">144dp</dimen>
-    <dimen name="num_pad_key_width">80dp</dimen>
+    <dimen name="num_pad_key_width">120dp</dimen>
     <dimen name="num_pad_key_height">80dp</dimen>
-    <dimen name="num_pad_key_margin_horizontal">@*android:dimen/car_padding_5</dimen>
-    <dimen name="num_pad_key_margin_bottom">@*android:dimen/car_padding_5</dimen>
     <dimen name="pin_entry_height">@dimen/num_pad_key_height</dimen>
     <dimen name="divider_height">1dp</dimen>
     <dimen name="key_enter_margin_top">128dp</dimen>
diff --git a/packages/CarSystemUI/res-keyguard/values/styles.xml b/packages/CarSystemUI/res-keyguard/values/styles.xml
index ecea30a..ca37428 100644
--- a/packages/CarSystemUI/res-keyguard/values/styles.xml
+++ b/packages/CarSystemUI/res-keyguard/values/styles.xml
@@ -23,12 +23,11 @@
         <item name="android:layout_width">@dimen/num_pad_key_width</item>
         <item name="android:layout_height">@dimen/num_pad_key_height</item>
         <item name="android:layout_marginBottom">@dimen/num_pad_key_margin_bottom</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
         <item name="textView">@id/pinEntry</item>
     </style>
 
     <style name="NumPadKeyButton.MiddleColumn">
-        <item name="android:layout_marginStart">@dimen/num_pad_key_margin_horizontal</item>
-        <item name="android:layout_marginEnd">@dimen/num_pad_key_margin_horizontal</item>
     </style>
 
     <style name="NumPadKeyButton.LastRow">
@@ -36,12 +35,10 @@
     </style>
 
     <style name="NumPadKeyButton.LastRow.MiddleColumn">
-        <item name="android:layout_marginStart">@dimen/num_pad_key_margin_horizontal</item>
-        <item name="android:layout_marginEnd">@dimen/num_pad_key_margin_horizontal</item>
     </style>
 
     <style name="KeyguardButton" parent="@android:style/Widget.DeviceDefault.Button">
-        <item name="android:background">@drawable/keyguard_button_background</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
         <item name="android:textColor">@color/button_text</item>
         <item name="android:textAllCaps">false</item>
     </style>
diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
index 534c51e..f987b5a 100644
--- a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
+++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
@@ -14,18 +14,18 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<FrameLayout
+
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/fullscreen_user_switcher"
+    android:id="@+id/user_switcher_container"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/car_user_switcher_background_color">
-
-    <LinearLayout
+    android:gravity="center">
+    <com.android.systemui.car.userswitcher.UserSwitcherContainer
         android:id="@+id/container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_alignParentTop="true"
+        android:background="@color/car_user_switcher_background_color"
         android:orientation="vertical">
 
         <include
@@ -45,5 +45,5 @@
                 android:layout_marginTop="@dimen/car_user_switcher_margin_top"/>
         </FrameLayout>
 
-    </LinearLayout>
-</FrameLayout>
+    </com.android.systemui.car.userswitcher.UserSwitcherContainer>
+</com.android.car.ui.FocusArea>
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
index cdc29eec..cb1aada 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -77,8 +77,8 @@
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:background="@null"
-                systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$QuickSettingActivity;launchFlags=0x24000000;end"
-            />
+                android:focusedByDefault="true"
+                systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$QuickSettingActivity;launchFlags=0x24000000;end"/>
             <com.android.systemui.statusbar.policy.Clock
                 android:id="@+id/clock"
                 android:layout_width="wrap_content"
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
index 9634950..cfa02a2 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
@@ -74,7 +74,8 @@
           android:id="@+id/qs"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
-          android:background="@null"/>
+          android:background="@null"
+          android:focusedByDefault="true"/>
       <com.android.systemui.statusbar.policy.Clock
           android:id="@+id/clock"
           android:layout_width="wrap_content"
diff --git a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
index f43f02d..c28da39 100644
--- a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
+++ b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
@@ -35,7 +35,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"/>
+        app:layout_constraintTop_toTopOf="parent"
+        app:shouldRestoreFocus="false"/>
 
     <View
         android:id="@+id/scrim"
diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml
index 0e45e43..51d23db 100644
--- a/packages/CarSystemUI/res/layout/notification_center_activity.xml
+++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml
@@ -22,10 +22,6 @@
     android:layout_height="match_parent"
     android:background="@color/notification_shade_background_color">
 
-    <com.android.car.ui.FocusParkingView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
-
     <View
         android:id="@+id/glass_pane"
         android:layout_width="match_parent"
@@ -37,20 +33,15 @@
         app:layout_constraintTop_toTopOf="parent"
     />
 
-    <com.android.car.ui.FocusArea
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:orientation="vertical"
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/notifications"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingBottom="@dimen/notification_shade_list_padding_bottom"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent">
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@+id/notifications"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:paddingBottom="@dimen/notification_shade_list_padding_bottom"/>
-    </com.android.car.ui.FocusArea>
+        app:layout_constraintTop_toTopOf="parent"/>
 
     <include layout="@layout/notification_handle_bar"/>
 
diff --git a/packages/CarSystemUI/res/layout/notification_panel_container.xml b/packages/CarSystemUI/res/layout/notification_panel_container.xml
index 3b53c6a..de69769 100644
--- a/packages/CarSystemUI/res/layout/notification_panel_container.xml
+++ b/packages/CarSystemUI/res/layout/notification_panel_container.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<FrameLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/notification_container"
     android:layout_width="match_parent"
diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
index e7295aa..3d6085c 100644
--- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
+++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
@@ -22,25 +22,29 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
+    <com.android.car.ui.FocusParkingView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
     <ViewStub android:id="@+id/notification_panel_stub"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout="@layout/notification_panel_container"
-              android:layout_marginBottom="@dimen/car_bottom_navigation_bar_height"/>
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/notification_panel_container"
+        android:layout_marginBottom="@dimen/car_bottom_navigation_bar_height"/>
 
     <ViewStub android:id="@+id/keyguard_stub"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout="@layout/keyguard_container" />
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/keyguard_container" />
 
     <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout="@layout/car_fullscreen_user_switcher"/>
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/car_fullscreen_user_switcher"/>
 
     <ViewStub android:id="@+id/user_switching_dialog_stub"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout="@layout/car_user_switching_dialog"/>
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/car_user_switching_dialog"/>
 
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index ec018f9..8fe59d0 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -148,6 +148,11 @@
     }
 
     @Override
+    protected int getFocusAreaViewId() {
+        return R.id.keyguard_container;
+    }
+
+    @Override
     protected boolean shouldShowNavigationBarInsets() {
         return true;
     }
@@ -233,9 +238,6 @@
     public void setOccluded(boolean occluded, boolean animate) {
         mIsOccluded = occluded;
         getOverlayViewGlobalStateController().setOccluded(occluded);
-        if (!occluded) {
-            reset(/* hideBouncerWhenShowing= */ false);
-        }
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/UnfocusablePasswordTextView.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/UnfocusablePasswordTextView.java
new file mode 100644
index 0000000..971e338
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/UnfocusablePasswordTextView.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.car.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.keyguard.PasswordTextView;
+
+/** A version of {@link PasswordTextView} that is not focusable. */
+public class UnfocusablePasswordTextView extends PasswordTextView {
+    public UnfocusablePasswordTextView(Context context) {
+        this(context, null);
+    }
+
+    public UnfocusablePasswordTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public UnfocusablePasswordTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public UnfocusablePasswordTextView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        setFocusableInTouchMode(false);
+        setFocusable(false);
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index fd804c7..3d79b06 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -28,6 +28,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.GestureDetector;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -165,6 +166,10 @@
         mEnableHeadsUpNotificationWhenNotificationShadeOpen = mResources.getBoolean(
                 com.android.car.notification.R.bool
                         .config_enableHeadsUpNotificationWhenNotificationShadeOpen);
+
+        // Inflate view on instantiation to properly initialize listeners even if panel has
+        // not been opened.
+        getOverlayViewGlobalStateController().inflateView(this);
     }
 
     // CommandQueue.Callbacks
@@ -218,6 +223,11 @@
     }
 
     @Override
+    protected int getFocusAreaViewId() {
+        return R.id.notification_container;
+    }
+
+    @Override
     protected boolean shouldShowNavigationBarInsets() {
         return true;
     }
@@ -239,12 +249,26 @@
 
     /** Reinflates the view. */
     public void reinflate() {
+        // Do not reinflate the view if it has not been inflated at all.
+        if (!isInflated()) return;
+
         ViewGroup container = (ViewGroup) getLayout();
         container.removeView(mNotificationView);
 
         mNotificationView = (CarNotificationView) LayoutInflater.from(mContext).inflate(
                 R.layout.notification_center_activity, container,
                 /* attachToRoot= */ false);
+        mNotificationView.setKeyEventHandler(
+                event -> {
+                    if (event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
+                        return false;
+                    }
+
+                    if (event.getAction() == KeyEvent.ACTION_UP && isPanelExpanded()) {
+                        toggle();
+                    }
+                    return true;
+                });
 
         container.addView(mNotificationView);
         onNotificationViewInflated();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
index aac4cfb..dd59efa 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
@@ -22,6 +22,7 @@
 import android.car.user.CarUserManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.view.KeyEvent;
 import android.view.View;
 
 import androidx.recyclerview.widget.GridLayoutManager;
@@ -67,6 +68,19 @@
 
     @Override
     protected void onFinishInflate() {
+        // Intercept back button.
+        UserSwitcherContainer container = getLayout().findViewById(R.id.container);
+        container.setKeyEventHandler(event -> {
+            if (event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
+                return false;
+            }
+
+            if (event.getAction() == KeyEvent.ACTION_UP && getLayout().isVisibleToUser()) {
+                stop();
+            }
+            return true;
+        });
+
         // Initialize user grid.
         mUserGridView = getLayout().findViewById(R.id.user_grid);
         GridLayoutManager layoutManager = new GridLayoutManager(mContext,
@@ -78,8 +92,13 @@
     }
 
     @Override
+    protected int getFocusAreaViewId() {
+        return R.id.user_switcher_container;
+    }
+
+    @Override
     protected boolean shouldFocusWindow() {
-        return false;
+        return true;
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitcherContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitcherContainer.java
new file mode 100644
index 0000000..5b62711
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitcherContainer.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.car.userswitcher;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/** Container for the user switcher which intercepts the key events. */
+public class UserSwitcherContainer extends LinearLayout {
+
+    private KeyEventHandler mKeyEventHandler;
+
+    public UserSwitcherContainer(@NonNull Context context) {
+        super(context);
+    }
+
+    public UserSwitcherContainer(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public UserSwitcherContainer(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public UserSwitcherContainer(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (super.dispatchKeyEvent(event)) {
+            return true;
+        }
+
+        if (mKeyEventHandler != null) {
+            return mKeyEventHandler.dispatchKeyEvent(event);
+        }
+
+        return false;
+    }
+
+    /** Sets a {@link KeyEventHandler} to help interact with the notification panel. */
+    public void setKeyEventHandler(KeyEventHandler keyEventHandler) {
+        mKeyEventHandler = keyEventHandler;
+    }
+
+    /** An interface to help interact with the notification panel. */
+    public interface KeyEventHandler {
+        /** Allows handling of a {@link KeyEvent} if it wasn't already handled by the superclass. */
+        boolean dispatchKeyEvent(KeyEvent event);
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
index 8adc1ad..7bc1776 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
@@ -17,12 +17,17 @@
 package com.android.systemui.car.window;
 
 import static android.view.WindowInsets.Type.statusBars;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
 
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.WindowInsets;
 
+import androidx.annotation.IdRes;
+
+import com.android.car.ui.FocusArea;
+
 /**
  * Owns a {@link View} that is present in SystemUIOverlayWindow.
  */
@@ -128,6 +133,66 @@
         return mOverlayViewGlobalStateController;
     }
 
+    /** Returns whether the view controlled by this controller is visible. */
+    public final boolean isVisible() {
+        return mLayout.getVisibility() == View.VISIBLE;
+    }
+
+    /**
+     * Returns the ID of the focus area that should receive focus when this view is the
+     * topmost view or {@link View#NO_ID} if there is no focus area.
+     */
+    @IdRes
+    protected int getFocusAreaViewId() {
+        return View.NO_ID;
+    }
+
+    /** Returns whether the view controlled by this controller has rotary focus. */
+    protected final boolean hasRotaryFocus() {
+        return !mLayout.isInTouchMode() && mLayout.hasFocus();
+    }
+
+    /**
+     * Sets whether this view allows rotary focus. This should be set to {@code true} for the
+     * topmost layer in the overlay window and {@code false} for the others.
+     */
+    public void setAllowRotaryFocus(boolean allowRotaryFocus) {
+        if (!isInflated()) {
+            return;
+        }
+
+        if (!(mLayout instanceof ViewGroup)) {
+            return;
+        }
+
+        ViewGroup viewGroup = (ViewGroup) mLayout;
+        viewGroup.setDescendantFocusability(allowRotaryFocus
+                ? ViewGroup.FOCUS_BEFORE_DESCENDANTS
+                : ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+    }
+
+    /**
+     * Refreshes the rotary focus in this view if we are in rotary mode. If the view already has
+     * rotary focus, it leaves the focus alone. Returns {@code true} if a new view was focused.
+     */
+    public boolean refreshRotaryFocusIfNeeded() {
+        if (mLayout.isInTouchMode()) {
+            return false;
+        }
+
+        if (hasRotaryFocus()) {
+            return false;
+        }
+
+        View view = mLayout.findViewById(getFocusAreaViewId());
+        if (view == null || !(view instanceof FocusArea)) {
+            return mLayout.requestFocus();
+        }
+
+        FocusArea focusArea = (FocusArea) view;
+        return focusArea.performAccessibilityAction(ACTION_FOCUS, /* arguments= */ null);
+    }
+
     /**
      * Returns {@code true} if heads up notifications should be displayed over this view.
      */
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
index 55f0975..204dde7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
@@ -29,6 +29,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
@@ -120,6 +121,7 @@
         refreshWindowFocus();
         refreshNavigationBarVisibility();
         refreshStatusBarVisibility();
+        refreshRotaryFocusIfNeeded();
 
         Log.d(TAG, "Content shown: " + viewController.getClass().getName());
         debugLog();
@@ -193,6 +195,7 @@
         refreshWindowFocus();
         refreshNavigationBarVisibility();
         refreshStatusBarVisibility();
+        refreshRotaryFocusIfNeeded();
 
         if (mZOrderVisibleSortedMap.isEmpty()) {
             setWindowVisible(false);
@@ -254,6 +257,17 @@
         }
     }
 
+    private void refreshRotaryFocusIfNeeded() {
+        for (OverlayViewController controller : mZOrderVisibleSortedMap.values()) {
+            boolean isTop = Objects.equals(controller, mHighestZOrder);
+            controller.setAllowRotaryFocus(isTop);
+        }
+
+        if (!mZOrderVisibleSortedMap.isEmpty()) {
+            mHighestZOrder.refreshRotaryFocusIfNeeded();
+        }
+    }
+
     /** Returns {@code true} is the window is visible. */
     public boolean isWindowVisible() {
         return mSystemUIOverlayWindowController.isWindowVisible();
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
index c9ec34f..c0b5c50 100644
--- a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
@@ -16,14 +16,15 @@
 
 package com.android.systemui.wm;
 
+import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IDisplayWindowInsetsController;
+import android.view.IWindowManager;
 import android.view.InsetsController;
-import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.WindowInsets;
 
@@ -48,30 +49,32 @@
 
     private static final String TAG = "DisplaySystemBarsController";
 
+    private final Context mContext;
+    private final Handler mHandler;
+
     private SparseArray<PerDisplay> mPerDisplaySparseArray;
 
     @Inject
     public DisplaySystemBarsController(
-            SystemWindows syswin,
+            Context context,
+            IWindowManager wmService,
             DisplayController displayController,
             @Main Handler mainHandler,
             TransactionPool transactionPool) {
-        super(syswin, displayController, mainHandler, transactionPool);
+        super(wmService, displayController, mainHandler::post, transactionPool);
+        mContext = context;
+        mHandler = mainHandler;
     }
 
     @Override
     public void onDisplayAdded(int displayId) {
         PerDisplay pd = new PerDisplay(displayId);
-        try {
-            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Unable to set insets controller on display " + displayId);
-        }
+        pd.register();
         // Lazy loading policy control filters instead of during boot.
         if (mPerDisplaySparseArray == null) {
             mPerDisplaySparseArray = new SparseArray<>();
-            BarControlPolicy.reloadFromSetting(mSystemWindows.mContext);
-            BarControlPolicy.registerContentObserver(mSystemWindows.mContext, mHandler, () -> {
+            BarControlPolicy.reloadFromSetting(mContext);
+            BarControlPolicy.registerContentObserver(mContext, mHandler, () -> {
                 int size = mPerDisplaySparseArray.size();
                 for (int i = 0; i < size; i++) {
                     mPerDisplaySparseArray.valueAt(i).modifyDisplayWindowInsets();
@@ -84,7 +87,7 @@
     @Override
     public void onDisplayRemoved(int displayId) {
         try {
-            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
+            mWmService.setDisplayWindowInsetsController(displayId, null);
         } catch (RemoteException e) {
             Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
         }
@@ -100,11 +103,10 @@
         String mPackageName;
 
         PerDisplay(int displayId) {
-            super(displayId,
-                    mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation());
+            super(displayId, mDisplayController.getDisplayLayout(displayId).rotation());
             mDisplayId = displayId;
             mInsetsController = new InsetsController(
-                    new DisplaySystemBarsInsetsControllerHost(mHandler, this));
+                    new DisplaySystemBarsInsetsControllerHost(mHandler, mInsetsControllerImpl));
         }
 
         @Override
@@ -121,13 +123,6 @@
         }
 
         @Override
-        public void insetsControlChanged(InsetsState insetsState,
-                InsetsSourceControl[] activeControls) {
-            super.insetsControlChanged(insetsState, activeControls);
-            mInsetsController.onControlsChanged(activeControls);
-        }
-
-        @Override
         public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 mInsetsController.hide(types);
@@ -166,7 +161,7 @@
             showInsets(barVisibilities[0], /* fromIme= */ false);
             hideInsets(barVisibilities[1], /* fromIme= */ false);
             try {
-                mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+                mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Unable to update window manager service.");
             }
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
index 294aa0d..d97b232 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
@@ -215,6 +215,16 @@
     }
 
     @Test
+    public void showView_nothingAlreadyShown_newHighestZOrder_isVisible() {
+        setupOverlayViewController1();
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsKey(
+                OVERLAY_VIEW_CONTROLLER_1_Z_ORDER)).isTrue();
+    }
+
+    @Test
     public void showView_nothingAlreadyShown_newHighestZOrder() {
         setupOverlayViewController1();
 
@@ -225,13 +235,12 @@
     }
 
     @Test
-    public void showView_nothingAlreadyShown_newHighestZOrder_isVisible() {
+    public void showView_nothingAlreadyShown_descendantsFocusable() {
         setupOverlayViewController1();
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
-        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsKey(
-                OVERLAY_VIEW_CONTROLLER_1_Z_ORDER)).isTrue();
+        verify(mOverlayViewController1).setAllowRotaryFocus(true);
     }
 
     @Test
@@ -332,6 +341,30 @@
     }
 
     @Test
+    public void showView_newHighestZOrder_topDescendantsFocusable() {
+        setupOverlayViewController1();
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setupOverlayViewController2();
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
+
+        verify(mOverlayViewController1).setAllowRotaryFocus(false);
+        verify(mOverlayViewController2).setAllowRotaryFocus(true);
+    }
+
+    @Test
+    public void showView_newHighestZOrder_refreshTopFocus() {
+        setupOverlayViewController1();
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setupOverlayViewController2();
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
+
+        verify(mOverlayViewController1, never()).refreshRotaryFocusIfNeeded();
+        verify(mOverlayViewController2).refreshRotaryFocusIfNeeded();
+    }
+
+    @Test
     public void showView_oldHighestZOrder() {
         setupOverlayViewController2();
         setOverlayViewControllerAsShowing(mOverlayViewController2);
@@ -345,9 +378,9 @@
     @Test
     public void showView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() {
         setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
-        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
         when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
@@ -360,11 +393,12 @@
     @Test
     public void showView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() {
         setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
-        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
         when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
@@ -374,9 +408,9 @@
     @Test
     public void showView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() {
         setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
-        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
         when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
@@ -389,11 +423,12 @@
     @Test
     public void showView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() {
         setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
-        setOverlayViewControllerAsShowing(mOverlayViewController2);
         when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
         when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
@@ -426,6 +461,30 @@
     }
 
     @Test
+    public void showView_oldHighestZOrder_topDescendantsFocusable() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mOverlayViewController1).setAllowRotaryFocus(false);
+        verify(mOverlayViewController2).setAllowRotaryFocus(true);
+    }
+
+    @Test
+    public void showView_oldHighestZOrder_refreshTopFocus() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mOverlayViewController1, never()).refreshRotaryFocusIfNeeded();
+        verify(mOverlayViewController2).refreshRotaryFocusIfNeeded();
+    }
+
+    @Test
     public void showView_somethingAlreadyShown_windowVisibleNotCalled() {
         setupOverlayViewController1();
         setOverlayViewControllerAsShowing(mOverlayViewController1);
@@ -577,10 +636,10 @@
     public void hideView_newHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
 
@@ -593,10 +652,10 @@
     public void hideView_newHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
         reset(mWindowInsetsController);
 
@@ -609,10 +668,10 @@
     public void hideView_newHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
 
@@ -625,10 +684,10 @@
     public void hideView_newHighestZOrder_shouldShowStatusBarTrue_statusBarShown() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
         reset(mWindowInsetsController);
 
@@ -668,10 +727,10 @@
     public void hideView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
 
@@ -684,11 +743,12 @@
     public void hideView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
 
@@ -699,10 +759,10 @@
     public void hideView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
         reset(mWindowInsetsController);
 
@@ -715,11 +775,12 @@
     public void hideView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarShown() {
         setupOverlayViewController1();
         setupOverlayViewController2();
-        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
-        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
 
@@ -917,7 +978,11 @@
 
     private void setOverlayViewControllerAsShowing(OverlayViewController overlayViewController) {
         mOverlayViewGlobalStateController.showView(overlayViewController, /* show= */ null);
+        View layout = overlayViewController.getLayout();
         reset(mSystemUIOverlayWindowController);
+        reset(overlayViewController);
         when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout);
+        when(overlayViewController.getLayout()).thenReturn(layout);
+        when(overlayViewController.isInflated()).thenReturn(true);
     }
 }
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
index 29cc8ee..765a4e7 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.car.settings.CarSettings;
 import android.os.Handler;
@@ -29,6 +30,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.IWindowManager;
+import android.view.Surface;
 
 import androidx.test.filters.SmallTest;
 
@@ -60,15 +62,20 @@
     private Handler mHandler;
     @Mock
     private TransactionPool mTransactionPool;
+    @Mock
+    private DisplayLayout mDisplayLayout;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mSystemWindows.mContext = mContext;
         mSystemWindows.mWmService = mIWindowManager;
+        when(mDisplayLayout.rotation()).thenReturn(Surface.ROTATION_0);
+        when(mDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mDisplayLayout);
 
         mController = new DisplaySystemBarsController(
-                mSystemWindows,
+                mContext,
+                mIWindowManager,
                 mDisplayController,
                 mHandler,
                 mTransactionPool
@@ -81,7 +88,8 @@
         mController.onDisplayAdded(DISPLAY_ID);
 
         verify(mIWindowManager).setDisplayWindowInsetsController(
-                eq(DISPLAY_ID), any(DisplaySystemBarsController.PerDisplay.class));
+                eq(DISPLAY_ID),
+                any(DisplayImeController.PerDisplay.DisplayWindowInsetsControllerImpl.class));
     }
 
     @Test
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index ea9b52c..e4e5b9f 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -31,6 +31,7 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
 
     <application
         android:allowClearUserData="true"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index e501e12..5ac059b 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -17,6 +17,7 @@
 package com.android.companiondevicemanager;
 
 import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import static java.util.Objects.requireNonNull;
 
@@ -58,6 +59,8 @@
             Log.e(LOG_TAG, "About to show UI, but no devices to show");
         }
 
+        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
         if (getService().mRequest.isSingleDevice()) {
             setContentView(R.layout.device_confirmation);
             final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 5675c99..665d262 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -343,17 +343,19 @@
         if (!wasSetUp) {
             return;
         }
-
-        // load dummy layout with OK button disabled until we override this layout in
-        // startInstallConfirm
-        bindUi();
-        checkIfAllowedAndInitiateInstall();
     }
 
     @Override
     protected void onResume() {
         super.onResume();
 
+        if (mAppSnippet != null) {
+            // load dummy layout with OK button disabled until we override this layout in
+            // startInstallConfirm
+            bindUi();
+            checkIfAllowedAndInitiateInstall();
+        }
+
         if (mOk != null) {
             mOk.setEnabled(mEnableOk);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index ce60faf..b3205d7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -25,6 +25,7 @@
 import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
 import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
 import static android.os.BatteryManager.EXTRA_PLUGGED;
+import static android.os.BatteryManager.EXTRA_PRESENT;
 import static android.os.BatteryManager.EXTRA_STATUS;
 
 import android.content.Context;
@@ -50,14 +51,16 @@
     public final int plugged;
     public final int health;
     public final int maxChargingWattage;
+    public final boolean present;
 
     public BatteryStatus(int status, int level, int plugged, int health,
-            int maxChargingWattage) {
+            int maxChargingWattage, boolean present) {
         this.status = status;
         this.level = level;
         this.plugged = plugged;
         this.health = health;
         this.maxChargingWattage = maxChargingWattage;
+        this.present = present;
     }
 
     public BatteryStatus(Intent batteryChangedIntent) {
@@ -65,6 +68,7 @@
         plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
         level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
         health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+        present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
 
         final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
                 -1);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 72a6074..9d47725 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -205,7 +205,6 @@
 
     void dispatchDeviceListUpdate() {
         final List<MediaDevice> mediaDevices = new ArrayList<>(mMediaDevices);
-        Collections.sort(mediaDevices, COMPARATOR);
         for (DeviceCallback callback : getCallbacks()) {
             callback.onDeviceListUpdate(mediaDevices);
         }
@@ -465,6 +464,7 @@
             synchronized (mMediaDevicesLock) {
                 mMediaDevices.clear();
                 mMediaDevices.addAll(devices);
+                Collections.sort(devices, COMPARATOR);
                 // Add disconnected bluetooth devices only when phone output device is available.
                 for (MediaDevice device : devices) {
                     final int type = device.getDeviceType();
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index cb610fc..bcde584 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -89,7 +89,7 @@
                         return value == null || value.length() < MAX_LENGTH;
                     }
                 });
-        VALIDATORS.put(System.FONT_SCALE, new InclusiveFloatRangeValidator(0.85f, 1.3f));
+        VALIDATORS.put(System.FONT_SCALE, new InclusiveFloatRangeValidator(0.25f, 5.0f));
         VALIDATORS.put(System.DIM_SCREEN, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 System.DISPLAY_COLOR_MODE,
diff --git a/packages/Shell/Android.bp b/packages/Shell/Android.bp
index 279cb84..092c543 100644
--- a/packages/Shell/Android.bp
+++ b/packages/Shell/Android.bp
@@ -7,13 +7,17 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+// used both for the android_app and android_library
+shell_srcs = ["src/**/*.java",":dumpstate_aidl"]
+shell_static_libs = ["androidx.legacy_legacy-support-v4"]
+
 android_app {
     name: "Shell",
-    srcs: ["src/**/*.java",":dumpstate_aidl"],
+    srcs: shell_srcs,
     aidl: {
         include_dirs: ["frameworks/native/cmds/dumpstate/binder"],
     },
-    static_libs: ["androidx.legacy_legacy-support-v4"],
+    static_libs: shell_static_libs,
     platform_apis: true,
     certificate: "platform",
     privileged: true,
@@ -21,3 +25,17 @@
         include_filter: ["com.android.shell.*"],
     },
 }
+
+// A library for product type like auto to create a new shell package
+// with product specific permissions.
+android_library {
+    name: "Shell-package-library",
+    srcs: shell_srcs,
+    aidl: {
+        include_dirs: ["frameworks/native/cmds/dumpstate/binder"],
+    },
+    resource_dirs: ["res"],
+    static_libs: shell_static_libs,
+    platform_apis: true,
+    manifest: "AndroidManifest.xml",
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 7b4cf0f..88112e3 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -803,7 +803,7 @@
         intent.setClass(context, BugreportProgressService.class);
         intent.putExtra(EXTRA_ID, info.id);
         return PendingIntent.getService(context, info.id, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
     /**
@@ -1263,7 +1263,7 @@
                 .setTicker(title)
                 .setContentText(content)
                 .setContentIntent(PendingIntent.getService(mContext, info.id, shareIntent,
-                        PendingIntent.FLAG_UPDATE_CURRENT))
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                 .setOnlyAlertOnce(false)
                 .setDeleteIntent(newCancelIntent(mContext, info));
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 9d52098..63f8b1f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -30,7 +30,7 @@
  */
 @ProvidesInterface(version = FalsingManager.VERSION)
 public interface FalsingManager {
-    int VERSION = 4;
+    int VERSION = 5;
 
     void onSuccessfulUnlock();
 
@@ -42,7 +42,8 @@
 
     boolean isUnlockingDisabled();
 
-    boolean isFalseTouch();
+    /** Returns true if the gesture should be rejected. */
+    boolean isFalseTouch(int interactionType);
 
     void onNotificatonStopDraggingDown();
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 02c4c5e..4b6efa9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -14,16 +14,16 @@
 
 package com.android.systemui.plugins.statusbar;
 
-import com.android.systemui.plugins.annotations.DependsOn;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
-
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
 @ProvidesInterface(version = NotificationSwipeActionHelper.VERSION)
 @DependsOn(target = SnoozeOption.class)
 public interface NotificationSwipeActionHelper {
@@ -52,7 +52,8 @@
 
     public boolean isDismissGesture(MotionEvent ev);
 
-    public boolean isFalseGesture(MotionEvent ev);
+    /** Returns true if the gesture should be rejected. */
+    boolean isFalseGesture();
 
     public boolean swipedFarEnough(float translation, float viewSize);
 
diff --git a/packages/SystemUI/res/drawable/ic_battery_unknown.xml b/packages/SystemUI/res/drawable/ic_battery_unknown.xml
new file mode 100644
index 0000000..8b2ba12
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_battery_unknown.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="12dp"
+        android:height="24dp"
+        android:viewportWidth="12.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M10.404,2.4L8.4,2.4L8.4,0L3.6,0L3.6,2.4L1.596,2.4C0.72,2.4 0,3.12 0,3.996L0,22.392C0,23.28 0.72,24 1.596,24L10.392,24C11.28,24 12,23.28 12,22.404L12,3.996C12,3.12 11.28,2.4 10.404,2.4ZM7.14,19.14L4.86,19.14L4.86,16.86L7.14,16.86L7.14,19.14ZM8.76,12.828C8.76,12.828 8.304,13.332 7.956,13.68C7.38,14.256 6.96,15.06 6.96,15.6L5.04,15.6C5.04,14.604 5.592,13.776 6.156,13.2L7.272,12.072C7.596,11.748 7.8,11.292 7.8,10.8C7.8,9.804 6.996,9 6,9C5.004,9 4.2,9.804 4.2,10.8L2.4,10.8C2.4,8.808 4.008,7.2 6,7.2C7.992,7.2 9.6,8.808 9.6,10.8C9.6,11.592 9.276,12.312 8.76,12.828L8.76,12.828Z"
+      android:fillColor="#ffffff" />
+</vector>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 0097738..827721c 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -581,4 +581,9 @@
     <integer name="controls_max_columns_adjust_below_width_dp">320</integer>
     <!-- If the config font scale is >= this value, potentially adjust the number of columns-->
     <item name="controls_max_columns_adjust_above_font_scale" translatable="false" format="float" type="dimen">1.25</item>
+
+    <!-- Whether or not to show a notification for an unknown battery state -->
+    <bool name="config_showNotificationForUnknownBatteryState">false</bool>
+    <!-- content URL in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false -->
+    <string translatable="false" name="config_batteryStateUnknownUrl"></string>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 824521e..174f5c7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -437,6 +437,8 @@
     <string name="accessibility_battery_three_bars">Battery three bars.</string>
     <!-- Content description of the battery when it is full for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_battery_full">Battery full.</string>
+    <!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_battery_unknown">Battery percentage unknown.</string>
 
     <!-- Content description of the phone signal when no signal for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_no_phone">No phone.</string>
@@ -2870,4 +2872,11 @@
     <string name="media_output_dialog_connect_failed">Couldn\'t connect. Try again.</string>
     <!-- Title for pairing item [CHAR LIMIT=60] -->
     <string name="media_output_dialog_pairing_new">Pair new device</string>
+
+    <!-- Title to display in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false
+    [CHAR LIMIT=NONE] -->
+    <string name="battery_state_unknown_notification_title">Problem reading your battery meter</string>
+    <!-- Text to display in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false
+    [CHAR LIMIT=NONE] -->
+    <string name="battery_state_unknown_notification_text">Tap for more information</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 60cd240..deaa425 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1696,7 +1696,7 @@
         }
 
         // Take a guess at initial SIM state, battery status and PLMN until we get an update
-        mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0, 0);
+        mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0, 0, true);
 
         // Watch for interesting updates
         final IntentFilter filter = new IntentFilter();
@@ -2563,6 +2563,8 @@
         final boolean wasPluggedIn = old.isPluggedIn();
         final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn
                 && (old.status != current.status);
+        final boolean nowPresent = current.present;
+        final boolean wasPresent = old.present;
 
         // change in plug state is always interesting
         if (wasPluggedIn != nowPluggedIn || stateChangedWhilePluggedIn) {
@@ -2584,6 +2586,11 @@
             return true;
         }
 
+        // Battery either showed up or disappeared
+        if (wasPresent != nowPresent) {
+            return true;
+        }
+
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 5235a45..10f9f54 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -33,6 +33,7 @@
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Handler;
 import android.provider.Settings;
@@ -95,12 +96,15 @@
     private int mTextColor;
     private int mLevel;
     private int mShowPercentMode = MODE_DEFAULT;
-    private boolean mForceShowPercent;
     private boolean mShowPercentAvailable;
     // Some places may need to show the battery conditionally, and not obey the tuner
     private boolean mIgnoreTunerUpdates;
     private boolean mIsSubscribedForTunerUpdates;
     private boolean mCharging;
+    // Error state where we know nothing about the current battery state
+    private boolean mBatteryStateUnknown;
+    // Lazily-loaded since this is expected to be a rare-if-ever state
+    private Drawable mUnknownStateDrawable;
 
     private DualToneHandler mDualToneHandler;
     private int mUser;
@@ -350,6 +354,11 @@
     }
 
     private void updatePercentText() {
+        if (mBatteryStateUnknown) {
+            setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
+            return;
+        }
+
         if (mBatteryController == null) {
             return;
         }
@@ -390,9 +399,13 @@
         final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
                 .getIntForUser(getContext().getContentResolver(),
                 SHOW_BATTERY_PERCENT, 0, mUser));
+        boolean shouldShow =
+                (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
+                || mShowPercentMode == MODE_ON
+                || mShowPercentMode == MODE_ESTIMATE;
+        shouldShow = shouldShow && !mBatteryStateUnknown;
 
-        if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
-                || mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE) {
+        if (shouldShow) {
             if (!showing) {
                 mBatteryPercentView = loadPercentView();
                 if (mPercentageStyleId != 0) { // Only set if specified as attribute
@@ -418,6 +431,32 @@
         scaleBatteryMeterViews();
     }
 
+    private Drawable getUnknownStateDrawable() {
+        if (mUnknownStateDrawable == null) {
+            mUnknownStateDrawable = mContext.getDrawable(R.drawable.ic_battery_unknown);
+            mUnknownStateDrawable.setTint(mTextColor);
+        }
+
+        return mUnknownStateDrawable;
+    }
+
+    @Override
+    public void onBatteryUnknownStateChanged(boolean isUnknown) {
+        if (mBatteryStateUnknown == isUnknown) {
+            return;
+        }
+
+        mBatteryStateUnknown = isUnknown;
+
+        if (mBatteryStateUnknown) {
+            mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
+        } else {
+            mBatteryIconView.setImageDrawable(mDrawable);
+        }
+
+        updateShowPercent();
+    }
+
     /**
      * Looks up the scale factor for status bar icons and scales the battery view by that amount.
      */
@@ -458,6 +497,10 @@
         if (mBatteryPercentView != null) {
             mBatteryPercentView.setTextColor(singleToneColor);
         }
+
+        if (mUnknownStateDrawable != null) {
+            mUnknownStateDrawable.setTint(singleToneColor);
+        }
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -467,8 +510,8 @@
         pw.println("    mDrawable.getPowerSave: " + powerSave);
         pw.println("    mBatteryPercentView.getText(): " + percent);
         pw.println("    mTextColor: #" + Integer.toHexString(mTextColor));
+        pw.println("    mBatteryStateUnknown: " + mBatteryStateUnknown);
         pw.println("    mLevel: " + mLevel);
-        pw.println("    mForceShowPercent: " + mForceShowPercent);
     }
 
     private final class SettingObserver extends ContentObserver {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index d17ca404..0bb8c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui;
 
+import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -697,14 +699,15 @@
         float translation = getTranslation(mCurrView);
         return ev.getActionMasked() == MotionEvent.ACTION_UP
                 && !mFalsingManager.isUnlockingDisabled()
-                && !isFalseGesture(ev) && (swipedFastEnough() || swipedFarEnough())
+                && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough())
                 && mCallback.canChildBeDismissedInDirection(mCurrView, translation > 0);
     }
 
-    public boolean isFalseGesture(MotionEvent ev) {
+    /** Returns true if the gesture should be rejected. */
+    public boolean isFalseGesture() {
         boolean falsingDetected = mCallback.isAntiFalsingNeeded();
         if (mFalsingManager.isClassifierEnabled()) {
-            falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
+            falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(NOTIFICATION_DISMISS);
         } else {
             falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 708002d..1f41038 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -32,6 +32,7 @@
 import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.LogBufferFreezer;
 import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
+import com.android.systemui.statusbar.policy.BatteryStateNotifier;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -44,18 +45,21 @@
     private final DumpHandler mDumpHandler;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final LogBufferFreezer mLogBufferFreezer;
+    private final BatteryStateNotifier mBatteryStateNotifier;
 
     @Inject
     public SystemUIService(
             @Main Handler mainHandler,
             DumpHandler dumpHandler,
             BroadcastDispatcher broadcastDispatcher,
-            LogBufferFreezer logBufferFreezer) {
+            LogBufferFreezer logBufferFreezer,
+            BatteryStateNotifier batteryStateNotifier) {
         super();
         mMainHandler = mainHandler;
         mDumpHandler = dumpHandler;
         mBroadcastDispatcher = broadcastDispatcher;
         mLogBufferFreezer = logBufferFreezer;
+        mBatteryStateNotifier = batteryStateNotifier;
     }
 
     @Override
@@ -68,6 +72,11 @@
         // Finish initializing dump logic
         mLogBufferFreezer.attach(mBroadcastDispatcher);
 
+        // If configured, set up a battery notification
+        if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
+            mBatteryStateNotifier.startListening();
+        }
+
         // For debugging RescueParty
         if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
             throw new RuntimeException();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
index 1d47fc5..7daad1c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
@@ -160,12 +160,12 @@
 
     @Override
     protected void handleResetAfterError() {
-        resetErrorView(mContext, mIndicatorView);
+        resetErrorView();
     }
 
     @Override
     protected void handleResetAfterHelp() {
-        resetErrorView(mContext, mIndicatorView);
+        resetErrorView();
     }
 
     @Override
@@ -185,7 +185,7 @@
 
         if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
                 (newState == STATE_AUTHENTICATING && mSize == AuthDialog.SIZE_MEDIUM)) {
-            resetErrorView(mContext, mIndicatorView);
+            resetErrorView();
         }
 
         // Do this last since the state variable gets updated.
@@ -204,9 +204,8 @@
         super.onAuthenticationFailed(failureReason);
     }
 
-    static void resetErrorView(Context context, TextView textView) {
-        textView.setTextColor(context.getResources().getColor(
-                R.color.biometric_dialog_gray, context.getTheme()));
-        textView.setVisibility(View.INVISIBLE);
+    private void resetErrorView() {
+        mIndicatorView.setTextColor(mTextColorHint);
+        mIndicatorView.setVisibility(View.INVISIBLE);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
index 176e9e6..45ee4ad 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
@@ -78,7 +78,7 @@
 
     private void showTouchSensorString() {
         mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor);
-        mIndicatorView.setTextColor(R.color.biometric_dialog_gray);
+        mIndicatorView.setTextColor(mTextColorHint);
     }
 
     private void updateIcon(int lastState, int newState) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 7c25d28..f9c6d32 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -82,7 +82,7 @@
      * Authenticated, dialog animating away soon.
      */
     protected static final int STATE_AUTHENTICATED = 6;
-    
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({STATE_IDLE, STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING, STATE_HELP,
             STATE_ERROR, STATE_PENDING_CONFIRMATION, STATE_AUTHENTICATED})
@@ -155,8 +155,8 @@
     private final Injector mInjector;
     private final Handler mHandler;
     private final AccessibilityManager mAccessibilityManager;
-    private final int mTextColorError;
-    private final int mTextColorHint;
+    protected final int mTextColorError;
+    protected final int mTextColorHint;
 
     private AuthPanelController mPanelController;
     private Bundle mBiometricPromptBundle;
@@ -169,7 +169,7 @@
     private TextView mSubtitleView;
     private TextView mDescriptionView;
     protected ImageView mIconView;
-    @VisibleForTesting protected TextView mIndicatorView;
+    protected TextView mIndicatorView;
     @VisibleForTesting Button mNegativeButton;
     @VisibleForTesting Button mPositiveButton;
     @VisibleForTesting Button mTryAgainButton;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index b71e3ad..ed9b904 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -43,6 +43,7 @@
 import com.android.internal.logging.InstanceId;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -623,7 +624,8 @@
 
     private int getUid(final Context context) {
         if (mAppUid != -1) return mAppUid;
-        final PackageManager pm = context.getPackageManager();
+        final PackageManager pm = StatusBar.getPackageManagerForUser(context,
+                mUser.getIdentifier());
         if (pm == null) return -1;
         try {
             final ApplicationInfo info = pm.getApplicationInfo(mShortcutInfo.getPackage(), 0);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 5deae92..fb819f0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -1386,10 +1386,10 @@
                     }
                 }
             }
-            mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
+            mDataRepository.removeBubbles(bubblesToBeRemovedFromRepository);
 
             if (update.addedBubble != null && mStackView != null) {
-                mDataRepository.addBubble(mCurrentUserId, update.addedBubble);
+                mDataRepository.addBubble(update.addedBubble);
                 mStackView.addBubble(update.addedBubble);
             }
 
@@ -1400,7 +1400,7 @@
             // At this point, the correct bubbles are inflated in the stack.
             // Make sure the order in bubble data is reflected in bubble row.
             if (update.orderChanged && mStackView != null) {
-                mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
+                mDataRepository.addBubbles(update.bubbles);
                 mStackView.updateBubbleOrder(update.bubbles);
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index db64a13..a363208 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.bubbles
 
 import android.annotation.SuppressLint
-import android.annotation.UserIdInt
 import android.content.pm.LauncherApps
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
@@ -51,31 +50,31 @@
      * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk
      * asynchronously.
      */
-    fun addBubble(@UserIdInt userId: Int, bubble: Bubble) = addBubbles(userId, listOf(bubble))
+    fun addBubble(bubble: Bubble) = addBubbles(listOf(bubble))
 
     /**
      * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk
      * asynchronously.
      */
-    fun addBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) {
+    fun addBubbles(bubbles: List<Bubble>) {
         if (DEBUG) Log.d(TAG, "adding ${bubbles.size} bubbles")
-        val entities = transform(userId, bubbles).also(volatileRepository::addBubbles)
+        val entities = transform(bubbles).also(volatileRepository::addBubbles)
         if (entities.isNotEmpty()) persistToDisk()
     }
 
     /**
      * Removes the bubbles from memory, then persists the snapshot to disk asynchronously.
      */
-    fun removeBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) {
+    fun removeBubbles(bubbles: List<Bubble>) {
         if (DEBUG) Log.d(TAG, "removing ${bubbles.size} bubbles")
-        val entities = transform(userId, bubbles).also(volatileRepository::removeBubbles)
+        val entities = transform(bubbles).also(volatileRepository::removeBubbles)
         if (entities.isNotEmpty()) persistToDisk()
     }
 
-    private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> {
+    private fun transform(bubbles: List<Bubble>): List<BubbleEntity> {
         return bubbles.mapNotNull { b ->
             BubbleEntity(
-                    userId,
+                    b.user.identifier,
                     b.packageName,
                     b.metadataShortcutId ?: return@mapNotNull null,
                     b.key,
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 1929fc4..a6cf69a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -48,6 +48,7 @@
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.lang.ref.WeakReference;
 import java.util.List;
@@ -146,7 +147,8 @@
             }
 
             // App name & app icon
-            PackageManager pm = c.getPackageManager();
+            PackageManager pm = StatusBar.getPackageManagerForUser(
+                    c, b.getUser().getIdentifier());
             ApplicationInfo appInfo;
             Drawable badgedIcon;
             Drawable appIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
index 646e620..6961b45 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -70,7 +70,7 @@
     }
 
     @Override
-    public boolean isFalseTouch() {
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
         return mIsFalseTouch;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index cc64fb5..decaec1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -262,7 +262,7 @@
     /**
      * @return true if the classifier determined that this is not a human interacting with the phone
      */
-    public boolean isFalseTouch() {
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
         if (FalsingLog.ENABLED) {
             // We're getting some false wtfs from touches that happen after the device went
             // to sleep. Only report missing sessions that happen when the device is interactive.
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index f35322b..be6c5f9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -187,8 +187,8 @@
     }
 
     @Override
-    public boolean isFalseTouch() {
-        return mInternalFalsingManager.isFalseTouch();
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+        return mInternalFalsingManager.isFalseTouch(interactionType);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index a50f9ce..9d847ca 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -189,7 +189,8 @@
     }
 
     @Override
-    public boolean isFalseTouch() {
+    public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+        mDataProvider.setInteractionType(interactionType);
         if (!mDataProvider.isDirty()) {
             return mPreviousResult;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
index ea46441..8d06748 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingDataProvider.java
@@ -116,7 +116,10 @@
      * interactionType is defined by {@link com.android.systemui.classifier.Classifier}.
      */
     final void setInteractionType(@Classifier.InteractionType int interactionType) {
-        this.mInteractionType = interactionType;
+        if (mInteractionType != interactionType) {
+            mInteractionType = interactionType;
+            mDirty = true;
+        }
     }
 
     public boolean isDirty() {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index aebf41b..2eadbd0 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -46,6 +46,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.systemui.plugins.SensorManagerPlugin;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -80,6 +81,7 @@
     private long mDebounceFrom;
     private boolean mSettingRegistered;
     private boolean mListening;
+    private boolean mListeningTouchScreenSensors;
 
     @VisibleForTesting
     public enum DozeSensorsUiEvent implements UiEventLogger.UiEventEnum {
@@ -222,22 +224,25 @@
     /**
      * If sensors should be registered and sending signals.
      */
-    public void setListening(boolean listen) {
-        if (mListening == listen) {
+    public void setListening(boolean listen, boolean includeTouchScreenSensors) {
+        if (mListening == listen && mListeningTouchScreenSensors == includeTouchScreenSensors) {
             return;
         }
         mListening = listen;
+        mListeningTouchScreenSensors = includeTouchScreenSensors;
         updateListening();
     }
 
     /**
      * Registers/unregisters sensors based on internal state.
      */
-    public void updateListening() {
+    private void updateListening() {
         boolean anyListening = false;
         for (TriggerSensor s : mSensors) {
-            s.setListening(mListening);
-            if (mListening) {
+            boolean listen = mListening
+                    && (!s.mRequiresTouchscreen || mListeningTouchScreenSensors);
+            s.setListening(listen);
+            if (listen) {
                 anyListening = true;
             }
         }
@@ -309,10 +314,14 @@
 
     /** Dump current state */
     public void dump(PrintWriter pw) {
+        pw.println("mListening=" + mListening);
+        pw.println("mListeningTouchScreenSensors=" + mListeningTouchScreenSensors);
+        IndentingPrintWriter idpw = new IndentingPrintWriter(pw, "  ");
+        idpw.increaseIndent();
         for (TriggerSensor s : mSensors) {
-            pw.println("  Sensor: " + s.toString());
+            idpw.println("Sensor: " + s.toString());
         }
-        pw.println("  ProxSensor: " + mProximitySensor.toString());
+        idpw.println("ProxSensor: " + mProximitySensor.toString());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index cbf8f57..043edee 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -38,6 +38,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.systemui.Dependency;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dock.DockManager;
@@ -408,15 +409,12 @@
                 break;
             case DOZE_PULSE_DONE:
                 mDozeSensors.requestTemporaryDisable();
-                // A pulse will temporarily disable sensors that require a touch screen.
-                // Let's make sure that they are re-enabled when the pulse is over.
-                mDozeSensors.updateListening();
                 break;
             case FINISH:
                 mBroadcastReceiver.unregister(mBroadcastDispatcher);
                 mDozeHost.removeCallback(mHostCallback);
                 mDockManager.removeListener(mDockEventListener);
-                mDozeSensors.setListening(false);
+                mDozeSensors.setListening(false, false);
                 mDozeSensors.setProxListening(false);
                 mWantSensors = false;
                 mWantProx = false;
@@ -424,20 +422,16 @@
                 break;
             default:
         }
+        mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors);
     }
 
     @Override
     public void onScreenState(int state) {
         mDozeSensors.onScreenState(state);
-        if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND
-                || state == Display.STATE_OFF) {
-            mDozeSensors.setProxListening(mWantProx);
-            mDozeSensors.setListening(mWantSensors);
-            mDozeSensors.setTouchscreenSensorsListening(mWantTouchScreenSensors);
-        } else {
-            mDozeSensors.setProxListening(false);
-            mDozeSensors.setListening(mWantSensors);
-        }
+        mDozeSensors.setProxListening(mWantProx && (state == Display.STATE_DOZE
+                || state == Display.STATE_DOZE_SUSPEND
+                || state == Display.STATE_OFF));
+        mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors);
     }
 
     private void checkTriggersAtInit() {
@@ -513,7 +507,9 @@
 
         pw.println(" pulsePending=" + mPulsePending);
         pw.println("DozeSensors:");
-        mDozeSensors.dump(pw);
+        IndentingPrintWriter idpw = new IndentingPrintWriter(pw, "  ");
+        idpw.increaseIndent();
+        mDozeSensors.dump(idpw);
     }
 
     private class TriggerReceiver extends BroadcastReceiver {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index 77cac50..4863999 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.R
+import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.util.animation.PhysicsAnimator
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -315,7 +316,8 @@
         return false
     }
 
-    private fun isFalseTouch() = falsingProtectionNeeded && falsingManager.isFalseTouch
+    private fun isFalseTouch() = falsingProtectionNeeded &&
+            falsingManager.isFalseTouch(NOTIFICATION_DISMISS)
 
     private fun getMaxTranslation() = if (showsSettingsButton) {
             settingsButton.width
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 833d088..2cc31f0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -70,6 +70,7 @@
 private const val DEFAULT_LUMINOSITY = 0.25f
 private const val LUMINOSITY_THRESHOLD = 0.05f
 private const val SATURATION_MULTIPLIER = 0.8f
+const val DEFAULT_COLOR = Color.DKGRAY
 
 private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
         emptyList(), emptyList(), "INVALID", null, null, null, true, null)
@@ -380,7 +381,7 @@
         } else {
             null
         }
-        val bgColor = artworkBitmap?.let { computeBackgroundColor(it) } ?: Color.DKGRAY
+        val bgColor = artworkBitmap?.let { computeBackgroundColor(it) } ?: DEFAULT_COLOR
 
         val mediaAction = getResumeMediaAction(resumeAction)
         foregroundExecutor.execute {
@@ -560,12 +561,14 @@
 
     private fun computeBackgroundColor(artworkBitmap: Bitmap?): Int {
         var color = Color.WHITE
-        if (artworkBitmap != null) {
-            // If we have art, get colors from that
+        if (artworkBitmap != null && artworkBitmap.width > 1 && artworkBitmap.height > 1) {
+            // If we have valid art, get colors from that
             val p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
                     .generate()
             val swatch = MediaNotificationProcessor.findBackgroundSwatch(p)
             color = swatch.rgb
+        } else {
+            return DEFAULT_COLOR
         }
         // Adapt background color, so it's always subdued and text is legible
         val tmpHsl = floatArrayOf(0f, 0f, 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 54df53d..29d77a7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -45,6 +45,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.EventLog;
 import android.util.Log;
 import android.util.Size;
 import android.view.SurfaceControl;
@@ -223,6 +224,7 @@
     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
     private PictureInPictureParams mPictureInPictureParams;
+    private int mOverridableMinSize;
 
     /**
      * If set to {@code true}, the entering animation will be skipped and we will wait for
@@ -244,6 +246,8 @@
         mPipBoundsHandler = boundsHandler;
         mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
+        mOverridableMinSize = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.overridable_minimal_size_pip_resizable_task);
         mSurfaceTransactionHelper = surfaceTransactionHelper;
         mPipAnimationController = pipAnimationController;
         mPipUiEventLoggerLogger = pipUiEventLogger;
@@ -949,7 +953,14 @@
         // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
         // without minWidth/minHeight
         if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
-            return new Size(windowLayout.minWidth, windowLayout.minHeight);
+            // If either dimension is smaller than the allowed minimum, adjust them
+            // according to mOverridableMinSize and log to SafeNet
+            if (windowLayout.minWidth < mOverridableMinSize
+                    || windowLayout.minHeight < mOverridableMinSize) {
+                EventLog.writeEvent(0x534e4554, "174302616", -1, "");
+            }
+            return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize),
+                    Math.max(windowLayout.minHeight, mOverridableMinSize));
         }
         return null;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 59118bf..cd01cb7 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -104,9 +104,9 @@
         uiExecutor.execute(notifyChanges)
     }
 
-    var allIndicatorsAvailable = isAllIndicatorsEnabled()
+    var allIndicatorsAvailable = false
         private set
-    var micCameraAvailable = isMicCameraEnabled()
+    var micCameraAvailable = false
         private set
 
     private val devicePropertiesChangedListener =
@@ -158,10 +158,6 @@
         }
 
     init {
-        deviceConfigProxy.addOnPropertiesChangedListener(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                uiExecutor,
-                devicePropertiesChangedListener)
         dumpManager.registerDumpable(TAG, this)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index 42dde40..b8a468e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -252,7 +252,7 @@
                 dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
         if (intent != null) {
             final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
-                    mContext, 0, intent, 0, null, UserHandle.CURRENT);
+                    mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
             b.setContentIntent(pendingIntent);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
index 5aeca5e..6c9f61b 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
@@ -141,14 +141,14 @@
     @ImeAnimationFlags
     public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
             boolean imeShouldShow, boolean imeIsFloating, SurfaceControl.Transaction t) {
-        mHiddenTop = hiddenTop;
-        mShownTop = shownTop;
-        mTargetShown = imeShouldShow;
         if (!isDividerVisible()) {
             return 0;
         }
-        final boolean splitIsVisible = !getView().isHidden();
+        mHiddenTop = hiddenTop;
+        mShownTop = shownTop;
+        mTargetShown = imeShouldShow;
         mSecondaryHasFocus = getSecondaryHasFocus(displayId);
+        final boolean splitIsVisible = !getView().isHidden();
         final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
                 && !imeIsFloating && !getLayout().mDisplayLayout.isLandscape()
                 && !mSplits.mDivider.isMinimized();
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index b6c6afd..e59dca9 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -618,7 +618,7 @@
             mEntranceAnimationRunning = false;
             mExitAnimationRunning = false;
             if (!dismissed && !wasMinimizeInteraction) {
-                WindowManagerProxy.applyResizeSplits(snapTarget.position, mSplitLayout);
+                mWindowManagerProxy.applyResizeSplits(snapTarget.position, mSplitLayout);
             }
             if (mCallback != null) {
                 mCallback.onDraggingEnd();
@@ -845,15 +845,7 @@
     }
 
     void enterSplitMode(boolean isHomeStackResizable) {
-        post(() -> {
-            final SurfaceControl sc = getWindowSurfaceControl();
-            if (sc == null) {
-                return;
-            }
-            Transaction t = mTiles.getTransaction();
-            t.show(sc).apply();
-            mTiles.releaseTransaction(t);
-        });
+        setHidden(false);
 
         SnapTarget miniMid =
                 mSplitLayout.getMinimizedSnapAlgorithm(isHomeStackResizable).getMiddleTarget();
@@ -880,16 +872,19 @@
     }
 
     void exitSplitMode() {
-        // Reset tile bounds
         final SurfaceControl sc = getWindowSurfaceControl();
         if (sc == null) {
             return;
         }
         Transaction t = mTiles.getTransaction();
-        t.hide(sc).apply();
+        t.hide(sc);
+        mImeController.setDimsHidden(t, true);
+        t.apply();
         mTiles.releaseTransaction(t);
+
+        // Reset tile bounds
         int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
-        WindowManagerProxy.applyResizeSplits(midPos, mSplitLayout);
+        mWindowManagerProxy.applyResizeSplits(midPos, mSplitLayout);
     }
 
     public void setMinimizedDockStack(boolean minimized, long animDuration,
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 410e3dd..e593db8 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -37,7 +37,6 @@
 import android.window.TaskOrganizer;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.TransactionPool;
@@ -112,10 +111,10 @@
         mExecutor.execute(mSetTouchableRegionRunnable);
     }
 
-    static void applyResizeSplits(int position, SplitDisplayLayout splitLayout) {
+    void applyResizeSplits(int position, SplitDisplayLayout splitLayout) {
         WindowContainerTransaction t = new WindowContainerTransaction();
         splitLayout.resizeSplits(position, t);
-        WindowOrganizer.applyTransaction(t);
+        applySyncTransaction(t);
     }
 
     private static boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 4fa7822..e61e05a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -163,7 +165,7 @@
         if (!mDragDownCallback.isFalsingCheckNeeded()) {
             return false;
         }
-        return mFalsingManager.isFalseTouch() || !mDraggedFarEnough;
+        return mFalsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN) || !mDraggedFarEnough;
     }
 
     private void captureStartingChild(float x, float y) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 39d2f71..155ec5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -123,6 +123,7 @@
     private int mChargingSpeed;
     private int mChargingWattage;
     private int mBatteryLevel;
+    private boolean mBatteryPresent = true;
     private long mChargingTimeRemaining;
     private float mDisclosureMaxAlpha;
     private String mMessageToShowOnScreenOn;
@@ -391,86 +392,103 @@
             mWakeLock.setAcquired(false);
         }
 
-        if (mVisible) {
-            // Walk down a precedence-ordered list of what indication
-            // should be shown based on user or device state
-            if (mDozing) {
-                // When dozing we ignore any text color and use white instead, because
-                // colors can be hard to read in low brightness.
-                mTextView.setTextColor(Color.WHITE);
-                if (!TextUtils.isEmpty(mTransientIndication)) {
-                    mTextView.switchIndication(mTransientIndication);
-                } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
-                    mTextView.switchIndication(mAlignmentIndication);
-                    mTextView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
-                } else if (mPowerPluggedIn || mEnableBatteryDefender) {
-                    String indication = computePowerIndication();
-                    if (animate) {
-                        animateText(mTextView, indication);
-                    } else {
-                        mTextView.switchIndication(indication);
-                    }
-                } else {
-                    String percentage = NumberFormat.getPercentInstance()
-                            .format(mBatteryLevel / 100f);
-                    mTextView.switchIndication(percentage);
-                }
-                return;
-            }
+        if (!mVisible) {
+            return;
+        }
 
-            int userId = KeyguardUpdateMonitor.getCurrentUser();
-            String trustGrantedIndication = getTrustGrantedIndication();
-            String trustManagedIndication = getTrustManagedIndication();
+        // A few places might need to hide the indication, so always start by making it visible
+        mIndicationArea.setVisibility(View.VISIBLE);
 
-            String powerIndication = null;
-            if (mPowerPluggedIn || mEnableBatteryDefender) {
-                powerIndication = computePowerIndication();
-            }
-
-            boolean isError = false;
-            if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
-                mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
-            } else if (!TextUtils.isEmpty(mTransientIndication)) {
-                if (powerIndication != null && !mTransientIndication.equals(powerIndication)) {
-                    String indication = mContext.getResources().getString(
-                            R.string.keyguard_indication_trust_unlocked_plugged_in,
-                            mTransientIndication, powerIndication);
-                    mTextView.switchIndication(indication);
-                } else {
-                    mTextView.switchIndication(mTransientIndication);
-                }
-                isError = mTransientTextIsError;
-            } else if (!TextUtils.isEmpty(trustGrantedIndication)
-                    && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
-                if (powerIndication != null) {
-                    String indication = mContext.getResources().getString(
-                            R.string.keyguard_indication_trust_unlocked_plugged_in,
-                            trustGrantedIndication, powerIndication);
-                    mTextView.switchIndication(indication);
-                } else {
-                    mTextView.switchIndication(trustGrantedIndication);
-                }
+        // Walk down a precedence-ordered list of what indication
+        // should be shown based on user or device state
+        if (mDozing) {
+            // When dozing we ignore any text color and use white instead, because
+            // colors can be hard to read in low brightness.
+            mTextView.setTextColor(Color.WHITE);
+            if (!TextUtils.isEmpty(mTransientIndication)) {
+                mTextView.switchIndication(mTransientIndication);
+            } else if (!mBatteryPresent) {
+                // If there is no battery detected, hide the indication and bail
+                mIndicationArea.setVisibility(View.GONE);
             } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
                 mTextView.switchIndication(mAlignmentIndication);
-                isError = true;
+                mTextView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
             } else if (mPowerPluggedIn || mEnableBatteryDefender) {
-                if (DEBUG_CHARGING_SPEED) {
-                    powerIndication += ",  " + (mChargingWattage / 1000) + " mW";
-                }
+                String indication = computePowerIndication();
                 if (animate) {
-                    animateText(mTextView, powerIndication);
+                    animateText(mTextView, indication);
                 } else {
-                    mTextView.switchIndication(powerIndication);
+                    mTextView.switchIndication(indication);
                 }
-            } else if (!TextUtils.isEmpty(trustManagedIndication)
-                    && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId)
-                    && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
-                mTextView.switchIndication(trustManagedIndication);
             } else {
-                mTextView.switchIndication(mRestingIndication);
+                String percentage = NumberFormat.getPercentInstance()
+                        .format(mBatteryLevel / 100f);
+                mTextView.switchIndication(percentage);
             }
-            mTextView.setTextColor(isError ? Utils.getColorError(mContext)
-                    : mInitialTextColorState);
+            return;
+        }
+
+        int userId = KeyguardUpdateMonitor.getCurrentUser();
+        String trustGrantedIndication = getTrustGrantedIndication();
+        String trustManagedIndication = getTrustManagedIndication();
+
+        String powerIndication = null;
+        if (mPowerPluggedIn || mEnableBatteryDefender) {
+            powerIndication = computePowerIndication();
+        }
+
+        // Some cases here might need to hide the indication (if the battery is not present)
+        boolean hideIndication = false;
+        boolean isError = false;
+        if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
+            mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
+        } else if (!TextUtils.isEmpty(mTransientIndication)) {
+            if (powerIndication != null && !mTransientIndication.equals(powerIndication)) {
+                String indication = mContext.getResources().getString(
+                                R.string.keyguard_indication_trust_unlocked_plugged_in,
+                                mTransientIndication, powerIndication);
+                mTextView.switchIndication(indication);
+                hideIndication = !mBatteryPresent;
+            } else {
+                mTextView.switchIndication(mTransientIndication);
+            }
+            isError = mTransientTextIsError;
+        } else if (!TextUtils.isEmpty(trustGrantedIndication)
+                && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
+            if (powerIndication != null) {
+                String indication = mContext.getResources().getString(
+                                R.string.keyguard_indication_trust_unlocked_plugged_in,
+                                trustGrantedIndication, powerIndication);
+                mTextView.switchIndication(indication);
+                hideIndication = !mBatteryPresent;
+            } else {
+                mTextView.switchIndication(trustGrantedIndication);
+            }
+        } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+            mTextView.switchIndication(mAlignmentIndication);
+            isError = true;
+            hideIndication = !mBatteryPresent;
+        } else if (mPowerPluggedIn || mEnableBatteryDefender) {
+            if (DEBUG_CHARGING_SPEED) {
+                powerIndication += ",  " + (mChargingWattage / 1000) + " mW";
+            }
+            if (animate) {
+                animateText(mTextView, powerIndication);
+            } else {
+                mTextView.switchIndication(powerIndication);
+            }
+            hideIndication = !mBatteryPresent;
+        } else if (!TextUtils.isEmpty(trustManagedIndication)
+                && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId)
+                && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
+            mTextView.switchIndication(trustManagedIndication);
+        } else {
+            mTextView.switchIndication(mRestingIndication);
+        }
+        mTextView.setTextColor(isError ? Utils.getColorError(mContext)
+                : mInitialTextColorState);
+        if (hideIndication) {
+            mIndicationArea.setVisibility(View.GONE);
         }
     }
 
@@ -654,6 +672,7 @@
         pw.println("  mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
         pw.println("  mDozing: " + mDozing);
         pw.println("  mBatteryLevel: " + mBatteryLevel);
+        pw.println("  mBatteryPresent: " + mBatteryPresent);
         pw.println("  mTextView.getText(): " + (mTextView == null ? null : mTextView.getText()));
         pw.println("  computePowerIndication(): " + computePowerIndication());
     }
@@ -694,6 +713,7 @@
             mBatteryLevel = status.level;
             mBatteryOverheated = status.isOverheated();
             mEnableBatteryDefender = mBatteryOverheated && status.isPluggedIn();
+            mBatteryPresent = status.present;
             try {
                 mChargingTimeRemaining = mPowerPluggedIn
                         ? mBatteryInfo.computeChargeTimeRemaining() : -1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 7b25853..38f96f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.Interpolators
 import com.android.systemui.R
+import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
@@ -106,7 +107,7 @@
     private var velocityTracker: VelocityTracker? = null
 
     private val isFalseTouch: Boolean
-        get() = falsingManager.isFalseTouch
+        get() = falsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN)
     var qsExpanded: Boolean = false
     var pulseExpandAbortListener: Runnable? = null
     var bouncerShowing: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 01d3103..e5a960e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -83,6 +83,9 @@
     public static final int STATE_DOT = 1;
     public static final int STATE_HIDDEN = 2;
 
+    /** Maximum allowed width or height for an icon drawable */
+    private static final int MAX_IMAGE_SIZE = 500;
+
     private static final String TAG = "StatusBarIconView";
     private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
             = new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -378,6 +381,13 @@
             Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
             return false;
         }
+
+        if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE
+                || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) {
+            Log.w(TAG, "Drawable is too large " + mIcon);
+            return false;
+        }
+
         if (withClear) {
             setImageDrawable(null);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index d7d09e0..ac528b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -209,7 +209,7 @@
                 || (isFastNonDismissGesture && isAbleToShowMenu);
         int menuSnapTarget = menuRow.getMenuSnapTarget();
         boolean isNonFalseMenuRevealingGesture =
-                !isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu;
+                !isFalseGesture() && isMenuRevealingGestureAwayFromMenu;
         if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture)
                 && menuSnapTarget != 0) {
             // Menu has not been snapped to previously and this is menu revealing gesture
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 603679a..14f8363 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -560,20 +560,21 @@
         if (mVocab != null) {
             app = mVocab.getOrDefault(mPackageName, -1);
         }
-        // Check if we are within the tightest bounds beyond which
-        // we would not need to run the ML model.
-        boolean withinRange = x <= mMLEnableWidth + mLeftInset
-                || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset);
-        if (!withinRange) {
+
+        // Denotes whether we should proceed with the gesture. Even if it is false, we may want to
+        // log it assuming it is not invalid due to exclusion.
+        boolean withinRange = x < mEdgeWidthLeft + mLeftInset
+                || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset);
+        if (withinRange) {
             int results = -1;
-            if (mUseMLModel && (results = getBackGesturePredictionsCategory(x, y, app)) != -1) {
-                withinRange = results == 1;
-            } else {
-                // Denotes whether we should proceed with the gesture.
-                // Even if it is false, we may want to log it assuming
-                // it is not invalid due to exclusion.
-                withinRange = x <= mEdgeWidthLeft + mLeftInset
-                        || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset);
+
+            // Check if we are within the tightest bounds beyond which we would not need to run the
+            // ML model
+            boolean withinMinRange = x < mMLEnableWidth + mLeftInset
+                    || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset);
+            if (!withinMinRange && mUseMLModel
+                    && (results = getBackGesturePredictionsCategory(x, y, app)) != -1) {
+                withinRange = (results == 1);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 858023d..ba94202 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -27,6 +27,7 @@
 
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -317,7 +318,9 @@
         // We snap back if the current translation is not far enough
         boolean snapBack = false;
         if (mCallback.needsAntiFalsing()) {
-            snapBack = snapBack || mFalsingManager.isFalseTouch();
+            snapBack = snapBack || mFalsingManager.isFalseTouch(
+                    mTargetedView == mRightIcon
+                            ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
         }
         snapBack = snapBack || isBelowFalsingThreshold();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 799e16c..53cbf02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,6 +18,7 @@
 
 import static android.view.View.GONE;
 
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 
@@ -69,6 +70,7 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.fragments.FragmentHostManager;
@@ -1204,7 +1206,7 @@
     }
 
     private boolean flingExpandsQs(float vel) {
-        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
+        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch(QUICK_SETTINGS)) {
             return false;
         }
         if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
@@ -1214,12 +1216,12 @@
         }
     }
 
-    private boolean isFalseTouch() {
+    private boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
         if (!mKeyguardAffordanceHelperCallback.needsAntiFalsing()) {
             return false;
         }
         if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
+            return mFalsingManager.isFalseTouch(interactionType);
         }
         return !mQsTouchAboveFalsingThreshold;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index e942d85..a4f9b06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+import static com.android.systemui.classifier.Classifier.UNLOCK;
+
 import static java.lang.Float.isNaN;
 
 import android.animation.Animator;
@@ -41,6 +45,7 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.classifier.Classifier;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -397,7 +402,12 @@
                 mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
                 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
             }
-            fling(vel, expand, isFalseTouch(x, y));
+            @Classifier.InteractionType int interactionType = vel > 0
+                    ? QUICK_SETTINGS : (
+                            mKeyguardStateController.canDismissLockScreen()
+                                    ? UNLOCK : BOUNCER_UNLOCK);
+
+            fling(vel, expand, isFalseTouch(x, y, interactionType));
             onTrackingStopped(expand);
             mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
             if (mUpdateFlingOnLayout) {
@@ -492,7 +502,11 @@
             return true;
         }
 
-        if (isFalseTouch(x, y)) {
+        @Classifier.InteractionType int interactionType = vel > 0
+                ? QUICK_SETTINGS : (
+                        mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
+
+        if (isFalseTouch(x, y, interactionType)) {
             return true;
         }
         if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
@@ -511,12 +525,13 @@
      * @param y the final y-coordinate when the finger was lifted
      * @return whether this motion should be regarded as a false touch
      */
-    private boolean isFalseTouch(float x, float y) {
+    private boolean isFalseTouch(float x, float y,
+            @Classifier.InteractionType int interactionType) {
         if (!mStatusBar.isFalsingThresholdNeeded()) {
             return false;
         }
         if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
+            return mFalsingManager.isFalseTouch(interactionType);
         }
         if (!mTouchAboveFalsingThreshold) {
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index e5a4679..5009fce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -97,6 +97,9 @@
         default void onPowerSaveChanged(boolean isPowerSave) {
         }
 
+        default void onBatteryUnknownStateChanged(boolean isUnknown) {
+        }
+
         default void onReverseChanged(boolean isReverse, int level, String name) {
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index d30f01a..ea79c24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.os.BatteryManager.EXTRA_PRESENT;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -70,6 +72,7 @@
     protected int mLevel;
     protected boolean mPluggedIn;
     protected boolean mCharging;
+    private boolean mStateUnknown = false;
     private boolean mCharged;
     protected boolean mPowerSave;
     private boolean mAodPowerSave;
@@ -126,6 +129,7 @@
         pw.print("  mCharging="); pw.println(mCharging);
         pw.print("  mCharged="); pw.println(mCharged);
         pw.print("  mPowerSave="); pw.println(mPowerSave);
+        pw.print("  mStateUnknown="); pw.println(mStateUnknown);
     }
 
     @Override
@@ -139,8 +143,11 @@
             mChangeCallbacks.add(cb);
         }
         if (!mHasReceivedBattery) return;
+
+        // Make sure new callbacks get the correct initial state
         cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
         cb.onPowerSaveChanged(mPowerSave);
+        cb.onBatteryUnknownStateChanged(mStateUnknown);
     }
 
     @Override
@@ -168,6 +175,13 @@
             mWirelessCharging = mCharging && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
                     == BatteryManager.BATTERY_PLUGGED_WIRELESS;
 
+            boolean present = intent.getBooleanExtra(EXTRA_PRESENT, true);
+            boolean unknown = !present;
+            if (unknown != mStateUnknown) {
+                mStateUnknown = unknown;
+                fireBatteryUnknownStateChanged();
+            }
+
             fireBatteryLevelChanged();
         } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
             updatePowerSave();
@@ -316,6 +330,15 @@
         }
     }
 
+    private void fireBatteryUnknownStateChanged() {
+        synchronized (mChangeCallbacks) {
+            final int n = mChangeCallbacks.size();
+            for (int i = 0; i < n; i++) {
+                mChangeCallbacks.get(i).onBatteryUnknownStateChanged(mStateUnknown);
+            }
+        }
+    }
+
     private void firePowerSaveChanged() {
         synchronized (mChangeCallbacks) {
             final int N = mChangeCallbacks.size();
@@ -340,6 +363,7 @@
             String level = args.getString("level");
             String plugged = args.getString("plugged");
             String powerSave = args.getString("powersave");
+            String present = args.getString("present");
             if (level != null) {
                 mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
             }
@@ -350,6 +374,10 @@
                 mPowerSave = powerSave.equals("true");
                 firePowerSaveChanged();
             }
+            if (present != null) {
+                mStateUnknown = !present.equals("true");
+                fireBatteryUnknownStateChanged();
+            }
             fireBatteryLevelChanged();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
new file mode 100644
index 0000000..92e5b78
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 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.statusbar.policy
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import com.android.systemui.R
+import com.android.systemui.util.concurrency.DelayableExecutor
+import javax.inject.Inject
+
+/**
+ * Listens for important battery states and sends non-dismissible system notifications if there is a
+ * problem
+ */
+class BatteryStateNotifier @Inject constructor(
+    val controller: BatteryController,
+    val noMan: NotificationManager,
+    val delayableExecutor: DelayableExecutor,
+    val context: Context
+) : BatteryController.BatteryStateChangeCallback {
+    var stateUnknown = false
+
+    fun startListening() {
+        controller.addCallback(this)
+    }
+
+    fun stopListening() {
+        controller.removeCallback(this)
+    }
+
+    override fun onBatteryUnknownStateChanged(isUnknown: Boolean) {
+        stateUnknown = isUnknown
+        if (stateUnknown) {
+            val channel = NotificationChannel("battery_status", "Battery status",
+                    NotificationManager.IMPORTANCE_DEFAULT)
+            noMan.createNotificationChannel(channel)
+
+            val intent = Intent(Intent.ACTION_VIEW,
+                    Uri.parse(context.getString(R.string.config_batteryStateUnknownUrl)))
+            val pi = PendingIntent.getActivity(context, 0, intent, 0)
+
+            val builder = Notification.Builder(context, channel.id)
+                    .setAutoCancel(false)
+                    .setContentTitle(
+                            context.getString(R.string.battery_state_unknown_notification_title))
+                    .setContentText(
+                            context.getString(R.string.battery_state_unknown_notification_text))
+                    .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                    .setContentIntent(pi)
+                    .setAutoCancel(true)
+                    .setOngoing(true)
+
+            noMan.notify(TAG, ID, builder.build())
+        } else {
+            scheduleNotificationCancel()
+        }
+    }
+
+    private fun scheduleNotificationCancel() {
+        val r = {
+            if (!stateUnknown) {
+                noMan.cancel(ID)
+            }
+        }
+        delayableExecutor.executeDelayed(r, DELAY_MILLIS)
+    }
+}
+
+private const val TAG = "BatteryStateNotifier"
+private const val ID = 666
+private const val DELAY_MILLIS: Long = 4 * 60 * 60 * 1000
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 08cd6e3..8d77c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -33,7 +33,7 @@
     static final String REASON_WRAP = "wrap";
 
     /**
-     * Default wake-lock timeout, to avoid battery regressions.
+     * Default wake-lock timeout in milliseconds, to avoid battery regressions.
      */
     long DEFAULT_MAX_TIMEOUT = 20000;
 
@@ -104,6 +104,7 @@
                 if (count == null) {
                     Log.wtf(TAG, "Releasing WakeLock with invalid reason: " + why,
                             new Throwable());
+                    return;
                 } else if (count == 1) {
                     mActiveClients.remove(why);
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index dd4ea57..20e09a2 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -24,12 +24,12 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IDisplayWindowInsetsController;
+import android.view.IWindowManager;
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
@@ -39,11 +39,15 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import androidx.annotation.BinderThread;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.view.IInputMethodManager;
 import com.android.systemui.TransactionPool;
 import com.android.systemui.dagger.qualifiers.Main;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -66,20 +70,22 @@
     private static final int DIRECTION_HIDE = 2;
     private static final int FLOATING_IME_BOTTOM_INSET = -80;
 
-    SystemWindows mSystemWindows;
-    final Handler mHandler;
+    protected final IWindowManager mWmService;
+    protected final Executor mMainExecutor;
     final TransactionPool mTransactionPool;
+    final DisplayController mDisplayController;
 
     final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
 
     final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
 
     @Inject
-    public DisplayImeController(SystemWindows syswin, DisplayController displayController,
-            @Main Handler mainHandler, TransactionPool transactionPool) {
-        mHandler = mainHandler;
-        mSystemWindows = syswin;
+    public DisplayImeController(IWindowManager wmService, DisplayController displayController,
+            @Main Executor mainExecutor, TransactionPool transactionPool) {
+        mWmService = wmService;
+        mMainExecutor = mainExecutor;
         mTransactionPool = transactionPool;
+        mDisplayController = displayController;
         displayController.addDisplayWindowListener(this);
     }
 
@@ -88,12 +94,8 @@
         // Add's a system-ui window-manager specifically for ime. This type is special because
         // WM will defer IME inset handling to it in multi-window scenarious.
         PerDisplay pd = new PerDisplay(displayId,
-                mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation());
-        try {
-            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Unable to set insets controller on display " + displayId);
-        }
+                mDisplayController.getDisplayLayout(displayId).rotation());
+        pd.register();
         mImePerDisplay.put(displayId, pd);
     }
 
@@ -103,7 +105,7 @@
         if (pd == null) {
             return;
         }
-        if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()
+        if (mDisplayController.getDisplayLayout(displayId).rotation()
                 != pd.mRotation && isImeShowing(displayId)) {
             pd.startAnimation(true, false /* forceRestart */);
         }
@@ -112,7 +114,7 @@
     @Override
     public void onDisplayRemoved(int displayId) {
         try {
-            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
+            mWmService.setDisplayWindowInsetsController(displayId, null);
         } catch (RemoteException e) {
             Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
         }
@@ -180,9 +182,12 @@
         }
     }
 
-    class PerDisplay extends IDisplayWindowInsetsController.Stub {
+    /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */
+    public class PerDisplay {
         final int mDisplayId;
         final InsetsState mInsetsState = new InsetsState();
+        protected final DisplayWindowInsetsControllerImpl mInsetsControllerImpl =
+                new DisplayWindowInsetsControllerImpl();
         InsetsSourceControl mImeSourceControl = null;
         int mAnimationDirection = DIRECTION_NONE;
         ValueAnimator mAnimation = null;
@@ -196,26 +201,32 @@
             mRotation = initialRotation;
         }
 
-        @Override
-        public void insetsChanged(InsetsState insetsState) {
-            mHandler.post(() -> {
-                if (mInsetsState.equals(insetsState)) {
-                    return;
-                }
-
-                final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
-                final Rect newFrame = newSource.getFrame();
-                final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
-
-                mInsetsState.set(insetsState, true /* copySources */);
-                if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
-                    if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
-                    startAnimation(mImeShowing, true /* forceRestart */);
-                }
-            });
+        public void register() {
+            try {
+                mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId);
+            }
         }
 
-        @Override
+        public void insetsChanged(InsetsState insetsState) {
+            if (mInsetsState.equals(insetsState)) {
+                return;
+            }
+
+            mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
+
+            final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
+            final Rect newFrame = newSource.getFrame();
+            final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
+
+            mInsetsState.set(insetsState, true /* copySources */);
+            if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
+                if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
+                startAnimation(mImeShowing, true /* forceRestart */);
+            }
+        }
+
         public void insetsControlChanged(InsetsState insetsState,
                 InsetsSourceControl[] activeControls) {
             insetsChanged(insetsState);
@@ -225,27 +236,25 @@
                         continue;
                     }
                     if (activeControl.getType() == InsetsState.ITYPE_IME) {
-                        mHandler.post(() -> {
-                            final Point lastSurfacePosition = mImeSourceControl != null
-                                    ? mImeSourceControl.getSurfacePosition() : null;
-                            final boolean positionChanged =
-                                    !activeControl.getSurfacePosition().equals(lastSurfacePosition);
-                            final boolean leashChanged =
-                                    !haveSameLeash(mImeSourceControl, activeControl);
-                            mImeSourceControl = activeControl;
-                            if (mAnimation != null) {
-                                if (positionChanged) {
-                                    startAnimation(mImeShowing, true /* forceRestart */);
-                                }
-                            } else {
-                                if (leashChanged) {
-                                    applyVisibilityToLeash();
-                                }
-                                if (!mImeShowing) {
-                                    removeImeSurface();
-                                }
+                        final Point lastSurfacePosition = mImeSourceControl != null
+                                ? mImeSourceControl.getSurfacePosition() : null;
+                        final boolean positionChanged =
+                                !activeControl.getSurfacePosition().equals(lastSurfacePosition);
+                        final boolean leashChanged =
+                                !haveSameLeash(mImeSourceControl, activeControl);
+                        mImeSourceControl = activeControl;
+                        if (mAnimation != null) {
+                            if (positionChanged) {
+                                startAnimation(mImeShowing, true /* forceRestart */);
                             }
-                        });
+                        } else {
+                            if (leashChanged) {
+                                applyVisibilityToLeash();
+                            }
+                            if (!mImeShowing) {
+                                removeImeSurface();
+                            }
+                        }
                     }
                 }
             }
@@ -265,25 +274,22 @@
             }
         }
 
-        @Override
         public void showInsets(int types, boolean fromIme) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
-            mHandler.post(() -> startAnimation(true /* show */, false /* forceRestart */));
+            startAnimation(true /* show */, false /* forceRestart */);
         }
 
-        @Override
         public void hideInsets(int types, boolean fromIme) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
-            mHandler.post(() -> startAnimation(false /* show */, false /* forceRestart */));
+            startAnimation(false /* show */, false /* forceRestart */);
         }
 
-        @Override
         public void topFocusedWindowChanged(String packageName) {
             // no-op
         }
@@ -294,7 +300,7 @@
         private void setVisibleDirectly(boolean visible) {
             mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
             try {
-                mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+                mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
             } catch (RemoteException e) {
             }
         }
@@ -313,7 +319,7 @@
             // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar
             // frame height so any reported frame that is <= nav-bar frame height is assumed to
             // be floating.
-            return frame.height() <= mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId)
+            return frame.height() <= mDisplayController.getDisplayLayout(mDisplayId)
                     .navBarFrameHeight();
         }
 
@@ -329,7 +335,7 @@
                 // pretend the ime has some size just below the screen.
                 mImeFrame.set(newFrame);
                 final int floatingInset = (int) (
-                        mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId).density()
+                        mDisplayController.getDisplayLayout(mDisplayId).density()
                                 * FLOATING_IME_BOTTOM_INSET);
                 mImeFrame.bottom -= floatingInset;
             } else if (newFrame.height() != 0) {
@@ -446,6 +452,47 @@
                 setVisibleDirectly(true /* visible */);
             }
         }
+
+        @VisibleForTesting
+        @BinderThread
+        public class DisplayWindowInsetsControllerImpl
+                extends IDisplayWindowInsetsController.Stub {
+            @Override
+            public void topFocusedWindowChanged(String packageName) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.topFocusedWindowChanged(packageName);
+                });
+            }
+
+            @Override
+            public void insetsChanged(InsetsState insetsState) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.insetsChanged(insetsState);
+                });
+            }
+
+            @Override
+            public void insetsControlChanged(InsetsState insetsState,
+                    InsetsSourceControl[] activeControls) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.insetsControlChanged(insetsState, activeControls);
+                });
+            }
+
+            @Override
+            public void showInsets(int types, boolean fromIme) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.showInsets(types, fromIme);
+                });
+            }
+
+            @Override
+            public void hideInsets(int types, boolean fromIme) throws RemoteException {
+                mMainExecutor.execute(() -> {
+                    PerDisplay.this.hideInsets(types, fromIme);
+                });
+            }
+        }
     }
 
     void removeImeSurface() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 1ad8856..8fc2d1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -44,11 +44,19 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.face.FaceManager;
 import android.os.Handler;
 import android.os.PowerManager;
+import android.os.UserHandle;
 import android.service.dreams.IDreamManager;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
@@ -77,6 +85,7 @@
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -89,6 +98,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.tests.R;
 import com.android.systemui.util.FloatingContentCoordinator;
 
 import com.google.common.collect.ImmutableList;
@@ -1023,6 +1033,74 @@
     }
 
     /**
+     * Verifies that the package manager for the user is used when loading info for the bubble.
+     */
+    @Test
+    public void test_bubbleViewInfoGetPackageForUser() throws Exception {
+        final int workProfileUserId = 10;
+        final UserHandle workUser = new UserHandle(workProfileUserId);
+        final String workPkg = "work.pkg";
+
+        final Bubble bubble = createBubble(workProfileUserId, workPkg);
+        assertEquals(workProfileUserId, bubble.getUser().getIdentifier());
+
+        final Context context = setUpContextWithPackageManager(workPkg, null /* AppInfo */);
+        when(context.getResources()).thenReturn(mContext.getResources());
+        final Context userContext = setUpContextWithPackageManager(workPkg,
+                mock(ApplicationInfo.class));
+
+        // If things are working correctly, StatusBar.getPackageManagerForUser will call this
+        when(context.createPackageContextAsUser(eq(workPkg), anyInt(), eq(workUser)))
+                .thenReturn(userContext);
+
+        BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
+                mBubbleController.getStackView(),
+                new BubbleIconFactory(mContext),
+                bubble,
+                true /* skipInflation */);
+
+        verify(userContext, times(1)).getPackageManager();
+        verify(context, times(1)).createPackageContextAsUser(eq(workPkg),
+                eq(Context.CONTEXT_RESTRICTED),
+                eq(workUser));
+        assertNotNull(info);
+    }
+
+    /** Creates a bubble using the userId and package. */
+    private Bubble createBubble(int userId, String pkg) {
+        final UserHandle userHandle = new UserHandle(userId);
+        NotificationEntry workEntry = new NotificationEntryBuilder()
+                .setPkg(pkg)
+                .setUser(userHandle)
+                .build();
+        workEntry.setBubbleMetadata(getMetadata());
+        workEntry.setFlagBubble(true);
+
+        return new Bubble(workEntry,
+                null,
+                mock(BubbleController.PendingIntentCanceledListener.class));
+    }
+
+    /** Creates a context that will return a PackageManager with specific AppInfo. */
+    private Context setUpContextWithPackageManager(String pkg, ApplicationInfo info)
+            throws Exception {
+        final PackageManager pm = mock(PackageManager.class);
+        when(pm.getApplicationInfo(eq(pkg), anyInt())).thenReturn(info);
+
+        if (info != null) {
+            Drawable d = mock(Drawable.class);
+            when(d.getBounds()).thenReturn(new Rect());
+            when(pm.getApplicationIcon(anyString())).thenReturn(d);
+            when(pm.getUserBadgedIcon(any(), any())).thenReturn(d);
+        }
+
+        final Context context = mock(Context.class);
+        when(context.getPackageName()).thenReturn(pkg);
+        when(context.getPackageManager()).thenReturn(pm);
+        return context;
+    }
+
+    /**
      * Sets the bubble metadata flags for this entry. These ]flags are normally set by
      * NotificationManagerService when the notification is sent, however, these tests do not
      * go through that path so we set them explicitly when testing.
@@ -1038,4 +1116,13 @@
         }
         bubbleMetadata.setFlags(flags);
     }
+
+    private Notification.BubbleMetadata getMetadata() {
+        Intent target = new Intent(mContext, BubblesTestActivity.class);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0);
+
+        return new Notification.BubbleMetadata.Builder(bubbleIntent,
+                Icon.createWithResource(mContext, R.drawable.android))
+                .build();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index ebd2c3a..35e3bb3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -77,8 +77,6 @@
     @Mock
     private Consumer<Boolean> mProxCallback;
     @Mock
-    private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy;
-    @Mock
     private TriggerSensor mTriggerSensor;
     @Mock
     private DozeLog mDozeLog;
@@ -110,7 +108,7 @@
 
     @Test
     public void testSensorDebounce() {
-        mDozeSensors.setListening(true);
+        mDozeSensors.setListening(true, true);
 
         mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
         mTestableLooper.processAllMessages();
@@ -128,7 +126,7 @@
     @Test
     public void testSetListening_firstTrue_registerSettingsObserver() {
         verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
-        mDozeSensors.setListening(true);
+        mDozeSensors.setListening(true, true);
 
         verify(mTriggerSensor).registerSettingsObserver(any(ContentObserver.class));
     }
@@ -136,8 +134,8 @@
     @Test
     public void testSetListening_twiceTrue_onlyRegisterSettingsObserverOnce() {
         verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
-        mDozeSensors.setListening(true);
-        mDozeSensors.setListening(true);
+        mDozeSensors.setListening(true, true);
+        mDozeSensors.setListening(true, true);
 
         verify(mTriggerSensor, times(1)).registerSettingsObserver(any(ContentObserver.class));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 655f933..be4fdb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -149,6 +149,7 @@
 
         clearInvocations(mSensors);
         mTriggers.transitionTo(DozeMachine.State.DOZE_PULSING, DozeMachine.State.DOZE_PULSE_DONE);
+        mTriggers.transitionTo(DozeMachine.State.DOZE_PULSE_DONE, DozeMachine.State.DOZE_AOD);
         waitForSensorManager();
         verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index b47ee29..2f99b2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -2,6 +2,7 @@
 
 import android.app.Notification.MediaStyle
 import android.app.PendingIntent
+import android.graphics.Bitmap
 import android.media.MediaDescription
 import android.media.MediaMetadata
 import android.media.session.MediaController
@@ -246,4 +247,26 @@
 
         verify(listener).onMediaDataRemoved(eq(KEY))
     }
+
+    @Test
+    fun testBadArtwork_doesNotUse() {
+        // WHEN notification has a too-small artwork
+        val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        val notif = SbnBuilder().run {
+            setPkg(PACKAGE_NAME)
+            modifyNotification(context).also {
+                it.setSmallIcon(android.R.drawable.ic_media_pause)
+                it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                it.setLargeIcon(artwork)
+            }
+            build()
+        }
+        mediaDataManager.onNotificationAdded(KEY, notif)
+
+        // THEN it loads and uses the default background color
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value!!.backgroundColor).isEqualTo(DEFAULT_COLOR)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
deleted file mode 100644
index 4ba29e6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2020 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.privacy
-
-import android.os.UserManager
-import android.provider.DeviceConfig
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.DeviceConfigProxy
-import com.android.systemui.util.DeviceConfigProxyFake
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class PrivacyItemControllerFlagsTest : SysuiTestCase() {
-    companion object {
-        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
-        fun <T> eq(value: T): T = Mockito.eq(value) ?: value
-        fun <T> any(): T = Mockito.any<T>()
-
-        private const val ALL_INDICATORS =
-                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
-        private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
-    }
-
-    @Mock
-    private lateinit var appOpsController: AppOpsController
-    @Mock
-    private lateinit var callback: PrivacyItemController.Callback
-    @Mock
-    private lateinit var userManager: UserManager
-    @Mock
-    private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock
-    private lateinit var dumpManager: DumpManager
-
-    private lateinit var privacyItemController: PrivacyItemController
-    private lateinit var executor: FakeExecutor
-    private lateinit var deviceConfigProxy: DeviceConfigProxy
-
-    fun PrivacyItemController(): PrivacyItemController {
-        return PrivacyItemController(
-                appOpsController,
-                executor,
-                executor,
-                broadcastDispatcher,
-                deviceConfigProxy,
-                userManager,
-                dumpManager
-        )
-    }
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        executor = FakeExecutor(FakeSystemClock())
-        deviceConfigProxy = DeviceConfigProxyFake()
-
-        privacyItemController = PrivacyItemController()
-        privacyItemController.addCallback(callback)
-
-        executor.runAllReady()
-    }
-
-    @Test
-    fun testNotListeningByDefault() {
-        assertFalse(privacyItemController.allIndicatorsAvailable)
-        assertFalse(privacyItemController.micCameraAvailable)
-
-        verify(appOpsController, never()).addCallback(any(), any())
-    }
-
-    @Test
-    fun testMicCameraChanged() {
-        changeMicCamera(true)
-        executor.runAllReady()
-
-        verify(callback).onFlagMicCameraChanged(true)
-        verify(callback, never()).onFlagAllChanged(anyBoolean())
-
-        assertTrue(privacyItemController.micCameraAvailable)
-        assertFalse(privacyItemController.allIndicatorsAvailable)
-    }
-
-    @Test
-    fun testAllChanged() {
-        changeAll(true)
-        executor.runAllReady()
-
-        verify(callback).onFlagAllChanged(true)
-        verify(callback, never()).onFlagMicCameraChanged(anyBoolean())
-
-        assertTrue(privacyItemController.allIndicatorsAvailable)
-        assertFalse(privacyItemController.micCameraAvailable)
-    }
-
-    @Test
-    fun testBothChanged() {
-        changeAll(true)
-        changeMicCamera(true)
-        executor.runAllReady()
-
-        verify(callback, atLeastOnce()).onFlagAllChanged(true)
-        verify(callback, atLeastOnce()).onFlagMicCameraChanged(true)
-
-        assertTrue(privacyItemController.allIndicatorsAvailable)
-        assertTrue(privacyItemController.micCameraAvailable)
-    }
-
-    @Test
-    fun testAll_listeningToAll() {
-        changeAll(true)
-        executor.runAllReady()
-
-        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
-    }
-
-    @Test
-    fun testMicCamera_listening() {
-        changeMicCamera(true)
-        executor.runAllReady()
-
-        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
-    }
-
-    @Test
-    fun testAll_listening() {
-        changeAll(true)
-        executor.runAllReady()
-
-        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
-    }
-
-    @Test
-    fun testAllFalse_notListening() {
-        changeAll(true)
-        executor.runAllReady()
-        changeAll(false)
-        executor.runAllReady()
-
-        verify(appOpsController).removeCallback(any(), any())
-    }
-
-    @Test
-    fun testSomeListening_stillListening() {
-        changeAll(true)
-        changeMicCamera(true)
-        executor.runAllReady()
-        changeAll(false)
-        executor.runAllReady()
-
-        verify(appOpsController, never()).removeCallback(any(), any())
-    }
-
-    @Test
-    fun testAllDeleted_stopListening() {
-        changeAll(true)
-        executor.runAllReady()
-        changeAll(null)
-        executor.runAllReady()
-
-        verify(appOpsController).removeCallback(any(), any())
-    }
-
-    @Test
-    fun testMicDeleted_stopListening() {
-        changeMicCamera(true)
-        executor.runAllReady()
-        changeMicCamera(null)
-        executor.runAllReady()
-
-        verify(appOpsController).removeCallback(any(), any())
-    }
-
-    private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
-    private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
-
-    private fun changeProperty(name: String, value: Boolean?) {
-        deviceConfigProxy.setProperty(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                name,
-                value?.toString(),
-                false
-        )
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
deleted file mode 100644
index 5c5df26..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2020 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.privacy
-
-import android.app.ActivityManager
-import android.app.AppOpsManager
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.DeviceConfig
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.appops.AppOpItem
-import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.DeviceConfigProxy
-import com.android.systemui.util.DeviceConfigProxyFake
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import org.hamcrest.Matchers.hasItem
-import org.hamcrest.Matchers.not
-import org.hamcrest.Matchers.nullValue
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertThat
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyList
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-@RunWithLooper
-class PrivacyItemControllerTest : SysuiTestCase() {
-
-    companion object {
-        val CURRENT_USER_ID = ActivityManager.getCurrentUser()
-        val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE
-        const val TEST_PACKAGE_NAME = "test"
-
-        private const val ALL_INDICATORS =
-                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
-        private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
-        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
-        fun <T> eq(value: T): T = Mockito.eq(value) ?: value
-        fun <T> any(): T = Mockito.any<T>()
-    }
-
-    @Mock
-    private lateinit var appOpsController: AppOpsController
-    @Mock
-    private lateinit var callback: PrivacyItemController.Callback
-    @Mock
-    private lateinit var userManager: UserManager
-    @Mock
-    private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock
-    private lateinit var dumpManager: DumpManager
-    @Captor
-    private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
-    @Captor
-    private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback>
-
-    private lateinit var privacyItemController: PrivacyItemController
-    private lateinit var executor: FakeExecutor
-    private lateinit var deviceConfigProxy: DeviceConfigProxy
-
-    fun PrivacyItemController(): PrivacyItemController {
-        return PrivacyItemController(
-                appOpsController,
-                executor,
-                executor,
-                broadcastDispatcher,
-                deviceConfigProxy,
-                userManager,
-                dumpManager
-        )
-    }
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        executor = FakeExecutor(FakeSystemClock())
-        deviceConfigProxy = DeviceConfigProxyFake()
-
-        changeAll(true)
-
-        doReturn(listOf(object : UserInfo() {
-            init {
-                id = CURRENT_USER_ID
-            }
-        })).`when`(userManager).getProfiles(anyInt())
-
-        privacyItemController = PrivacyItemController()
-    }
-
-    @Test
-    fun testSetListeningTrueByAddingCallback() {
-        privacyItemController.addCallback(callback)
-        executor.runAllReady()
-        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
-                any())
-        verify(callback).onPrivacyItemsChanged(anyList())
-    }
-
-    @Test
-    fun testSetListeningFalseByRemovingLastCallback() {
-        privacyItemController.addCallback(callback)
-        executor.runAllReady()
-        verify(appOpsController, never()).removeCallback(any(),
-                any())
-        privacyItemController.removeCallback(callback)
-        executor.runAllReady()
-        verify(appOpsController).removeCallback(eq(PrivacyItemController.OPS),
-                any())
-        verify(callback).onPrivacyItemsChanged(emptyList())
-    }
-
-    @Test
-    fun testDistinctItems() {
-        doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
-                AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-
-        privacyItemController.addCallback(callback)
-        executor.runAllReady()
-        verify(callback).onPrivacyItemsChanged(capture(argCaptor))
-        assertEquals(1, argCaptor.value.size)
-    }
-
-    @Test
-    fun testRegisterReceiver_allUsers() {
-        privacyItemController.addCallback(callback)
-        executor.runAllReady()
-        verify(broadcastDispatcher, atLeastOnce()).registerReceiver(
-                eq(privacyItemController.userSwitcherReceiver), any(), eq(null), eq(UserHandle.ALL))
-        verify(broadcastDispatcher, never())
-                .unregisterReceiver(eq(privacyItemController.userSwitcherReceiver))
-    }
-
-    @Test
-    fun testReceiver_ACTION_USER_FOREGROUND() {
-        privacyItemController.userSwitcherReceiver.onReceive(context,
-                Intent(Intent.ACTION_USER_SWITCHED))
-        executor.runAllReady()
-        verify(userManager).getProfiles(anyInt())
-    }
-
-    @Test
-    fun testReceiver_ACTION_MANAGED_PROFILE_ADDED() {
-        privacyItemController.userSwitcherReceiver.onReceive(context,
-                Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE))
-        executor.runAllReady()
-        verify(userManager).getProfiles(anyInt())
-    }
-
-    @Test
-    fun testReceiver_ACTION_MANAGED_PROFILE_REMOVED() {
-        privacyItemController.userSwitcherReceiver.onReceive(context,
-                Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE))
-        executor.runAllReady()
-        verify(userManager).getProfiles(anyInt())
-    }
-
-    @Test
-    fun testAddMultipleCallbacks() {
-        val otherCallback = mock(PrivacyItemController.Callback::class.java)
-        privacyItemController.addCallback(callback)
-        executor.runAllReady()
-        verify(callback).onPrivacyItemsChanged(anyList())
-
-        privacyItemController.addCallback(otherCallback)
-        executor.runAllReady()
-        verify(otherCallback).onPrivacyItemsChanged(anyList())
-        // Adding a callback should not unnecessarily call previous ones
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun testMultipleCallbacksAreUpdated() {
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-
-        val otherCallback = mock(PrivacyItemController.Callback::class.java)
-        privacyItemController.addCallback(callback)
-        privacyItemController.addCallback(otherCallback)
-        executor.runAllReady()
-        reset(callback)
-        reset(otherCallback)
-
-        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
-        argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
-        executor.runAllReady()
-        verify(callback).onPrivacyItemsChanged(anyList())
-        verify(otherCallback).onPrivacyItemsChanged(anyList())
-    }
-
-    @Test
-    fun testRemoveCallback() {
-        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-        val otherCallback = mock(PrivacyItemController.Callback::class.java)
-        privacyItemController.addCallback(callback)
-        privacyItemController.addCallback(otherCallback)
-        executor.runAllReady()
-        executor.runAllReady()
-        reset(callback)
-        reset(otherCallback)
-
-        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
-        privacyItemController.removeCallback(callback)
-        argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
-        executor.runAllReady()
-        verify(callback, never()).onPrivacyItemsChanged(anyList())
-        verify(otherCallback).onPrivacyItemsChanged(anyList())
-    }
-
-    @Test
-    fun testListShouldNotHaveNull() {
-        doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0),
-                        AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-        privacyItemController.addCallback(callback)
-        executor.runAllReady()
-        executor.runAllReady()
-
-        verify(callback).onPrivacyItemsChanged(capture(argCaptor))
-        assertEquals(1, argCaptor.value.size)
-        assertThat(argCaptor.value, not(hasItem(nullValue())))
-    }
-
-    @Test
-    fun testListShouldBeCopy() {
-        val list = listOf(PrivacyItem(PrivacyType.TYPE_CAMERA,
-                PrivacyApplication("", TEST_UID)))
-        privacyItemController.privacyList = list
-        val privacyList = privacyItemController.privacyList
-        assertEquals(list, privacyList)
-        assertTrue(list !== privacyList)
-    }
-
-    @Test
-    fun testNotListeningWhenIndicatorsDisabled() {
-        changeAll(false)
-        privacyItemController.addCallback(callback)
-        executor.runAllReady()
-        verify(appOpsController, never()).addCallback(eq(PrivacyItemController.OPS),
-                any())
-    }
-
-    @Test
-    fun testNotSendingLocationWhenOnlyMicCamera() {
-        changeAll(false)
-        changeMicCamera(true)
-        executor.runAllReady()
-
-        doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
-                AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-
-        privacyItemController.addCallback(callback)
-        executor.runAllReady()
-
-        verify(callback).onPrivacyItemsChanged(capture(argCaptor))
-
-        assertEquals(1, argCaptor.value.size)
-        assertEquals(PrivacyType.TYPE_CAMERA, argCaptor.value[0].privacyType)
-    }
-
-    @Test
-    fun testNotUpdated_LocationChangeWhenOnlyMicCamera() {
-        doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
-                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-
-        privacyItemController.addCallback(callback)
-        changeAll(false)
-        changeMicCamera(true)
-        executor.runAllReady()
-        reset(callback) // Clean callback
-
-        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
-        argCaptorCallback.value.onActiveStateChanged(
-                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
-
-        verify(callback, never()).onPrivacyItemsChanged(any())
-    }
-
-    private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
-    private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
-
-    private fun changeProperty(name: String, value: Boolean?) {
-        deviceConfigProxy.setProperty(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                name,
-                value?.toString(),
-                false
-        )
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 052f338..91144be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -495,7 +495,7 @@
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
                 80 /* level */, BatteryManager.BATTERY_PLUGGED_WIRELESS, 100 /* health */,
-                0 /* maxChargingWattage */);
+                0 /* maxChargingWattage */, true /* present */);
 
         mController.getKeyguardCallback().onRefreshBatteryInfo(status);
         verify(mIBatteryStats).computeChargeTimeRemaining();
@@ -507,7 +507,7 @@
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
                 80 /* level */, 0 /* plugged */, 100 /* health */,
-                0 /* maxChargingWattage */);
+                0 /* maxChargingWattage */, true /* present */);
 
         mController.getKeyguardCallback().onRefreshBatteryInfo(status);
         verify(mIBatteryStats, never()).computeChargeTimeRemaining();
@@ -553,7 +553,8 @@
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
                 80 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
-                BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */);
+                BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */,
+                true /* present */);
 
         mController.getKeyguardCallback().onRefreshBatteryInfo(status);
         mController.setVisible(true);
@@ -569,7 +570,8 @@
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
                 80 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
-                BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */);
+                BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */,
+                true /* present */);
 
         mController.getKeyguardCallback().onRefreshBatteryInfo(status);
         mController.setVisible(true);
@@ -585,7 +587,8 @@
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
                 100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
-                BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */);
+                BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */,
+                true /* present */);
 
         mController.getKeyguardCallback().onRefreshBatteryInfo(status);
         mController.setVisible(true);
@@ -599,7 +602,7 @@
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
                 90 /* level */, 0 /* plugged */, BatteryManager.BATTERY_HEALTH_OVERHEAT,
-                0 /* maxChargingWattage */);
+                0 /* maxChargingWattage */, true /* present */);
 
         mController.getKeyguardCallback().onRefreshBatteryInfo(status);
         mController.setDozing(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 9971e0c..daa805a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -35,6 +35,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
@@ -123,4 +124,13 @@
         assertEquals("Transparent backgrounds should fallback to drawable color",
                 color, mIconView.getStaticDrawableColor());
     }
+
+    @Test
+    public void testGiantImageNotAllowed() {
+        Bitmap largeBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
+        Icon icon = Icon.createWithBitmap(largeBitmap);
+        StatusBarIcon largeIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+                icon, 0, 0, "");
+        assertFalse(mIconView.set(largeIcon));
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index eca48c8..e985a61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -16,7 +16,11 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.os.BatteryManager.EXTRA_PRESENT;
+
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Intent;
@@ -30,6 +34,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -93,4 +98,36 @@
         Assert.assertFalse(mBatteryController.isAodPowerSave());
     }
 
+    @Test
+    public void testBatteryPresentState_notPresent() {
+        // GIVEN a battery state callback listening for changes
+        BatteryStateChangeCallback cb = mock(BatteryStateChangeCallback.class);
+        mBatteryController.addCallback(cb);
+
+        // WHEN the state of the battery becomes unknown
+        Intent i = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        i.putExtra(EXTRA_PRESENT, false);
+        mBatteryController.onReceive(getContext(), i);
+
+        // THEN the callback is notified
+        verify(cb, atLeastOnce()).onBatteryUnknownStateChanged(true);
+    }
+
+    @Test
+    public void testBatteryPresentState_callbackAddedAfterStateChange() {
+        // GIVEN a battery state callback
+        BatteryController.BatteryStateChangeCallback cb =
+                mock(BatteryController.BatteryStateChangeCallback.class);
+
+        // GIVEN the state has changed before adding a new callback
+        Intent i = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        i.putExtra(EXTRA_PRESENT, false);
+        mBatteryController.onReceive(getContext(), i);
+
+        // WHEN a callback is added
+        mBatteryController.addCallback(cb);
+
+        // THEN it is informed about the battery state
+        verify(cb, atLeastOnce()).onBatteryUnknownStateChanged(true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
new file mode 100644
index 0000000..dcd57f1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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.statusbar.policy
+
+import android.app.NotificationManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+private fun <T> anyObject(): T {
+    return Mockito.anyObject<T>()
+}
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper()
+@SmallTest
+class BatteryStateNotifierTest : SysuiTestCase() {
+    @Mock private lateinit var batteryController: BatteryController
+    @Mock private lateinit var noMan: NotificationManager
+
+    private val clock = FakeSystemClock()
+    private val executor = FakeExecutor(clock)
+
+    private lateinit var notifier: BatteryStateNotifier
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        notifier = BatteryStateNotifier(batteryController, noMan, executor, context)
+        notifier.startListening()
+
+        context.ensureTestableResources()
+    }
+
+    @Test
+    fun testNotifyWhenStateUnknown() {
+        notifier.onBatteryUnknownStateChanged(true)
+        verify(noMan).notify(anyString(), anyInt(), anyObject())
+    }
+
+    @Test
+    fun testCancelAfterDelay() {
+        notifier.onBatteryUnknownStateChanged(true)
+        notifier.onBatteryUnknownStateChanged(false)
+
+        clock.advanceTime(DELAY_MILLIS + 1)
+        verify(noMan).cancel(anyInt())
+    }
+}
+
+// From BatteryStateNotifier.kt
+private const val DELAY_MILLIS: Long = 40 * 60 * 60 * 1000
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 0ac8f74..91f948d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -8,6 +8,13 @@
 }
 
 filegroup {
+    name: "services.core-sources-deviceconfig-interface",
+    srcs: [
+         "java/com/android/server/utils/DeviceConfigInterface.java"
+    ],
+}
+
+filegroup {
     name: "services.core-sources",
     srcs: ["java/**/*.java"],
     exclude_srcs: [":connectivity-service-srcs"],
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 65ac784..6ca8770 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1508,6 +1508,8 @@
                             if (Objects.equals(newService, oldService)) return;
 
                             Slog.i(TAG, "health: new instance registered " + mInstanceName);
+                            // #init() may be called with null callback. Skip null callbacks.
+                            if (mCallback == null) return;
                             mCallback.onRegistration(oldService, newService, mInstanceName);
                         } catch (NoSuchElementException | RemoteException ex) {
                             Slog.e(TAG, "health: Cannot get instance '" + mInstanceName
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c65b8e5..cca95ca 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@
 
 import android.Manifest;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
@@ -509,14 +510,21 @@
         }
     }
 
-    private @Nullable VolumeInfo findStorageForUuid(String volumeUuid) {
+    private @Nullable VolumeInfo findStorageForUuidAsUser(String volumeUuid,
+            @UserIdInt int userId) {
         final StorageManager storage = mContext.getSystemService(StorageManager.class);
         if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
-            return storage.findVolumeById(VolumeInfo.ID_EMULATED_INTERNAL + ";" + 0);
+            return storage.findVolumeById(VolumeInfo.ID_EMULATED_INTERNAL + ";" + userId);
         } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
             return storage.getPrimaryPhysicalVolume();
         } else {
-            return storage.findEmulatedForPrivate(storage.findVolumeByUuid(volumeUuid));
+            VolumeInfo info = storage.findVolumeByUuid(volumeUuid);
+            if (info == null) {
+                Slog.w(TAG, "findStorageForUuidAsUser cannot find volumeUuid:" + volumeUuid);
+                return null;
+            }
+            String emulatedUuid = info.getId().replace("private", "emulated") + ";" + userId;
+            return storage.findVolumeById(emulatedUuid);
         }
     }
 
@@ -2764,8 +2772,9 @@
                 return;
 
             } else {
-                from = findStorageForUuid(mPrimaryStorageUuid);
-                to = findStorageForUuid(volumeUuid);
+                int currentUserId = mCurrentUserId;
+                from = findStorageForUuidAsUser(mPrimaryStorageUuid, currentUserId);
+                to = findStorageForUuidAsUser(volumeUuid, currentUserId);
 
                 if (from == null) {
                     Slog.w(TAG, "Failing move due to missing from volume " + mPrimaryStorageUuid);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 609416d..2c2a753 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -3071,7 +3071,8 @@
                     .setContentTitle(title)
                     .setContentText(subtitle)
                     .setContentIntent(PendingIntent.getActivityAsUser(mContext, 0, intent,
-                            PendingIntent.FLAG_CANCEL_CURRENT, null, user))
+                            PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+                            null, user))
                     .build();
         installNotification(getCredentialPermissionNotificationId(
                 account, authTokenType, uid), n, packageName, user.getIdentifier());
@@ -5293,7 +5294,8 @@
                         .setContentTitle(String.format(notificationTitleFormat, account.name))
                         .setContentText(message)
                         .setContentIntent(PendingIntent.getActivityAsUser(
-                                mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT,
+                                mContext, 0, intent,
+                                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
                                 null, new UserHandle(userId)))
                         .build();
                 installNotification(id, n, packageName, userId);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ca4b9c3..9cecd8b 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4878,17 +4878,24 @@
             return true;
         }
 
-        if (r.app != null) {
+        if (r != null && r.app != null) {
             ActiveInstrumentation instr = r.app.getActiveInstrumentation();
             if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
                 return true;
             }
         }
 
-        final boolean hasAllowBackgroundActivityStartsToken = r.app != null
-                ? !r.app.mAllowBackgroundActivityStartsTokens.isEmpty() : false;
-        if (hasAllowBackgroundActivityStartsToken) {
-            return true;
+        for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
+            final ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i);
+            if (pr.uid == callingUid) {
+                if (!pr.mAllowBackgroundActivityStartsTokens.isEmpty()) {
+                    return true;
+                }
+                if (pr.getWindowProcessController()
+                        .areBackgroundActivityStartsAllowedByGracePeriodSafe()) {
+                    return true;
+                }
+            }
         }
 
         if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
@@ -4907,6 +4914,10 @@
             return true;
         }
 
+        if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) {
+            return true;
+        }
+
         final boolean isWhiteListedPackage =
                 mWhiteListAllowWhileInUsePermissionInFgs.contains(callingPackage);
         if (isWhiteListedPackage) {
@@ -4920,4 +4931,10 @@
         }
         return false;
     }
+
+    boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid,
+            String callingPackage) {
+        return shouldAllowWhileInUsePermissionInFgsLocked(
+                callingPackage, callingPid, callingUid, null, null, false);
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cc5a25a..4b3fbf0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1266,6 +1266,13 @@
     final PendingTempWhitelists mPendingTempWhitelist = new PendingTempWhitelists(this);
 
     /**
+     * List of uids that are allowed to have while-in-use permission when FGS is started from
+     * background.
+     */
+    private final FgsWhileInUseTempAllowList mFgsWhileInUseTempAllowList =
+            new FgsWhileInUseTempAllowList();
+
+    /**
      * Information about and control over application operations
      */
     final AppOpsService mAppOpsService;
@@ -2128,7 +2135,7 @@
                         0,
                         new HostingRecord("system"));
                 app.setPersistent(true);
-                app.pid = MY_PID;
+                app.pid = app.mPidForCompact = MY_PID;
                 app.getWindowProcessController().setPid(MY_PID);
                 app.maxAdj = ProcessList.SYSTEM_ADJ;
                 app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
@@ -5109,6 +5116,9 @@
         EventLogTags.writeAmProcBound(app.userId, app.pid, app.processName);
 
         app.curAdj = app.setAdj = app.verifiedAdj = ProcessList.INVALID_ADJ;
+        synchronized (mOomAdjuster.mCachedAppOptimizer) {
+            app.mSetAdjForCompact = ProcessList.INVALID_ADJ;
+        }
         mOomAdjuster.setAttachingSchedGroupLocked(app);
         app.forcingToImportant = null;
         updateProcessForegroundLocked(app, false, 0, false);
@@ -6004,9 +6014,7 @@
     }
 
     private boolean isAppBad(ApplicationInfo info) {
-        synchronized (this) {
-            return mAppErrors.isBadProcessLocked(info);
-        }
+        return mAppErrors.isBadProcess(info.processName, info.uid);
     }
 
     // NOTE: this is an internal method used by the OnShellCommand implementation only and should
@@ -19944,6 +19952,24 @@
         public boolean isPendingTopUid(int uid) {
             return mPendingStartActivityUids.isPendingTopUid(uid);
         }
+
+        @Override
+        public void tempAllowWhileInUsePermissionInFgs(int uid, long duration) {
+            mFgsWhileInUseTempAllowList.add(uid, duration);
+        }
+
+        @Override
+        public boolean isTempAllowlistedForFgsWhileInUse(int uid) {
+            return mFgsWhileInUseTempAllowList.isAllowed(uid);
+        }
+
+        @Override
+        public boolean canAllowWhileInUsePermissionInFgs(int pid, int uid,
+                @NonNull String packageName) {
+            synchronized (ActivityManagerService.this) {
+                return mServices.canAllowWhileInUsePermissionInFgsLocked(pid, uid, packageName);
+            }
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
@@ -20544,7 +20570,7 @@
         int callerUid = Binder.getCallingUid();
 
         // Only system can toggle the freezer state
-        if (callerUid == SYSTEM_UID) {
+        if (callerUid == SYSTEM_UID || Build.IS_DEBUGGABLE) {
             return mOomAdjuster.mCachedAppOptimizer.enableFreezer(enable);
         } else {
             throw new SecurityException("Caller uid " + callerUid + " cannot set freezer state ");
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 50d2cab..9838f01 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -97,9 +97,19 @@
      * a minimum amount of time; they are removed from it when they are
      * later restarted (hopefully due to some user action).  The value is the
      * time it was added to the list.
+     *
+     * Read access is UNLOCKED, and must either be based on a single lookup
+     * call on the current mBadProcesses instance, or a local copy of that
+     * reference must be made and the local copy treated as the source of
+     * truth.  Mutations are performed by synchronizing on mBadProcessLock,
+     * cloning the existing mBadProcesses instance, performing the mutation,
+     * then changing the volatile "live" mBadProcesses reference to point to the
+     * mutated version.  These operations are very rare compared to lookups:
+     * we intentionally trade additional cost for mutations for eliminating
+     * lock operations from the simple lookup cases.
      */
-    private final ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<>();
-
+    private volatile ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<>();
+    private final Object mBadProcessLock = new Object();
 
     AppErrors(Context context, ActivityManagerService service, PackageWatchdog watchdog) {
         context.assertRuntimeOverlayThemable();
@@ -109,7 +119,8 @@
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId, String dumpPackage) {
-        if (mProcessCrashTimes.getMap().isEmpty() && mBadProcesses.getMap().isEmpty()) {
+        final ProcessMap<BadProcessInfo> badProcesses = mBadProcesses;
+        if (mProcessCrashTimes.getMap().isEmpty() && badProcesses.getMap().isEmpty()) {
             return;
         }
 
@@ -144,8 +155,8 @@
 
         }
 
-        if (!mBadProcesses.getMap().isEmpty()) {
-            final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap();
+        if (!badProcesses.getMap().isEmpty()) {
+            final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = badProcesses.getMap();
             final int processCount = pmap.size();
             for (int ip = 0; ip < processCount; ip++) {
                 final long btoken = proto.start(AppErrorsProto.BAD_PROCESSES);
@@ -209,9 +220,10 @@
             }
         }
 
-        if (!mBadProcesses.getMap().isEmpty()) {
+        final ProcessMap<BadProcessInfo> badProcesses = mBadProcesses;
+        if (!badProcesses.getMap().isEmpty()) {
             boolean printed = false;
-            final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap();
+            final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = badProcesses.getMap();
             final int processCount = pmap.size();
             for (int ip = 0; ip < processCount; ip++) {
                 final String pname = pmap.keyAt(ip);
@@ -263,12 +275,27 @@
         return needSep;
     }
 
-    boolean isBadProcessLocked(ApplicationInfo info) {
-        return mBadProcesses.get(info.processName, info.uid) != null;
+    boolean isBadProcess(final String processName, final int uid) {
+        // NO LOCKING for the simple lookup
+        return mBadProcesses.get(processName, uid) != null;
     }
 
-    void clearBadProcessLocked(ApplicationInfo info) {
-        mBadProcesses.remove(info.processName, info.uid);
+    void clearBadProcess(final String processName, final int uid) {
+        synchronized (mBadProcessLock) {
+            final ProcessMap<BadProcessInfo> badProcesses = new ProcessMap<>();
+            badProcesses.putAll(mBadProcesses);
+            badProcesses.remove(processName, uid);
+            mBadProcesses = badProcesses;
+        }
+    }
+
+    void markBadProcess(final String processName, final int uid, BadProcessInfo info) {
+        synchronized (mBadProcessLock) {
+            final ProcessMap<BadProcessInfo> badProcesses = new ProcessMap<>();
+            badProcesses.putAll(mBadProcesses);
+            badProcesses.put(processName, uid, info);
+            mBadProcesses = badProcesses;
+        }
     }
 
     void resetProcessCrashTimeLocked(ApplicationInfo info) {
@@ -737,10 +764,10 @@
                         app.info.processName);
                 if (!app.isolated) {
                     // XXX We don't have a way to mark isolated processes
-                    // as bad, since they don't have a peristent identity.
-                    mBadProcesses.put(app.info.processName, app.uid,
+                    // as bad, since they don't have a persistent identity.
+                    markBadProcess(app.info.processName, app.uid,
                             new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
-                    mProcessCrashTimes.remove(app.info.processName, app.uid);
+                    mProcessCrashTimes.remove(app.processName, app.uid);
                 }
                 app.bad = true;
                 app.removed = true;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 36d4a38..2f776fc 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -133,7 +133,7 @@
     static final int REPORT_UNFREEZE_MSG = 4;
 
     //TODO:change this static definition into a configurable flag.
-    static final int FREEZE_TIMEOUT_MS = 10000;
+    static final long FREEZE_TIMEOUT_MS = 600000;
 
     static final int DO_FREEZE = 1;
     static final int REPORT_UNFREEZE = 2;
@@ -151,6 +151,7 @@
      */
     final ServiceThread mCachedAppOptimizerThread;
 
+    @GuardedBy("this")
     private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
             new ArrayList<ProcessRecord>();
     private final ActivityManagerService mAm;
@@ -348,51 +349,74 @@
 
     @GuardedBy("mAm")
     void compactAppSome(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_SOME;
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
-                COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
+        synchronized (this) {
+            app.reqCompactAction = COMPACT_PROCESS_SOME;
+            if (!app.mPendingCompact) {
+                app.mPendingCompact = true;
+                mPendingCompactionProcesses.add(app);
+                mCompactionHandler.sendMessage(
+                        mCompactionHandler.obtainMessage(
+                        COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
+            }
+        }
     }
 
     @GuardedBy("mAm")
     void compactAppFull(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_FULL;
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
-                COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
-
+        synchronized (this) {
+            app.reqCompactAction = COMPACT_PROCESS_FULL;
+            if (!app.mPendingCompact) {
+                app.mPendingCompact = true;
+                mPendingCompactionProcesses.add(app);
+                mCompactionHandler.sendMessage(
+                        mCompactionHandler.obtainMessage(
+                        COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
+            }
+        }
     }
 
     @GuardedBy("mAm")
     void compactAppPersistent(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
-                    COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+        synchronized (this) {
+            app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
+            if (!app.mPendingCompact) {
+                app.mPendingCompact = true;
+                mPendingCompactionProcesses.add(app);
+                mCompactionHandler.sendMessage(
+                        mCompactionHandler.obtainMessage(
+                        COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+            }
+        }
     }
 
     @GuardedBy("mAm")
     boolean shouldCompactPersistent(ProcessRecord app, long now) {
-        return (app.lastCompactTime == 0
-                || (now - app.lastCompactTime) > mCompactThrottlePersistent);
+        synchronized (this) {
+            return (app.lastCompactTime == 0
+                    || (now - app.lastCompactTime) > mCompactThrottlePersistent);
+        }
     }
 
     @GuardedBy("mAm")
     void compactAppBfgs(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_BFGS;
-        mPendingCompactionProcesses.add(app);
-        mCompactionHandler.sendMessage(
-                mCompactionHandler.obtainMessage(
-                    COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+        synchronized (this) {
+            app.reqCompactAction = COMPACT_PROCESS_BFGS;
+            if (!app.mPendingCompact) {
+                app.mPendingCompact = true;
+                mPendingCompactionProcesses.add(app);
+                mCompactionHandler.sendMessage(
+                        mCompactionHandler.obtainMessage(
+                        COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+            }
+        }
     }
 
     @GuardedBy("mAm")
     boolean shouldCompactBFGS(ProcessRecord app, long now) {
-        return (app.lastCompactTime == 0
-                || (now - app.lastCompactTime) > mCompactThrottleBFGS);
+        synchronized (this) {
+            return (app.lastCompactTime == 0
+                    || (now - app.lastCompactTime) > mCompactThrottleBFGS);
+        }
     }
 
     @GuardedBy("mAm")
@@ -722,10 +746,12 @@
 
     // This will ensure app will be out of the freezer for at least FREEZE_TIMEOUT_MS
     void unfreezeTemporarily(ProcessRecord app) {
-        synchronized (mAm) {
-            if (app.frozen) {
-                unfreezeAppLocked(app);
-                freezeAppAsync(app);
+        if (mUseFreezer) {
+            synchronized (mAm) {
+                if (app.frozen) {
+                    unfreezeAppLocked(app);
+                    freezeAppAsync(app);
+                }
             }
         }
     }
@@ -852,18 +878,19 @@
                     LastCompactionStats lastCompactionStats;
                     int lastOomAdj = msg.arg1;
                     int procState = msg.arg2;
-                    synchronized (mAm) {
+                    synchronized (CachedAppOptimizer.this) {
                         proc = mPendingCompactionProcesses.remove(0);
 
                         pendingAction = proc.reqCompactAction;
-                        pid = proc.pid;
+                        pid = proc.mPidForCompact;
                         name = proc.processName;
+                        proc.mPendingCompact = false;
 
                         // don't compact if the process has returned to perceptible
                         // and this is only a cached/home/prev compaction
                         if ((pendingAction == COMPACT_PROCESS_SOME
                                 || pendingAction == COMPACT_PROCESS_FULL)
-                                && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
+                                && (proc.mSetAdjForCompact <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
                             if (DEBUG_COMPACTION) {
                                 Slog.d(TAG_AM,
                                         "Skipping compaction as process " + name + " is "
@@ -1050,7 +1077,7 @@
                                     lastOomAdj, ActivityManager.processStateAmToProto(procState),
                                     zramFreeKbBefore, zramFreeKbAfter);
                         }
-                        synchronized (mAm) {
+                        synchronized (CachedAppOptimizer.this) {
                             proc.lastCompactTime = end;
                             proc.lastCompactAction = pendingAction;
                         }
@@ -1101,15 +1128,26 @@
         }
 
         private void freezeProcess(ProcessRecord proc) {
-            final int pid;
-            final String name;
+            final int pid = proc.pid;
+            final String name = proc.processName;
             final long unfrozenDuration;
             final boolean frozen;
 
-            synchronized (mAm) {
-                pid = proc.pid;
-                name = proc.processName;
+            try {
+                // pre-check for locks to avoid unnecessary freeze/unfreeze operations
+                if (Process.hasFileLocks(pid)) {
+                    if (DEBUG_FREEZER) {
+                        Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
+                    }
+                    return;
+                }
+            } catch (Exception e) {
+                Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
+                        + "): " + e);
+                return;
+            }
 
+            synchronized (mAm) {
                 if (proc.curAdj < ProcessList.CACHED_APP_MIN_ADJ
                         || proc.shouldNotFreeze) {
                     if (DEBUG_FREEZER) {
@@ -1141,29 +1179,50 @@
                 frozen = proc.frozen;
             }
 
-            if (frozen) {
-                if (DEBUG_FREEZER) {
-                    Slog.d(TAG_AM, "froze " + pid + " " + name);
+            if (!frozen) {
+                return;
+            }
+
+
+            if (DEBUG_FREEZER) {
+                Slog.d(TAG_AM, "froze " + pid + " " + name);
+            }
+
+            EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
+
+            try {
+                freezeBinder(pid, true);
+            } catch (RuntimeException e) {
+                Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
+                proc.kill("Unable to freeze binder interface",
+                        ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+            }
+
+            // See above for why we're not taking mPhenotypeFlagLock here
+            if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
+                FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
+                        FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
+                        pid,
+                        name,
+                        unfrozenDuration);
+            }
+
+            try {
+                // post-check to prevent races
+                if (Process.hasFileLocks(pid)) {
+                    if (DEBUG_FREEZER) {
+                        Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
+                    }
+
+                    synchronized (mAm) {
+                        unfreezeAppLocked(proc);
+                    }
                 }
-
-                EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
-
-                try {
-                    freezeBinder(pid, true);
-                } catch (RuntimeException e) {
-                    Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
-                    proc.kill("Unable to freeze binder interface",
-                            ApplicationExitInfo.REASON_OTHER,
-                            ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
-                }
-
-                // See above for why we're not taking mPhenotypeFlagLock here
-                if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
-                    FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
-                            FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
-                            pid,
-                            name,
-                            unfrozenDuration);
+            } catch (Exception e) {
+                Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
+                synchronized (mAm) {
+                    unfreezeAppLocked(proc);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/FgsWhileInUseTempAllowList.java b/services/core/java/com/android/server/am/FgsWhileInUseTempAllowList.java
new file mode 100644
index 0000000..ed0f264
--- /dev/null
+++ b/services/core/java/com/android/server/am/FgsWhileInUseTempAllowList.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseLongArray;
+
+/**
+ * List of uids that are allowed to have while-in-use permission when FGS is started
+ * from background.
+ */
+final class FgsWhileInUseTempAllowList {
+    /**
+     * This list is supposed to have a small number of entries. If exceeds MAX_SIZE, log a warning
+     * message.
+     */
+    private static final int MAX_SIZE = 100;
+    /**
+     * The key is the UID, the value is expiration elapse time in ms of this temp-allowed UID.
+     */
+    private final SparseLongArray mTempAllowListFgs = new SparseLongArray();
+
+    private final Object mLock = new Object();
+
+    void add(int uid, long durationMs) {
+        synchronized (mLock) {
+            if (durationMs <= 0) {
+                Slog.e(TAG_AM, "FgsWhileInUseTempAllowList bad duration:" + durationMs
+                        + " uid: " + uid);
+                return;
+            }
+            // The temp allowlist should be a short list with only a few entries in it.
+            final int size = mTempAllowListFgs.size();
+            if (size > MAX_SIZE) {
+                Slog.w(TAG_AM, "FgsWhileInUseTempAllowList length:" + size + " exceeds "
+                        + MAX_SIZE);
+            }
+            final long now = SystemClock.elapsedRealtime();
+            for (int index = mTempAllowListFgs.size() - 1; index >= 0; index--) {
+                if (mTempAllowListFgs.valueAt(index) < now) {
+                    mTempAllowListFgs.removeAt(index);
+                }
+            }
+            final long existingExpirationTime = mTempAllowListFgs.get(uid, -1);
+            final long expirationTime = now + durationMs;
+            if (existingExpirationTime == -1 || existingExpirationTime < expirationTime) {
+                mTempAllowListFgs.put(uid, expirationTime);
+            }
+        }
+    }
+
+    boolean isAllowed(int uid) {
+        synchronized (mLock) {
+            final int index = mTempAllowListFgs.indexOfKey(uid);
+            if (index < 0) {
+                return false;
+            } else if (mTempAllowListFgs.valueAt(index) < SystemClock.elapsedRealtime()) {
+                mTempAllowListFgs.removeAt(index);
+                return false;
+            } else {
+                return true;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 58f3894..5b0a443 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2198,6 +2198,9 @@
             }
             app.setAdj = app.curAdj;
             app.verifiedAdj = ProcessList.INVALID_ADJ;
+            synchronized (mCachedAppOptimizer) {
+                app.mSetAdjForCompact = app.setAdj;
+            }
         }
 
         final int curSchedGroup = app.getCurrentSchedulingGroup();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 1667901..c04f50e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2351,7 +2351,7 @@
             if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) {
                 // If we are in the background, then check to see if this process
                 // is bad.  If so, we will just silently fail.
-                if (mService.mAppErrors.isBadProcessLocked(info)) {
+                if (mService.mAppErrors.isBadProcess(info.processName, info.uid)) {
                     if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid
                             + "/" + info.processName);
                     return null;
@@ -2364,11 +2364,11 @@
                 if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid
                         + "/" + info.processName);
                 mService.mAppErrors.resetProcessCrashTimeLocked(info);
-                if (mService.mAppErrors.isBadProcessLocked(info)) {
+                if (mService.mAppErrors.isBadProcess(info.processName, info.uid)) {
                     EventLog.writeEvent(EventLogTags.AM_PROC_GOOD,
                             UserHandle.getUserId(info.uid), info.uid,
                             info.processName);
-                    mService.mAppErrors.clearBadProcessLocked(info);
+                    mService.mAppErrors.clearBadProcess(info.processName, info.uid);
                     if (app != null) {
                         app.bad = false;
                     }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 4775541..41ab279 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -59,6 +59,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.procstats.ProcessState;
 import com.android.internal.app.procstats.ProcessStats;
@@ -162,8 +163,11 @@
     int curCapability;          // Current capability flags of this process. For example,
                                 // PROCESS_CAPABILITY_FOREGROUND_LOCATION is one capability.
     int setCapability;          // Last set capability flags.
+    @GuardedBy("mService.mOomAdjuster.mCachedAppOptimizer")
     long lastCompactTime;       // The last time that this process was compacted
+    @GuardedBy("mService.mOomAdjuster.mCachedAppOptimizer")
     int reqCompactAction;       // The most recent compaction action requested for this app.
+    @GuardedBy("mService.mOomAdjuster.mCachedAppOptimizer")
     int lastCompactAction;      // The most recent compaction action performed for this app.
     boolean frozen;             // True when the process is frozen.
     long freezeUnfreezeTime;    // Last time the app was (un)frozen, 0 for never
@@ -352,6 +356,24 @@
 
     boolean mReachable; // Whether or not this process is reachable from given process
 
+    /**
+     * The snapshot of {@link #setAdj}, meant to be read by {@link CachedAppOptimizer} only.
+     */
+    @GuardedBy("mService.mOomAdjuster.mCachedAppOptimizer")
+    int mSetAdjForCompact;
+
+    /**
+     * The snapshot of {@link #pid}, meant to be read by {@link CachedAppOptimizer} only.
+     */
+    @GuardedBy("mService.mOomAdjuster.mCachedAppOptimizer")
+    int mPidForCompact;
+
+    /**
+     * This process has been scheduled for a memory compaction.
+     */
+    @GuardedBy("mService.mOomAdjuster.mCachedAppOptimizer")
+    boolean mPendingCompact;
+
     void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
             long startTime) {
         this.startUid = startUid;
@@ -450,8 +472,10 @@
                 pw.print(" setRaw="); pw.print(setRawAdj);
                 pw.print(" cur="); pw.print(curAdj);
                 pw.print(" set="); pw.println(setAdj);
-        pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
-                pw.print(" lastCompactAction="); pw.println(lastCompactAction);
+        synchronized (mService.mOomAdjuster.mCachedAppOptimizer) {
+            pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
+            pw.print(" lastCompactAction="); pw.println(lastCompactAction);
+        }
         pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
                 pw.print(" setSchedGroup="); pw.print(setSchedGroup);
                 pw.print(" systemNoUi="); pw.print(systemNoUi);
@@ -678,6 +702,9 @@
 
     public void setPid(int _pid) {
         pid = _pid;
+        synchronized (mService.mOomAdjuster.mCachedAppOptimizer) {
+            mPidForCompact = _pid;
+        }
         mWindowProcessController.setPid(pid);
         procStatFile = null;
         shortStringName = null;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index f2236d7..23f4611 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -583,6 +583,10 @@
                         if (mActivityManagerInternal != null
                                 && mActivityManagerInternal.isPendingTopUid(uid)) {
                             return MODE_ALLOWED;
+                        } else if (mActivityManagerInternal != null
+                                && mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(
+                                        uid)) {
+                            return MODE_ALLOWED;
                         } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
                             return MODE_ALLOWED;
                         } else {
@@ -592,6 +596,10 @@
                         if (mActivityManagerInternal != null
                                 && mActivityManagerInternal.isPendingTopUid(uid)) {
                             return MODE_ALLOWED;
+                        } else if (mActivityManagerInternal != null
+                                && mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(
+                                        uid)) {
+                            return MODE_ALLOWED;
                         } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
                             return MODE_ALLOWED;
                         } else {
@@ -3003,8 +3011,8 @@
 
         // This is a workaround for R QPR, new API change is not allowed. We only allow the current
         // voice recognizer is also the voice interactor to noteproxy op.
-        final boolean isTrustVoiceServiceProxy =
-                AppOpsManager.isTrustedVoiceServiceProxy(mContext, proxyPackageName, code);
+        final boolean isTrustVoiceServiceProxy = AppOpsManager.isTrustedVoiceServiceProxy(mContext,
+                proxyPackageName, code, UserHandle.getUserId(proxyUid));
         final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
         final boolean isProxyTrusted = mContext.checkPermission(
                 Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 8528686..713f9c8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1484,6 +1484,14 @@
         }
     }
 
+    void setDisplayModeDirectorLoggingEnabled(boolean enabled) {
+        synchronized (mSyncRoot) {
+            if (mDisplayModeDirector != null) {
+                mDisplayModeDirector.setLoggingEnabled(enabled);
+            }
+        }
+    }
+
     void setAmbientColorTemperatureOverride(float cct) {
         if (mDisplayPowerController != null) {
             synchronized (mSyncRoot) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 0c6c797..d1d0496 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -54,6 +54,10 @@
                 return setDisplayWhiteBalanceLoggingEnabled(true);
             case "dwb-logging-disable":
                 return setDisplayWhiteBalanceLoggingEnabled(false);
+            case "dmd-logging-enable":
+                return setDisplayModeDirectorLoggingEnabled(true);
+            case "dmd-logging-disable":
+                return setDisplayModeDirectorLoggingEnabled(false);
             case "dwb-set-cct":
                 return setAmbientColorTemperatureOverride();
             default:
@@ -80,6 +84,10 @@
         pw.println("    Enable display white-balance logging.");
         pw.println("  dwb-logging-disable");
         pw.println("    Disable display white-balance logging.");
+        pw.println("  dmd-logging-enable");
+        pw.println("    Enable display mode director logging.");
+        pw.println("  dmd-logging-disable");
+        pw.println("    Disable display mode director logging.");
         pw.println("  dwb-set-cct CCT");
         pw.println("    Sets the ambient color temperature override to CCT (use -1 to disable).");
         pw.println();
@@ -132,6 +140,11 @@
         return 0;
     }
 
+    private int setDisplayModeDirectorLoggingEnabled(boolean enabled) {
+        mService.setDisplayModeDirectorLoggingEnabled(enabled);
+        return 0;
+    }
+
     private int setAmbientColorTemperatureOverride() {
         String cctText = getNextArg();
         if (cctText == null) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index baa43cfd..dfa87bc 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -46,13 +46,18 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.utils.DeviceConfigInterface;
 
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 
 /**
@@ -61,12 +66,14 @@
  */
 public class DisplayModeDirector {
     private static final String TAG = "DisplayModeDirector";
-    private static final boolean DEBUG = false;
+    private boolean mLoggingEnabled;
 
     private static final int MSG_REFRESH_RATE_RANGE_CHANGED = 1;
-    private static final int MSG_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
+    private static final int MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
     private static final int MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED = 3;
-    private static final int MSG_REFRESH_RATE_IN_ZONE_CHANGED = 4;
+    private static final int MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED = 4;
+    private static final int MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED = 5;
+    private static final int MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED = 6;
 
     // Special ID used to indicate that given vote is to be applied globally, rather than to a
     // specific display.
@@ -79,6 +86,13 @@
     private final Context mContext;
 
     private final DisplayModeDirectorHandler mHandler;
+    private final Injector mInjector;
+
+    private final AppRequestObserver mAppRequestObserver;
+    private final SettingsObserver mSettingsObserver;
+    private final DisplayObserver mDisplayObserver;
+    private final DeviceConfigInterface mDeviceConfig;
+    private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
 
     // A map from the display ID to the collection of votes and their priority. The latter takes
     // the form of another map from the priority to the vote itself so that each priority is
@@ -89,17 +103,19 @@
     // A map from the display ID to the default mode of that display.
     private SparseArray<Display.Mode> mDefaultModeByDisplay;
 
-    private final AppRequestObserver mAppRequestObserver;
-    private final SettingsObserver mSettingsObserver;
-    private final DisplayObserver mDisplayObserver;
     private BrightnessObserver mBrightnessObserver;
 
-    private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
     private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
 
     public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
+        this(context, handler, new RealInjector());
+    }
+
+    public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
+            @NonNull Injector injector) {
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
+        mInjector = injector;
         mVotesByDisplay = new SparseArray<>();
         mSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay =  new SparseArray<>();
@@ -108,6 +124,7 @@
         mDisplayObserver = new DisplayObserver(context, handler);
         mBrightnessObserver = new BrightnessObserver(context, handler);
         mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
+        mDeviceConfig = injector.getDeviceConfig();
     }
 
     /**
@@ -129,6 +146,14 @@
 
     }
 
+    public void setLoggingEnabled(boolean loggingEnabled) {
+        if (mLoggingEnabled == loggingEnabled) {
+            return;
+        }
+        mLoggingEnabled = loggingEnabled;
+        mBrightnessObserver.setLoggingEnabled(loggingEnabled);
+    }
+
     @NonNull
     private SparseArray<Vote> getVotesLocked(int displayId) {
         SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
@@ -230,7 +255,7 @@
 
                 availableModes = filterModes(modes, primarySummary);
                 if (availableModes.length > 0) {
-                    if (DEBUG) {
+                    if (mLoggingEnabled) {
                         Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes)
                                 + " with lowest priority considered "
                                 + Vote.priorityToString(lowestConsideredPriority)
@@ -243,7 +268,7 @@
                     break;
                 }
 
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.w(TAG, "Couldn't find available modes with lowest priority set to "
                             + Vote.priorityToString(lowestConsideredPriority)
                             + " and with the following constraints: "
@@ -265,7 +290,7 @@
                     Math.min(appRequestSummary.minRefreshRate, primarySummary.minRefreshRate);
             appRequestSummary.maxRefreshRate =
                     Math.max(appRequestSummary.maxRefreshRate, primarySummary.maxRefreshRate);
-            if (DEBUG) {
+            if (mLoggingEnabled) {
                 Slog.i(TAG,
                         String.format("App request range: [%.0f %.0f]",
                                 appRequestSummary.minRefreshRate,
@@ -292,7 +317,7 @@
         for (Display.Mode mode : supportedModes) {
             if (mode.getPhysicalWidth() != summary.width
                     || mode.getPhysicalHeight() != summary.height) {
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size"
                             + ": desiredWidth=" + summary.width
                             + ": desiredHeight=" + summary.height
@@ -307,7 +332,7 @@
             // comparison.
             if (refreshRate < (summary.minRefreshRate - FLOAT_TOLERANCE)
                     || refreshRate > (summary.maxRefreshRate + FLOAT_TOLERANCE)) {
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.w(TAG, "Discarding mode " + mode.getModeId()
                             + ", outside refresh rate bounds"
                             + ": minRefreshRate=" + summary.minRefreshRate
@@ -348,6 +373,23 @@
     }
 
     /**
+     * Retrieve the Vote for the given display and priority. Intended only for testing purposes.
+     *
+     * @param displayId the display to query for
+     * @param priority the priority of the vote to return
+     * @return the vote corresponding to the given {@code displayId} and {@code priority},
+     *         or {@code null} if there isn't one
+     */
+    @VisibleForTesting
+    @Nullable
+    Vote getVote(int displayId, int priority) {
+        synchronized (mLock) {
+            SparseArray<Vote> votes = getVotesLocked(displayId);
+            return votes.get(priority);
+        }
+    }
+
+    /**
      * Print the object's state and debug information into the given stream.
      *
      * @param pw The stream to dump information to.
@@ -390,7 +432,7 @@
     }
 
     private void updateVoteLocked(int displayId, int priority, Vote vote) {
-        if (DEBUG) {
+        if (mLoggingEnabled) {
             Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
                     + ", priority=" + Vote.priorityToString(priority)
                     + ", vote=" + vote + ")");
@@ -411,7 +453,7 @@
         }
 
         if (votes.size() == 0) {
-            if (DEBUG) {
+            if (mLoggingEnabled) {
                 Slog.i(TAG, "No votes left for display " + displayId + ", removing.");
             }
             mVotesByDisplay.remove(displayId);
@@ -465,6 +507,17 @@
     }
 
     @VisibleForTesting
+    BrightnessObserver getBrightnessObserver() {
+        return mBrightnessObserver;
+    }
+
+    @VisibleForTesting
+    SettingsObserver getSettingsObserver() {
+        return mSettingsObserver;
+    }
+
+
+    @VisibleForTesting
     DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
             float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
         synchronized (mLock) {
@@ -492,16 +545,35 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_BRIGHTNESS_THRESHOLDS_CHANGED:
+                case MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED: {
+                    Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
+                    mBrightnessObserver.onDeviceConfigLowBrightnessThresholdsChanged(
+                            thresholds.first, thresholds.second);
+                    break;
+                }
+
+                case MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED: {
+                    int refreshRateInZone = msg.arg1;
+                    mBrightnessObserver.onDeviceConfigRefreshRateInLowZoneChanged(
+                            refreshRateInZone);
+                    break;
+                }
+
+                case MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED: {
                     Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
 
-                    if (thresholds != null) {
-                        mBrightnessObserver.onDeviceConfigThresholdsChanged(
-                                thresholds.first, thresholds.second);
-                    } else {
-                        mBrightnessObserver.onDeviceConfigThresholdsChanged(null, null);
-                    }
+                    mBrightnessObserver.onDeviceConfigHighBrightnessThresholdsChanged(
+                            thresholds.first, thresholds.second);
+
                     break;
+                }
+
+                case MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED: {
+                    int refreshRateInZone = msg.arg1;
+                    mBrightnessObserver.onDeviceConfigRefreshRateInHighZoneChanged(
+                            refreshRateInZone);
+                    break;
+                }
 
                 case MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED:
                     Float defaultPeakRefreshRate = (Float) msg.obj;
@@ -509,12 +581,6 @@
                             defaultPeakRefreshRate);
                     break;
 
-                case MSG_REFRESH_RATE_IN_ZONE_CHANGED:
-                    int refreshRateInZone = msg.arg1;
-                    mBrightnessObserver.onDeviceConfigRefreshRateInZoneChanged(
-                            refreshRateInZone);
-                    break;
-
                 case MSG_REFRESH_RATE_RANGE_CHANGED:
                     DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener =
                             (DesiredDisplayModeSpecsListener) msg.obj;
@@ -684,10 +750,11 @@
         // by all other considerations. It acts to set a default frame rate for a device.
         public static final int PRIORITY_DEFAULT_REFRESH_RATE = 0;
 
-        // LOW_BRIGHTNESS votes for a single refresh rate like [60,60], [90,90] or null.
+        // FLICKER votes for a single refresh rate like [60,60], [90,90] or null.
         // If the higher voters result is a range, it will fix the rate to a single choice.
-        // It's used to avoid rate switch in certain conditions.
-        public static final int PRIORITY_LOW_BRIGHTNESS = 1;
+        // It's used to avoid refresh rate switches in certain conditions which may result in the
+        // user seeing the display flickering when the switches occur.
+        public static final int PRIORITY_FLICKER = 1;
 
         // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
         // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
@@ -760,8 +827,8 @@
             switch (priority) {
                 case PRIORITY_DEFAULT_REFRESH_RATE:
                     return "PRIORITY_DEFAULT_REFRESH_RATE";
-                case PRIORITY_LOW_BRIGHTNESS:
-                    return "PRIORITY_LOW_BRIGHTNESS";
+                case PRIORITY_FLICKER:
+                    return "PRIORITY_FLICKER";
                 case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
                     return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE";
                 case PRIORITY_APP_REQUEST_REFRESH_RATE:
@@ -786,7 +853,8 @@
         }
     }
 
-    private final class SettingsObserver extends ContentObserver {
+    @VisibleForTesting
+    final class SettingsObserver extends ContentObserver {
         private final Uri mPeakRefreshRateSetting =
                 Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
         private final Uri mMinRefreshRateSetting =
@@ -809,8 +877,7 @@
 
         public void observe() {
             final ContentResolver cr = mContext.getContentResolver();
-            cr.registerContentObserver(mPeakRefreshRateSetting, false /*notifyDescendants*/, this,
-                    UserHandle.USER_SYSTEM);
+            mInjector.registerPeakRefreshRateObserver(cr, this);
             cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this,
                     UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
@@ -828,6 +895,13 @@
             }
         }
 
+        public void setDefaultRefreshRate(float refreshRate) {
+            synchronized (mLock) {
+                mDefaultRefreshRate = refreshRate;
+                updateRefreshRateSettingLocked();
+            }
+        }
+
         public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) {
             if (defaultPeakRefreshRate == null) {
                 defaultPeakRefreshRate = (float) mContext.getResources().getInteger(
@@ -1032,6 +1106,7 @@
         @Override
         public void onDisplayChanged(int displayId) {
             updateDisplayModes(displayId);
+            // TODO: Break the coupling between DisplayObserver and BrightnessObserver.
             mBrightnessObserver.onDisplayChanged(displayId);
         }
 
@@ -1070,15 +1145,17 @@
      */
     @VisibleForTesting
     public class BrightnessObserver extends ContentObserver {
-        // TODO: brightnessfloat: change this to the float setting
-        private final Uri mDisplayBrightnessSetting =
-                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
         private final static int LIGHT_SENSOR_RATE_MS = 250;
-        private int[] mDisplayBrightnessThresholds;
-        private int[] mAmbientBrightnessThresholds;
+        private int[] mLowDisplayBrightnessThresholds;
+        private int[] mLowAmbientBrightnessThresholds;
+        private int[] mHighDisplayBrightnessThresholds;
+        private int[] mHighAmbientBrightnessThresholds;
         // valid threshold if any item from the array >= 0
-        private boolean mShouldObserveDisplayChange;
-        private boolean mShouldObserveAmbientChange;
+        private boolean mShouldObserveDisplayLowChange;
+        private boolean mShouldObserveAmbientLowChange;
+        private boolean mShouldObserveDisplayHighChange;
+        private boolean mShouldObserveAmbientHighChange;
+        private boolean mLoggingEnabled;
 
         private SensorManager mSensorManager;
         private Sensor mLightSensor;
@@ -1086,50 +1163,134 @@
         // Take it as low brightness before valid sensor data comes
         private float mAmbientLux = -1.0f;
         private AmbientFilter mAmbientFilter;
+        private int mBrightness = -1;
 
         private final Context mContext;
 
-        // Enable light sensor only when mShouldObserveAmbientChange is true, screen is on, peak
-        // refresh rate changeable and low power mode off. After initialization, these states will
+        // Enable light sensor only when mShouldObserveAmbientLowChange is true or
+        // mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate
+        // changeable and low power mode off. After initialization, these states will
         // be updated from the same handler thread.
-        private boolean mScreenOn = false;
+        private int mDefaultDisplayState = Display.STATE_UNKNOWN;
         private boolean mRefreshRateChangeable = false;
         private boolean mLowPowerModeEnabled = false;
 
-        private int mRefreshRateInZone;
+        private int mRefreshRateInLowZone;
+        private int mRefreshRateInHighZone;
 
         BrightnessObserver(Context context, Handler handler) {
             super(handler);
             mContext = context;
-            mDisplayBrightnessThresholds = context.getResources().getIntArray(
+            mLowDisplayBrightnessThresholds = context.getResources().getIntArray(
                     R.array.config_brightnessThresholdsOfPeakRefreshRate);
-            mAmbientBrightnessThresholds = context.getResources().getIntArray(
+            mLowAmbientBrightnessThresholds = context.getResources().getIntArray(
                     R.array.config_ambientThresholdsOfPeakRefreshRate);
 
-            if (mDisplayBrightnessThresholds.length != mAmbientBrightnessThresholds.length) {
-                throw new RuntimeException("display brightness threshold array and ambient "
-                        + "brightness threshold array have different length");
+            if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
+                throw new RuntimeException("display low brightness threshold array and ambient "
+                        + "brightness threshold array have different length: "
+                        + "displayBrightnessThresholds="
+                        + Arrays.toString(mLowDisplayBrightnessThresholds)
+                        + ", ambientBrightnessThresholds="
+                        + Arrays.toString(mLowAmbientBrightnessThresholds));
             }
+
+            mHighDisplayBrightnessThresholds = context.getResources().getIntArray(
+                    R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+            mHighAmbientBrightnessThresholds = context.getResources().getIntArray(
+                    R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+            if (mHighDisplayBrightnessThresholds.length
+                    != mHighAmbientBrightnessThresholds.length) {
+                throw new RuntimeException("display high brightness threshold array and ambient "
+                        + "brightness threshold array have different length: "
+                        + "displayBrightnessThresholds="
+                        + Arrays.toString(mHighDisplayBrightnessThresholds)
+                        + ", ambientBrightnessThresholds="
+                        + Arrays.toString(mHighAmbientBrightnessThresholds));
+            }
+            mRefreshRateInHighZone = context.getResources().getInteger(
+                    R.integer.config_fixedRefreshRateInHighZone);
+        }
+
+        /**
+         * @return the refresh to lock to when in a low brightness zone
+         */
+        @VisibleForTesting
+        int getRefreshRateInLowZone() {
+            return mRefreshRateInLowZone;
+        }
+
+        /**
+         * @return the display brightness thresholds for the low brightness zones
+         */
+        @VisibleForTesting
+        int[] getLowDisplayBrightnessThresholds() {
+            return mLowDisplayBrightnessThresholds;
+        }
+
+        /**
+         * @return the ambient brightness thresholds for the low brightness zones
+         */
+        @VisibleForTesting
+        int[] getLowAmbientBrightnessThresholds() {
+            return mLowAmbientBrightnessThresholds;
+        }
+
+        public void registerLightSensor(SensorManager sensorManager, Sensor lightSensor) {
+            mSensorManager = sensorManager;
+            mLightSensor = lightSensor;
+
+            mSensorManager.registerListener(mLightSensorListener,
+                    mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
         }
 
         public void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
+            final ContentResolver cr = mContext.getContentResolver();
+            mBrightness = Settings.System.getIntForUser(cr,
+                    Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
 
             // DeviceConfig is accessible after system ready.
-            int[] brightnessThresholds = mDeviceConfigDisplaySettings.getBrightnessThresholds();
-            int[] ambientThresholds = mDeviceConfigDisplaySettings.getAmbientThresholds();
+            int[] lowDisplayBrightnessThresholds =
+                    mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds();
+            int[] lowAmbientBrightnessThresholds =
+                    mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds();
 
-            if (brightnessThresholds != null && ambientThresholds != null
-                    && brightnessThresholds.length == ambientThresholds.length) {
-                mDisplayBrightnessThresholds = brightnessThresholds;
-                mAmbientBrightnessThresholds = ambientThresholds;
+            if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null
+                    && lowDisplayBrightnessThresholds.length
+                    == lowAmbientBrightnessThresholds.length) {
+                mLowDisplayBrightnessThresholds = lowDisplayBrightnessThresholds;
+                mLowAmbientBrightnessThresholds = lowAmbientBrightnessThresholds;
             }
 
-            mRefreshRateInZone = mDeviceConfigDisplaySettings.getRefreshRateInZone();
+
+            int[] highDisplayBrightnessThresholds =
+                    mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds();
+            int[] highAmbientBrightnessThresholds =
+                    mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds();
+
+            if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null
+                    && highDisplayBrightnessThresholds.length
+                    == highAmbientBrightnessThresholds.length) {
+                mHighDisplayBrightnessThresholds = highDisplayBrightnessThresholds;
+                mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds;
+            }
+
+            mRefreshRateInLowZone = mDeviceConfigDisplaySettings.getRefreshRateInLowZone();
+            mRefreshRateInHighZone = mDeviceConfigDisplaySettings.getRefreshRateInHighZone();
+
             restartObserver();
             mDeviceConfigDisplaySettings.startListening();
         }
 
+        public void setLoggingEnabled(boolean loggingEnabled) {
+            if (mLoggingEnabled == loggingEnabled) {
+                return;
+            }
+            mLoggingEnabled = loggingEnabled;
+            mLightSensorListener.setLoggingEnabled(loggingEnabled);
+        }
+
         public void onRefreshRateSettingChangedLocked(float min, float max) {
             boolean changeable = (max - min > 1f && max > 60f);
             if (mRefreshRateChangeable != changeable) {
@@ -1137,7 +1298,7 @@
                 updateSensorStatus();
                 if (!changeable) {
                     // Revoke previous vote from BrightnessObserver
-                    updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, null);
+                    updateVoteLocked(Vote.PRIORITY_FLICKER, null);
                 }
             }
         }
@@ -1149,25 +1310,48 @@
             }
         }
 
-        public void onDeviceConfigThresholdsChanged(int[] brightnessThresholds,
+        public void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds,
                 int[] ambientThresholds) {
-            if (brightnessThresholds != null && ambientThresholds != null
-                    && brightnessThresholds.length == ambientThresholds.length) {
-                mDisplayBrightnessThresholds = brightnessThresholds;
-                mAmbientBrightnessThresholds = ambientThresholds;
+            if (displayThresholds != null && ambientThresholds != null
+                    && displayThresholds.length == ambientThresholds.length) {
+                mLowDisplayBrightnessThresholds = displayThresholds;
+                mLowAmbientBrightnessThresholds = ambientThresholds;
             } else {
                 // Invalid or empty. Use device default.
-                mDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+                mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray(
                         R.array.config_brightnessThresholdsOfPeakRefreshRate);
-                mAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+                mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray(
                         R.array.config_ambientThresholdsOfPeakRefreshRate);
             }
             restartObserver();
         }
 
-        public void onDeviceConfigRefreshRateInZoneChanged(int refreshRate) {
-            if (refreshRate != mRefreshRateInZone) {
-                mRefreshRateInZone = refreshRate;
+        public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) {
+            if (refreshRate != mRefreshRateInLowZone) {
+                mRefreshRateInLowZone = refreshRate;
+                restartObserver();
+            }
+        }
+
+        public void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds,
+                int[] ambientThresholds) {
+            if (displayThresholds != null && ambientThresholds != null
+                    && displayThresholds.length == ambientThresholds.length) {
+                mHighDisplayBrightnessThresholds = displayThresholds;
+                mHighAmbientBrightnessThresholds = ambientThresholds;
+            } else {
+                // Invalid or empty. Use device default.
+                mHighDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+                        R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+                mHighAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+                        R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+            }
+            restartObserver();
+        }
+
+        public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) {
+            if (refreshRate != mRefreshRateInHighZone) {
+                mRefreshRateInHighZone = refreshRate;
                 restartObserver();
             }
         }
@@ -1175,48 +1359,95 @@
         public void dumpLocked(PrintWriter pw) {
             pw.println("  BrightnessObserver");
             pw.println("    mAmbientLux: " + mAmbientLux);
-            pw.println("    mRefreshRateInZone: " + mRefreshRateInZone);
+            pw.println("    mBrightness: " + mBrightness);
+            pw.println("    mDefaultDisplayState: " + mDefaultDisplayState);
+            pw.println("    mLowPowerModeEnabled: " + mLowPowerModeEnabled);
+            pw.println("    mRefreshRateChangeable: " + mRefreshRateChangeable);
+            pw.println("    mShouldObserveDisplayLowChange: " + mShouldObserveDisplayLowChange);
+            pw.println("    mShouldObserveAmbientLowChange: " + mShouldObserveAmbientLowChange);
+            pw.println("    mRefreshRateInLowZone: " + mRefreshRateInLowZone);
 
-            for (int d: mDisplayBrightnessThresholds) {
-                pw.println("    mDisplayBrightnessThreshold: " + d);
+            for (int d : mLowDisplayBrightnessThresholds) {
+                pw.println("    mDisplayLowBrightnessThreshold: " + d);
             }
 
-            for (int d: mAmbientBrightnessThresholds) {
-                pw.println("    mAmbientBrightnessThreshold: " + d);
+            for (int d : mLowAmbientBrightnessThresholds) {
+                pw.println("    mAmbientLowBrightnessThreshold: " + d);
+            }
+
+            pw.println("    mShouldObserveDisplayHighChange: " + mShouldObserveDisplayHighChange);
+            pw.println("    mShouldObserveAmbientHighChange: " + mShouldObserveAmbientHighChange);
+            pw.println("    mRefreshRateInHighZone: " + mRefreshRateInHighZone);
+
+            for (int d : mHighDisplayBrightnessThresholds) {
+                pw.println("    mDisplayHighBrightnessThresholds: " + d);
+            }
+
+            for (int d : mHighAmbientBrightnessThresholds) {
+                pw.println("    mAmbientHighBrightnessThresholds: " + d);
             }
 
             mLightSensorListener.dumpLocked(pw);
+
+            if (mAmbientFilter != null) {
+                IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                ipw.setIndent("    ");
+                mAmbientFilter.dump(ipw);
+            }
         }
 
         public void onDisplayChanged(int displayId) {
             if (displayId == Display.DEFAULT_DISPLAY) {
-                onScreenOn(isDefaultDisplayOn());
+                updateDefaultDisplayState();
             }
         }
 
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
             synchronized (mLock) {
-                onBrightnessChangedLocked();
+                final ContentResolver cr = mContext.getContentResolver();
+                int brightness = Settings.System.getIntForUser(cr,
+                        Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
+                if (brightness != mBrightness) {
+                    mBrightness = brightness;
+                    onBrightnessChangedLocked();
+                }
             }
         }
 
         private void restartObserver() {
-            mShouldObserveDisplayChange = checkShouldObserve(mDisplayBrightnessThresholds);
-            mShouldObserveAmbientChange = checkShouldObserve(mAmbientBrightnessThresholds);
-
             final ContentResolver cr = mContext.getContentResolver();
-            if (mShouldObserveDisplayChange) {
-                // Content Service does not check if an listener has already been registered.
-                // To ensure only one listener is registered, force an unregistration first.
-                cr.unregisterContentObserver(this);
-                cr.registerContentObserver(mDisplayBrightnessSetting,
-                        false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM);
+
+            if (mRefreshRateInLowZone > 0) {
+                mShouldObserveDisplayLowChange = hasValidThreshold(
+                        mLowDisplayBrightnessThresholds);
+                mShouldObserveAmbientLowChange = hasValidThreshold(
+                        mLowAmbientBrightnessThresholds);
             } else {
-                cr.unregisterContentObserver(this);
+                mShouldObserveDisplayLowChange = false;
+                mShouldObserveAmbientLowChange = false;
             }
 
-            if (mShouldObserveAmbientChange) {
+            if (mRefreshRateInHighZone > 0) {
+                mShouldObserveDisplayHighChange = hasValidThreshold(
+                        mHighDisplayBrightnessThresholds);
+                mShouldObserveAmbientHighChange = hasValidThreshold(
+                        mHighAmbientBrightnessThresholds);
+            } else {
+                mShouldObserveDisplayHighChange = false;
+                mShouldObserveAmbientHighChange = false;
+            }
+
+            if (mShouldObserveDisplayLowChange || mShouldObserveDisplayHighChange) {
+                // Content Service does not check if an listener has already been registered.
+                // To ensure only one listener is registered, force an unregistration first.
+                mInjector.unregisterBrightnessObserver(cr, this);
+                mInjector.registerBrightnessObserver(cr, this);
+            } else {
+                mInjector.unregisterBrightnessObserver(cr, this);
+            }
+
+            if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
                 Resources resources = mContext.getResources();
                 String lightSensorType = resources.getString(
                         com.android.internal.R.string.config_displayLightSensorType);
@@ -1242,8 +1473,6 @@
 
                     mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res);
                     mLightSensor = lightSensor;
-
-                    onScreenOn(isDefaultDisplayOn());
                 }
             } else {
                 mAmbientFilter = null;
@@ -1262,11 +1491,7 @@
          * Checks to see if at least one value is positive, in which case it is necessary to listen
          * to value changes.
          */
-        private boolean checkShouldObserve(int[] a) {
-            if (mRefreshRateInZone <= 0) {
-                return false;
-            }
-
+        private boolean hasValidThreshold(int[] a) {
             for (int d: a) {
                 if (d >= 0) {
                     return true;
@@ -1276,13 +1501,13 @@
             return false;
         }
 
-        private boolean isInsideZone(int brightness, float lux) {
-            for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
-                int disp = mDisplayBrightnessThresholds[i];
-                int ambi = mAmbientBrightnessThresholds[i];
+        private boolean isInsideLowZone(int brightness, float lux) {
+            for (int i = 0; i < mLowDisplayBrightnessThresholds.length; i++) {
+                int disp = mLowDisplayBrightnessThresholds[i];
+                int ambi = mLowAmbientBrightnessThresholds[i];
 
                 if (disp >= 0 && ambi >= 0) {
-                    if (brightness <= disp && mAmbientLux <= ambi) {
+                    if (brightness <= disp && lux <= ambi) {
                         return true;
                     }
                 } else if (disp >= 0) {
@@ -1290,7 +1515,7 @@
                         return true;
                     }
                 } else if (ambi >= 0) {
-                    if (mAmbientLux <= ambi) {
+                    if (lux <= ambi) {
                         return true;
                     }
                 }
@@ -1298,27 +1523,85 @@
 
             return false;
         }
-        // TODO: brightnessfloat: make it use float not int
+
+        private boolean isInsideHighZone(int brightness, float lux) {
+            for (int i = 0; i < mHighDisplayBrightnessThresholds.length; i++) {
+                int disp = mHighDisplayBrightnessThresholds[i];
+                int ambi = mHighAmbientBrightnessThresholds[i];
+
+                if (disp >= 0 && ambi >= 0) {
+                    if (brightness >= disp && lux >= ambi) {
+                        return true;
+                    }
+                } else if (disp >= 0) {
+                    if (brightness >= disp) {
+                        return true;
+                    }
+                } else if (ambi >= 0) {
+                    if (lux >= ambi) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
         private void onBrightnessChangedLocked() {
-            int brightness = Settings.System.getInt(mContext.getContentResolver(),
-                    Settings.System.SCREEN_BRIGHTNESS, -1);
-
             Vote vote = null;
-            boolean insideZone = isInsideZone(brightness, mAmbientLux);
-            if (insideZone) {
-                vote = Vote.forRefreshRates(mRefreshRateInZone, mRefreshRateInZone);
+
+            if (mBrightness < 0) {
+                // Either the setting isn't available or we shouldn't be observing yet anyways.
+                // Either way, just bail out since there's nothing we can do here.
+                return;
             }
 
-            if (DEBUG) {
-                Slog.d(TAG, "Display brightness " + brightness + ", ambient lux " +  mAmbientLux +
-                        ", Vote " + vote);
+            boolean insideLowZone = hasValidLowZone() && isInsideLowZone(mBrightness, mAmbientLux);
+            if (insideLowZone) {
+                vote = Vote.forRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone);
             }
-            updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote);
+
+            boolean insideHighZone = hasValidHighZone()
+                    && isInsideHighZone(mBrightness, mAmbientLux);
+            if (insideHighZone) {
+                vote = Vote.forRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone);
+            }
+
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " +  mAmbientLux
+                        + ", Vote " + vote);
+            }
+            updateVoteLocked(Vote.PRIORITY_FLICKER, vote);
         }
 
-        private void onScreenOn(boolean on) {
-            if (mScreenOn != on) {
-                mScreenOn = on;
+        private boolean hasValidLowZone() {
+            return mRefreshRateInLowZone > 0
+                    && (mShouldObserveDisplayLowChange || mShouldObserveAmbientLowChange);
+        }
+
+        private boolean hasValidHighZone() {
+            return mRefreshRateInHighZone > 0
+                    && (mShouldObserveDisplayHighChange || mShouldObserveAmbientHighChange);
+        }
+
+        private void updateDefaultDisplayState() {
+            Display display = mContext.getSystemService(DisplayManager.class)
+                    .getDisplay(Display.DEFAULT_DISPLAY);
+            if (display == null) {
+                return;
+            }
+
+            setDefaultDisplayState(display.getState());
+        }
+
+        @VisibleForTesting
+        public void setDefaultDisplayState(int state) {
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "setDefaultDisplayState: mDefaultDisplayState = "
+                        + mDefaultDisplayState + ", state = " + state);
+            }
+
+            if (mDefaultDisplayState != state) {
+                mDefaultDisplayState = state;
                 updateSensorStatus();
             }
         }
@@ -1328,55 +1611,89 @@
                 return;
             }
 
-            if (mShouldObserveAmbientChange && mScreenOn && !mLowPowerModeEnabled
-                    && mRefreshRateChangeable) {
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "updateSensorStatus: mShouldObserveAmbientLowChange = "
+                        + mShouldObserveAmbientLowChange + ", mShouldObserveAmbientHighChange = "
+                        + mShouldObserveAmbientHighChange);
+                Slog.d(TAG, "updateSensorStatus: mLowPowerModeEnabled = "
+                        + mLowPowerModeEnabled + ", mRefreshRateChangeable = "
+                        + mRefreshRateChangeable);
+            }
+
+            if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
+                     && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
                 mSensorManager.registerListener(mLightSensorListener,
                         mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
+                if (mLoggingEnabled) {
+                    Slog.d(TAG, "updateSensorStatus: registerListener");
+                }
             } else {
                 mLightSensorListener.removeCallbacks();
                 mSensorManager.unregisterListener(mLightSensorListener);
+                if (mLoggingEnabled) {
+                    Slog.d(TAG, "updateSensorStatus: unregisterListener");
+                }
             }
         }
 
-        private boolean isDefaultDisplayOn() {
-            final Display display = mContext.getSystemService(DisplayManager.class)
-                    .getDisplay(Display.DEFAULT_DISPLAY);
-            return display.getState() != Display.STATE_OFF
-                    && mContext.getSystemService(PowerManager.class).isInteractive();
+        private boolean isDeviceActive() {
+            return mDefaultDisplayState == Display.STATE_ON;
         }
 
         private final class LightSensorEventListener implements SensorEventListener {
             final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
             private float mLastSensorData;
+            private long mTimestamp;
+            private boolean mLoggingEnabled;
 
             public void dumpLocked(PrintWriter pw) {
                 pw.println("    mLastSensorData: " + mLastSensorData);
+                pw.println("    mTimestamp: " + formatTimestamp(mTimestamp));
+            }
+
+
+            public void setLoggingEnabled(boolean loggingEnabled) {
+                if (mLoggingEnabled == loggingEnabled) {
+                    return;
+                }
+                mLoggingEnabled = loggingEnabled;
             }
 
             @Override
             public void onSensorChanged(SensorEvent event) {
                 mLastSensorData = event.values[0];
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.d(TAG, "On sensor changed: " + mLastSensorData);
                 }
 
-                boolean zoneChanged = isDifferentZone(mLastSensorData, mAmbientLux);
-                if (zoneChanged && mLastSensorData < mAmbientLux) {
-                    // Easier to see flicker at lower brightness environment. Forget the history to
-                    // get immediate response.
-                    mAmbientFilter.clear();
+                boolean lowZoneChanged = isDifferentZone(mLastSensorData, mAmbientLux,
+                        mLowAmbientBrightnessThresholds);
+                boolean highZoneChanged = isDifferentZone(mLastSensorData, mAmbientLux,
+                        mHighAmbientBrightnessThresholds);
+                if ((lowZoneChanged && mLastSensorData < mAmbientLux)
+                        || (highZoneChanged && mLastSensorData > mAmbientLux)) {
+                    // Easier to see flicker at lower brightness environment or high brightness
+                    // environment. Forget the history to get immediate response.
+                    if (mAmbientFilter != null) {
+                        mAmbientFilter.clear();
+                    }
                 }
 
                 long now = SystemClock.uptimeMillis();
-                mAmbientFilter.addValue(now, mLastSensorData);
+                mTimestamp = System.currentTimeMillis();
+                if (mAmbientFilter != null) {
+                    mAmbientFilter.addValue(now, mLastSensorData);
+                }
 
                 mHandler.removeCallbacks(mInjectSensorEventRunnable);
                 processSensorData(now);
 
-                if (zoneChanged && mLastSensorData > mAmbientLux) {
+                if ((lowZoneChanged && mLastSensorData > mAmbientLux)
+                        || (highZoneChanged && mLastSensorData < mAmbientLux)) {
                     // Sensor may not report new event if there is no brightness change.
                     // Need to keep querying the temporal filter for the latest estimation,
-                    // until enter in higher lux zone or is interrupted by a new sensor event.
+                    // until sensor readout and filter estimation are in the same zone or
+                    // is interrupted by a new sensor event.
                     mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS);
                 }
             }
@@ -1390,18 +1707,26 @@
                 mHandler.removeCallbacks(mInjectSensorEventRunnable);
             }
 
+            private String formatTimestamp(long time) {
+                SimpleDateFormat dateFormat =
+                        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+                return dateFormat.format(new Date(time));
+            }
+
             private void processSensorData(long now) {
-                mAmbientLux = mAmbientFilter.getEstimate(now);
+                if (mAmbientFilter != null) {
+                    mAmbientLux = mAmbientFilter.getEstimate(now);
+                } else {
+                    mAmbientLux = mLastSensorData;
+                }
 
                 synchronized (mLock) {
                     onBrightnessChangedLocked();
                 }
             }
 
-            private boolean isDifferentZone(float lux1, float lux2) {
-                for (int z = 0; z < mAmbientBrightnessThresholds.length; z++) {
-                    final float boundary = mAmbientBrightnessThresholds[z];
-
+            private boolean isDifferentZone(float lux1, float lux2, int[] luxThresholds) {
+                for (final float boundary : luxThresholds) {
                     // Test each boundary. See if the current value and the new value are at
                     // different sides.
                     if ((lux1 <= boundary && lux2 > boundary)
@@ -1421,7 +1746,10 @@
                     processSensorData(now);
 
                     // Inject next event if there is a possible zone change.
-                    if (isDifferentZone(mLastSensorData, mAmbientLux)) {
+                    if (isDifferentZone(mLastSensorData, mAmbientLux,
+                            mLowAmbientBrightnessThresholds)
+                            || isDifferentZone(mLastSensorData, mAmbientLux,
+                            mHighAmbientBrightnessThresholds)) {
                         mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS);
                     }
                 }
@@ -1434,33 +1762,75 @@
         }
 
         public void startListening() {
-            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+            mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                     BackgroundThread.getExecutor(), this);
         }
 
         /*
          * Return null if no such property or wrong format (not comma separated integers).
          */
-        public int[] getBrightnessThresholds() {
+        public int[] getLowDisplayBrightnessThresholds() {
             return getIntArrayProperty(
                     DisplayManager.DeviceConfig.
-                            KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS);
+                            KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
         }
 
         /*
          * Return null if no such property or wrong format (not comma separated integers).
          */
-        public int[] getAmbientThresholds() {
+        public int[] getLowAmbientBrightnessThresholds() {
             return getIntArrayProperty(
                     DisplayManager.DeviceConfig.
-                            KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS);
+                            KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+        }
+
+        public int getRefreshRateInLowZone() {
+            int defaultRefreshRateInZone = mContext.getResources().getInteger(
+                    R.integer.config_defaultRefreshRateInZone);
+
+            int refreshRate = mDeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
+                    defaultRefreshRateInZone);
+
+            return refreshRate;
+        }
+
+        /*
+         * Return null if no such property or wrong format (not comma separated integers).
+         */
+        public int[] getHighDisplayBrightnessThresholds() {
+            return getIntArrayProperty(
+                    DisplayManager.DeviceConfig
+                            .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS);
+        }
+
+        /*
+         * Return null if no such property or wrong format (not comma separated integers).
+         */
+        public int[] getHighAmbientBrightnessThresholds() {
+            return getIntArrayProperty(
+                    DisplayManager.DeviceConfig
+                            .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS);
+        }
+
+        public int getRefreshRateInHighZone() {
+            int defaultRefreshRateInZone = mContext.getResources().getInteger(
+                    R.integer.config_fixedRefreshRateInHighZone);
+
+            int refreshRate = mDeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
+                    defaultRefreshRateInZone);
+
+            return refreshRate;
         }
 
         /*
          * Return null if no such property
          */
         public Float getDefaultPeakRefreshRate() {
-            float defaultPeakRefreshRate = DeviceConfig.getFloat(
+            float defaultPeakRefreshRate = mDeviceConfig.getFloat(
                     DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                     DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
 
@@ -1470,36 +1840,35 @@
             return defaultPeakRefreshRate;
         }
 
-        public int getRefreshRateInZone() {
-            int defaultRefreshRateInZone = mContext.getResources().getInteger(
-                    R.integer.config_defaultRefreshRateInZone);
-
-            int refreshRate = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_ZONE,
-                    defaultRefreshRateInZone);
-
-            return refreshRate;
-        }
-
         @Override
         public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
-            int[] brightnessThresholds = getBrightnessThresholds();
-            int[] ambientThresholds = getAmbientThresholds();
             Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
-            int refreshRateInZone = getRefreshRateInZone();
-
-            mHandler.obtainMessage(MSG_BRIGHTNESS_THRESHOLDS_CHANGED,
-                    new Pair<int[], int[]>(brightnessThresholds, ambientThresholds))
-                    .sendToTarget();
             mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
                     defaultPeakRefreshRate).sendToTarget();
-            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_ZONE_CHANGED, refreshRateInZone,
-                    0).sendToTarget();
+
+            int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds();
+            int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds();
+            int refreshRateInLowZone = getRefreshRateInLowZone();
+
+            mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED,
+                    new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
+                    .sendToTarget();
+            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, 0)
+                    .sendToTarget();
+
+            int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
+            int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
+            int refreshRateInHighZone = getRefreshRateInHighZone();
+
+            mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED,
+                    new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
+                    .sendToTarget();
+            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0)
+                    .sendToTarget();
         }
 
         private int[] getIntArrayProperty(String prop) {
-            String strArray = DeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
+            String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
                     null);
 
             if (strArray != null) {
@@ -1526,4 +1895,52 @@
         }
     }
 
+    interface Injector {
+        // TODO: brightnessfloat: change this to the float setting
+        Uri DISPLAY_BRIGHTNESS_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
+        Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
+
+        @NonNull
+        DeviceConfigInterface getDeviceConfig();
+
+        void registerBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer);
+
+        void unregisterBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer);
+
+        void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer);
+    }
+
+    @VisibleForTesting
+    static class RealInjector implements Injector {
+
+        @Override
+        @NonNull
+        public DeviceConfigInterface getDeviceConfig() {
+            return DeviceConfigInterface.REAL;
+        }
+
+        @Override
+        public void registerBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.registerContentObserver(DISPLAY_BRIGHTNESS_URI, false /*notifyDescendants*/,
+                    observer, UserHandle.USER_SYSTEM);
+        }
+
+        @Override
+        public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.unregisterContentObserver(observer);
+        }
+
+        @Override
+        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
+                    observer, UserHandle.USER_SYSTEM);
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 5732317..551df49 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -91,6 +91,10 @@
 
     private boolean mPendingStatusChangeBroadcast;
 
+    private static final String[] RECEIVER_PERMISSIONS_FOR_BROADCAST = {
+            android.Manifest.permission.ACCESS_FINE_LOCATION,
+    };
+
     // Called with SyncRoot lock held.
     public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
             Context context, Handler handler, Listener listener,
@@ -432,7 +436,8 @@
         }
 
         // Send protected broadcast about wifi display status to registered receivers.
-        getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+        getContext().createContextAsUser(UserHandle.ALL, 0)
+                .sendBroadcastWithMultiplePermissions(intent, RECEIVER_PERMISSIONS_FOR_BROADCAST);
     }
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 5bd3c57..8017a44 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -841,6 +841,9 @@
     }
 
     private void injectBestLocation(Location location) {
+        if (location.isFromMockProvider()) {
+            return;
+        }
         if (DEBUG) {
             Log.d(TAG, "injectBestLocation: " + location);
         }
@@ -942,6 +945,9 @@
     }
 
     private void injectLocation(Location location) {
+        if (location.isFromMockProvider()) {
+            return;
+        }
         if (location.hasAccuracy()) {
             if (DEBUG) {
                 Log.d(TAG, "injectLocation: " + location);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 6b5295f..7c73e24 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -35,6 +35,7 @@
 import android.service.gatekeeper.GateKeeperResponse;
 import android.service.gatekeeper.IGateKeeperService;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -904,7 +905,7 @@
         if (!tokenMap.containsKey(userId)) {
             return Collections.emptySet();
         }
-        return tokenMap.get(userId).keySet();
+        return new ArraySet<>(tokenMap.get(userId).keySet());
     }
 
     public boolean removePendingToken(long handle, int userId) {
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 6e655ea..d1eaaf8 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -196,6 +197,8 @@
         // TODO: Find a way to also send PID/UID in secure way.
         mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName);
 
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setBackgroundActivityStartsAllowed(true);
         if (mPendingIntent != null) {
             if (DEBUG_KEY_EVENT) {
                 Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent "
@@ -203,7 +206,8 @@
             }
             try {
                 mPendingIntent.send(
-                        context, resultCode, mediaButtonIntent, onFinishedListener, handler);
+                        context, resultCode, mediaButtonIntent, onFinishedListener, handler,
+                        /* requiredPermission= */null, options.toBundle());
             } catch (PendingIntent.CanceledException e) {
                 Log.w(TAG, "Error sending key event to media button receiver " + mPendingIntent, e);
                 return false;
@@ -226,7 +230,8 @@
                         break;
                     default:
                         // Legacy behavior for other cases.
-                        context.sendBroadcastAsUser(mediaButtonIntent, userHandle);
+                        context.sendBroadcastAsUser(mediaButtonIntent, userHandle,
+                                /* requiredPermission= */null, options.toBundle());
                 }
             } catch (Exception e) {
                 Log.w(TAG, "Error sending media button to the restored intent "
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 02b7582..348e9c1 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -986,6 +986,12 @@
         public boolean sendMediaButton(String packageName, int pid, int uid,
                 boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
             try {
+                if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
+                    final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction())
+                            + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode());
+                    mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+                            pid, uid, packageName, reason);
+                }
                 if (asSystemService) {
                     mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
                             Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb);
@@ -1003,6 +1009,12 @@
         public boolean sendMediaButton(String packageName, int pid, int uid,
                 boolean asSystemService, KeyEvent keyEvent) {
             try {
+                if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
+                    final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction())
+                            + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode());
+                    mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+                            pid, uid, packageName, reason);
+                }
                 if (asSystemService) {
                     mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
                             Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), 0, null);
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 818b2b7..59e4e2c 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -25,6 +25,7 @@
 import static com.android.server.media.MediaKeyDispatcher.isTripleTapOverridden;
 
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
@@ -113,6 +114,8 @@
             + /* Buffer for delayed delivery of key event */ 50;
     private static final int MULTI_TAP_TIMEOUT = ViewConfiguration.getMultiPressTimeout();
 
+    private static final int TEMP_ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS_DURATION_MS = 10_000;
+
     private final Context mContext;
     private final SessionManagerImpl mSessionManagerImpl;
     private final MessageHandler mHandler = new MessageHandler();
@@ -132,6 +135,7 @@
     private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
             new ArrayList<>();
 
+    private ActivityManagerInternal mActivityManagerInternal;
     private KeyguardManager mKeyguardManager;
     private AudioManagerInternal mAudioManagerInternal;
     private ContentResolver mContentResolver;
@@ -166,6 +170,7 @@
     public void onStart() {
         publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
         Watchdog.getInstance().addMonitor(this);
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(mContext);
@@ -491,6 +496,25 @@
         throw new IllegalArgumentException("packageName is not owned by the calling process");
     }
 
+    void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
+            int callingPid, int callingUid, String callingPackage, String reason) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            enforcePackageName(callingPackage, callingUid);
+            if (targetUid != callingUid
+                    && mActivityManagerInternal.canAllowWhileInUsePermissionInFgs(callingPid,
+                    callingUid, callingPackage)) {
+                Log.d(TAG, "tempAllowlistTargetPkgIfPossible callingPackage:"
+                        + callingPackage + " targetPackage:" + targetPackage
+                        + " reason:" + reason);
+                mActivityManagerInternal.tempAllowWhileInUsePermissionInFgs(targetUid,
+                        TEMP_ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS_DURATION_MS);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     /**
      * Checks a caller's authorization to register an IRemoteControlDisplay.
      * Authorization is granted if one of the following is true:
diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java
index 20df271..b199325 100644
--- a/services/core/java/com/android/server/media/MediaShellCommand.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -64,7 +64,7 @@
         }
         if (sThread == null) {
             Looper.prepare();
-            sThread = ActivityThread.systemMain();
+            sThread = ActivityThread.currentActivityThread();
             Context context = sThread.getSystemContext();
             sMediaSessionManager =
                     (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c01a115..80ade30 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5694,7 +5694,7 @@
                     + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId);
         }
 
-        checkRestrictedCategories(notification);
+        checkRestrictedCategories(pkg, notification);
 
         // Fix the notification as best we can.
         try {
@@ -7157,15 +7157,7 @@
                     // so need to check the notification still valide for vibrate.
                     synchronized (mNotificationLock) {
                         if (mNotificationsByKey.get(record.getKey()) != null) {
-                            // Vibrator checks the appops for the op package, not the caller,
-                            // so we need to add the bypass dnd flag to be heard. it's ok to
-                            // always add this flag here because we've already checked that we can
-                            // bypass dnd
-                            AudioAttributes.Builder aab =
-                                    new AudioAttributes.Builder(record.getAudioAttributes())
-                                    .setFlags(FLAG_BYPASS_INTERRUPTION_POLICY);
-                            mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getOpPkg(),
-                                    effect, "Notification (delayed)", aab.build());
+                            vibrate(record, effect, true);
                         } else {
                             Slog.e(TAG, "No vibration for canceled notification : "
                                     + record.getKey());
@@ -7173,8 +7165,7 @@
                     }
                 }).start();
             } else {
-                mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getPackageName(),
-                        effect, "Notification", record.getAudioAttributes());
+                vibrate(record, effect, false);
             }
             return true;
         } finally{
@@ -7182,6 +7173,16 @@
         }
     }
 
+    private void vibrate(NotificationRecord record, VibrationEffect effect, boolean delayed) {
+        // We need to vibrate as "android" so we can breakthrough DND. VibratorManagerService
+        // doesn't have a concept of vibrating on an app's behalf, so add the app information
+        // to the reason so we can still debug from bugreports
+        String reason = "Notification (" + record.getSbn().getOpPkg() + " "
+                + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : "");
+        mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME,
+                effect, reason, record.getAudioAttributes());
+    }
+
     private boolean isNotificationForCurrentUser(NotificationRecord record) {
         final int currentUser;
         final long token = Binder.clearCallingIdentity();
@@ -8541,7 +8542,7 @@
      * Check if the notification is of a category type that is restricted to system use only,
      * if so throw SecurityException
      */
-    private void checkRestrictedCategories(final Notification notification) {
+    private void checkRestrictedCategories(final String pkg, final Notification notification) {
         try {
             if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) {
                 return;
@@ -8551,10 +8552,24 @@
                     + "restrictions check thus the check will be done anyway");
         }
         if (Notification.CATEGORY_CAR_EMERGENCY.equals(notification.category)
-                || Notification.CATEGORY_CAR_WARNING.equals(notification.category)
-                || Notification.CATEGORY_CAR_INFORMATION.equals(notification.category)) {
+                || Notification.CATEGORY_CAR_WARNING.equals(notification.category)) {
                     checkCallerIsSystem();
         }
+
+        if (Notification.CATEGORY_CAR_INFORMATION.equals(notification.category)) {
+            checkCallerIsSystemOrSUW(pkg);
+        }
+    }
+
+    private void checkCallerIsSystemOrSUW(final String pkg) {
+
+        final PackageManagerInternal pmi = LocalServices.getService(
+                PackageManagerInternal.class);
+        String suwPkg =  pmi.getSetupWizardPackageName();
+        if (suwPkg != null && suwPkg.equals(pkg)) {
+            return;
+        }
+        checkCallerIsSystem();
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 9a9e733..da472be 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -37,6 +37,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.PackageManagerService;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -463,6 +464,7 @@
         return PendingIntent.getBroadcast(mContext,
                 REQUEST_CODE_REPOST,
                 new Intent(REPOST_ACTION)
+                        .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
                         .setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build())
                         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
                         .putExtra(EXTRA_KEY, key)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 58ffba2..624358c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -20,12 +20,15 @@
 import static android.Manifest.permission.INSTALL_PACKAGES;
 import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
 import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static android.Manifest.permission.QUERY_ALL_PACKAGES;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
 import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_BROWSABLE;
 import static android.content.Intent.CATEGORY_DEFAULT;
 import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.EXTRA_LONG_VERSION_CODE;
@@ -3665,8 +3668,6 @@
         PackageParser.readConfigUseRoundIcon(mContext.getResources());
 
         mServiceStartWithDelay = SystemClock.uptimeMillis() + (60 * 1000L);
-
-        Slog.i(TAG, "Fix for b/169414761 is applied");
     }
 
     /**
@@ -6202,6 +6203,10 @@
 
     @Override
     public List<String> getAllPackages() {
+        // Allow iorapd to call this method.
+        if (Binder.getCallingUid() != Process.IORAPD_UID) {
+            enforceSystemOrRootOrShell("getAllPackages is limited to privileged callers");
+        }
         final int callingUid = Binder.getCallingUid();
         final int callingUserId = UserHandle.getUserId(callingUid);
         synchronized (mLock) {
@@ -6480,14 +6485,10 @@
                     true /*allowDynamicSplits*/);
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
-            final boolean queryMayBeFiltered =
-                    UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID
-                            && !resolveForStart;
-
             final ResolveInfo bestChoice =
                     chooseBestActivity(
                             intent, resolvedType, flags, privateResolveFlags, query, userId,
-                            queryMayBeFiltered);
+                            queryMayBeFiltered(filterCallingUid, resolveForStart));
             final boolean nonBrowserOnly =
                     (privateResolveFlags & PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0;
             if (nonBrowserOnly && bestChoice != null && bestChoice.handleAllWebDataURI) {
@@ -6499,6 +6500,25 @@
         }
     }
 
+    /**
+     * Returns whether the query may be filtered to packages which are visible to the caller.
+     * Filtering occurs except in the following cases:
+     * <ul>
+     *     <li>system processes
+     *     <li>applications granted {@link android.Manifest.permission#QUERY_ALL_PACKAGES}
+     *     <li>when querying to start an app
+     * </ul>
+     *
+     * @param filterCallingUid the UID of the calling application
+     * @param queryForStart whether query is to start an app
+     * @return whether filtering may occur
+     */
+    private boolean queryMayBeFiltered(int filterCallingUid, boolean queryForStart) {
+        return UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID
+                && checkUidPermission(QUERY_ALL_PACKAGES, filterCallingUid) != PERMISSION_GRANTED
+                && !queryForStart;
+    }
+
     @Override
     public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) {
         if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID)) {
@@ -6864,7 +6884,7 @@
             boolean removeMatches, boolean debug, int userId) {
         return findPreferredActivityNotLocked(
                 intent, resolvedType, flags, query, priority, always, removeMatches, debug, userId,
-                UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
+                queryMayBeFiltered(Binder.getCallingUid(), /* queryForStart= */ false));
     }
 
     // TODO: handle preferred activities missing while user has amnesia
@@ -12526,6 +12546,7 @@
                     if (hasOldPkg) {
                         mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg,
                                 allPackageNames);
+                        mPermissionManager.revokeStoragePermissionsIfScopeExpanded(pkg, oldPkg);
                     }
                     if (hasPermissionDefinitionChanges) {
                         mPermissionManager.revokeRuntimePermissionsIfPermissionDefinitionChanged(
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 0aa51ee..5c6703a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -169,6 +169,8 @@
             switch (cmd) {
                 case "path":
                     return runPath();
+                case "validate":
+                    return runValidate();
                 case "dump":
                     return runDump();
                 case "list":
@@ -323,6 +325,17 @@
         return -1;
     }
 
+    private int runValidate() {
+        final PrintWriter pw = getOutPrintWriter();
+        String identifier = getNextArgRequired();
+        if ("169414761".equals(identifier)) {
+            pw.println("applied");
+        } else {
+            pw.println("missing");
+        }
+        return 0;
+    }
+
     /**
      * Shows module info
      *
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 3ffca02..b500e16 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -206,6 +206,9 @@
     private static final int USER_PERMISSION_FLAGS = FLAG_PERMISSION_USER_SET
             | FLAG_PERMISSION_USER_FIXED;
 
+    /** All storage permissions */
+    private static final List<String> STORAGE_PERMISSIONS = new ArrayList<>();
+
     /** If the permission of the value is granted, so is the key */
     private static final Map<String, String> FULLER_PERMISSION_MAP = new HashMap<>();
 
@@ -214,6 +217,9 @@
                 Manifest.permission.ACCESS_FINE_LOCATION);
         FULLER_PERMISSION_MAP.put(Manifest.permission.INTERACT_ACROSS_USERS,
                 Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
     }
 
     /** Lock to protect internal data access */
@@ -2266,6 +2272,49 @@
     }
 
     /**
+     * If the app is updated, and has scoped storage permissions, then it is possible that the
+     * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
+     * @param newPackage The new package that was installed
+     * @param oldPackage The old package that was updated
+     */
+    private void revokeStoragePermissionsIfScopeExpanded(
+            @NonNull AndroidPackage newPackage,
+            @NonNull AndroidPackage oldPackage,
+            @NonNull PermissionCallback permissionCallback) {
+        boolean downgradedSdk = oldPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q
+                && newPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q;
+        boolean upgradedSdk = oldPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q
+                && newPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q;
+        boolean newlyRequestsLegacy = !upgradedSdk && !oldPackage.isRequestLegacyExternalStorage()
+                && newPackage.isRequestLegacyExternalStorage();
+
+        if (!newlyRequestsLegacy && !downgradedSdk) {
+            return;
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserId(newPackage.getUid());
+        int numRequestedPermissions = newPackage.getRequestedPermissions().size();
+        for (int i = 0; i < numRequestedPermissions; i++) {
+            PermissionInfo permInfo = getPermissionInfo(newPackage.getRequestedPermissions().get(i),
+                    newPackage.getPackageName(), 0);
+            if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) {
+                continue;
+            }
+
+            EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(),
+                    "Revoking permission " + permInfo.name + " from package "
+                            + newPackage.getPackageName() + " as either the sdk downgraded "
+                            + downgradedSdk + " or newly requested legacy full storage "
+                            + newlyRequestsLegacy);
+
+            revokeRuntimePermissionInternal(permInfo.name, newPackage.getPackageName(),
+                    false, callingUid, userId, null, permissionCallback);
+        }
+
+    }
+
+    /**
      * We might auto-grant permissions if any permission of the group is already granted. Hence if
      * the group of a granted permission changes we need to revoke it to avoid having permissions of
      * the new group auto-granted.
@@ -4734,6 +4783,19 @@
                 @UserIdInt int userId) {
             return PermissionManagerService.this.isPermissionsReviewRequired(pkg, userId);
         }
+        /**
+         * If the app is updated, and has scoped storage permissions, then it is possible that the
+         * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
+         * @param newPackage The new package that was installed
+         * @param oldPackage The old package that was updated
+         */
+        public void revokeStoragePermissionsIfScopeExpanded(
+                @NonNull AndroidPackage newPackage,
+                @NonNull AndroidPackage oldPackage
+        ) {
+            PermissionManagerService.this.revokeStoragePermissionsIfScopeExpanded(newPackage,
+                    oldPackage, mDefaultPermissionCallback);
+        }
 
         @Override
         public void revokeRuntimePermissionsIfGroupChanged(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 393e852..a8e842f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -266,6 +266,17 @@
             @NonNull ArrayList<String> allPackageNames);
 
     /**
+     * If the app is updated, and has scoped storage permissions, then it is possible that the
+     * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
+     * @param newPackage The new package that was installed
+     * @param oldPackage The old package that was updated
+     */
+    public abstract void revokeStoragePermissionsIfScopeExpanded(
+            @NonNull AndroidPackage newPackage,
+            @NonNull AndroidPackage oldPackage
+    );
+
+    /**
      * Add all permissions in the given package.
      * <p>
      * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 76d4142..5decf5e 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1565,9 +1565,6 @@
         // Aggregate times for the same uids.
         SparseArray<long[]> aggregated = new SparseArray<>();
         mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
-            // For uids known to be aggregated from many entries allow mutating in place to avoid
-            // many copies. Otherwise, copy before aggregating.
-            boolean mutateInPlace = false;
             if (UserHandle.isIsolated(uid)) {
                 // Skip individual isolated uids because they are recycled and quickly removed from
                 // the underlying data source.
@@ -1575,26 +1572,18 @@
             } else if (UserHandle.isSharedAppGid(uid)) {
                 // All shared app gids are accounted together.
                 uid = LAST_SHARED_APPLICATION_GID;
-                mutateInPlace = true;
-            } else if (UserHandle.isApp(uid)) {
-                // Apps are accounted under their app id.
+            } else {
+                // Everything else is accounted under their base uid.
                 uid = UserHandle.getAppId(uid);
             }
 
             long[] aggCpuFreqTimeMs = aggregated.get(uid);
-            if (aggCpuFreqTimeMs != null) {
-                if (!mutateInPlace) {
-                    aggCpuFreqTimeMs = Arrays.copyOf(aggCpuFreqTimeMs, cpuFreqTimeMs.length);
-                    aggregated.put(uid, aggCpuFreqTimeMs);
-                }
-                for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
-                    aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
-                }
-            } else {
-                if (mutateInPlace) {
-                    cpuFreqTimeMs = Arrays.copyOf(cpuFreqTimeMs, cpuFreqTimeMs.length);
-                }
-                aggregated.put(uid, cpuFreqTimeMs);
+            if (aggCpuFreqTimeMs == null) {
+                aggCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+                aggregated.put(uid, aggCpuFreqTimeMs);
+            }
+            for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+                aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
             }
         });
 
@@ -2621,7 +2610,6 @@
         try {
             // force procstats to flush & combine old files into one store
             long lastHighWaterMark = readProcStatsHighWaterMark(section);
-            List<ParcelFileDescriptor> statsFiles = new ArrayList<>();
 
             ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS];
             for (int i = 0; i < protoStreams.length; i++) {
@@ -2631,7 +2619,7 @@
             ProcessStats procStats = new ProcessStats(false);
             // Force processStatsService to aggregate all in-storage and in-memory data.
             long highWaterMark = processStatsService.getCommittedStatsMerged(
-                    lastHighWaterMark, section, true, statsFiles, procStats);
+                    lastHighWaterMark, section, true, null, procStats);
             procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
 
             for (int i = 0; i < protoStreams.length; i++) {
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index 1c29c69..af26289 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -23,6 +23,7 @@
 import static com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
 
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
@@ -34,13 +35,12 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelableException;
 import android.os.RemoteCallback;
+import android.os.RemoteException;
 import android.os.UserHandle;
-import android.os.UserManagerInternal;
 import android.os.storage.StorageManagerInternal;
 import android.os.storage.StorageVolume;
 import android.service.storage.ExternalStorageService;
 import android.service.storage.IExternalStorageService;
-import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -48,14 +48,14 @@
 import com.android.server.LocalServices;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 /**
  * Controls the lifecycle of the {@link ActiveConnection} to an {@link ExternalStorageService}
@@ -66,25 +66,20 @@
 
     private static final int DEFAULT_REMOTE_TIMEOUT_SECONDS = 20;
 
-    private final Object mLock = new Object();
+    private final Object mSessionsLock = new Object();
     private final Context mContext;
     private final int mUserId;
     private final StorageSessionController mSessionController;
     private final ActiveConnection mActiveConnection = new ActiveConnection();
-    private final boolean mIsDemoUser;
     @GuardedBy("mLock") private final Map<String, Session> mSessions = new HashMap<>();
-    @GuardedBy("mLock") @Nullable private HandlerThread mHandlerThread;
+    private final HandlerThread mHandlerThread;
 
     public StorageUserConnection(Context context, int userId, StorageSessionController controller) {
         mContext = Objects.requireNonNull(context);
         mUserId = Preconditions.checkArgumentNonnegative(userId);
         mSessionController = controller;
-        mIsDemoUser = LocalServices.getService(UserManagerInternal.class)
-                .getUserInfo(userId).isDemo();
-        if (mIsDemoUser) {
-            mHandlerThread = new HandlerThread("StorageUserConnectionThread-" + mUserId);
-            mHandlerThread.start();
-        }
+        mHandlerThread = new HandlerThread("StorageUserConnectionThread-" + mUserId);
+        mHandlerThread.start();
     }
 
     /**
@@ -101,13 +96,12 @@
         Objects.requireNonNull(upperPath);
         Objects.requireNonNull(lowerPath);
 
-        prepareRemote();
-        synchronized (mLock) {
+        Session session = new Session(sessionId, upperPath, lowerPath);
+        synchronized (mSessionsLock) {
             Preconditions.checkArgument(!mSessions.containsKey(sessionId));
-            Session session = new Session(sessionId, upperPath, lowerPath);
             mSessions.put(sessionId, session);
-            mActiveConnection.startSessionLocked(session, pfd);
         }
+        mActiveConnection.startSession(session, pfd);
     }
 
     /**
@@ -121,10 +115,13 @@
         Objects.requireNonNull(sessionId);
         Objects.requireNonNull(vol);
 
-        prepareRemote();
-        synchronized (mLock) {
-            mActiveConnection.notifyVolumeStateChangedLocked(sessionId, vol);
+        synchronized (mSessionsLock) {
+            if (!mSessions.containsKey(sessionId)) {
+                Slog.i(TAG, "No session found for sessionId: " + sessionId);
+                return;
+            }
         }
+        mActiveConnection.notifyVolumeStateChanged(sessionId, vol);
     }
 
     /**
@@ -135,7 +132,7 @@
      * with {@link #waitForExit}.
      **/
     public Session removeSession(String sessionId) {
-        synchronized (mLock) {
+        synchronized (mSessionsLock) {
             return mSessions.remove(sessionId);
         }
     }
@@ -153,10 +150,7 @@
         }
 
         Slog.i(TAG, "Waiting for session end " + session + " ...");
-        prepareRemote();
-        synchronized (mLock) {
-            mActiveConnection.endSessionLocked(session);
-        }
+        mActiveConnection.endSession(session);
     }
 
     /** Restarts all available sessions for a user without blocking.
@@ -164,7 +158,7 @@
      * Any failures will be ignored.
      **/
     public void resetUserSessions() {
-        synchronized (mLock) {
+        synchronized (mSessionsLock) {
             if (mSessions.isEmpty()) {
                 // Nothing to reset if we have no sessions to restart; we typically
                 // hit this path if the user was consciously shut down.
@@ -179,7 +173,7 @@
      * Removes all sessions, without waiting.
      */
     public void removeAllSessions() {
-        synchronized (mLock) {
+        synchronized (mSessionsLock) {
             Slog.i(TAG, "Removing  " + mSessions.size() + " sessions for user: " + mUserId + "...");
             mSessions.clear();
         }
@@ -191,68 +185,54 @@
      */
     public void close() {
         mActiveConnection.close();
-        if (mIsDemoUser) {
-            mHandlerThread.quit();
-        }
+        mHandlerThread.quit();
     }
 
     /** Returns all created sessions. */
     public Set<String> getAllSessionIds() {
-        synchronized (mLock) {
+        synchronized (mSessionsLock) {
             return new HashSet<>(mSessions.keySet());
         }
     }
 
-    private void prepareRemote() throws ExternalStorageServiceException {
-        try {
-            waitForLatch(mActiveConnection.bind(), "remote_prepare_user " + mUserId);
-        } catch (IllegalStateException | TimeoutException e) {
-            throw new ExternalStorageServiceException("Failed to prepare remote", e);
-        }
-    }
-
-    private void waitForLatch(CountDownLatch latch, String reason) throws TimeoutException {
-        try {
-            if (!latch.await(DEFAULT_REMOTE_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
-                // TODO(b/140025078): Call ActivityManager ANR API?
-                Slog.wtf(TAG, "Failed to bind to the ExternalStorageService for user " + mUserId);
-                throw new TimeoutException("Latch wait for " + reason + " elapsed");
-            }
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new IllegalStateException("Latch wait for " + reason + " interrupted");
-        }
+    @FunctionalInterface
+    interface AsyncStorageServiceCall {
+        void run(@NonNull IExternalStorageService service, RemoteCallback callback) throws
+                RemoteException;
     }
 
     private final class ActiveConnection implements AutoCloseable {
+        private final Object mLock = new Object();
+
         // Lifecycle connection to the external storage service, needed to unbind.
         @GuardedBy("mLock") @Nullable private ServiceConnection mServiceConnection;
-        // True if we are connecting, either bound or binding
-        // False && mRemote != null means we are connected
-        // False && mRemote == null means we are neither connecting nor connected
-        @GuardedBy("mLock") @Nullable private boolean mIsConnecting;
-        // Binder object representing the external storage service.
-        // Non-null indicates we are connected
-        @GuardedBy("mLock") @Nullable private IExternalStorageService mRemote;
-        // Exception, if any, thrown from #startSessionLocked or #endSessionLocked
-        // Local variables cannot be referenced from a lambda expression :( so we
-        // save the exception received in the callback here. Since we guard access
-        // (and clear the exception state) with the same lock which we hold during
-        // the entire transaction, there is no risk of race.
-        @GuardedBy("mLock") @Nullable private ParcelableException mLastException;
-        // Not guarded by any lock intentionally and non final because we cannot
-        // reset latches so need to create a new one after one use
-        private CountDownLatch mLatch;
+
+        // A future that holds the remote interface
+        @GuardedBy("mLock")
+        @Nullable private CompletableFuture<IExternalStorageService> mRemoteFuture;
+
+        // A list of outstanding futures for async calls, for which we are still waiting
+        // for a callback. Used to unblock waiters if the service dies.
+        @GuardedBy("mLock")
+        private ArrayList<CompletableFuture<Void>> mOutstandingOps = new ArrayList<>();
 
         @Override
         public void close() {
             ServiceConnection oldConnection = null;
             synchronized (mLock) {
                 Slog.i(TAG, "Closing connection for user " + mUserId);
-                mIsConnecting = false;
                 oldConnection = mServiceConnection;
                 mServiceConnection = null;
-                mRemote = null;
+                if (mRemoteFuture != null) {
+                    // Let folks who are waiting for the connection know it ain't gonna happen
+                    mRemoteFuture.cancel(true);
+                    mRemoteFuture = null;
+                }
+                // Let folks waiting for callbacks from the remote know it ain't gonna happen
+                for (CompletableFuture<Void> op : mOutstandingOps) {
+                    op.cancel(true);
+                }
+                mOutstandingOps.clear();
             }
 
             if (oldConnection != null) {
@@ -266,37 +246,37 @@
             }
         }
 
-        public boolean isActiveLocked(Session session) {
-            if (!session.isInitialisedLocked()) {
-                Slog.i(TAG, "Session not initialised " + session);
-                return false;
-            }
+        private void waitForAsync(AsyncStorageServiceCall asyncCall) throws Exception {
+            CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
+            CompletableFuture<Void> opFuture = new CompletableFuture<>();
 
-            if (mRemote == null) {
-                throw new IllegalStateException("Valid session with inactive connection");
+            try {
+                synchronized (mLock) {
+                    mOutstandingOps.add(opFuture);
+                }
+                serviceFuture.thenCompose(service -> {
+                    try {
+                        asyncCall.run(service,
+                                new RemoteCallback(result -> setResult(result, opFuture)));
+                    } catch (RemoteException e) {
+                        opFuture.completeExceptionally(e);
+                    }
+
+                    return opFuture;
+                }).get(DEFAULT_REMOTE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            } finally {
+                synchronized (mLock) {
+                    mOutstandingOps.remove(opFuture);
+                }
             }
-            return true;
         }
 
-        public void startSessionLocked(Session session, ParcelFileDescriptor fd)
+        public void startSession(Session session, ParcelFileDescriptor fd)
                 throws ExternalStorageServiceException {
-            if (!isActiveLocked(session)) {
-                try {
-                    fd.close();
-                } catch (IOException e) {
-                    // ignore
-                }
-                return;
-            }
-
-            CountDownLatch latch = new CountDownLatch(1);
             try {
-                mRemote.startSession(session.sessionId,
+                waitForAsync((service, callback) -> service.startSession(session.sessionId,
                         FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE,
-                        fd, session.upperPath, session.lowerPath, new RemoteCallback(result ->
-                                setResultLocked(latch, result)));
-                waitForLatch(latch, "start_session " + session);
-                maybeThrowExceptionLocked();
+                        fd, session.upperPath, session.lowerPath, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to start session: " + session, e);
             } finally {
@@ -308,73 +288,49 @@
             }
         }
 
-        public void endSessionLocked(Session session) throws ExternalStorageServiceException {
-            if (!isActiveLocked(session)) {
-                // Nothing to end, not started yet
-                return;
-            }
-
-            CountDownLatch latch = new CountDownLatch(1);
+        public void endSession(Session session) throws ExternalStorageServiceException {
             try {
-                mRemote.endSession(session.sessionId, new RemoteCallback(result ->
-                        setResultLocked(latch, result)));
-                waitForLatch(latch, "end_session " + session);
-                maybeThrowExceptionLocked();
+                waitForAsync((service, callback) ->
+                        service.endSession(session.sessionId, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to end session: " + session, e);
             }
         }
 
-        public void notifyVolumeStateChangedLocked(String sessionId, StorageVolume vol) throws
+
+        public void notifyVolumeStateChanged(String sessionId, StorageVolume vol) throws
                 ExternalStorageServiceException {
-            CountDownLatch latch = new CountDownLatch(1);
             try {
-                mRemote.notifyVolumeStateChanged(sessionId, vol, new RemoteCallback(
-                        result -> setResultLocked(latch, result)));
-                waitForLatch(latch, "notify_volume_state_changed " + vol);
-                maybeThrowExceptionLocked();
+                waitForAsync((service, callback) ->
+                        service.notifyVolumeStateChanged(sessionId, vol, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to notify volume state changed "
                         + "for vol : " + vol, e);
             }
         }
 
-        private void setResultLocked(CountDownLatch latch, Bundle result) {
-            mLastException = result.getParcelable(EXTRA_ERROR);
-            latch.countDown();
-        }
-
-        private void maybeThrowExceptionLocked() throws IOException {
-            if (mLastException != null) {
-                ParcelableException lastException = mLastException;
-                mLastException = null;
-                try {
-                    lastException.maybeRethrow(IOException.class);
-                } catch (IOException e) {
-                    throw e;
-                }
-                throw new RuntimeException(lastException);
+        private void setResult(Bundle result, CompletableFuture<Void> future) {
+            ParcelableException ex = result.getParcelable(EXTRA_ERROR);
+            if (ex != null) {
+                future.completeExceptionally(ex);
+            } else {
+                future.complete(null);
             }
         }
 
-        public CountDownLatch bind() throws ExternalStorageServiceException {
+        private CompletableFuture<IExternalStorageService> connectIfNeeded() throws
+                ExternalStorageServiceException {
             ComponentName name = mSessionController.getExternalStorageServiceComponentName();
             if (name == null) {
                 // Not ready to bind
                 throw new ExternalStorageServiceException(
                         "Not ready to bind to the ExternalStorageService for user " + mUserId);
             }
-
             synchronized (mLock) {
-                if (mRemote != null || mIsConnecting) {
-                    // Connected or connecting (bound or binding)
-                    // Will wait on a latch that will countdown when we connect, unless we are
-                    // connected and the latch has already countdown, yay!
-                    return mLatch;
-                } // else neither connected nor connecting
-
-                mLatch = new CountDownLatch(1);
-                mIsConnecting = true;
+                if (mRemoteFuture != null) {
+                    return mRemoteFuture;
+                }
+                CompletableFuture<IExternalStorageService> future = new CompletableFuture<>();
                 mServiceConnection = new ServiceConnection() {
                     @Override
                     public void onServiceConnected(ComponentName name, IBinder service) {
@@ -406,16 +362,9 @@
 
                     private void handleConnection(IBinder service) {
                         synchronized (mLock) {
-                            if (mIsConnecting) {
-                                mRemote = IExternalStorageService.Stub.asInterface(service);
-                                mIsConnecting = false;
-                                mLatch.countDown();
-                                // Separate thread so we don't block the main thead
-                                return;
-                            }
+                            future.complete(
+                                    IExternalStorageService.Stub.asInterface(service));
                         }
-                        Slog.wtf(TAG, "Connection closed to the ExternalStorageService for user "
-                                + mUserId);
                     }
 
                     private void handleDisconnection() {
@@ -429,32 +378,19 @@
                 };
 
                 Slog.i(TAG, "Binding to the ExternalStorageService for user " + mUserId);
-                if (mIsDemoUser) {
-                    // Schedule on a worker thread for demo user to avoid deadlock
-                    if (mContext.bindServiceAsUser(new Intent().setComponent(name),
-                                    mServiceConnection,
-                                    Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
-                                    mHandlerThread.getThreadHandler(),
-                                    UserHandle.of(mUserId))) {
-                        Slog.i(TAG, "Bound to the ExternalStorageService for user " + mUserId);
-                        return mLatch;
-                    } else {
-                        mIsConnecting = false;
-                        throw new ExternalStorageServiceException(
-                                "Failed to bind to the ExternalStorageService for user " + mUserId);
-                    }
+                // Schedule on a worker thread, because the system server main thread can be
+                // very busy early in boot.
+                if (mContext.bindServiceAsUser(new Intent().setComponent(name),
+                                mServiceConnection,
+                                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
+                                mHandlerThread.getThreadHandler(),
+                                UserHandle.of(mUserId))) {
+                    Slog.i(TAG, "Bound to the ExternalStorageService for user " + mUserId);
+                    mRemoteFuture = future;
+                    return future;
                 } else {
-                    if (mContext.bindServiceAsUser(new Intent().setComponent(name),
-                                    mServiceConnection,
-                                    Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
-                                    UserHandle.of(mUserId))) {
-                        Slog.i(TAG, "Bound to the ExternalStorageService for user " + mUserId);
-                        return mLatch;
-                    } else {
-                        mIsConnecting = false;
-                        throw new ExternalStorageServiceException(
-                                "Failed to bind to the ExternalStorageService for user " + mUserId);
-                    }
+                    throw new ExternalStorageServiceException(
+                            "Failed to bind to the ExternalStorageService for user " + mUserId);
                 }
             }
         }
@@ -476,10 +412,5 @@
             return "[SessionId: " + sessionId + ". UpperPath: " + upperPath + ". LowerPath: "
                     + lowerPath + "]";
         }
-
-        @GuardedBy("mLock")
-        public boolean isInitialisedLocked() {
-            return !TextUtils.isEmpty(upperPath) && !TextUtils.isEmpty(lowerPath);
-        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/utils/DeviceConfigInterface.java b/services/core/java/com/android/server/utils/DeviceConfigInterface.java
similarity index 90%
rename from services/core/java/com/android/server/wm/utils/DeviceConfigInterface.java
rename to services/core/java/com/android/server/utils/DeviceConfigInterface.java
index ab7e7f6..ff60903 100644
--- a/services/core/java/com/android/server/wm/utils/DeviceConfigInterface.java
+++ b/services/core/java/com/android/server/utils/DeviceConfigInterface.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.utils;
+package com.android.server.utils;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -54,6 +54,11 @@
     boolean getBoolean(@NonNull String namespace, @NonNull String name, boolean defaultValue);
 
     /**
+     * @see DeviceConfig#getFloat
+     */
+    float getFloat(@NonNull String namespace, @NonNull String name, float defaultValue);
+
+    /**
      * @see DeviceConfig#addOnPropertiesChangedListener
      */
     void addOnPropertiesChangedListener(@NonNull String namespace, @NonNull Executor executor,
@@ -96,6 +101,12 @@
         }
 
         @Override
+        public float getFloat(@NonNull String namespace, @NonNull String name,
+                float defaultValue) {
+            return DeviceConfig.getFloat(namespace, name, defaultValue);
+        }
+
+        @Override
         public void addOnPropertiesChangedListener(String namespace, Executor executor,
                 DeviceConfig.OnPropertiesChangedListener listener) {
             DeviceConfig.addOnPropertiesChangedListener(namespace, executor, listener);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 8fe8853..8826883 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -39,7 +39,7 @@
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
 import android.app.WallpaperManager.SetWallpaperFlags;
-import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.backup.WallpaperBackupHelper;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -2861,10 +2861,10 @@
         if (!uidMatchPackage) {
             return false;   // callingPackage was faked.
         }
-
-        // TODO(b/144048540): DPM needs to take into account the userId, not just the package.
-        final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        if (dpm.isDeviceOwnerApp(callingPackage) || dpm.isProfileOwnerApp(callingPackage)) {
+        DevicePolicyManagerInternal devicePolicyManagerInternal =
+                LocalServices.getService(DevicePolicyManagerInternal.class);
+        if (devicePolicyManagerInternal != null &&
+                devicePolicyManagerInternal.isDeviceOrProfileOwnerInCallingUser(callingPackage)) {
             return true;
         }
         final int callingUserId = UserHandle.getCallingUserId();
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 8f59eef..eec2e41 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -264,7 +264,7 @@
                 return;
             }
             mLastLaunchedActivity = r;
-            if (!r.noDisplay) {
+            if (!r.noDisplay && !r.mDrawn) {
                 if (DEBUG_METRICS) Slog.i(TAG, "Add pending draw " + r);
                 mPendingDrawActivities.add(r);
             }
@@ -546,7 +546,7 @@
                     + " processSwitch=" + processSwitch + " info=" + info);
         }
 
-        if (launchedActivity.mDrawn) {
+        if (launchedActivity.mDrawn && launchedActivity.isVisible()) {
             // Launched activity is already visible. We cannot measure windows drawn delay.
             abort(info, "launched activity already visible");
             return;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index fd8fa82..113797c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2538,7 +2538,11 @@
 
     private void resumeTargetStackIfNeeded() {
         if (mDoResume) {
-            mRootWindowContainer.resumeFocusedStacksTopActivities(mTargetStack, null, mOptions);
+            if (mTargetStack.isFocusable()) {
+                mRootWindowContainer.resumeFocusedStacksTopActivities(mTargetStack, null, mOptions);
+            } else {
+                mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            }
         } else {
             ActivityOptions.abort(mOptions);
         }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8ae955a..fb2afb1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1513,6 +1513,13 @@
         }
         final int rotation = rotationForActivityInDifferentOrientation(r);
         if (rotation == ROTATION_UNDEFINED) {
+            // The display rotation won't be changed by current top activity. The client side
+            // adjustments of previous rotated activity should be cleared earlier. Otherwise if
+            // the current top is in the same process, it may get the rotated state. The transform
+            // will be cleared later with transition callback to ensure smooth animation.
+            if (hasTopFixedRotationLaunchingApp()) {
+                mFixedRotationLaunchingApp.notifyFixedRotationTransform(false /* enabled */);
+            }
             return false;
         }
         if (!r.getParent().matchParentBounds()) {
@@ -5877,6 +5884,11 @@
                 Slog.w(TAG, "Failed to deliver showInsets", e);
             }
         }
+
+        @Override
+        public boolean getImeRequestedVisibility(@InternalInsetsType int type) {
+            return getInsetsStateController().getImeSourceProvider().isImeShowing();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
index aac6b25..e925b05 100644
--- a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
+++ b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
@@ -27,7 +27,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
-import com.android.server.wm.utils.DeviceConfigInterface;
+import com.android.server.utils.DeviceConfigInterface;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 5ab48e15..d9dde75 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -35,6 +35,7 @@
     private InsetsControlTarget mImeTargetFromIme;
     private Runnable mShowImeRunner;
     private boolean mIsImeLayoutDrawn;
+    private boolean mImeShowing;
 
     ImeInsetsSourceProvider(InsetsSource source,
             InsetsStateController stateController, DisplayContent displayContent) {
@@ -74,6 +75,7 @@
 
                 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
                         target.getWindow() != null ? target.getWindow().getName() : "");
+                setImeShowing(true);
                 target.showInsets(WindowInsets.Type.ime(), true /* fromIme */);
                 if (target != mImeTargetFromIme && mImeTargetFromIme != null) {
                     ProtoLog.w(WM_DEBUG_IME,
@@ -147,11 +149,29 @@
     @Override
     public void dump(PrintWriter pw, String prefix) {
         super.dump(pw, prefix);
+        pw.print(prefix);
+        pw.print("mImeShowing=");
+        pw.print(mImeShowing);
         if (mImeTargetFromIme != null) {
-            pw.print(prefix);
-            pw.print("showImePostLayout pending for mImeTargetFromIme=");
+            pw.print(" showImePostLayout pending for mImeTargetFromIme=");
             pw.print(mImeTargetFromIme);
-            pw.println();
         }
+        pw.println();
+    }
+
+    /**
+     * Sets whether the IME is currently supposed to be showing according to
+     * InputMethodManagerService.
+     */
+    public void setImeShowing(boolean imeShowing) {
+        mImeShowing = imeShowing;
+    }
+
+    /**
+     * Returns whether the IME is currently supposed to be showing according to
+     * InputMethodManagerService.
+     */
+    public boolean isImeShowing() {
+        return mImeShowing;
     }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 5e7ed3f..2af2a97 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -39,6 +39,13 @@
     }
 
     /**
+     * @return The requested visibility of this target.
+     */
+    default boolean getImeRequestedVisibility(@InsetsState.InternalInsetsType int type) {
+        return InsetsState.getDefaultVisibility(type);
+    }
+
+    /**
      * @return The requested {@link InsetsState} of this target.
      */
     default InsetsState getRequestedInsetsState() {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index be1d0fc..d0012d0 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -27,6 +27,7 @@
 import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 
 import android.annotation.Nullable;
 import android.app.StatusBarManager;
@@ -128,6 +129,9 @@
 
     /** Updates the target which can control system bars. */
     void updateBarControlTarget(@Nullable WindowState focusedWin) {
+        if (focusedWin != null && (focusedWin.mAttrs.type == TYPE_APPLICATION_STARTING)) {
+            return;
+        }
         if (mFocusedWin != focusedWin){
             abortTransient();
         }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 9fdfbd0..ca83d54 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -279,7 +279,7 @@
         }
         mAdapter = new ControlAdapter();
         if (getSource().getType() == ITYPE_IME) {
-            setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
+            setClientVisible(target.getImeRequestedVisibility(mSource.getType()));
         }
         final Transaction t = mDisplayContent.getPendingTransaction();
         mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index c4a42ab..281d2c9 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -512,7 +512,7 @@
             setStatusBarState(mLockTaskModeState, userId);
             setKeyguardState(mLockTaskModeState, userId);
             if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
-                lockKeyguardIfNeeded();
+                lockKeyguardIfNeeded(userId);
             }
             if (getDevicePolicyManager() != null) {
                 getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
@@ -824,15 +824,15 @@
      * Helper method for locking the device immediately. This may be necessary when the device
      * leaves the pinned mode.
      */
-    private void lockKeyguardIfNeeded() {
-        if (shouldLockKeyguard()) {
+    private void lockKeyguardIfNeeded(int userId) {
+        if (shouldLockKeyguard(userId)) {
             mWindowManager.lockNow(null);
             mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
             getLockPatternUtils().requireCredentialEntry(USER_ALL);
         }
     }
 
-    private boolean shouldLockKeyguard() {
+    private boolean shouldLockKeyguard(int userId) {
         // This functionality should be kept consistent with
         // com.android.settings.security.ScreenPinningSettings (see b/127605586)
         try {
@@ -842,7 +842,7 @@
         } catch (Settings.SettingNotFoundException e) {
             // Log to SafetyNet for b/127605586
             android.util.EventLog.writeEvent(0x534e4554, "127605586", -1, "");
-            return getLockPatternUtils().isSecure(USER_CURRENT);
+            return getLockPatternUtils().isSecure(userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index db3c74f..b19b8c1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4597,11 +4597,11 @@
         }
         final boolean wasHidden = isForceHidden();
         mForceHiddenFlags = newFlags;
-        if (wasHidden && isFocusableAndVisible()) {
-            // The change in force-hidden state will change visibility without triggering a stack
-            // order change, so we should reset the preferred top focusable stack to ensure it's not
-            // used if a new activity is started from this task.
-            getDisplayArea().resetPreferredTopFocusableStackIfBelow(this);
+        if (wasHidden != isForceHidden() && isTopActivityFocusable()) {
+            // The change in force-hidden state will change visibility without triggering a root
+            // task order change, so we should reset the preferred top focusable root task to ensure
+            // it's not used if a new activity is started from this task.
+            getDisplayArea().resetPreferredTopFocusableRootTaskIfNeeded(this);
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 79f3b83..676d7e5 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -773,9 +773,10 @@
         onStackOrderChanged(stack);
     }
 
-    void resetPreferredTopFocusableStackIfBelow(Task task) {
+    /** Reset the mPreferredTopFocusableRootTask if it is or below the given task. */
+    void resetPreferredTopFocusableRootTaskIfNeeded(Task task) {
         if (mPreferredTopFocusableStack != null
-                && mPreferredTopFocusableStack.compareTo(task) < 0) {
+                && mPreferredTopFocusableStack.compareTo(task) <= 0) {
             mPreferredTopFocusableStack = null;
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index b0c5dbc..a5ebf9a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -23,7 +23,7 @@
 import android.provider.DeviceConfig;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.utils.DeviceConfigInterface;
+import com.android.server.utils.DeviceConfigInterface;
 
 import java.io.PrintWriter;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b7a2eb3..744afb9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -282,8 +282,8 @@
 import com.android.server.power.ShutdownThread;
 import com.android.server.protolog.ProtoLogImpl;
 import com.android.server.protolog.common.ProtoLog;
+import com.android.server.utils.DeviceConfigInterface;
 import com.android.server.utils.PriorityDump;
-import com.android.server.wm.utils.DeviceConfigInterface;
 
 import java.io.BufferedWriter;
 import java.io.DataInputStream;
@@ -7637,6 +7637,9 @@
                     dc.mInputMethodControlTarget.hideInsets(
                             WindowInsets.Type.ime(), true /* fromIme */);
                 }
+                if (dc != null) {
+                    dc.getInsetsStateController().getImeSourceProvider().setImeShowing(false);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index df49ac7..e6a35f1 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -455,15 +455,13 @@
         mAllowBackgroundActivityStarts = allowBackgroundActivityStarts;
     }
 
-    boolean areBackgroundActivityStartsAllowed() {
-        // allow if the whitelisting flag was explicitly set
-        if (mAllowBackgroundActivityStarts) {
-            if (DEBUG_ACTIVITY_STARTS) {
-                Slog.d(TAG, "[WindowProcessController(" + mPid
-                        + ")] Activity start allowed: mAllowBackgroundActivityStarts = true");
-            }
-            return true;
+    public boolean areBackgroundActivityStartsAllowedByGracePeriodSafe() {
+        synchronized (mAtm.mGlobalLockWithoutBoost) {
+            return areBackgroundActivityStartsAllowedByGracePeriod();
         }
+    }
+
+    boolean areBackgroundActivityStartsAllowedByGracePeriod() {
         // allow if any activity in the caller has either started or finished very recently, and
         // it must be started or finished after last stop app switches time.
         final long now = SystemClock.uptimeMillis();
@@ -485,8 +483,24 @@
                         + ACTIVITY_BG_START_GRACE_PERIOD_MS
                         + "ms grace period but also within stop app switch window");
             }
-
         }
+        return false;
+    }
+
+    boolean areBackgroundActivityStartsAllowed() {
+        // allow if the whitelisting flag was explicitly set
+        if (mAllowBackgroundActivityStarts) {
+            if (DEBUG_ACTIVITY_STARTS) {
+                Slog.d(TAG, "[WindowProcessController(" + mPid
+                        + ")] Activity start allowed: mAllowBackgroundActivityStarts = true");
+            }
+            return true;
+        }
+
+        if (areBackgroundActivityStartsAllowedByGracePeriod()) {
+            return true;
+        }
+
         // allow if the proc is instrumenting with background activity starts privs
         if (mInstrumentingWithBackgroundActivityStartPrivileges) {
             if (DEBUG_ACTIVITY_STARTS) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index d86f6c9..9b1526e 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -622,11 +622,6 @@
         state.mIsTransforming = false;
         if (applyDisplayRotation != null) {
             applyDisplayRotation.run();
-        } else {
-            // The display will not rotate to the rotation of this container, let's cancel them.
-            for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
-                state.mAssociatedTokens.get(i).cancelFixedRotationTransform();
-            }
         }
         // The state is cleared at the end, because it is used to indicate that other windows can
         // use seamless rotation when applying rotation to display.
@@ -634,11 +629,15 @@
             final WindowToken token = state.mAssociatedTokens.get(i);
             token.mFixedRotationTransformState = null;
             token.notifyFixedRotationTransform(false /* enabled */);
+            if (applyDisplayRotation == null) {
+                // Notify cancellation because the display does not change rotation.
+                token.cancelFixedRotationTransform();
+            }
         }
     }
 
     /** Notifies application side to enable or disable the rotation adjustment of display info. */
-    private void notifyFixedRotationTransform(boolean enabled) {
+    void notifyFixedRotationTransform(boolean enabled) {
         FixedRotationAdjustments adjustments = null;
         // A token may contain windows of the same processes or different processes. The list is
         // used to avoid sending the same adjustments to a process multiple times.
@@ -682,7 +681,6 @@
             // The window may be detached or detaching.
             return;
         }
-        notifyFixedRotationTransform(false /* enabled */);
         final int originalRotation = getWindowConfiguration().getRotation();
         onConfigurationChanged(parent.getConfiguration());
         onCancelFixedRotationTransform(originalRotation);
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 5fde550..7a6d310 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -191,19 +191,18 @@
     static std::array<std::atomic<HalSupport>,
                       static_cast<int32_t>(Boost::DISPLAY_UPDATE_IMMINENT) + 1>
             boostSupportedArray = {HalSupport::UNKNOWN};
+    size_t idx = static_cast<size_t>(boost);
 
     // Quick return if boost is not supported by HAL
-    if (boost > Boost::DISPLAY_UPDATE_IMMINENT ||
-        boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::OFF) {
+    if (idx >= boostSupportedArray.size() || boostSupportedArray[idx] == HalSupport::OFF) {
         ALOGV("Skipped setPowerBoost %s because HAL doesn't support it", toString(boost).c_str());
         return;
     }
 
-    if (boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::UNKNOWN) {
+    if (boostSupportedArray[idx] == HalSupport::UNKNOWN) {
         bool isSupported = false;
         handle->isBoostSupported(boost, &isSupported);
-        boostSupportedArray[static_cast<int32_t>(boost)] =
-            isSupported ? HalSupport::ON : HalSupport::OFF;
+        boostSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
         if (!isSupported) {
             ALOGV("Skipped setPowerBoost %s because HAL doesn't support it",
                   toString(boost).c_str());
@@ -231,19 +230,18 @@
     // Need to increase the array if more mode supported.
     static std::array<std::atomic<HalSupport>, static_cast<int32_t>(Mode::DISPLAY_INACTIVE) + 1>
             modeSupportedArray = {HalSupport::UNKNOWN};
+    size_t idx = static_cast<size_t>(mode);
 
     // Quick return if mode is not supported by HAL
-    if (mode > Mode::DISPLAY_INACTIVE ||
-        modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::OFF) {
+    if (idx >= modeSupportedArray.size() || modeSupportedArray[idx] == HalSupport::OFF) {
         ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
         return false;
     }
 
-    if (modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::UNKNOWN) {
+    if (modeSupportedArray[idx] == HalSupport::UNKNOWN) {
         bool isSupported = false;
         handle->isModeSupported(mode, &isSupported);
-        modeSupportedArray[static_cast<int32_t>(mode)] =
-            isSupported ? HalSupport::ON : HalSupport::OFF;
+        modeSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
         if (!isSupported) {
             ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
             return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 736a6f6..a486e91 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12872,6 +12872,26 @@
                     ? AppOpsManager.MODE_ALLOWED
                     : AppOpsManager.opToDefaultMode(AppOpsManager.OP_INTERACT_ACROSS_PROFILES);
         }
+
+        public boolean isDeviceOrProfileOwnerInCallingUser(String packageName) {
+            return isDeviceOwnerInCallingUser(packageName)
+                    || isProfileOwnerInCallingUser(packageName);
+        }
+
+        private boolean isDeviceOwnerInCallingUser(String packageName) {
+            final ComponentName deviceOwnerInCallingUser =
+                    DevicePolicyManagerService.this.getDeviceOwnerComponent(
+                            /* callingUserOnly= */ true);
+            return deviceOwnerInCallingUser != null
+                    && packageName.equals(deviceOwnerInCallingUser.getPackageName());
+        }
+
+        private boolean isProfileOwnerInCallingUser(String packageName) {
+            final ComponentName profileOwnerInCallingUser =
+                    getProfileOwnerAsUser(UserHandle.getCallingUserId());
+            return profileOwnerInCallingUser != null
+                    && packageName.equals(profileOwnerInCallingUser.getPackageName());
+        }
     }
 
     private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 96a44a4..8d245ce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -134,11 +134,11 @@
         ApplicationInfo ai = new ApplicationInfo();
         ai.packageName = packageName;
         ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
-        app.pid = pid;
+        app.pid = app.mPidForCompact = pid;
         app.info.uid = packageUid;
         // Exact value does not mater, it can be any state for which compaction is allowed.
         app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-        app.setAdj = 905;
+        app.setAdj = app.mSetAdjForCompact = 905;
         return app;
     }
 
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 08e2def..11d050c 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -57,7 +57,6 @@
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
-
     ],
 
     aidl: {
@@ -119,6 +118,7 @@
         "utils/**/*.java",
         "utils/**/*.kt",
         "utils-mockito/**/*.kt",
+        ":services.core-sources-deviceconfig-interface",
     ],
     static_libs: [
         "junit",
@@ -135,6 +135,7 @@
         "utils/**/*.java",
         "utils/**/*.kt",
         "utils-mockito/**/*.kt",
+        ":services.core-sources-deviceconfig-interface",
     ],
     static_libs: [
         "junit",
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 43a396d..c6f6fa8 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -16,49 +16,100 @@
 
 package com.android.server.display;
 
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
+import static com.android.server.display.DisplayModeDirector.Vote.PRIORITY_FLICKER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.ContextWrapper;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.display.DisplayModeDirector.BrightnessObserver;
 import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
 import com.android.server.display.DisplayModeDirector.Vote;
+import com.android.server.testutils.FakeDeviceConfigInterface;
 
 import com.google.common.truth.Truth;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DisplayModeDirectorTest {
     // The tolerance within which we consider something approximately equals.
+    private static final String TAG = "DisplayModeDirectorTest";
+    private static final boolean DEBUG = false;
     private static final float FLOAT_TOLERANCE = 0.01f;
 
     private Context mContext;
+    private FakesInjector mInjector;
+    private Handler mHandler;
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        mInjector = new FakesInjector();
+        mHandler = new Handler(Looper.getMainLooper());
     }
 
     private DisplayModeDirector createDirectorFromRefreshRateArray(
             float[] refreshRates, int baseModeId) {
         DisplayModeDirector director =
-                new DisplayModeDirector(mContext, new Handler(Looper.getMainLooper()));
+                new DisplayModeDirector(mContext, mHandler, mInjector);
         int displayId = 0;
         Display.Mode[] modes = new Display.Mode[refreshRates.length];
         for (int i = 0; i < refreshRates.length; i++) {
@@ -159,9 +210,9 @@
     }
 
     @Test
-    public void testBrightnessHasLowerPriorityThanUser() {
-        assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_REFRESH_RATE);
-        assertTrue(Vote.PRIORITY_LOW_BRIGHTNESS < Vote.PRIORITY_APP_REQUEST_SIZE);
+    public void testFlickerHasLowerPriorityThanUser() {
+        assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_REFRESH_RATE);
+        assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_SIZE);
 
         int displayId = 0;
         DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
@@ -169,7 +220,7 @@
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(displayId, votes);
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90));
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
@@ -177,7 +228,7 @@
 
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90));
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
@@ -185,7 +236,7 @@
 
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90));
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
@@ -193,7 +244,7 @@
 
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 60));
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
@@ -202,10 +253,10 @@
 
     @Test
     public void testAppRequestRefreshRateRange() {
-        // Confirm that the app request range doesn't include low brightness or min refresh rate
-        // settings, but does include everything else.
+        // Confirm that the app request range doesn't include flicker or min refresh rate settings,
+        // but does include everything else.
         assertTrue(
-                Vote.PRIORITY_LOW_BRIGHTNESS < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
+                PRIORITY_FLICKER < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
         assertTrue(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE
                 < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
         assertTrue(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE
@@ -216,7 +267,7 @@
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(displayId, votes);
-        votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60));
+        votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
         Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
@@ -302,4 +353,375 @@
         verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
         verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
     }
+
+    @Test
+    public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+        SensorManager sensorManager = createMockSensorManager(createLightSensor());
+
+        final int initialRefreshRate = 60;
+        mInjector.getDeviceConfig().setRefreshRateInLowZone(initialRefreshRate);
+        director.start(sensorManager);
+        assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
+                .isEqualTo(initialRefreshRate);
+
+        final int updatedRefreshRate = 90;
+        mInjector.getDeviceConfig().setRefreshRateInLowZone(updatedRefreshRate);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
+                .isEqualTo(updatedRefreshRate);
+    }
+
+    @Test
+    public void testBrightnessObserverThresholdsInZone() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+        SensorManager sensorManager = createMockSensorManager(createLightSensor());
+
+        final int[] initialDisplayThresholds = { 10 };
+        final int[] initialAmbientThresholds = { 20 };
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setLowDisplayBrightnessThresholds(initialDisplayThresholds);
+        config.setLowAmbientBrightnessThresholds(initialAmbientThresholds);
+        director.start(sensorManager);
+
+        assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
+                .isEqualTo(initialDisplayThresholds);
+        assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
+                .isEqualTo(initialAmbientThresholds);
+
+        final int[] updatedDisplayThresholds = { 9, 14 };
+        final int[] updatedAmbientThresholds = { -1, 19 };
+        config.setLowDisplayBrightnessThresholds(updatedDisplayThresholds);
+        config.setLowAmbientBrightnessThresholds(updatedAmbientThresholds);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
+                .isEqualTo(updatedDisplayThresholds);
+        assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
+                .isEqualTo(updatedAmbientThresholds);
+    }
+
+    @Test
+    public void testLockFpsForLowZone() throws Exception {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInLowZone(90);
+        config.setLowDisplayBrightnessThresholds(new int[] { 10 });
+        config.setLowAmbientBrightnessThresholds(new int[] { 20 });
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+        director.start(sensorManager);
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        setBrightness(10);
+        // Sensor reads 20 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
+        assertVoteForRefreshRateLocked(vote, 90 /*fps*/);
+
+        setBrightness(125);
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
+        assertThat(vote).isNull();
+    }
+
+    @Test
+    public void testLockFpsForHighZone() throws Exception {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInHighZone(60);
+        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+        director.start(sensorManager);
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        setBrightness(100);
+        // Sensor reads 2000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
+        assertThat(vote).isNull();
+
+        setBrightness(255);
+        // Sensor reads 9000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
+        assertVoteForRefreshRateLocked(vote, 60 /*fps*/);
+    }
+
+    @Test
+    public void testSensorRegistration() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+        director.start(sensorManager);
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+
+        // Dispaly state changed from On to Doze
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_DOZE);
+        Mockito.verify(sensorManager)
+                .unregisterListener(listenerCaptor.capture());
+
+        // Dispaly state changed from Doze to On
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+        Mockito.verify(sensorManager, times(2))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+
+    }
+
+    private void assertVoteForRefreshRateLocked(Vote vote, float refreshRate) {
+        assertThat(vote).isNotNull();
+        final DisplayModeDirector.RefreshRateRange expectedRange =
+                new DisplayModeDirector.RefreshRateRange(refreshRate, refreshRate);
+        assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
+    }
+
+    private static class FakeDeviceConfig extends FakeDeviceConfigInterface {
+        @Override
+        public String getProperty(String namespace, String name) {
+            Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
+            return super.getProperty(namespace, name);
+        }
+
+        @Override
+        public void addOnPropertiesChangedListener(
+                String namespace,
+                Executor executor,
+                DeviceConfig.OnPropertiesChangedListener listener) {
+            Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
+            super.addOnPropertiesChangedListener(namespace, executor, listener);
+        }
+
+        void setRefreshRateInLowZone(int fps) {
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_LOW_ZONE,
+                    String.valueOf(fps));
+        }
+
+        void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
+            String thresholds = toPropertyValue(brightnessThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Brightness Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        void setLowAmbientBrightnessThresholds(int[] ambientThresholds) {
+            String thresholds = toPropertyValue(ambientThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Ambient Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        void setRefreshRateInHighZone(int fps) {
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HIGH_ZONE,
+                    String.valueOf(fps));
+        }
+
+        void setHighDisplayBrightnessThresholds(int[] brightnessThresholds) {
+            String thresholds = toPropertyValue(brightnessThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Brightness Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        void setHighAmbientBrightnessThresholds(int[] ambientThresholds) {
+            String thresholds = toPropertyValue(ambientThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Ambient Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        @NonNull
+        private static String toPropertyValue(@NonNull int[] intArray) {
+            return Arrays.stream(intArray)
+                    .mapToObj(Integer::toString)
+                    .collect(Collectors.joining(","));
+        }
+    }
+
+    private void setBrightness(int brightness) {
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS,
+                brightness);
+        mInjector.notifyBrightnessChanged();
+        waitForIdleSync();
+    }
+
+    private void setPeakRefreshRate(float fps) {
+        Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
+                 fps);
+        mInjector.notifyPeakRefreshRateChanged();
+        waitForIdleSync();
+    }
+
+    private static SensorManager createMockSensorManager(Sensor... sensors) {
+        SensorManager sensorManager = Mockito.mock(SensorManager.class);
+        when(sensorManager.getSensorList(anyInt())).then((invocation) -> {
+            List<Sensor> requestedSensors = new ArrayList<>();
+            int type = invocation.getArgument(0);
+            for (Sensor sensor : sensors) {
+                if (sensor.getType() == type || type == Sensor.TYPE_ALL) {
+                    requestedSensors.add(sensor);
+                }
+            }
+            return requestedSensors;
+        });
+
+        when(sensorManager.getDefaultSensor(anyInt())).then((invocation) -> {
+            int type = invocation.getArgument(0);
+            for (Sensor sensor : sensors) {
+                if (sensor.getType() == type) {
+                    return sensor;
+                }
+            }
+            return null;
+        });
+        return sensorManager;
+    }
+
+    private static Sensor createLightSensor() {
+        try {
+            return TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        } catch (Exception e) {
+            // There's nothing we can do if this fails, just throw a RuntimeException so that we
+            // don't have to mark every function that might call this as throwing Exception
+            throw new RuntimeException("Failed to create a light sensor", e);
+        }
+    }
+
+    private void waitForIdleSync() {
+        mHandler.runWithScissors(() -> { }, 500 /*timeout*/);
+    }
+
+    static class FakesInjector implements DisplayModeDirector.Injector {
+        private final FakeDeviceConfig mDeviceConfig;
+        private ContentObserver mBrightnessObserver;
+        private ContentObserver mPeakRefreshRateObserver;
+
+        FakesInjector() {
+            mDeviceConfig = new FakeDeviceConfig();
+        }
+
+        @NonNull
+        public FakeDeviceConfig getDeviceConfig() {
+            return mDeviceConfig;
+        }
+
+        @Override
+        public void registerBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            if (mBrightnessObserver != null) {
+                throw new IllegalStateException("Tried to register a second brightness observer");
+            }
+            mBrightnessObserver = observer;
+        }
+
+        @Override
+        public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mBrightnessObserver = null;
+        }
+
+        void notifyBrightnessChanged() {
+            if (mBrightnessObserver != null) {
+                mBrightnessObserver.dispatchChange(false /*selfChange*/, DISPLAY_BRIGHTNESS_URI);
+            }
+        }
+
+        @Override
+        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mPeakRefreshRateObserver = observer;
+        }
+
+        void notifyPeakRefreshRateChanged() {
+            if (mPeakRefreshRateObserver != null) {
+                mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
+                        PEAK_REFRESH_RATE_URI);
+            }
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 2c2fdca..f33f9ed 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -246,7 +246,8 @@
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertTrue(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, 0, PRIMARY_USER_ID).getResponseCode());
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
 
@@ -275,7 +276,8 @@
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, 0, PRIMARY_USER_ID).getResponseCode());
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mLocalService.setLockCredentialWithToken(nonePassword(), handle, token, PRIMARY_USER_ID);
@@ -301,7 +303,8 @@
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, 0, PRIMARY_USER_ID).getResponseCode());
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mService.setLockCredential(pattern, password, PRIMARY_USER_ID);
@@ -377,6 +380,36 @@
     }
 
     @Test
+    public void testActivateMultipleEscrowTokens() throws Exception {
+        byte[] token0 = "some-high-entropy-secure-token-0".getBytes();
+        byte[] token1 = "some-high-entropy-secure-token-1".getBytes();
+        byte[] token2 = "some-high-entropy-secure-token-2".getBytes();
+
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential pattern = newPattern("123654");
+        initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+
+        long handle0 = mLocalService.addEscrowToken(token0, PRIMARY_USER_ID, null);
+        long handle1 = mLocalService.addEscrowToken(token1, PRIMARY_USER_ID, null);
+        long handle2 = mLocalService.addEscrowToken(token2, PRIMARY_USER_ID, null);
+
+        // Activate token
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, 0, PRIMARY_USER_ID).getResponseCode());
+
+        // Verify tokens work
+        assertTrue(mLocalService.isEscrowTokenActive(handle0, PRIMARY_USER_ID));
+        assertTrue(mLocalService.setLockCredentialWithToken(
+                pattern, handle0, token0, PRIMARY_USER_ID));
+        assertTrue(mLocalService.isEscrowTokenActive(handle1, PRIMARY_USER_ID));
+        assertTrue(mLocalService.setLockCredentialWithToken(
+                pattern, handle1, token1, PRIMARY_USER_ID));
+        assertTrue(mLocalService.isEscrowTokenActive(handle2, PRIMARY_USER_ID));
+        assertTrue(mLocalService.setLockCredentialWithToken(
+                pattern, handle2, token2, PRIMARY_USER_ID));
+    }
+
+    @Test
     public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
         LockscreenCredential password = newPassword("password");
         LockscreenCredential pattern = newPattern("123654");
@@ -503,7 +536,8 @@
         reset(mDevicePolicyManager);
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
-        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, 0, PRIMARY_USER_ID).getResponseCode());
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         mService.onCleanupUser(PRIMARY_USER_ID);
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/FakeDeviceConfigInterface.java b/services/tests/servicestests/utils/com/android/server/testutils/FakeDeviceConfigInterface.java
similarity index 92%
rename from services/tests/wmtests/src/com/android/server/wm/utils/FakeDeviceConfigInterface.java
rename to services/tests/servicestests/utils/com/android/server/testutils/FakeDeviceConfigInterface.java
index 2904a5b..a67f645 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/FakeDeviceConfigInterface.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/FakeDeviceConfigInterface.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.utils;
+package com.android.server.testutils;
 
 import android.annotation.NonNull;
 import android.provider.DeviceConfig;
@@ -22,6 +22,7 @@
 import android.util.Pair;
 
 import com.android.internal.util.Preconditions;
+import com.android.server.utils.DeviceConfigInterface;
 
 import java.lang.reflect.Constructor;
 import java.util.HashMap;
@@ -122,6 +123,19 @@
     }
 
     @Override
+    public float getFloat(String namespace, String name, float defaultValue) {
+        String value = getProperty(namespace, name);
+        if (value == null) {
+            return defaultValue;
+        }
+        try {
+            return Float.parseFloat(value);
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
+    @Override
     public boolean getBoolean(String namespace, String name, boolean defaultValue) {
         String value = getProperty(namespace, name);
         return value != null ? Boolean.parseBoolean(value) : defaultValue;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index ca2ef95..ad15a99 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -57,6 +57,7 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
@@ -77,10 +78,12 @@
 import com.android.internal.util.IntPair;
 import com.android.server.UiServiceTestCase;
 import com.android.server.lights.LogicalLight;
+import com.android.server.pm.PackageManagerService;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -407,12 +410,17 @@
     }
 
     private void verifyVibrate() {
+        ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class);
         verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
-                anyString(), any());
+                anyString(), captor.capture());
+        assertEquals(0, (captor.getValue().getAllFlags()
+                & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
     }
 
     private void verifyVibrate(int times) {
-        verify(mVibrator, times(times)).vibrate(anyInt(), anyString(), any(), anyString(), any());
+        verify(mVibrator, times(times)).vibrate(eq(Process.SYSTEM_UID),
+                eq(PackageManagerService.PLATFORM_PACKAGE_NAME), any(), anyString(),
+                any(AudioAttributes.class));
     }
 
     private void verifyVibrateLooped() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ee89e1c..38446cc 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -111,6 +111,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutServiceInternal;
@@ -178,7 +179,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -244,6 +244,8 @@
     Resources mResources;
     @Mock
     RankingHandler mRankingHandler;
+    @Mock
+    protected PackageManagerInternal mPackageManagerInternal;
 
     private static final int MAX_POST_DELAY = 1000;
 
@@ -1188,6 +1190,26 @@
     }
 
     @Test
+    public void testEnqueuedRestrictedNotifications_asSuwApp() throws Exception {
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+        when(mPackageManagerInternal.getSetupWizardPackageName()).thenReturn(PKG);
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0))
+                .thenReturn(true);
+
+        final StatusBarNotification sbn =
+                generateNotificationRecord(mTestNotificationChannel, 0, "", false).getSbn();
+        sbn.getNotification().category = Notification.CATEGORY_CAR_INFORMATION;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testEnqueuedRestrictedNotifications_asSuwApp",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+
+        waitForIdle();
+        assertEquals(1, mBinderService.getActiveNotifications(PKG).length);
+    }
+
+    @Test
     public void testBlockedNotifications_blockedByAssistant() throws Exception {
         when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 3deeea2..c2ead5f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -49,6 +49,7 @@
 
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.UiServiceTestCase;
+import com.android.server.pm.PackageManagerService;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -257,6 +258,17 @@
     }
 
     @Test
+    public void testSnoozeSentToAndroid() throws Exception {
+        NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+        mSnoozeHelper.snooze(r, 1000);
+        ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAm, times(1)).setExactAndAllowWhileIdle(
+                anyInt(), anyLong(), captor.capture());
+        assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
+                captor.getValue().getIntent().getPackage());
+    }
+
+    @Test
     public void testSnooze() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         mSnoozeHelper.snooze(r, (String) null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 5b516a9..53a6d3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -312,6 +312,22 @@
     }
 
     @Test
+    public void testActivityDrawnBeforeTransition() {
+        mTopActivity.setVisible(false);
+        notifyActivityLaunching(mTopActivity.intent);
+        // Assume the activity is launched the second time consecutively. The drawn event is from
+        // the first time (omitted in test) launch that is earlier than transition.
+        mTopActivity.mDrawn = true;
+        notifyWindowsDrawn(mTopActivity);
+        notifyActivityLaunched(START_SUCCESS, mTopActivity);
+        // If the launching activity was drawn when starting transition, the launch event should
+        // be reported successfully.
+        notifyTransitionStarting(mTopActivity);
+
+        verifyOnActivityLaunchFinished(mTopActivity);
+    }
+
+    @Test
     public void testActivityRecordProtoIsNotTooBig() {
         // The ActivityRecordProto must not be too big, otherwise converting it at runtime
         // will become prohibitively expensive.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d99606b..b282205 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -88,6 +88,7 @@
 import android.annotation.SuppressLint;
 import android.app.ActivityTaskManager;
 import android.app.WindowConfiguration;
+import android.app.servertransaction.FixedRotationAdjustmentsItem;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -1346,6 +1347,36 @@
     }
 
     @Test
+    public void testClearIntermediateFixedRotationAdjustments() throws RemoteException {
+        final ActivityRecord activity = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+                .setDisplay(mDisplayContent).build().getTopMostActivity();
+        mDisplayContent.setFixedRotationLaunchingApp(activity,
+                (mDisplayContent.getRotation() + 1) % 4);
+        // Create a window so FixedRotationAdjustmentsItem can be sent.
+        createWindow(null, TYPE_APPLICATION_STARTING, activity, "AppWin");
+        final ActivityRecord activity2 = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+                .setDisplay(mDisplayContent).build().getTopMostActivity();
+        activity2.setVisible(false);
+        clearInvocations(mWm.mAtmService.getLifecycleManager());
+        // The first activity has applied fixed rotation but the second activity becomes the top
+        // before the transition is done and it has the same rotation as display, so the dispatched
+        // rotation adjustment of first activity must be cleared.
+        mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(activity2,
+                false /* checkOpening */);
+
+        final ArgumentCaptor<FixedRotationAdjustmentsItem> adjustmentsCaptor =
+                ArgumentCaptor.forClass(FixedRotationAdjustmentsItem.class);
+        verify(mWm.mAtmService.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
+                eq(activity.app.getThread()), adjustmentsCaptor.capture());
+        // The transformation is kept for animation in real case.
+        assertTrue(activity.hasFixedRotationTransform());
+        final FixedRotationAdjustmentsItem clearAdjustments = FixedRotationAdjustmentsItem.obtain(
+                activity.token, null /* fixedRotationAdjustments */);
+        // The captor may match other items. The first one must be the item to clear adjustments.
+        assertEquals(clearAdjustments, adjustmentsCaptor.getAllValues().get(0));
+    }
+
+    @Test
     public void testRemoteRotation() {
         DisplayContent dc = createNewDisplay();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
index f53894a..112b2e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
@@ -31,7 +31,7 @@
 
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
-import com.android.server.wm.utils.FakeDeviceConfigInterface;
+import com.android.server.testutils.FakeDeviceConfigInterface;
 
 import org.junit.After;
 import org.junit.Test;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index 91cfd4e..59d195b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -18,7 +18,9 @@
 
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.PixelFormat;
@@ -64,4 +66,22 @@
         mImeProvider.scheduleShowImePostLayout(target);
         assertTrue(mImeProvider.isImeTargetFromDisplayContentAndImeSame());
     }
+
+    @Test
+    public void testIsImeShowing() {
+        WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+        makeWindowVisibleAndDrawn(ime);
+        mImeProvider.setWindow(ime, null, null);
+
+        WindowState target = createWindow(null, TYPE_APPLICATION, "app");
+        mDisplayContent.mInputMethodTarget = target;
+        mDisplayContent.mInputMethodControlTarget = target;
+
+        mImeProvider.scheduleShowImePostLayout(target);
+        assertFalse(mImeProvider.isImeShowing());
+        mImeProvider.checkShowImePostLayout();
+        assertTrue(mImeProvider.isImeShowing());
+        mImeProvider.setImeShowing(false);
+        assertFalse(mImeProvider.isImeShowing());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index e345bec..fdc01b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -450,7 +450,7 @@
         Settings.Secure.clearProviderForTest();
 
         // AND a password is set
-        when(mLockPatternUtils.isSecure(anyInt()))
+        when(mLockPatternUtils.isSecure(TEST_USER_ID))
                 .thenReturn(true);
 
         // AND there is a task record
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerConstantsTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerConstantsTest.java
index 5210011..7a0ef0d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerConstantsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerConstantsTest.java
@@ -32,7 +32,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wm.utils.FakeDeviceConfigInterface;
+import com.android.server.testutils.FakeDeviceConfigInterface;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 156298c..60242fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -37,6 +37,8 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
+
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
@@ -261,6 +263,13 @@
         }
     }
 
+    static void makeWindowVisibleAndDrawn(WindowState... windows) {
+        makeWindowVisible(windows);
+        for (WindowState win : windows) {
+            win.mWinAnimator.mDrawState = HAS_DRAWN;
+        }
+    }
+
     /** Creates a {@link ActivityStack} and adds it to the specified {@link DisplayContent}. */
     ActivityStack createTaskStackOnDisplay(DisplayContent dc) {
         return createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f027cd1..92a3c39 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4919,7 +4919,7 @@
         sDefaults.putString(KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING, "");
         sDefaults.putBoolean(KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED, false);
         sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false);
-        sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, false);
+        sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true);
         sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0);
         sDefaults.putStringArray(KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY,
                 new String[]{"ia", "default", "ims", "mms", "dun", "emergency"});
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index c140249..a5a8958 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -891,6 +891,13 @@
     public static final int CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION = 1623;
 
     /**
+     * Call failed because of network congestion, resource is not available,
+     * or no circuit or channel available, etc.
+     * @hide
+     */
+    public static final int CODE_NETWORK_CONGESTION = 1624;
+
+    /**
      * The dialed RTT call should be retried without RTT
      * @hide
      */
@@ -1076,6 +1083,7 @@
             CODE_REJECT_VT_AVPF_NOT_ALLOWED,
             CODE_REJECT_ONGOING_ENCRYPTED_CALL,
             CODE_REJECT_ONGOING_CS_CALL,
+            CODE_NETWORK_CONGESTION,
             CODE_RETRY_ON_IMS_WITHOUT_RTT,
             CODE_OEM_CAUSE_1,
             CODE_OEM_CAUSE_2,
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 97078c3..e72b26c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2032,6 +2032,11 @@
     int setImsProvisioningString(int subId, int key, String value);
 
     /**
+     * Start emergency callback mode for testing.
+     */
+    void startEmergencyCallbackMode();
+
+    /**
      * Update Emergency Number List for Test Mode.
      */
     void updateEmergencyNumberListTestMode(int action, in EmergencyNumber num);