Merge "Also log when mayWait=false"
diff --git a/api/current.txt b/api/current.txt
index 2512b17..e6b44f1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -102,7 +102,7 @@
     field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT";
     field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
     field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
-    field public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+    field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
     field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
     field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
     field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
@@ -15389,8 +15389,8 @@
     method public boolean setState(@NonNull int[]);
     method public void setTint(@ColorInt int);
     method public void setTintList(@Nullable android.content.res.ColorStateList);
-    method @Deprecated public void setTintMode(@NonNull android.graphics.PorterDuff.Mode);
-    method public void setTintMode(@NonNull android.graphics.BlendMode);
+    method @Deprecated public void setTintMode(@Nullable android.graphics.PorterDuff.Mode);
+    method public void setTintMode(@Nullable android.graphics.BlendMode);
     method public boolean setVisible(boolean, boolean);
     method public void unscheduleSelf(@NonNull Runnable);
   }
@@ -17058,6 +17058,7 @@
 
   public class CaptureFailure {
     method public long getFrameNumber();
+    method @Nullable public String getPhysicalCameraId();
     method public int getReason();
     method @NonNull public android.hardware.camera2.CaptureRequest getRequest();
     method public int getSequenceId();
@@ -42534,6 +42535,7 @@
     method public static int getpid();
     method public static int getppid();
     method public static java.net.SocketAddress getsockname(java.io.FileDescriptor) throws android.system.ErrnoException;
+    method @NonNull public static android.system.StructTimeval getsockoptTimeval(@NonNull java.io.FileDescriptor, int, int) throws android.system.ErrnoException;
     method public static int gettid();
     method public static int getuid();
     method public static byte[] getxattr(String, String) throws android.system.ErrnoException;
@@ -42584,6 +42586,7 @@
     method @Deprecated public static void setgid(int) throws android.system.ErrnoException;
     method public static int setsid() throws android.system.ErrnoException;
     method public static void setsockoptInt(java.io.FileDescriptor, int, int, int) throws android.system.ErrnoException;
+    method public static void setsockoptTimeval(@NonNull java.io.FileDescriptor, int, int, @NonNull android.system.StructTimeval) throws android.system.ErrnoException;
     method @Deprecated public static void setuid(int) throws android.system.ErrnoException;
     method public static void setxattr(String, String, byte[], int) throws android.system.ErrnoException;
     method public static void shutdown(java.io.FileDescriptor, int) throws android.system.ErrnoException;
@@ -42789,6 +42792,10 @@
     field public static final int F_SETOWN;
     field public static final int F_UNLCK;
     field public static final int F_WRLCK;
+    field public static final int ICMP6_ECHO_REPLY;
+    field public static final int ICMP6_ECHO_REQUEST;
+    field public static final int ICMP_ECHO;
+    field public static final int ICMP_ECHOREPLY;
     field public static final int IFA_F_DADFAILED;
     field public static final int IFA_F_DEPRECATED;
     field public static final int IFA_F_HOMEADDRESS;
@@ -43161,6 +43168,13 @@
     field public final long tv_sec;
   }
 
+  public final class StructTimeval {
+    method @NonNull public static android.system.StructTimeval fromMillis(long);
+    method public long toMillis();
+    field public final long tv_sec;
+    field public final long tv_usec;
+  }
+
   public final class StructUtsname {
     ctor public StructUtsname(String, String, String, String, String);
     field public final String machine;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4b0c05f..ddae34c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5440,13 +5440,14 @@
     }
 
     /**
-     * Called by a device or profile owner to set whether auto time is required. If auto time is
-     * required, no user will be able set the date and time and network date and time will be used.
+     * Called by a device owner, or alternatively a profile owner from Android 8.0 (API level 26) or
+     * higher, to set whether auto time is required. If auto time is required, no user will be able
+     * set the date and time and network date and time will be used.
      * <p>
      * Note: if auto time is required the user can still manually set the time zone.
      * <p>
-     * The calling device admin must be a device or profile owner. If it is not, a security
-     * exception will be thrown.
+     * The calling device admin must be a device owner, or alternatively a profile owner from
+     * Android 8.0 (API level 26) or higher. If it is not, a security exception will be thrown.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param required Whether auto time is set required or not.
@@ -10789,8 +10790,8 @@
     }
 
     /**
-     * Returns whether the device is being used as a managed kiosk, as defined in the CDD. As of
-     * this release, these requirements are as follows:
+     * Returns whether the device is being used as a managed kiosk. These requirements are as
+     * follows:
      * <ul>
      *     <li>The device is in Lock Task (therefore there is also a Device Owner app on the
      *     device)</li>
@@ -10829,11 +10830,11 @@
     }
 
     /**
-     * Returns whether the device is being used as an unattended managed kiosk, as defined in the
-     * CDD. As of this release, these requirements are as follows:
+     * Returns whether the device is being used as an unattended managed kiosk. These requirements
+     * are as follows:
      * <ul>
-     *     <li>The device is being used as a managed kiosk, as defined in the CDD and verified at
-     *     {@link #isManagedKiosk()}</li>
+     *     <li>The device is being used as a managed kiosk, as defined at {@link
+     *     #isManagedKiosk()}</li>
      *     <li>The device has not received user input for at least 30 minutes</li>
      * </ul>
      *
diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java
index cd2bc5f..20ca4a3 100644
--- a/core/java/android/hardware/camera2/CaptureFailure.java
+++ b/core/java/android/hardware/camera2/CaptureFailure.java
@@ -17,6 +17,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -61,17 +62,19 @@
     private final boolean mDropped;
     private final int mSequenceId;
     private final long mFrameNumber;
+    private final String mErrorPhysicalCameraId;
 
     /**
      * @hide
      */
     public CaptureFailure(CaptureRequest request, int reason,
-            boolean dropped, int sequenceId, long frameNumber) {
+            boolean dropped, int sequenceId, long frameNumber, String errorPhysicalCameraId) {
         mRequest = request;
         mReason = reason;
         mDropped = dropped;
         mSequenceId = sequenceId;
         mFrameNumber = frameNumber;
+        mErrorPhysicalCameraId = errorPhysicalCameraId;
     }
 
     /**
@@ -155,4 +158,17 @@
     public int getSequenceId() {
         return mSequenceId;
     }
+
+    /**
+     * The physical camera device ID in case the capture failure comes from a {@link CaptureRequest}
+     * with configured physical camera streams for a logical camera.
+     *
+     * @return String The physical camera device ID of the respective failing output.
+     *         {@code null} in case the capture request has no associated physical camera device.
+     * @see CaptureRequest.Builder#setPhysicalCameraKey
+     * @see android.hardware.camera2.params.OutputConfiguration#setPhysicalCameraId
+     */
+    public @Nullable String getPhysicalCameraId() {
+        return mErrorPhysicalCameraId;
+    }
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 57b608f..fc12b09 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -2232,6 +2232,7 @@
             final int requestId = resultExtras.getRequestId();
             final int subsequenceId = resultExtras.getSubsequenceId();
             final long frameNumber = resultExtras.getFrameNumber();
+            final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId();
             final CaptureCallbackHolder holder =
                     CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
 
@@ -2287,7 +2288,8 @@
                     reason,
                     /*dropped*/ mayHaveBuffers,
                     requestId,
-                    frameNumber);
+                    frameNumber,
+                    errorPhysicalCameraId);
 
                 failureDispatch = new Runnable() {
                     @Override
diff --git a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
index edc3d91..1ff5bd5 100644
--- a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
+++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
@@ -29,6 +29,7 @@
     private long frameNumber;
     private int partialResultCount;
     private int errorStreamId;
+    private String errorPhysicalCameraId;
 
     public static final @android.annotation.NonNull Parcelable.Creator<CaptureResultExtras> CREATOR =
             new Parcelable.Creator<CaptureResultExtras>() {
@@ -49,7 +50,8 @@
 
     public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId,
                                int precaptureTriggerId, long frameNumber,
-                               int partialResultCount, int errorStreamId) {
+                               int partialResultCount, int errorStreamId,
+                               String errorPhysicalCameraId) {
         this.requestId = requestId;
         this.subsequenceId = subsequenceId;
         this.afTriggerId = afTriggerId;
@@ -57,6 +59,7 @@
         this.frameNumber = frameNumber;
         this.partialResultCount = partialResultCount;
         this.errorStreamId = errorStreamId;
+        this.errorPhysicalCameraId = errorPhysicalCameraId;
     }
 
     @Override
@@ -73,6 +76,12 @@
         dest.writeLong(frameNumber);
         dest.writeInt(partialResultCount);
         dest.writeInt(errorStreamId);
+        if ((errorPhysicalCameraId != null) && !errorPhysicalCameraId.isEmpty()) {
+            dest.writeBoolean(true);
+            dest.writeString(errorPhysicalCameraId);
+        } else {
+            dest.writeBoolean(false);
+        }
     }
 
     public void readFromParcel(Parcel in) {
@@ -83,6 +92,14 @@
         frameNumber = in.readLong();
         partialResultCount = in.readInt();
         errorStreamId = in.readInt();
+        boolean errorPhysicalCameraIdPresent = in.readBoolean();
+        if (errorPhysicalCameraIdPresent) {
+            errorPhysicalCameraId = in.readString();
+        }
+    }
+
+    public String getErrorPhysicalCameraId() {
+        return errorPhysicalCameraId;
     }
 
     public int getRequestId() {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index aff09f2..908ed09 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -109,11 +109,11 @@
         }
         if (holder == null) {
             return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
-                    ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE);
+                    ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null);
         }
         return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
                 /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(),
-                /*partialResultCount*/1, errorStreamId);
+                /*partialResultCount*/1, errorStreamId, null);
     }
 
     /**
diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java
index a7b280b..2fab404 100644
--- a/core/java/android/speech/tts/TtsEngines.java
+++ b/core/java/android/speech/tts/TtsEngines.java
@@ -15,8 +15,10 @@
  */
 package android.speech.tts;
 
-import org.xmlpull.v1.XmlPullParserException;
+import static android.provider.Settings.Secure.getString;
 
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -27,10 +29,6 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-
-import static android.provider.Settings.Secure.getString;
-
-import android.annotation.UnsupportedAppUsage;
 import android.provider.Settings;
 import android.speech.tts.TextToSpeech.Engine;
 import android.speech.tts.TextToSpeech.EngineInfo;
@@ -39,6 +37,8 @@
 import android.util.Log;
 import android.util.Xml;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -522,7 +522,8 @@
      * read back, will evaluate to {@link Locale#getDefault()}.
      */
     @UnsupportedAppUsage
-    public synchronized void updateLocalePrefForEngine(String engineName, Locale newLocale) {
+    public synchronized void updateLocalePrefForEngine(
+            @NonNull String engineName, Locale newLocale) {
         final String prefList = Settings.Secure.getString(mContext.getContentResolver(),
                 Settings.Secure.TTS_DEFAULT_LOCALE);
         if (DBG) {
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index c794a69..8fbbcf4 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -16,11 +16,20 @@
 
 package android.view;
 
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
+
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.os.SystemClock;
+import android.util.StatsLog;
 
 /**
  * Detects various gestures and events using the supplied {@link MotionEvent}s.
@@ -251,8 +260,12 @@
     private boolean mAlwaysInTapRegion;
     private boolean mAlwaysInBiggerTapRegion;
     private boolean mIgnoreNextUpEvent;
+    // Whether a classification has been recorded by statsd for the current event stream. Reset on
+    // ACTION_DOWN.
+    private boolean mHasRecordedClassification;
 
     private MotionEvent mCurrentDownEvent;
+    private MotionEvent mCurrentMotionEvent;
     private MotionEvent mPreviousUpEvent;
 
     /**
@@ -297,6 +310,7 @@
                     break;
 
                 case LONG_PRESS:
+                    recordGestureClassification(msg.arg1);
                     dispatchLongPress();
                     break;
 
@@ -304,6 +318,8 @@
                     // If the user's finger is still down, do not count it as a tap
                     if (mDoubleTapListener != null) {
                         if (!mStillDown) {
+                            recordGestureClassification(
+                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
                             mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                         } else {
                             mDeferConfirmSingleTap = true;
@@ -501,6 +517,11 @@
 
         final int action = ev.getAction();
 
+        if (mCurrentMotionEvent != null) {
+            mCurrentMotionEvent.recycle();
+        }
+        mCurrentMotionEvent = MotionEvent.obtain(ev);
+
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
         }
@@ -569,6 +590,8 @@
                             && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
                         // This is a second tap
                         mIsDoubleTapping = true;
+                        recordGestureClassification(
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
                         // Give a callback with the first tap of the double-tap
                         handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
                         // Give a callback with down event of the double-tap
@@ -590,11 +613,17 @@
                 mStillDown = true;
                 mInLongPress = false;
                 mDeferConfirmSingleTap = false;
+                mHasRecordedClassification = false;
 
                 if (mIsLongpressEnabled) {
                     mHandler.removeMessages(LONG_PRESS);
-                    mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
-                            + ViewConfiguration.getLongPressTimeout());
+                    mHandler.sendMessageAtTime(
+                            mHandler.obtainMessage(
+                                    LONG_PRESS,
+                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
+                                    0 /* arg2 */),
+                            mCurrentDownEvent.getDownTime()
+                                    + ViewConfiguration.getLongPressTimeout());
                 }
                 mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
                         mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
@@ -613,6 +642,8 @@
                 final float scrollY = mLastFocusY - focusY;
                 if (mIsDoubleTapping) {
                     // Give the move events of the double-tap
+                    recordGestureClassification(
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
                     handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                 } else if (mAlwaysInTapRegion) {
                     final int deltaX = (int) (focusX - mDownFocusX);
@@ -635,8 +666,12 @@
                             // reschedule long press with a modified timeout.
                             mHandler.removeMessages(LONG_PRESS);
                             final long longPressTimeout = ViewConfiguration.getLongPressTimeout();
-                            mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime()
-                                    + (long) (longPressTimeout * multiplier));
+                            mHandler.sendMessageAtTime(
+                                    mHandler.obtainMessage(
+                                            LONG_PRESS,
+                                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
+                                            0 /* arg2 */),
+                                    ev.getDownTime() + (long) (longPressTimeout * multiplier));
                         }
                         // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
                         // until the gesture is resolved.
@@ -646,6 +681,8 @@
                     }
 
                     if (distance > slopSquare) {
+                        recordGestureClassification(
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
                         handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                         mLastFocusX = focusX;
                         mLastFocusY = focusY;
@@ -659,6 +696,7 @@
                         mAlwaysInBiggerTapRegion = false;
                     }
                 } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
+                    recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
                     handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                     mLastFocusX = focusX;
                     mLastFocusY = focusY;
@@ -667,7 +705,11 @@
                         motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                 if (deepPress && hasPendingLongPress) {
                     mHandler.removeMessages(LONG_PRESS);
-                    mHandler.sendEmptyMessage(LONG_PRESS);
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(
+                                  LONG_PRESS,
+                                  TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS,
+                                  0 /* arg2 */));
                 }
                 break;
 
@@ -676,11 +718,15 @@
                 MotionEvent currentUpEvent = MotionEvent.obtain(ev);
                 if (mIsDoubleTapping) {
                     // Finally, give the up event of the double-tap
+                    recordGestureClassification(
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
                     handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                 } else if (mInLongPress) {
                     mHandler.removeMessages(TAP);
                     mInLongPress = false;
                 } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
+                    recordGestureClassification(
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
                     handled = mListener.onSingleTapUp(ev);
                     if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                         mDoubleTapListener.onSingleTapConfirmed(ev);
@@ -821,4 +867,26 @@
         mInLongPress = true;
         mListener.onLongPress(mCurrentDownEvent);
     }
+
+    private void recordGestureClassification(int classification) {
+        if (mHasRecordedClassification
+                || classification
+                    == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
+            // Only record the first classification for an event stream.
+            return;
+        }
+        if (mCurrentDownEvent == null || mCurrentMotionEvent == null) {
+            // If the complete event stream wasn't seen, don't record anything.
+            mHasRecordedClassification = true;
+            return;
+        }
+        StatsLog.write(
+                StatsLog.TOUCH_GESTURE_CLASSIFIED,
+                getClass().getName(),
+                classification,
+                (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()),
+                (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(),
+                                   mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY()));
+        mHasRecordedClassification = true;
+    }
 }
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 13b0cc0..b76f2a1 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -17,7 +17,10 @@
 package android.view;
 
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.SIZE;
+import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.indexOf;
 
 import android.annotation.IntDef;
@@ -55,6 +58,12 @@
             TYPE_SIDE_BAR_1,
             TYPE_SIDE_BAR_2,
             TYPE_SIDE_BAR_3,
+            TYPE_TOP_GESTURES,
+            TYPE_BOTTOM_GESTURES,
+            TYPE_LEFT_GESTURES,
+            TYPE_RIGHT_GESTURES,
+            TYPE_TOP_TAPPABLE_ELEMENT,
+            TYPE_BOTTOM_TAPPABLE_ELEMENT,
             TYPE_IME
     })
     public @interface InternalInsetType {}
@@ -73,8 +82,16 @@
     public static final int TYPE_SIDE_BAR_2 = 2;
     public static final int TYPE_SIDE_BAR_3 = 3;
 
+    public static final int TYPE_TOP_GESTURES = 4;
+    public static final int TYPE_BOTTOM_GESTURES = 5;
+    public static final int TYPE_LEFT_GESTURES = 6;
+    public static final int TYPE_RIGHT_GESTURES = 7;
+    public static final int TYPE_TOP_TAPPABLE_ELEMENT = 8;
+    public static final int TYPE_BOTTOM_TAPPABLE_ELEMENT = 9;
+
     /** Input method window. */
-    public static final int TYPE_IME = 4;
+    public static final int TYPE_IME = 10;
+
     static final int LAST_TYPE = TYPE_IME;
 
     // Derived types
@@ -137,17 +154,6 @@
                 && legacyContentInsets != null && legacyStableInsets != null) {
             WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
             WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
-
-            // TODO: set system gesture insets based on actual system gesture area.
-            typeInsetsMap[Type.indexOf(Type.systemGestures())] = Insets.of(legacyContentInsets);
-            typeInsetsMap[Type.indexOf(Type.mandatorySystemGestures())] =
-                    Insets.of(legacyContentInsets);
-            typeInsetsMap[Type.indexOf(Type.tappableElement())] = Insets.of(legacyContentInsets);
-
-            typeMaxInsetsMap[Type.indexOf(Type.systemGestures())] = Insets.of(legacyStableInsets);
-            typeMaxInsetsMap[Type.indexOf(Type.mandatorySystemGestures())] =
-                    Insets.of(legacyStableInsets);
-            typeMaxInsetsMap[Type.indexOf(Type.tappableElement())] = Insets.of(legacyStableInsets);
         }
         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
             InsetsSource source = mSources.get(type);
@@ -159,7 +165,9 @@
                     && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR);
             boolean skipIme = source.getType() == TYPE_IME
                     && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0;
-            if (skipSystemBars || skipIme) {
+            boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                    && (toPublicType(type) & Type.compatSystemInsets()) != 0;
+            if (skipSystemBars || skipIme || skipLegacyTypes) {
                 typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
                 continue;
             }
@@ -183,7 +191,25 @@
             @Nullable boolean[] typeVisibilityMap) {
         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
 
-        int index = indexOf(toPublicType(source.getType()));
+        int type = toPublicType(source.getType());
+        processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+                insets, type);
+
+        if (type == MANDATORY_SYSTEM_GESTURES) {
+            // Mandatory system gestures are also system gestures.
+            // TODO: find a way to express this more generally. One option would be to define
+            //       Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
+            //       ability to set systemGestureInsets() independently from
+            //       mandatorySystemGestureInsets() in the Builder.
+            processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+                    insets, SYSTEM_GESTURES);
+        }
+    }
+
+    private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
+            @InsetSide @Nullable SparseIntArray typeSideMap,
+            @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
+        int index = indexOf(type);
         Insets existing = typeInsetsMap[index];
         if (existing == null) {
             typeInsetsMap[index] = insets;
@@ -300,6 +326,15 @@
                 return Type.SIDE_BARS;
             case TYPE_IME:
                 return Type.IME;
+            case TYPE_TOP_GESTURES:
+            case TYPE_BOTTOM_GESTURES:
+                return Type.MANDATORY_SYSTEM_GESTURES;
+            case TYPE_LEFT_GESTURES:
+            case TYPE_RIGHT_GESTURES:
+                return Type.SYSTEM_GESTURES;
+            case TYPE_TOP_TAPPABLE_ELEMENT:
+            case TYPE_BOTTOM_TAPPABLE_ELEMENT:
+                return Type.TAPPABLE_ELEMENT;
             default:
                 throw new IllegalArgumentException("Unknown type: " + type);
         }
@@ -336,10 +371,20 @@
                 return "TYPE_SIDE_BAR_2";
             case TYPE_SIDE_BAR_3:
                 return "TYPE_SIDE_BAR_3";
-            case TYPE_IME:
-                return "TYPE_IME";
+            case TYPE_TOP_GESTURES:
+                return "TYPE_TOP_GESTURES";
+            case TYPE_BOTTOM_GESTURES:
+                return "TYPE_BOTTOM_GESTURES";
+            case TYPE_LEFT_GESTURES:
+                return "TYPE_LEFT_GESTURES";
+            case TYPE_RIGHT_GESTURES:
+                return "TYPE_RIGHT_GESTURES";
+            case TYPE_TOP_TAPPABLE_ELEMENT:
+                return "TYPE_TOP_TAPPABLE_ELEMENT";
+            case TYPE_BOTTOM_TAPPABLE_ELEMENT:
+                return "TYPE_BOTTOM_TAPPABLE_ELEMENT";
             default:
-                return "TYPE_UNKNOWN";
+                return "TYPE_UNKNOWN_" + type;
         }
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 24f4c14..65fe87f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,6 +17,10 @@
 package android.view;
 
 import static android.content.res.Resources.ID_NULL;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
 
@@ -96,6 +100,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.StateSet;
+import android.util.StatsLog;
 import android.util.SuperNotCalledException;
 import android.util.TypedValue;
 import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
@@ -4062,11 +4067,11 @@
     }, formatToHexString = true)
 
     /* @hide */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769414)
     public int mPrivateFlags;
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768943)
     int mPrivateFlags2;
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 129147060)
     int mPrivateFlags3;
 
     private int mPrivateFlags4;
@@ -14571,7 +14576,12 @@
                     if (clickable) {
                         setPressed(true, x, y);
                     }
-                    checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
+                    checkForLongClick(
+                            ViewConfiguration.getLongPressTimeout(),
+                            x,
+                            y,
+                            // This is not a touch gesture -- do not classify it as one.
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION);
                     return true;
                 }
             }
@@ -15312,7 +15322,11 @@
                     mHasPerformedLongPress = false;
 
                     if (!clickable) {
-                        checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
+                        checkForLongClick(
+                                ViewConfiguration.getLongPressTimeout(),
+                                x,
+                                y,
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                         break;
                     }
 
@@ -15336,7 +15350,11 @@
                     } else {
                         // Not inside a scrolling container, so show the feedback right away
                         setPressed(true, x, y);
-                        checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
+                        checkForLongClick(
+                                ViewConfiguration.getLongPressTimeout(),
+                                x,
+                                y,
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                     }
                     break;
 
@@ -15373,7 +15391,11 @@
                                     * ambiguousMultiplier);
                             // Subtract the time already spent
                             delay -= event.getEventTime() - event.getDownTime();
-                            checkForLongClick(delay, x, y);
+                            checkForLongClick(
+                                    delay,
+                                    x,
+                                    y,
+                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                         }
                         touchSlop *= ambiguousMultiplier;
                     }
@@ -15395,7 +15417,11 @@
                     if (deepPress && hasPendingLongPressCallback()) {
                         // process the long click action immediately
                         removeLongPressCallback();
-                        checkForLongClick(0 /* send immediately */, x, y);
+                        checkForLongClick(
+                                0 /* send immediately */,
+                                x,
+                                y,
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                     }
 
                     break;
@@ -26143,7 +26169,7 @@
         }
     }
 
-    private void checkForLongClick(long delay, float x, float y) {
+    private void checkForLongClick(long delay, float x, float y, int classification) {
         if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
             mHasPerformedLongPress = false;
 
@@ -26153,6 +26179,7 @@
             mPendingCheckForLongPress.setAnchor(x, y);
             mPendingCheckForLongPress.rememberWindowAttachCount();
             mPendingCheckForLongPress.rememberPressedState();
+            mPendingCheckForLongPress.setClassification(classification);
             postDelayed(mPendingCheckForLongPress, delay);
         }
     }
@@ -27710,11 +27737,17 @@
         private float mX;
         private float mY;
         private boolean mOriginalPressedState;
+        /**
+         * The classification of the long click being checked: one of the
+         * StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants.
+         */
+        private int mClassification;
 
         @Override
         public void run() {
             if ((mOriginalPressedState == isPressed()) && (mParent != null)
                     && mOriginalWindowAttachCount == mWindowAttachCount) {
+                recordGestureClassification(mClassification);
                 if (performLongClick(mX, mY)) {
                     mHasPerformedLongPress = true;
                 }
@@ -27733,6 +27766,10 @@
         public void rememberPressedState() {
             mOriginalPressedState = isPressed();
         }
+
+        public void setClassification(int classification) {
+            mClassification = classification;
+        }
     }
 
     private final class CheckForTap implements Runnable {
@@ -27745,17 +27782,28 @@
             setPressed(true, x, y);
             final long delay =
                     ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout();
-            checkForLongClick(delay, x, y);
+            checkForLongClick(delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
         }
     }
 
     private final class PerformClick implements Runnable {
         @Override
         public void run() {
+            recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
             performClickInternal();
         }
     }
 
+    /** Records a classification for the current event stream. */
+    private void recordGestureClassification(int classification) {
+        if (classification == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
+            return;
+        }
+        // To avoid negatively impacting View performance, the latency and displacement metrics
+        // are omitted.
+        StatsLog.write(StatsLog.TOUCH_GESTURE_CLASSIFIED, getClass().getName(), classification);
+    }
+
     /**
      * This method returns a ViewPropertyAnimator object, which can be used to animate
      * specific properties on this View.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 49166ad..ca64969 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1916,16 +1916,10 @@
             }
             contentInsets = ensureInsetsNonNegative(contentInsets, "content");
             stableInsets = ensureInsetsNonNegative(stableInsets, "stable");
-            if (sNewInsetsMode != NEW_INSETS_MODE_NONE) {
-                mLastWindowInsets = mInsetsController.calculateInsets(
-                        mContext.getResources().getConfiguration().isScreenRound(),
-                        mAttachInfo.mAlwaysConsumeSystemBars, displayCutout,
-                        contentInsets, stableInsets, mWindowAttributes.softInputMode);
-            } else {
-                mLastWindowInsets = new WindowInsets(contentInsets, stableInsets,
-                        mContext.getResources().getConfiguration().isScreenRound(),
-                        mAttachInfo.mAlwaysConsumeSystemBars, displayCutout);
-            }
+            mLastWindowInsets = mInsetsController.calculateInsets(
+                    mContext.getResources().getConfiguration().isScreenRound(),
+                    mAttachInfo.mAlwaysConsumeSystemBars, displayCutout,
+                    contentInsets, stableInsets, mWindowAttributes.softInputMode);
         }
         return mLastWindowInsets;
     }
@@ -3986,7 +3980,7 @@
 
     void systemGestureExclusionChanged() {
         final List<Rect> rectsForWindowManager = mGestureExclusionTracker.computeChangedRects();
-        if (rectsForWindowManager != null) {
+        if (rectsForWindowManager != null && mView != null) {
             try {
                 mWindowSession.reportSystemGestureExclusionChanged(mWindow, rectsForWindowManager);
             } catch (RemoteException e) {
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index ffa769a..2d292ef 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -29,9 +29,6 @@
 import static android.view.WindowInsets.Type.all;
 import static android.view.WindowInsets.Type.compatSystemInsets;
 import static android.view.WindowInsets.Type.indexOf;
-import static android.view.WindowInsets.Type.mandatorySystemGestures;
-import static android.view.WindowInsets.Type.systemGestures;
-import static android.view.WindowInsets.Type.tappableElement;
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -225,10 +222,6 @@
         }
         Insets[] typeInsetMap = new Insets[SIZE];
         assignCompatInsets(typeInsetMap, insets);
-        // TODO: set system gesture insets based on actual system gesture area.
-        typeInsetMap[indexOf(systemGestures())] = Insets.of(insets);
-        typeInsetMap[indexOf(mandatorySystemGestures())] = Insets.of(insets);
-        typeInsetMap[indexOf(tappableElement())] = Insets.of(insets);
         return typeInsetMap;
     }
 
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 4413585..678a252 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -321,45 +321,6 @@
         }
     }
 
-    /**
-     * If the ApplicationInfo provided is for a stub WebView, fix up the object to include the
-     * required values from the donor package. If the ApplicationInfo is for a full WebView,
-     * leave it alone. Throws MissingWebViewPackageException if the donor is missing.
-     */
-    private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm)
-            throws MissingWebViewPackageException {
-        String donorPackageName = null;
-        if (ai.metaData != null) {
-            donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage");
-        }
-        if (donorPackageName != null) {
-            PackageInfo donorPackage;
-            try {
-                donorPackage = pm.getPackageInfo(
-                        donorPackageName,
-                        PackageManager.GET_SHARED_LIBRARY_FILES
-                        | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
-                        | PackageManager.MATCH_UNINSTALLED_PACKAGES
-                        | PackageManager.MATCH_FACTORY_ONLY);
-            } catch (PackageManager.NameNotFoundException e) {
-                throw new MissingWebViewPackageException("Failed to find donor package: " +
-                                                         donorPackageName);
-            }
-            ApplicationInfo donorInfo = donorPackage.applicationInfo;
-
-            // Replace the stub's code locations with the donor's.
-            ai.sourceDir = donorInfo.sourceDir;
-            ai.splitSourceDirs = donorInfo.splitSourceDirs;
-            ai.nativeLibraryDir = donorInfo.nativeLibraryDir;
-            ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir;
-
-            // Copy the donor's primary and secondary ABIs, since the stub doesn't have native code
-            // and so they are unset.
-            ai.primaryCpuAbi = donorInfo.primaryCpuAbi;
-            ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi;
-        }
-    }
-
     @UnsupportedAppUsage
     private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException {
         Application initialApplication = AppGlobals.getInitialApplication();
@@ -411,7 +372,6 @@
             verifyPackageInfo(response.packageInfo, newPackageInfo);
 
             ApplicationInfo ai = newPackageInfo.applicationInfo;
-            fixupStubApplicationInfo(ai, pm);
 
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                     "initialApplication.createApplicationContext");
@@ -494,18 +454,14 @@
      */
     public static int onWebViewProviderChanged(PackageInfo packageInfo) {
         int startedRelroProcesses = 0;
-        ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
         try {
-            fixupStubApplicationInfo(packageInfo.applicationInfo,
-                                     AppGlobals.getInitialApplication().getPackageManager());
-
             startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
         } catch (Throwable t) {
             // Log and discard errors at this stage as we must not crash the system server.
             Log.e(LOGTAG, "error preparing webview native library", t);
         }
 
-        WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
+        WebViewZygote.onWebViewProviderChanged(packageInfo);
 
         return startedRelroProcesses;
     }
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 09aa066..62f54b9 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -16,8 +16,6 @@
 
 package android.webkit;
 
-import android.app.LoadedApk;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -29,10 +27,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
 /** @hide */
 public class WebViewZygote {
     private static final String LOGTAG = "WebViewZygote";
@@ -56,13 +50,6 @@
     private static PackageInfo sPackage;
 
     /**
-     * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from
-     * #onWebViewProviderChanged().
-     */
-    @GuardedBy("sLock")
-    private static ApplicationInfo sPackageOriginalAppInfo;
-
-    /**
      * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote
      * will not be started.
      */
@@ -110,11 +97,9 @@
         }
     }
 
-    public static void onWebViewProviderChanged(PackageInfo packageInfo,
-                                                ApplicationInfo originalAppInfo) {
+    static void onWebViewProviderChanged(PackageInfo packageInfo) {
         synchronized (sLock) {
             sPackage = packageInfo;
-            sPackageOriginalAppInfo = originalAppInfo;
 
             // If multi-process is not enabled, then do not start the zygote service.
             if (!sMultiprocessEnabled) {
@@ -165,34 +150,7 @@
                     Process.FIRST_ISOLATED_UID,
                     Integer.MAX_VALUE); // TODO(b/123615476) deal with user-id ranges properly
             ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
-
-            if (sPackageOriginalAppInfo.sourceDir.equals(sPackage.applicationInfo.sourceDir)) {
-                // No stub WebView is involved here, so we can preload the package the "clean" way
-                // using the ApplicationInfo.
-                sZygote.preloadApp(sPackage.applicationInfo, abi);
-            } else {
-                // Legacy path to support the stub WebView.
-                // Reuse the logic from LoadedApk to determine the correct paths and pass them to
-                // the zygote as strings.
-                final List<String> zipPaths = new ArrayList<>(10);
-                final List<String> libPaths = new ArrayList<>(10);
-                LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
-                final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
-                final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
-                        TextUtils.join(File.pathSeparator, zipPaths);
-
-                String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
-
-                // Use the original ApplicationInfo to determine what the original classpath would
-                // have been to use as a cache key.
-                LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
-                final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
-                        TextUtils.join(File.pathSeparator, zipPaths);
-
-                Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
-                sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
-                                             Build.SUPPORTED_ABIS[0]);
-            }
+            sZygote.preloadApp(sPackage.applicationInfo, abi);
         } catch (Exception e) {
             Log.e(LOGTAG, "Error connecting to webview zygote", e);
             stopZygoteLocked();
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f14b50d..07d28eb 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -24,7 +24,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
-import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.prediction.AppPredictionContext;
@@ -197,6 +196,11 @@
     private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
     private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3;
     private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;
+    private static final int LIST_VIEW_UPDATE_MESSAGE = 5;
+
+    private static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
+
+    private boolean mListViewDataChanged = false;
 
     @Retention(SOURCE)
     @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
@@ -213,10 +217,13 @@
     private final Handler mChooserHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
+            if (mChooserListAdapter == null || isDestroyed()) {
+                return;
+            }
+
             switch (msg.what) {
                 case CHOOSER_TARGET_SERVICE_RESULT:
                     if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
-                    if (isDestroyed()) break;
                     final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
                     if (!mServiceConnections.contains(sri.connection)) {
                         Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
@@ -240,17 +247,22 @@
                     if (DEBUG) {
                         Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
                     }
-                    if (mChooserListAdapter == null || isDestroyed()) {
-                        break;
-                    }
+
                     unbindRemainingServices();
                     sendVoiceChoicesIfNeeded();
                     mChooserListAdapter.completeServiceTargetLoading();
                     break;
 
+                case LIST_VIEW_UPDATE_MESSAGE:
+                    if (DEBUG) {
+                        Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
+                    }
+
+                    mChooserListAdapter.refreshListView();
+                    break;
+
                 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
                     if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
-                    if (isDestroyed()) break;
                     final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
                     if (resultInfo.resultTargets != null) {
                         mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
@@ -829,6 +841,7 @@
             mRefinementResultReceiver = null;
         }
         unbindRemainingServices();
+        mChooserHandler.removeMessages(LIST_VIEW_UPDATE_MESSAGE);
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
         if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
@@ -1872,6 +1885,23 @@
             }
         }
 
+        @Override
+        public void notifyDataSetChanged() {
+            if (!mListViewDataChanged) {
+                mChooserHandler.sendEmptyMessageDelayed(LIST_VIEW_UPDATE_MESSAGE,
+                        LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+                mListViewDataChanged = true;
+            }
+        }
+
+        private void refreshListView() {
+            if (mListViewDataChanged) {
+                super.notifyDataSetChanged();
+            }
+            mListViewDataChanged = false;
+        }
+
+
         private void createPlaceHolders() {
             mServiceTargets.clear();
             for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
@@ -1893,7 +1923,7 @@
             }
 
             if (mServiceTargets != null) {
-                if (getDisplayInfoCount() == 0) {
+                if (getDisplayResolveInfoCount() == 0) {
                     // b/109676071: When packages change, onListRebuilt() is called before
                     // ResolverActivity.mDisplayList is re-populated; pruning now would cause the
                     // list to disappear briefly, so instead we detect this case (the
@@ -1906,12 +1936,14 @@
                 if (DEBUG) {
                     Log.d(TAG, "querying direct share targets from ShortcutManager");
                 }
+
                 queryDirectShareTargets(this);
             }
             if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
                 if (DEBUG) {
                     Log.d(TAG, "List built querying services");
                 }
+
                 queryTargetServices(this);
             }
         }
@@ -2007,7 +2039,7 @@
             offset += callerTargetCount;
 
             return filtered ? super.getItem(position - offset)
-                    : getDisplayInfoAt(position - offset);
+                    : getDisplayResolveInfo(position - offset);
         }
 
         public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 9f9e083..f47469a 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1230,7 +1230,7 @@
         final ImageView iconView = findViewById(R.id.icon);
         final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
         if (iconView != null && iconInfo != null) {
-            new LoadIconIntoViewTask(iconInfo, iconView).execute();
+            new LoadIconTask(iconInfo, iconView).execute();
         }
     }
 
@@ -1871,14 +1871,6 @@
             return mDisplayList.size();
         }
 
-        public int getDisplayInfoCount() {
-            return mDisplayList.size();
-        }
-
-        public DisplayResolveInfo getDisplayInfoAt(int index) {
-            return mDisplayList.get(index);
-        }
-
         @Nullable
         public TargetInfo getItem(int position) {
             if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
@@ -1966,9 +1958,10 @@
 
             if (info instanceof DisplayResolveInfo
                     && !((DisplayResolveInfo) info).hasDisplayIcon()) {
-                new LoadAdapterIconTask((DisplayResolveInfo) info).execute();
+                new LoadIconTask((DisplayResolveInfo) info, holder.icon).execute();
+            } else {
+                holder.icon.setImageDrawable(info.getDisplayIcon());
             }
-            holder.icon.setImageDrawable(info.getDisplayIcon());
         }
     }
 
@@ -2087,13 +2080,15 @@
 
     }
 
-    abstract class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
+    class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
         protected final DisplayResolveInfo mDisplayResolveInfo;
         private final ResolveInfo mResolveInfo;
+        private final ImageView mTargetView;
 
-        public LoadIconTask(DisplayResolveInfo dri) {
+        LoadIconTask(DisplayResolveInfo dri, ImageView target) {
             mDisplayResolveInfo = dri;
             mResolveInfo = dri.getResolveInfo();
+            mTargetView = target;
         }
 
         @Override
@@ -2103,37 +2098,12 @@
 
         @Override
         protected void onPostExecute(Drawable d) {
-            mDisplayResolveInfo.setDisplayIcon(d);
-        }
-    }
-
-    class LoadAdapterIconTask extends LoadIconTask {
-        public LoadAdapterIconTask(DisplayResolveInfo dri) {
-            super(dri);
-        }
-
-        @Override
-        protected void onPostExecute(Drawable d) {
-            super.onPostExecute(d);
             if (mProfileView != null && mAdapter.getOtherProfile() == mDisplayResolveInfo) {
                 bindProfileView();
+            } else {
+                mDisplayResolveInfo.setDisplayIcon(d);
+                mTargetView.setImageDrawable(d);
             }
-            mAdapter.notifyDataSetChanged();
-        }
-    }
-
-    class LoadIconIntoViewTask extends LoadIconTask {
-        private final ImageView mTargetView;
-
-        public LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target) {
-            super(dri);
-            mTargetView = target;
-        }
-
-        @Override
-        protected void onPostExecute(Drawable d) {
-            super.onPostExecute(d);
-            mTargetView.setImageDrawable(d);
         }
     }
 
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index dfb6c08..9a9c9d1 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -48,6 +48,7 @@
 import "frameworks/base/core/proto/android/service/package.proto";
 import "frameworks/base/core/proto/android/service/print.proto";
 import "frameworks/base/core/proto/android/service/procstats.proto";
+import "frameworks/base/core/proto/android/service/restricted_image.proto";
 import "frameworks/base/core/proto/android/service/usb.proto";
 import "frameworks/base/core/proto/android/util/event_log_tags.proto";
 import "frameworks/base/core/proto/android/util/log.proto";
@@ -314,6 +315,12 @@
         (section).args = "role --proto"
     ];
 
+    optional android.service.restricted_image.RestrictedImagesDumpProto restricted_images = 3025 [
+        (section).type = SECTION_DUMPSYS,
+        (section).userdebug_and_eng_only = true,
+        (section).args = "incidentcompanion --restricted_image"
+    ];
+
     // Reserved for OEMs.
     extensions 50000 to 100000;
 }
diff --git a/core/proto/android/service/restricted_image.proto b/core/proto/android/service/restricted_image.proto
new file mode 100644
index 0000000..4a33d47
--- /dev/null
+++ b/core/proto/android/service/restricted_image.proto
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.service.restricted_image;
+
+option java_multiple_files = true;
+option java_outer_classname = "RestrictedImage";
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+// Restricted Image proto is for collecting images from the user with their
+// permission for the purpose of debugging photos.
+message RestrictedImagesDumpProto {
+    option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+    repeated RestrictedImageSetProto sets = 1;
+}
+
+message RestrictedImageSetProto {
+    option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+    // Name of the service producing the data.
+    optional string category = 1;
+
+    // The images
+    repeated RestrictedImageProto images = 2;
+
+    // Additional metadata
+    optional bytes metadata = 3;
+}
+
+message RestrictedImageProto {
+    option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+    // Type of image data
+    optional string mime_type = 1;
+
+    // The image data
+    optional bytes image_data = 2;
+
+    // Metadata about the image.  Typically this has another proto schema,
+    // but it is undefined exactly what that is in AOSP code.
+    optional bytes metadata = 3;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ba7a93f..ab86c42 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1014,6 +1014,9 @@
          call with the option to redirect the call to a different number or
          abort the call altogether.
          <p>Protection level: dangerous
+
+         @deprecated Applications should use {@link android.telecom.CallRedirectionService} instead
+         of the {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL} broadcast.
     -->
     <permission android:name="android.permission.PROCESS_OUTGOING_CALLS"
         android:permissionGroup="android.permission-group.UNDEFINED"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2139453..5cbe003 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3241,6 +3241,12 @@
          Only applies if the device display is not square. -->
     <bool name="config_navBarCanMove">true</bool>
 
+    <!-- Controls whether the navigation bar lets through taps. -->
+    <bool name="config_navBarTapThrough">false</bool>
+
+    <!-- Controls the size of the back gesture inset. -->
+    <dimen name="config_backGestureInset">0dp</dimen>
+
     <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
          These values are in DPs and will be converted to pixel sizes internally. -->
     <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string>
@@ -4006,4 +4012,6 @@
         <item>system_server_wtf</item>
     </string-array>
 
+    <!-- Which binder services to include in incident reports containing restricted images. -->
+    <string-array name="config_restrictedImagesServices" translatable="false"/>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 874bde1..a29a4f8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2845,6 +2845,8 @@
   <java-symbol type="integer" name="config_navBarOpacityMode" />
   <java-symbol type="integer" name="config_navBarInteractionMode" />
   <java-symbol type="bool" name="config_navBarCanMove" />
+  <java-symbol type="bool" name="config_navBarTapThrough" />
+  <java-symbol type="dimen" name="config_backGestureInset" />
   <java-symbol type="color" name="system_bar_background_semi_transparent" />
 
   <!-- EditText suggestion popup. -->
@@ -3688,6 +3690,7 @@
   <java-symbol type="integer" name="config_attentionApiTimeout" />
 
   <java-symbol type="string" name="config_incidentReportApproverPackage" />
+  <java-symbol type="array" name="config_restrictedImagesServices" />
 
   <!-- Display White-Balance -->
   <java-symbol type="integer" name="config_displayWhiteBalanceBrightnessSensorRate" />
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index e4142a9..adc04fb0 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -688,20 +688,20 @@
      * {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint.
      * </p>
      *
-     * @param tintMode A Porter-Duff blending mode
+     * @param tintMode A Porter-Duff blending mode to apply to the drawable, a value of null sets
+     *                 the default Porter-Diff blending mode value
+     *                 of {@link PorterDuff.Mode#SRC_IN}
      * @see #setTint(int)
      * @see #setTintList(ColorStateList)
      *
      * @deprecated use {@link #setTintMode(BlendMode)} instead
      */
     @Deprecated
-    public void setTintMode(@NonNull PorterDuff.Mode tintMode) {
+    public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
         if (!mSetTintModeInvoked) {
             mSetTintModeInvoked = true;
-            BlendMode mode = BlendMode.fromValue(tintMode.nativeInt);
-            if (mode != null) {
-                setTintMode(mode);
-            }
+            BlendMode mode = tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null;
+            setTintMode(mode != null ? mode : Drawable.DEFAULT_BLEND_MODE);
             mSetTintModeInvoked = false;
         }
     }
@@ -716,17 +716,16 @@
      * {@link #setColorFilter(ColorFilter)}
      * </p>
      *
-     * @param blendMode
+     * @param blendMode BlendMode to apply to the drawable, a value of null sets the default
+     *                  blend mode value of {@link BlendMode#SRC_IN}
      * @see #setTint(int)
      * @see #setTintList(ColorStateList)
      */
-    public void setTintMode(@NonNull BlendMode blendMode) {
+    public void setTintMode(@Nullable BlendMode blendMode) {
         if (!mSetBlendModeInvoked) {
             mSetBlendModeInvoked = true;
             PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode);
-            if (mode != null) {
-                setTintMode(mode);
-            }
+            setTintMode(mode != null ? mode : Drawable.DEFAULT_TINT_MODE);
             mSetBlendModeInvoked = false;
         }
     }
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 6cce319..b76e49c 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -209,8 +209,8 @@
     mVkManager->initialize();
     GrContextOptions options;
     initGrContextOptions(options);
-    // TODO: get a string describing the SPIR-V compiler version and use it here
-    cacheManager().configureContext(&options, nullptr, 0);
+    auto vkDriverVersion = mVkManager->getDriverVersion();
+    cacheManager().configureContext(&options, &vkDriverVersion, sizeof(vkDriverVersion));
     sk_sp<GrContext> grContext = mVkManager->createContext(options);
     LOG_ALWAYS_FATAL_IF(!grContext.get());
     setGrContext(grContext);
@@ -408,12 +408,13 @@
 }
 
 void RenderThread::preload() {
-    std::thread eglInitThread([]() {
-        //TODO: don't load EGL drivers for Vulkan, when HW bitmap uploader is refactored.
-        eglGetDisplay(EGL_DEFAULT_DISPLAY);
-    });
-    eglInitThread.detach();
-    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+    // EGL driver is always preloaded only if HWUI renders with GL.
+    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
+        std::thread eglInitThread([]() {
+            eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        });
+        eglInitThread.detach();
+    } else {
         requireVkContext();
     }
     HardwareBitmapUploader::initialize();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index c929098..ac62ff4 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -170,6 +170,7 @@
     VkPhysicalDeviceProperties physDeviceProperties;
     mGetPhysicalDeviceProperties(mPhysicalDevice, &physDeviceProperties);
     LOG_ALWAYS_FATAL_IF(physDeviceProperties.apiVersion < VK_MAKE_VERSION(1, 1, 0));
+    mDriverVersion = physDeviceProperties.driverVersion;
 
     // query to get the initial queue props size
     uint32_t queueCount;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index c2d1802..54333f3 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -82,6 +82,8 @@
 
     sk_sp<GrContext> createContext(const GrContextOptions& options);
 
+    uint32_t getDriverVersion() const { return mDriverVersion; }
+
 private:
     friend class VulkanSurface;
     // Sets up the VkInstance and VkDevice objects. Also fills out the passed in
@@ -178,6 +180,7 @@
     };
     SwapBehavior mSwapBehavior = SwapBehavior::Discard;
     GrVkExtensions mExtensions;
+    uint32_t mDriverVersion = 0;
 };
 
 } /* namespace renderthread */
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index dd5c9f5..b4588e0 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -25,6 +25,10 @@
 
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
 
+    <!-- Permissions must be defined here, and not in the base manifest, as the network stack
+         running in the system server process does not need any permission, and having privileged
+         permissions added would cause crashes on startup unless they are also added to the
+         privileged permissions whitelist for that package. -->
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
index 4d4ceed..b4eeefd 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
@@ -72,6 +72,10 @@
         public static final String COLNAME_ASSIGNEDV4ADDRESS = "assignedV4Address";
         public static final String COLTYPE_ASSIGNEDV4ADDRESS = "INTEGER";
 
+        public static final String COLNAME_ASSIGNEDV4ADDRESSEXPIRY = "assignedV4AddressExpiry";
+        // The lease expiry timestamp in uint of milliseconds
+        public static final String COLTYPE_ASSIGNEDV4ADDRESSEXPIRY = "BIGINT";
+
         // Please note that the group hint is only a *hint*, hence its name. The client can offer
         // this information to nudge the grouping in the decision it thinks is right, but it can't
         // decide for the memory store what is the same L3 network.
@@ -86,13 +90,14 @@
         public static final String COLTYPE_MTU = "INTEGER DEFAULT -1";
 
         public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
-                + TABLENAME                 + " ("
-                + COLNAME_L2KEY             + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, "
-                + COLNAME_EXPIRYDATE        + " " + COLTYPE_EXPIRYDATE        + ", "
-                + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", "
-                + COLNAME_GROUPHINT         + " " + COLTYPE_GROUPHINT         + ", "
-                + COLNAME_DNSADDRESSES      + " " + COLTYPE_DNSADDRESSES      + ", "
-                + COLNAME_MTU               + " " + COLTYPE_MTU               + ")";
+                + TABLENAME                       + " ("
+                + COLNAME_L2KEY                   + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, "
+                + COLNAME_EXPIRYDATE              + " " + COLTYPE_EXPIRYDATE              + ", "
+                + COLNAME_ASSIGNEDV4ADDRESS       + " " + COLTYPE_ASSIGNEDV4ADDRESS       + ", "
+                + COLNAME_ASSIGNEDV4ADDRESSEXPIRY + " " + COLTYPE_ASSIGNEDV4ADDRESSEXPIRY + ", "
+                + COLNAME_GROUPHINT               + " " + COLTYPE_GROUPHINT               + ", "
+                + COLNAME_DNSADDRESSES            + " " + COLTYPE_DNSADDRESSES            + ", "
+                + COLNAME_MTU                     + " " + COLTYPE_MTU                     + ")";
         public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
     }
 
@@ -134,7 +139,7 @@
     /** The SQLite DB helper */
     public static class DbHelper extends SQLiteOpenHelper {
         // Update this whenever changing the schema.
-        private static final int SCHEMA_VERSION = 2;
+        private static final int SCHEMA_VERSION = 3;
         private static final String DATABASE_FILENAME = "IpMemoryStore.db";
 
         public DbHelper(@NonNull final Context context) {
@@ -153,10 +158,27 @@
         @Override
         public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
                 final int newVersion) {
-            // No upgrade supported yet.
-            db.execSQL(NetworkAttributesContract.DROP_TABLE);
-            db.execSQL(PrivateDataContract.DROP_TABLE);
-            onCreate(db);
+            try {
+                if (oldVersion < 2) {
+                    // upgrade from version 1 to version 2
+                    // since we starts from version 2, do nothing here
+                }
+
+                if (oldVersion < 3) {
+                    // upgrade from version 2 to version 3
+                    final String sqlUpgradeAddressExpiry = "alter table"
+                            + " " + NetworkAttributesContract.TABLENAME + " ADD"
+                            + " " + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY
+                            + " " + NetworkAttributesContract.COLTYPE_ASSIGNEDV4ADDRESSEXPIRY;
+                    db.execSQL(sqlUpgradeAddressExpiry);
+                }
+            } catch (SQLiteException e) {
+                Log.e(TAG, "Could not upgrade to the new version", e);
+                // create database with new version
+                db.execSQL(NetworkAttributesContract.DROP_TABLE);
+                db.execSQL(PrivateDataContract.DROP_TABLE);
+                onCreate(db);
+            }
         }
 
         /** Called when the database is downgraded */
@@ -204,6 +226,10 @@
             values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
                     inet4AddressToIntHTH(attributes.assignedV4Address));
         }
+        if (null != attributes.assignedV4AddressExpiry) {
+            values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY,
+                    attributes.assignedV4AddressExpiry);
+        }
         if (null != attributes.groupHint) {
             values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
         }
@@ -251,6 +277,8 @@
         final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
         final int assignedV4AddressInt = getInt(cursor,
                 NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
+        final long assignedV4AddressExpiry = getLong(cursor,
+                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY, 0);
         final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
         final byte[] dnsAddressesBlob =
                 getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
@@ -258,6 +286,9 @@
         if (0 != assignedV4AddressInt) {
             builder.setAssignedV4Address(intToInet4AddressHTH(assignedV4AddressInt));
         }
+        if (0 != assignedV4AddressExpiry) {
+            builder.setAssignedV4AddressExpiry(assignedV4AddressExpiry);
+        }
         builder.setGroupHint(groupHint);
         if (null != dnsAddressesBlob) {
             builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index d0e58b8..071ff26 100644
--- a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -228,6 +228,7 @@
     public void testNetworkAttributes() throws UnknownHostException {
         final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
         na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        na.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
         na.setGroupHint("hint1");
         na.setMtu(219);
         final String l2Key = FAKE_KEYS[0];
@@ -257,6 +258,8 @@
                                     + status.resultCode, status.isSuccess());
                             assertEquals(l2Key, key);
                             assertEquals(attributes.assignedV4Address, attr.assignedV4Address);
+                            assertEquals(attributes.assignedV4AddressExpiry,
+                                    attr.assignedV4AddressExpiry);
                             assertEquals(attributes.groupHint, attr.groupHint);
                             assertEquals(attributes.mtu, attr.mtu);
                             assertEquals(attributes2.dnsAddresses, attr.dnsAddresses);
@@ -278,7 +281,7 @@
         // Verify that this test does not miss any new field added later.
         // If any field is added to NetworkAttributes it must be tested here for storing
         // and retrieving.
-        assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+        assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
                 .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 3acbcd3..8a88a4c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -53,6 +53,7 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 
@@ -65,8 +66,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -197,6 +200,9 @@
 
     private final Context mContext;
 
+    private WifiManager mWifiManager;
+    private WifiManager.ActionListener mConnectListener;
+
     private String ssid;
     private String bssid;
     private int security;
@@ -1068,8 +1074,10 @@
     /**
      * Starts the OSU Provisioning flow.
      */
-    public void startOsuProvisioning() {
-        mContext.getSystemService(WifiManager.class).startSubscriptionProvisioning(
+    public void startOsuProvisioning(@Nullable WifiManager.ActionListener connectListener) {
+        mConnectListener = connectListener;
+
+        getWifiManager().startSubscriptionProvisioning(
                 mOsuProvider,
                 mContext.getMainExecutor(),
                 new AccessPointProvisioningCallback()
@@ -1539,12 +1547,20 @@
         return string;
     }
 
+    private WifiManager getWifiManager() {
+        if (mWifiManager == null) {
+            mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        }
+        return mWifiManager;
+    }
+
     /**
      * Callbacks relaying changes to the AccessPoint representation.
      *
      * <p>All methods are invoked on the Main Thread.
      */
     public interface AccessPointListener {
+
         /**
          * Indicates a change to the externally visible state of the AccessPoint trigger by an
          * update of ScanResults, saved configuration state, connection state, or score
@@ -1561,7 +1577,6 @@
          *                    changed
          */
         @MainThread void onAccessPointChanged(AccessPoint accessPoint);
-
         /**
          * Indicates the "wifi pie signal level" has changed, retrieved via calls to
          * {@link AccessPoint#getLevel()}.
@@ -1643,11 +1658,46 @@
             mOsuProvisioningComplete = true;
             mOsuFailure = null;
             mOsuStatus = null;
+
             ThreadUtils.postOnMainThread(() -> {
                 if (mAccessPointListener != null) {
                     mAccessPointListener.onAccessPointChanged(AccessPoint.this);
                 }
             });
+
+            // Connect to the freshly provisioned network.
+            WifiManager wifiManager = getWifiManager();
+
+            PasspointConfiguration passpointConfig = wifiManager
+                    .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(mOsuProvider))
+                    .get(mOsuProvider);
+            if (passpointConfig == null) {
+                Log.e(TAG, "Missing PasspointConfiguration for newly provisioned network!");
+                if (mConnectListener != null) {
+                    mConnectListener.onFailure(0);
+                }
+                return;
+            }
+
+            String fqdn = passpointConfig.getHomeSp().getFqdn();
+            for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing :
+                    wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) {
+                WifiConfiguration config = pairing.first;
+                if (TextUtils.equals(config.FQDN, fqdn)) {
+                    List<ScanResult> homeScans =
+                            pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
+                    List<ScanResult> roamingScans =
+                            pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK);
+
+                    AccessPoint connectionAp =
+                            new AccessPoint(mContext, config, homeScans, roamingScans);
+                    wifiManager.connect(connectionAp.getConfig(), mConnectListener);
+                    return;
+                }
+            }
+            if (mConnectListener != null) {
+                mConnectListener.onFailure(0);
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 9c8e3f4..8e40271 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -41,6 +41,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.hotspot2.OsuProvider;
@@ -53,6 +54,7 @@
 import android.text.SpannableString;
 import android.text.format.DateUtils;
 import android.util.ArraySet;
+import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -72,6 +74,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -95,9 +98,12 @@
 
     private Context mContext;
     private WifiInfo mWifiInfo;
+    @Mock private Context mMockContext;
+    @Mock private WifiManager mMockWifiManager;
     @Mock private RssiCurve mockBadgeCurve;
     @Mock private WifiNetworkScoreCache mockWifiNetworkScoreCache;
     @Mock private AccessPoint.AccessPointListener mMockAccessPointListener;
+    @Mock private WifiManager.ActionListener mMockConnectListener;
     private static final int NETWORK_ID = 123;
     private static final int DEFAULT_RSSI = -55;
 
@@ -1360,6 +1366,9 @@
                 .isEqualTo(mContext.getString(R.string.tap_to_sign_up));
     }
 
+    /**
+     * Verifies that the summary of an OSU entry updates based on provisioning status.
+     */
     @Test
     public void testOsuAccessPointSummary_showsProvisioningUpdates() {
         AccessPoint osuAccessPoint = new AccessPoint(mContext, createOsuProvider(),
@@ -1411,4 +1420,82 @@
         assertThat(osuAccessPoint.getSummary())
                 .isEqualTo(mContext.getString(R.string.osu_sign_up_complete));
     }
+
+    /**
+     * Verifies that after provisioning through an OSU provider, we connect to the freshly
+     * provisioned network.
+     */
+    @Test
+    public void testOsuAccessPoint_connectsAfterProvisioning() {
+        // Set up mock for WifiManager.getAllMatchingWifiConfigs
+        WifiConfiguration config = new WifiConfiguration();
+        config.FQDN = "fqdn";
+        Map<Integer, List<ScanResult>> scanMapping = new HashMap<>();
+        scanMapping.put(WifiManager.PASSPOINT_HOME_NETWORK, mScanResults);
+        Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> configMapPair =
+                new Pair<>(config, scanMapping);
+        List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingWifiConfig =
+                new ArrayList<>();
+        matchingWifiConfig.add(configMapPair);
+        when(mMockWifiManager.getAllMatchingWifiConfigs(any())).thenReturn(matchingWifiConfig);
+
+        // Set up mock for WifiManager.getMatchingPasspointConfigsForOsuProviders
+        OsuProvider provider = createOsuProvider();
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("fqdn");
+        homeSp.setFriendlyName("Test Provider");
+        passpointConfig.setHomeSp(homeSp);
+        Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>();
+        osuProviderConfigMap.put(provider, passpointConfig);
+        when(mMockWifiManager
+                .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(provider)))
+                .thenReturn(osuProviderConfigMap);
+
+        when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+
+        AccessPoint osuAccessPoint = new AccessPoint(mMockContext, provider, mScanResults);
+        osuAccessPoint.setListener(mMockAccessPointListener);
+
+        AccessPoint.AccessPointProvisioningCallback provisioningCallback =
+                osuAccessPoint.new AccessPointProvisioningCallback();
+        provisioningCallback.onProvisioningComplete();
+
+        verify(mMockWifiManager).connect(any(), any());
+    }
+
+    /**
+     * Verifies that after provisioning through an OSU provider, we call the connect listener's
+     * onFailure() method if we cannot find the network we just provisioned.
+     */
+    @Test
+    public void testOsuAccessPoint_noMatchingConfigsAfterProvisioning_callsOnFailure() {
+        // Set up mock for WifiManager.getAllMatchingWifiConfigs
+        when(mMockWifiManager.getAllMatchingWifiConfigs(any())).thenReturn(new ArrayList<>());
+
+        // Set up mock for WifiManager.getMatchingPasspointConfigsForOsuProviders
+        OsuProvider provider = createOsuProvider();
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("fqdn");
+        homeSp.setFriendlyName("Test Provider");
+        passpointConfig.setHomeSp(homeSp);
+        Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>();
+        osuProviderConfigMap.put(provider, passpointConfig);
+        when(mMockWifiManager
+                .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(provider)))
+                .thenReturn(osuProviderConfigMap);
+
+        when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+
+        AccessPoint osuAccessPoint = new AccessPoint(mMockContext, provider, mScanResults);
+        osuAccessPoint.setListener(mMockAccessPointListener);
+        osuAccessPoint.startOsuProvisioning(mMockConnectListener);
+
+        AccessPoint.AccessPointProvisioningCallback provisioningCallback =
+                osuAccessPoint.new AccessPointProvisioningCallback();
+        provisioningCallback.onProvisioningComplete();
+
+        verify(mMockConnectListener).onFailure(anyInt());
+    }
 }
diff --git a/packages/SystemUI/res/drawable/ic_camera.xml b/packages/SystemUI/res/drawable/ic_camera.xml
new file mode 100644
index 0000000..b330875
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_camera.xml
@@ -0,0 +1,25 @@
+<?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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="17dp"
+        android:height="17dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFF"
+        android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_camera.xml b/packages/SystemUI/res/drawable/stat_sys_camera.xml
index eb3e963..c914262 100644
--- a/packages/SystemUI/res/drawable/stat_sys_camera.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_camera.xml
@@ -18,14 +18,5 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
     android:insetLeft="3dp"
-    android:insetRight="3dp">
-    <vector
-            android:width="17dp"
-            android:height="17dp"
-            android:viewportWidth="24.0"
-            android:viewportHeight="24.0">
-        <path
-            android:fillColor="#FFF"
-            android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/>
-    </vector>
-</inset>
+    android:insetRight="3dp"
+    android:drawable="@drawable/ic_camera" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
index d49aff9..3786812 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
@@ -39,7 +39,7 @@
             android:layout_height="wrap_content"
             android:paddingEnd="12dp"
             android:paddingBottom="4dp"
-            android:textColor="@color/ksh_keyword_color"
+            android:textColor="?android:attr/textColorPrimary"
             android:textSize="16sp"
             android:maxLines="5"
             android:singleLine="false"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b82c250..290d75b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -129,7 +129,6 @@
 
     <!-- Keyboard shortcuts colors -->
     <color name="ksh_application_group_color">#fff44336</color>
-    <color name="ksh_keyword_color">#d9000000</color>
     <color name="ksh_key_item_color">@color/material_grey_600</color>
     <color name="ksh_key_item_background">@color/material_grey_100</color>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 116ccd2..6eb279a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -23,8 +23,6 @@
     <dimen name="navigation_bar_size">@*android:dimen/navigation_bar_height</dimen>
     <!-- Minimum swipe distance to catch the swipe gestures to invoke assist or switch tasks. -->
     <dimen name="navigation_bar_min_swipe_distance">48dp</dimen>
-    <!-- The default distance from a side of the device to start an edge swipe from -->
-    <dimen name="navigation_bar_default_edge_width">48dp</dimen>
     <dimen name="navigation_bar_default_edge_height">500dp</dimen>
 
     <!-- thickness (height) of the dead zone at the top of the navigation bar,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 46f4c86..d0c17b7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -112,4 +112,22 @@
         return context.getResources().getInteger(
                 com.android.internal.R.integer.config_navBarInteractionMode);
     }
+
+    /**
+     * @return {@code true} if the navbar can be clicked through
+     */
+    public static boolean isNavBarClickThrough(Context context) {
+        return context.getResources().getBoolean(
+                com.android.internal.R.bool.config_navBarTapThrough);
+    }
+
+    /**
+     * @return the edge sensitivity width in px
+     */
+    public static int getEdgeSensitivityWidth(Context context) {
+        return context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.config_backGestureInset);
+    }
+
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index e84c648..3aa9f73 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -293,6 +293,17 @@
     }
 
     /**
+     * Request the stack expand if needed, then select the specified Bubble as current.
+     *
+     * @param notificationKey the notification key for the bubble to be selected
+     */
+    public void expandStackAndSelectBubble(String notificationKey) {
+        if (mStackView != null && mBubbleData.getBubble(notificationKey) != null) {
+            mStackView.setExpandedBubble(notificationKey);
+        }
+    }
+
+    /**
      * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
      */
     void dismissStack(@DismissReason int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 7f39e47..7e6ddcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -128,7 +128,8 @@
     private KeyCharacterMap mBackupKeyCharacterMap;
 
     private KeyboardShortcuts(Context context) {
-        this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_Light);
+        this.mContext = new ContextThemeWrapper(
+                context, android.R.style.Theme_DeviceDefault_Settings);
         this.mPackageManager = AppGlobals.getPackageManager();
         loadResources(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index b788f53..fd2f720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -78,8 +78,7 @@
         row.setJustClicked(true);
         DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
 
-        // If it was a bubble we should close it
-        if (row.getEntry().isBubble()) {
+        if (!row.getEntry().isBubble()) {
             mBubbleController.collapseStack();
         }
 
@@ -95,7 +94,8 @@
      */
     public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
         Notification notification = sbn.getNotification();
-        if (notification.contentIntent != null || notification.fullScreenIntent != null) {
+        if (notification.contentIntent != null || notification.fullScreenIntent != null
+                || row.getEntry().isBubble()) {
             row.setOnClickListener(this);
         } else {
             row.setOnClickListener(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index b91cdaf..d3e5af8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -220,8 +220,6 @@
             StatusBarNotification sbn,
             ExpandableNotificationRow row) {
         row.setIsLowPriority(entry.ambient);
-        // bind the click event to the content area
-        checkNotNull(mNotificationClicker).register(row, sbn);
 
         // Extract target SDK version.
         try {
@@ -257,6 +255,9 @@
         row.setNeedsRedaction(
                 Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
         row.inflateViews();
+
+        // bind the click event to the content area
+        checkNotNull(mNotificationClicker).register(row, sbn);
     }
 
     private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
index 7ea72c7..47a1054 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
@@ -17,7 +17,9 @@
 package com.android.systemui.statusbar.phone;
 
 import android.annotation.IntDef;
+import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Point;
@@ -25,6 +27,8 @@
 import android.os.Handler;
 import android.provider.Settings;
 
+import com.android.systemui.shared.system.QuickStepContract;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -33,7 +37,7 @@
  * prototypes to run in the system. The class will handle communication changes from the settings
  * app and call back to listeners.
  */
-public class NavigationPrototypeController extends ContentObserver {
+public class NavigationPrototypeController extends ContentObserver implements ComponentCallbacks {
     private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback";
     private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome";
     private static final String PROTOTYPE_ENABLED = "prototype_enabled";
@@ -85,9 +89,9 @@
         registerObserver(HIDE_HOME_BUTTON_SETTING);
         registerObserver(GESTURE_MATCH_SETTING);
         registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING);
-        registerObserver(EDGE_SENSITIVITY_WIDTH_SETTING);
         registerObserver(SHOW_HOME_HANDLE_SETTING);
         registerObserver(ENABLE_ASSISTANT_GESTURE);
+        mContext.registerComponentCallbacks(this);
     }
 
     /**
@@ -95,6 +99,7 @@
      */
     public void unregister() {
         mContext.getContentResolver().unregisterContentObserver(this);
+        mContext.unregisterComponentCallbacks(this);
     }
 
     @Override
@@ -115,9 +120,6 @@
             } else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) {
                 mListener.onColorAdaptChanged(
                         NavBarTintController.isEnabled(mContext));
-            } else if (path.endsWith(EDGE_SENSITIVITY_WIDTH_SETTING)) {
-                mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(),
-                        getEdgeSensitivityHeight());
             } else if (path.endsWith(SHOW_HOME_HANDLE_SETTING)) {
                 mListener.onHomeHandleVisiblilityChanged(showHomeHandle());
             } else if (path.endsWith(ENABLE_ASSISTANT_GESTURE)) {
@@ -130,8 +132,7 @@
      * @return the width for edge swipe
      */
     public int getEdgeSensitivityWidth() {
-        // TODO: Move into resource
-        return convertDpToPixel(getGlobalInt(EDGE_SENSITIVITY_WIDTH_SETTING, 48));
+        return QuickStepContract.getEdgeSensitivityWidth(mContext);
     }
 
     /**
@@ -203,6 +204,18 @@
         return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
     }
 
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (mListener != null) {
+            mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(),
+                    getEdgeSensitivityHeight());
+        }
+    }
+
+    @Override
+    public void onLowMemory() {
+    }
+
     public interface OnPrototypeChangedListener {
         void onGestureRemap(@GestureAction int[] actions);
         void onBackButtonVisibilityChanged(boolean visible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index 25cb7d0..8053ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -29,7 +29,6 @@
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
 import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM;
-import static com.android.systemui.statusbar.phone.NavigationPrototypeController.EDGE_SENSITIVITY_WIDTH_SETTING;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -264,10 +263,7 @@
                 mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
                 mAllowGestureDetection = true;
                 mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed();
-                final int defaultRegionThreshold = mContext.getResources()
-                        .getDimensionPixelOffset(R.dimen.navigation_bar_default_edge_width);
-                mGestureRegionThreshold = convertDpToPixel(getIntGlobalSetting(mContext,
-                        EDGE_SENSITIVITY_WIDTH_SETTING, defaultRegionThreshold));
+                mGestureRegionThreshold = QuickStepContract.getEdgeSensitivityWidth(mContext);
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
@@ -357,7 +353,7 @@
                         if (mCurrentAction != null) {
                             mCurrentAction.endGesture();
                         }
-                    } else if (QuickStepContract.isGesturalMode(mContext)
+                    } else if (QuickStepContract.isNavBarClickThrough(mContext)
                             && !mClickThroughPressed) {
                         // Enable click through functionality where no gesture has been detected and
                         // not passed the drag slop so inject a touch event at the same location
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index db91d01..7e06232 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -25,6 +25,7 @@
 import static android.app.StatusBarManager.windowStateToString;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 
+import static com.android.systemui.Dependency.BG_HANDLER;
 import static com.android.systemui.Dependency.MAIN_HANDLER;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
@@ -1076,7 +1077,7 @@
                 mLockscreenUserManager, shadeController, mKeyguardMonitor,
                 mNotificationInterruptionStateProvider, mMetricsLogger,
                 new LockPatternUtils(mContext), Dependency.get(MAIN_HANDLER),
-                mActivityIntentHelper);
+                Dependency.get(BG_HANDLER), mActivityIntentHelper, mBubbleController);
 
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 215f5c4..e4af15c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -48,6 +48,7 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -98,7 +99,9 @@
     private final CommandQueue mCommandQueue;
     private final IDreamManager mDreamManager;
     private final Handler mMainThreadHandler;
+    private final Handler mBackgroundHandler;
     private final ActivityIntentHelper mActivityIntentHelper;
+    private final BubbleController mBubbleController;
 
     private boolean mIsCollapsingToShowActivityOverLockscreen;
 
@@ -125,7 +128,9 @@
             MetricsLogger metricsLogger,
             LockPatternUtils lockPatternUtils,
             Handler mainThreadHandler,
-            ActivityIntentHelper activityIntentHelper) {
+            Handler backgroundHandler,
+            ActivityIntentHelper activityIntentHelper,
+            BubbleController bubbleController) {
         mContext = context;
         mNotificationPanel = panel;
         mPresenter = presenter;
@@ -147,6 +152,7 @@
         mAssistManager = assistManager;
         mGroupManager = groupManager;
         mLockPatternUtils = lockPatternUtils;
+        mBackgroundHandler = backgroundHandler;
         mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
             public void onPendingEntryAdded(NotificationEntry entry) {
@@ -156,6 +162,7 @@
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mMainThreadHandler = mainThreadHandler;
         mActivityIntentHelper = activityIntentHelper;
+        mBubbleController = bubbleController;
     }
 
     /**
@@ -178,14 +185,24 @@
         final PendingIntent intent = notification.contentIntent != null
                 ? notification.contentIntent
                 : notification.fullScreenIntent;
+        final boolean isBubble = row.getEntry().isBubble();
+
+        // This code path is now executed for notification without a contentIntent.
+        // The only valid case is Bubble notifications. Guard against other cases
+        // entering here.
+        if (intent == null && !isBubble) {
+            Log.e(TAG, "onNotificationClicked called for non-clickable notification!");
+            return;
+        }
+
         final String notificationKey = sbn.getKey();
 
-        boolean isActivityIntent = intent.isActivity();
+        boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble;
         final boolean afterKeyguardGone = isActivityIntent
                 && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
                 mLockscreenUserManager.getCurrentUserId());
         final boolean wasOccluded = mShadeController.isOccluded();
-        boolean showOverLockscreen = mKeyguardMonitor.isShowing()
+        boolean showOverLockscreen = mKeyguardMonitor.isShowing() && intent != null
                 && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(),
                 mLockscreenUserManager.getCurrentUserId());
         ActivityStarter.OnDismissAction postKeyguardAction =
@@ -244,9 +261,8 @@
             mShadeController.addAfterKeyguardGoneRunnable(runnable);
             mShadeController.collapsePanel();
         } else {
-            new Thread(runnable).start();
+            mBackgroundHandler.postAtFrontOfQueue(runnable);
         }
-
         return !mNotificationPanel.isFullyCollapsed();
     }
 
@@ -287,6 +303,7 @@
         }
         Intent fillInIntent = null;
         NotificationEntry entry = row.getEntry();
+        final boolean isBubble = entry.isBubble();
         CharSequence remoteInputText = null;
         if (!TextUtils.isEmpty(entry.remoteInputText)) {
             remoteInputText = entry.remoteInputText;
@@ -295,8 +312,12 @@
             fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
                     remoteInputText.toString());
         }
-        startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent);
-        if (isActivityIntent) {
+        if (isBubble) {
+            expandBubbleStackOnMainThread(notificationKey);
+        } else {
+            startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent);
+        }
+        if (isActivityIntent || isBubble) {
             mAssistManager.hideAssist();
         }
         if (shouldCollapse()) {
@@ -316,18 +337,29 @@
         } catch (RemoteException ex) {
             // system process is dead if we're here.
         }
-        if (parentToCancelFinal != null) {
-            removeNotification(parentToCancelFinal);
-        }
-        if (shouldAutoCancel(sbn)
-                || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
-                notificationKey)) {
-            // Automatically remove all notifications that we may have kept around longer
-            removeNotification(sbn);
+        if (!isBubble) {
+            if (parentToCancelFinal != null) {
+                removeNotification(parentToCancelFinal);
+            }
+            if (shouldAutoCancel(sbn)
+                    || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
+                    notificationKey)) {
+                // Automatically remove all notifications that we may have kept around longer
+                removeNotification(sbn);
+            }
         }
         mIsCollapsingToShowActivityOverLockscreen = false;
     }
 
+    private void expandBubbleStackOnMainThread(String notificationKey) {
+        if (Looper.getMainLooper().isCurrentThread()) {
+            mBubbleController.expandStackAndSelectBubble(notificationKey);
+        } else {
+            mMainThreadHandler.post(
+                    () -> mBubbleController.expandStackAndSelectBubble(notificationKey));
+        }
+    }
+
     private void startNotificationIntent(PendingIntent intent, Intent fillInIntent,
             ExpandableNotificationRow row, boolean wasOccluded, boolean isActivityIntent) {
         RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row,
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 14bc71b..9fa85d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -438,6 +438,22 @@
     }
 
     @Test
+    public void testExpandStackAndSelectBubble_removedFirst() {
+        final String key = mRow.getEntry().key;
+
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+
+        assertTrue(mRow.getEntry().isBubble());
+
+        // Simulate notification cancellation.
+        mEntryListener.onEntryRemoved(mRow.getEntry(), null /* notificationVisibility (unused) */,
+                false /* removedbyUser */);
+
+        mBubbleController.expandStackAndSelectBubble(key);
+    }
+
+    @Test
     public void testMarkNewNotificationAsBubble() {
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         assertTrue(mRow.getEntry().isBubble());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 5ea4636..7e089a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -151,6 +151,16 @@
 
     /**
      * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
+     */
+    public ExpandableNotificationRow createBubble()
+            throws Exception {
+        Notification n = createNotification(false /* isGroupSummary */,
+                null /* groupKey */, makeBubbleMetadata(null));
+        return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
+    }
+
+    /**
+     * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
      *
      * @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent}
      */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 20af1ac..41e82cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -24,7 +24,10 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.KeyguardManager;
@@ -49,6 +52,7 @@
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -101,22 +105,23 @@
     private KeyguardMonitor mKeyguardMonitor;
     @Mock
     private Handler mHandler;
+    @Mock
+    private BubbleController mBubbleController;
 
     @Mock
     private ActivityIntentHelper mActivityIntentHelper;
     @Mock
     private PendingIntent mContentIntent;
     @Mock
+    private Intent mContentIntentInner;
+    @Mock
     private NotificationData mNotificationData;
-    @Mock
-    private NotificationEntry mNotificationEntry;
-    @Mock
-    private NotificationEntry mBubbleEntry;
 
     private NotificationActivityStarter mNotificationActivityStarter;
 
     private NotificationTestHelper mNotificationTestHelper;
-    ExpandableNotificationRow mNotificationRow;
+    private ExpandableNotificationRow mNotificationRow;
+    private ExpandableNotificationRow mBubbleNotificationRow;
 
     private final Answer<Void> mCallOnDismiss = answerVoid(
             (ActivityStarter.OnDismissAction dismissAction, Runnable cancel,
@@ -129,14 +134,32 @@
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
         when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
 
-        mActiveNotifications = new ArrayList<>();
-        mActiveNotifications.add(mNotificationEntry);
-        mActiveNotifications.add(mBubbleEntry);
-        when(mNotificationData.getActiveNotifications()).thenReturn(mActiveNotifications);
-        when(mNotificationEntry.getRow()).thenReturn(mNotificationRow);
+        when(mContentIntent.isActivity()).thenReturn(true);
+        when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
+        when(mContentIntent.getIntent()).thenReturn(mContentIntentInner);
 
         mNotificationTestHelper = new NotificationTestHelper(mContext);
+
+        // Create standard notification with contentIntent
         mNotificationRow = mNotificationTestHelper.createRow();
+        StatusBarNotification sbn = mNotificationRow.getStatusBarNotification();
+        sbn.getNotification().contentIntent = mContentIntent;
+        sbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
+
+        // Create bubble notification row with contentIntent
+        mBubbleNotificationRow = mNotificationTestHelper.createBubble();
+        StatusBarNotification bubbleSbn = mBubbleNotificationRow.getStatusBarNotification();
+        bubbleSbn.getNotification().contentIntent = mContentIntent;
+        bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
+        // Do what BubbleController's NotificationEntryListener#onPendingEntryAdded does:
+        mBubbleNotificationRow.getEntry().setIsBubble(true);
+        mBubbleNotificationRow.getEntry().setShowInShadeWhenBubble(true);
+
+        mActiveNotifications = new ArrayList<>();
+        mActiveNotifications.add(mNotificationRow.getEntry());
+        mActiveNotifications.add(mBubbleNotificationRow.getEntry());
+        when(mNotificationData.getActiveNotifications()).thenReturn(mActiveNotifications);
+        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
 
         mNotificationActivityStarter = new StatusBarNotificationActivityStarter(getContext(),
                 mock(CommandQueue.class), mAssistManager, mock(NotificationPanelView.class),
@@ -147,16 +170,8 @@
                 mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class),
                 mock(NotificationLockscreenUserManager.class), mShadeController, mKeyguardMonitor,
                 mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
-                mock(LockPatternUtils.class), mHandler, mActivityIntentHelper);
-
-
-        when(mContentIntent.isActivity()).thenReturn(true);
-        when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
-
-        // SBNActivityStarter expects contentIntent or fullScreenIntent to be set
-        mNotificationRow.getEntry().notification.getNotification().contentIntent = mContentIntent;
-
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+                mock(LockPatternUtils.class), mHandler, mHandler, mActivityIntentHelper,
+                mBubbleController);
 
         // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
         doAnswer(mCallOnDismiss).when(mActivityStarter).dismissKeyguardThenExecute(
@@ -173,33 +188,26 @@
         // set up Handler to synchronously invoke the Runnable arg
         doAnswer(answerVoid(Runnable::run))
                 .when(mHandler).post(any(Runnable.class));
+
+        doAnswer(answerVoid(Runnable::run))
+                .when(mHandler).postAtFrontOfQueue(any(Runnable.class));
     }
 
     @Test
-    public void testOnNotificationClicked_whileKeyguardVisible()
+    public void testOnNotificationClicked_keyGuardShowing()
             throws PendingIntent.CanceledException, RemoteException {
         // Given
+        StatusBarNotification sbn = mNotificationRow.getStatusBarNotification();
+        sbn.getNotification().contentIntent = mContentIntent;
+        sbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
+
         when(mKeyguardMonitor.isShowing()).thenReturn(true);
         when(mShadeController.isOccluded()).thenReturn(true);
-        when(mContentIntent.isActivity()).thenReturn(true);
-        when(mActivityIntentHelper.wouldShowOverLockscreen(any(Intent.class), anyInt()))
-                .thenReturn(false);
-        when(mActivityIntentHelper.wouldLaunchResolverActivity(any(Intent.class), anyInt()))
-                .thenReturn(false);
-
-        StatusBarNotification statusBarNotification = mNotificationRow.getEntry().notification;
-        statusBarNotification.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
 
         // When
-        mNotificationActivityStarter.onNotificationClicked(statusBarNotification,
-                mNotificationRow);
+        mNotificationActivityStarter.onNotificationClicked(sbn, mNotificationRow);
 
         // Then
-        verify(mActivityStarter).dismissKeyguardThenExecute(
-                any(ActivityStarter.OnDismissAction.class),
-                any() /* cancel */,
-                anyBoolean() /* afterKeyguardGone */);
-
         verify(mShadeController, atLeastOnce()).collapsePanel();
 
         verify(mContentIntent).sendAndReturnResult(
@@ -214,9 +222,100 @@
         verify(mAssistManager).hideAssist();
 
         verify(mStatusBarService).onNotificationClick(
-                eq(mNotificationRow.getEntry().key), any(NotificationVisibility.class));
+                eq(sbn.getKey()), any(NotificationVisibility.class));
 
         // Notification is removed due to FLAG_AUTO_CANCEL
-        verify(mEntryManager).performRemoveNotification(eq(statusBarNotification));
+        verify(mEntryManager).performRemoveNotification(eq(sbn));
+    }
+
+    @Test
+    public void testOnNotificationClicked_bubble_noContentIntent_noKeyGuard()
+            throws RemoteException {
+        StatusBarNotification sbn = mBubbleNotificationRow.getStatusBarNotification();
+
+        // Given
+        sbn.getNotification().contentIntent = null;
+
+        // When
+        mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
+
+        // Then
+        verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
+
+        // This is called regardless, and simply short circuits when there is nothing to do.
+        verify(mShadeController, atLeastOnce()).collapsePanel();
+
+        verify(mAssistManager).hideAssist();
+
+        verify(mStatusBarService).onNotificationClick(
+                eq(sbn.getKey()), any(NotificationVisibility.class));
+
+        // The content intent should NOT be sent on click.
+        verifyZeroInteractions(mContentIntent);
+
+        // Notification should not be cancelled.
+        verify(mEntryManager, never()).performRemoveNotification(eq(sbn));
+    }
+
+    @Test
+    public void testOnNotificationClicked_bubble_noContentIntent_keyGuardShowing()
+            throws RemoteException {
+        StatusBarNotification sbn = mBubbleNotificationRow.getStatusBarNotification();
+
+        // Given
+        sbn.getNotification().contentIntent = null;
+        when(mKeyguardMonitor.isShowing()).thenReturn(true);
+        when(mShadeController.isOccluded()).thenReturn(true);
+
+        // When
+        mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
+
+        // Then
+        verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
+
+        verify(mShadeController, atLeastOnce()).collapsePanel();
+
+        verify(mAssistManager).hideAssist();
+
+        verify(mStatusBarService).onNotificationClick(
+                eq(sbn.getKey()), any(NotificationVisibility.class));
+
+        // The content intent should NOT be sent on click.
+        verifyZeroInteractions(mContentIntent);
+
+        // Notification should not be cancelled.
+        verify(mEntryManager, never()).performRemoveNotification(eq(sbn));
+    }
+
+    @Test
+    public void testOnNotificationClicked_bubble_withContentIntent_keyGuardShowing()
+            throws RemoteException {
+        StatusBarNotification sbn = mBubbleNotificationRow.getStatusBarNotification();
+
+        // Given
+        sbn.getNotification().contentIntent = mContentIntent;
+        when(mKeyguardMonitor.isShowing()).thenReturn(true);
+        when(mShadeController.isOccluded()).thenReturn(true);
+
+        // When
+        mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
+
+        // Then
+        verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
+
+        verify(mShadeController, atLeastOnce()).collapsePanel();
+
+        verify(mAssistManager).hideAssist();
+
+        verify(mStatusBarService).onNotificationClick(
+                eq(sbn.getKey()), any(NotificationVisibility.class));
+
+        // The content intent should NOT be sent on click.
+        verify(mContentIntent).getIntent();
+        verify(mContentIntent).isActivity();
+        verifyNoMoreInteractions(mContentIntent);
+
+        // Notification should not be cancelled.
+        verify(mEntryManager, never()).performRemoveNotification(eq(sbn));
     }
 }
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
index 545a3cc..23d2e7b 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
@@ -26,4 +26,11 @@
     <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
          Only applies if the device display is not square. -->
     <bool name="config_navBarCanMove">false</bool>
-</resources>
\ No newline at end of file
+
+    <!-- Controls whether the navigation bar lets through taps. -->
+    <bool name="config_navBarTapThrough">true</bool>
+
+    <!-- Controls the size of the back gesture inset. -->
+    <dimen name="config_backGestureInset">48dp</dimen>
+
+</resources>
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index f50364d..164837a 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -39,7 +39,9 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -50,10 +52,22 @@
     private static final String TAG = "ExplicitHealthCheckController";
     private final Object mLock = new Object();
     private final Context mContext;
-    @GuardedBy("mLock") @Nullable private StateCallback mStateCallback;
+
+    // Called everytime the service is connected, so the watchdog can sync it's state with
+    // the health check service. In practice, should never be null after it has been #setEnabled.
+    @GuardedBy("mLock") @Nullable private Runnable mOnConnected;
+    // Called everytime a package passes the health check, so the watchdog is notified of the
+    // passing check. In practice, should never be null after it has been #setEnabled.
+    @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
+    // Actual binder object to the explicit health check service.
     @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
-    @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
+    // Cache for packages supporting explicit health checks. This cache should not change while
+    // the health check service is running.
     @GuardedBy("mLock") @Nullable private List<String> mSupportedPackages;
+    // Connection to the explicit health check service, necessary to unbind
+    @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
+    // Bind state of the explicit health check service.
+    @GuardedBy("mLock") private boolean mEnabled;
 
     ExplicitHealthCheckController(Context context) {
         mContext = context;
@@ -61,28 +75,40 @@
 
     /**
      * Requests an explicit health check for {@code packageName}.
-     * After this request, the callback registered on {@link startService} can receive explicit
+     * After this request, the callback registered on {@link #setCallbacks} can receive explicit
      * health check passed results.
      *
      * @throws IllegalStateException if the service is not started
      */
     public void request(String packageName) throws RemoteException {
         synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+
             enforceServiceReadyLocked();
+
+            Slog.i(TAG, "Requesting health check for package " + packageName);
             mRemoteService.request(packageName);
         }
     }
 
     /**
      * Cancels all explicit health checks for {@code packageName}.
-     * After this request, the callback registered on {@link startService} can no longer receive
+     * After this request, the callback registered on {@link #setCallbacks} can no longer receive
      * explicit health check passed results.
      *
      * @throws IllegalStateException if the service is not started
      */
     public void cancel(String packageName) throws RemoteException {
         synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+
             enforceServiceReadyLocked();
+
+            Slog.i(TAG, "Cancelling health check for package " + packageName);
             mRemoteService.cancel(packageName);
         }
     }
@@ -95,13 +121,21 @@
      */
     public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
         synchronized (mLock) {
+            if (!mEnabled) {
+                consumer.accept(Collections.emptyList());
+                return;
+            }
+
             enforceServiceReadyLocked();
+
             if (mSupportedPackages == null) {
+                Slog.d(TAG, "Getting health check supported packages");
                 mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
                     mSupportedPackages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES);
                     consumer.accept(mSupportedPackages);
                 }));
             } else {
+                Slog.d(TAG, "Getting cached health check supported packages");
                 consumer.accept(mSupportedPackages);
             }
         }
@@ -115,95 +149,113 @@
      */
     public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
         synchronized (mLock) {
+            if (!mEnabled) {
+                consumer.accept(Collections.emptyList());
+                return;
+            }
+
             enforceServiceReadyLocked();
+
+            Slog.d(TAG, "Getting health check requested packages");
             mRemoteService.getRequestedPackages(new RemoteCallback(
                     result -> consumer.accept(
                             result.getStringArrayList(EXTRA_REQUESTED_PACKAGES))));
         }
     }
 
+    /** Enables or disables explicit health checks. */
+    public void setEnabled(boolean enabled) {
+        synchronized (mLock) {
+            if (enabled == mEnabled) {
+                return;
+            }
+
+            Slog.i(TAG, "Setting explicit health checks enabled " + enabled);
+            mEnabled = enabled;
+            if (enabled) {
+                bindService();
+            } else {
+                unbindService();
+            }
+        }
+    }
+
     /**
-     * Starts the explicit health check service.
-     *
-     * @param stateCallback will receive important state changes changes
-     * @param passedConsumer will accept packages that pass explicit health checks
-     *
-     * @throws IllegalStateException if the service is already started
+     * Sets callbacks to listen to important events from the controller.
+     * Should be called at initialization.
      */
-    public void startService(StateCallback stateCallback, Consumer<String> passedConsumer) {
+    public void setCallbacks(Runnable onConnected, Consumer<String> passedConsumer) {
+        Preconditions.checkNotNull(onConnected);
+        Preconditions.checkNotNull(passedConsumer);
+        mOnConnected = onConnected;
+        mPassedConsumer = passedConsumer;
+    }
+
+    /** Binds to the explicit health check service. */
+    private void bindService() {
         synchronized (mLock) {
             if (mRemoteService != null) {
-                throw new IllegalStateException("Explicit health check service already started.");
+                return;
             }
-            mStateCallback = stateCallback;
+            ComponentName component = getServiceComponentNameLocked();
+            if (component == null) {
+                Slog.wtf(TAG, "Explicit health check service not found");
+                return;
+            }
+
+            Intent intent = new Intent();
+            intent.setComponent(component);
+            // TODO: Fix potential race conditions during mConnection state transitions.
+            // E.g after #onServiceDisconected, the mRemoteService object is invalid until
+            // we get an #onServiceConnected.
             mConnection = new ServiceConnection() {
                 @Override
                 public void onServiceConnected(ComponentName name, IBinder service) {
-                    synchronized (mLock) {
-                        mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
-                        try {
-                            mRemoteService.setCallback(new RemoteCallback(result -> {
-                                String packageName =
-                                        result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
-                                if (!TextUtils.isEmpty(packageName)) {
-                                    passedConsumer.accept(packageName);
-                                } else {
-                                    Slog.w(TAG, "Empty package passed explicit health check?");
-                                }
-                            }));
-                            mStateCallback.onStart();
-                            Slog.i(TAG, "Explicit health check service is connected " + name);
-                        } catch (RemoteException e) {
-                            Slog.wtf(TAG, "Coud not setCallback on explicit health check service");
-                        }
-                    }
+                    initState(service);
+                    Slog.i(TAG, "Explicit health check service is connected " + name);
                 }
 
                 @Override
                 @MainThread
                 public void onServiceDisconnected(ComponentName name) {
-                    resetState();
+                    // Service crashed or process was killed, #onServiceConnected will be called.
+                    // Don't need to re-bind.
                     Slog.i(TAG, "Explicit health check service is disconnected " + name);
                 }
 
                 @Override
                 public void onBindingDied(ComponentName name) {
-                    resetState();
+                    // Application hosting service probably got updated
+                    // Need to re-bind.
+                    synchronized (mLock) {
+                        if (mEnabled) {
+                            unbindService();
+                            bindService();
+                        }
+                    }
                     Slog.i(TAG, "Explicit health check service binding is dead " + name);
                 }
 
                 @Override
                 public void onNullBinding(ComponentName name) {
-                    resetState();
-                    Slog.i(TAG, "Explicit health check service binding is null " + name);
+                    // Should never happen. Service returned null from #onBind.
+                    Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
                 }
             };
 
-            ComponentName component = getServiceComponentNameLocked();
-            if (component != null) {
-                Intent intent = new Intent();
-                intent.setComponent(component);
-                mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE,
-                        UserHandle.of(UserHandle.USER_SYSTEM));
-            }
+            Slog.i(TAG, "Binding to explicit health service");
+            mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE,
+                    UserHandle.of(UserHandle.USER_SYSTEM));
         }
     }
 
-    // TODO: Differentiate between expected vs unexpected stop?
-    /** Callback to receive important {@link ExplicitHealthCheckController} state changes. */
-    abstract static class StateCallback {
-        /** The controller is ready and we can request explicit health checks for packages */
-        public void onStart() {}
-
-        /** The controller is not ready and we cannot request explicit health checks for packages */
-        public void onStop() {}
-    }
-
-    /** Stops the explicit health check service. */
-    public void stopService() {
+    /** Unbinds the explicit health check service. */
+    private void unbindService() {
         synchronized (mLock) {
             if (mRemoteService != null) {
+                Slog.i(TAG, "Unbinding from explicit health service");
                 mContext.unbindService(mConnection);
+                mRemoteService = null;
             }
         }
     }
@@ -247,19 +299,41 @@
         return name;
     }
 
-    private void resetState() {
+    private void initState(IBinder service) {
         synchronized (mLock) {
-            mStateCallback.onStop();
-            mStateCallback = null;
             mSupportedPackages = null;
-            mRemoteService = null;
-            mConnection = null;
+            mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
+            try {
+                mRemoteService.setCallback(new RemoteCallback(result -> {
+                    String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
+                    if (!TextUtils.isEmpty(packageName)) {
+                        synchronized (mLock) {
+                            if (mPassedConsumer == null) {
+                                Slog.w(TAG, "Health check passed for package " + packageName
+                                        + "but no consumer registered.");
+                            } else {
+                                mPassedConsumer.accept(packageName);
+                            }
+                        }
+                    } else {
+                        Slog.w(TAG, "Empty package passed explicit health check?");
+                    }
+                }));
+                if (mOnConnected == null) {
+                    Slog.w(TAG, "Health check service connected but no runnable registered.");
+                } else {
+                    mOnConnected.run();
+                }
+            } catch (RemoteException e) {
+                Slog.wtf(TAG, "Could not setCallback on explicit health check service");
+            }
         }
     }
 
     @GuardedBy("mLock")
     private void enforceServiceReadyLocked() {
         if (mRemoteService == null) {
+            // TODO: Try to bind to service
             throw new IllegalStateException("Explicit health check service not ready");
         }
     }
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 660109c..2ba4d97 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -26,11 +26,12 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.Log;
 import android.util.Slog;
 import android.util.Xml;
 
@@ -54,10 +55,12 @@
 import java.lang.annotation.Retention;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Monitors the health of packages on the system and notifies interested observers when packages
@@ -84,10 +87,10 @@
     private final Object mLock = new Object();
     // System server context
     private final Context mContext;
-    // Handler to run package cleanup runnables
-    private final Handler mTimerHandler;
-    // Handler for processing IO and observer actions
-    private final Handler mWorkerHandler;
+    // Handler to run short running tasks
+    private final Handler mShortTaskHandler;
+    // Handler for processing IO and long running tasks
+    private final Handler mLongTaskHandler;
     // Contains (observer-name -> observer-handle) that have ever been registered from
     // previous boots. Observers with all packages expired are periodically pruned.
     // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
@@ -97,6 +100,12 @@
     private final AtomicFile mPolicyFile;
     // Runnable to prune monitored packages that have expired
     private final Runnable mPackageCleanup;
+    private final ExplicitHealthCheckController mHealthCheckController;
+    // Flag to control whether explicit health checks are supported or not
+    @GuardedBy("mLock")
+    private boolean mIsHealthCheckEnabled = true;
+    @GuardedBy("mLock")
+    private boolean mIsPackagesReady;
     // Last SystemClock#uptimeMillis a package clean up was executed.
     // 0 if mPackageCleanup not running.
     private long mUptimeAtLastRescheduleMs;
@@ -104,32 +113,30 @@
     // 0 if mPackageCleanup not running.
     private long mDurationAtLastReschedule;
 
-    // TODO(b/120598832): Remove redundant context param
     private PackageWatchdog(Context context) {
-        mContext = context;
-        mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
-                        "package-watchdog.xml"));
-        mTimerHandler = new Handler(Looper.myLooper());
-        mWorkerHandler = BackgroundThread.getHandler();
-        mPackageCleanup = this::rescheduleCleanup;
-        loadFromFile();
+        // Needs to be constructed inline
+        this(context, new AtomicFile(
+                        new File(new File(Environment.getDataDirectory(), "system"),
+                                "package-watchdog.xml")),
+                new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
+                new ExplicitHealthCheckController(context));
     }
 
     /**
-     * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers
-     * and creates package-watchdog.xml in an apps data directory.
+     * Creates a PackageWatchdog that allows injecting dependencies.
      */
     @VisibleForTesting
-    PackageWatchdog(Context context, Looper looper) {
+    PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
+            Handler longTaskHandler, ExplicitHealthCheckController controller) {
         mContext = context;
-        mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
-        mTimerHandler = new Handler(looper);
-        mWorkerHandler = mTimerHandler;
+        mPolicyFile = policyFile;
+        mShortTaskHandler = shortTaskHandler;
+        mLongTaskHandler = longTaskHandler;
         mPackageCleanup = this::rescheduleCleanup;
+        mHealthCheckController = controller;
         loadFromFile();
     }
 
-
     /** Creates or gets singleton instance of PackageWatchdog. */
     public static PackageWatchdog getInstance(Context context) {
         synchronized (PackageWatchdog.class) {
@@ -141,6 +148,20 @@
     }
 
     /**
+     * Called during boot to notify when packages are ready on the device so we can start
+     * binding.
+     */
+    public void onPackagesReady() {
+        synchronized (mLock) {
+            mIsPackagesReady = true;
+            mHealthCheckController.setCallbacks(this::updateHealthChecks,
+                    packageName -> onHealthCheckPassed(packageName));
+            // Controller is disabled at creation until here where we may enable it
+            mHealthCheckController.setEnabled(mIsHealthCheckEnabled);
+        }
+    }
+
+    /**
      * Registers {@code observer} to listen for package failures
      *
      * <p>Observers are expected to call this on boot. It does not specify any packages but
@@ -163,40 +184,63 @@
      * Starts observing the health of the {@code packages} for {@code observer} and notifies
      * {@code observer} of any package failures within the monitoring duration.
      *
-     * <p>If monitoring a package with {@code withExplicitHealthCheck}, at the end of the monitoring
-     * duration if {@link #onExplicitHealthCheckPassed} was never called,
+     * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
+     * duration if {@link #onHealthCheckPassed} was never called,
      * {@link PackageHealthObserver#execute} will be called as if the package failed.
      *
      * <p>If {@code observer} is already monitoring a package in {@code packageNames},
      * the monitoring window of that package will be reset to {@code durationMs} and the health
-     * check state will be reset to a default depending on {@code withExplictHealthCheck}.
+     * check state will be reset to a default depending on if the package is contained in
+     * {@link mPackagesWithExplicitHealthCheckEnabled}.
      *
      * @throws IllegalArgumentException if {@code packageNames} is empty
      * or {@code durationMs} is less than 1
      */
-    public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
-            long durationMs, boolean withExplicitHealthCheck) {
-        if (packageNames.isEmpty() || durationMs < 1) {
+    public void startObservingHealth(PackageHealthObserver observer, List<String> packages,
+            long durationMs) {
+        if (packages.isEmpty() || durationMs < 1) {
             throw new IllegalArgumentException("Observation not started, no packages specified"
                     + "or invalid duration");
         }
+        if (!mIsPackagesReady) {
+            // TODO: Queue observation requests when packages are not ready
+            Slog.w(TAG, "Attempt to observe when packages not ready");
+            return;
+        }
+
+        try {
+            Slog.i(TAG, "Getting packages supporting explicit health check");
+            mHealthCheckController.getSupportedPackages(supportedPackages ->
+                    startObservingInner(observer, packages, durationMs, supportedPackages));
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to fetch supported explicit health check packages");
+        }
+    }
+
+    private void startObservingInner(PackageHealthObserver observer,
+            List<String> packageNames, long durationMs,
+            List<String> healthCheckSupportedPackages) {
+        Slog.i(TAG, "Start observing packages " + packageNames
+                + ". Explicit health check supported packages " + healthCheckSupportedPackages);
         List<MonitoredPackage> packages = new ArrayList<>();
         for (int i = 0; i < packageNames.size(); i++) {
-            // When observing packages withExplicitHealthCheck,
-            // MonitoredPackage#mHasExplicitHealthCheckPassed will be false initially.
-            packages.add(new MonitoredPackage(packageNames.get(i), durationMs,
-                            !withExplicitHealthCheck));
+            String packageName = packageNames.get(i);
+            boolean shouldEnableHealthCheck = healthCheckSupportedPackages.contains(packageName);
+            // If we should enable explicit health check for a package,
+            // MonitoredPackage#mHasHealthCheckPassed will be false
+            // until PackageWatchdog#onHealthCheckPassed
+            packages.add(new MonitoredPackage(packageName, durationMs, !shouldEnableHealthCheck));
         }
         synchronized (mLock) {
             ObserverInternal oldObserver = mAllObservers.get(observer.getName());
             if (oldObserver == null) {
-                Slog.d(TAG, observer.getName() + " started monitoring health of packages "
-                        + packageNames);
+                Slog.d(TAG, observer.getName() + " started monitoring health "
+                        + "of packages " + packageNames);
                 mAllObservers.put(observer.getName(),
                         new ObserverInternal(observer.getName(), packages));
             } else {
-                Slog.d(TAG, observer.getName() + " added the following packages to monitor "
-                        + packageNames);
+                Slog.d(TAG, observer.getName() + " added the following "
+                        + "packages to monitor " + packageNames);
                 oldObserver.updatePackages(packages);
             }
         }
@@ -204,9 +248,97 @@
         // Always reschedule because we may need to expire packages
         // earlier than we are already scheduled for
         rescheduleCleanup();
+        updateHealthChecks();
         saveToFileAsync();
     }
 
+    private void requestCheck(String packageName) {
+        try {
+            Slog.d(TAG, "Requesting explicit health check for " + packageName);
+            mHealthCheckController.request(packageName);
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to request explicit health check for " + packageName, e);
+        }
+    }
+
+    private void cancelCheck(String packageName) {
+        try {
+            Slog.d(TAG, "Cancelling explicit health check for " + packageName);
+            mHealthCheckController.cancel(packageName);
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to cancel explicit health check for " + packageName, e);
+        }
+    }
+
+    private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
+            Consumer<String> action) {
+        Iterator<String> iterator = collection1.iterator();
+        while (iterator.hasNext()) {
+            String packageName = iterator.next();
+            if (!collection2.contains(packageName)) {
+                action.accept(packageName);
+            }
+        }
+    }
+
+    private void updateChecksInner(List<String> supportedPackages,
+            List<String> previousRequestedPackages) {
+        boolean shouldUpdateFile = false;
+
+        synchronized (mLock) {
+            Slog.i(TAG, "Updating explicit health checks. Supported packages: " + supportedPackages
+                    + ". Requested packages: " + previousRequestedPackages);
+            Set<String> newRequestedPackages = new ArraySet<>();
+            Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
+            while (oit.hasNext()) {
+                ObserverInternal observer = oit.next();
+                Iterator<MonitoredPackage> pit =
+                        observer.mPackages.values().iterator();
+                while (pit.hasNext()) {
+                    MonitoredPackage monitoredPackage = pit.next();
+                    String packageName = monitoredPackage.mName;
+                    if (!monitoredPackage.mHasPassedHealthCheck) {
+                        if (supportedPackages.contains(packageName)) {
+                            newRequestedPackages.add(packageName);
+                        } else {
+                            shouldUpdateFile = true;
+                            monitoredPackage.mHasPassedHealthCheck = true;
+                        }
+                    }
+                }
+            }
+            // TODO: Support ending the binding if newRequestedPackages is empty.
+            // Will have to re-bind when we #startObservingHealth.
+
+            // Cancel packages no longer requested
+            actOnDifference(previousRequestedPackages, newRequestedPackages, p -> cancelCheck(p));
+            // Request packages not yet requested
+            actOnDifference(newRequestedPackages, previousRequestedPackages, p -> requestCheck(p));
+        }
+
+        if (shouldUpdateFile) {
+            saveToFileAsync();
+        }
+    }
+
+    private void updateHealthChecks() {
+        mShortTaskHandler.post(() -> {
+            try {
+                Slog.i(TAG, "Updating explicit health checks for all available packages");
+                mHealthCheckController.getSupportedPackages(supported -> {
+                    try {
+                        mHealthCheckController.getRequestedPackages(
+                                requested -> updateChecksInner(supported, requested));
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to get requested health check packages", e);
+                    }
+                });
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get supported health check package", e);
+            }
+        });
+    }
+
     /**
      * Unregisters {@code observer} from listening to package failure.
      * Additionally, this stops observing any packages that may have previously been observed
@@ -250,7 +382,7 @@
      * <p>This method could be called frequently if there is a severe problem on the device.
      */
     public void onPackageFailure(List<VersionedPackage> packages) {
-        mWorkerHandler.post(() -> {
+        mLongTaskHandler.post(() -> {
             synchronized (mLock) {
                 if (mAllObservers.isEmpty()) {
                     return;
@@ -286,49 +418,32 @@
         });
     }
 
-    /**
-     * Updates the observers monitoring {@code packageName} that explicit health check has passed.
-     *
-     * <p> This update is strictly for registered observers at the time of the call
-     * Observers that register after this signal will have no knowledge of prior signals and will
-     * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
-     *
-     * <p> {@code packageName} can still be considered failed if reported by
-     * {@link #onPackageFailure} before the package expires.
-     *
-     * <p> Triggered by components outside the system server when they are fully functional after an
-     * update.
-     */
-    public void onExplicitHealthCheckPassed(String packageName) {
-        Slog.i(TAG, "Health check passed for package: " + packageName);
-        boolean shouldUpdateFile = false;
-        synchronized (mLock) {
-            for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
-                ObserverInternal observer = mAllObservers.valueAt(observerIdx);
-                MonitoredPackage monitoredPackage = observer.mPackages.get(packageName);
-                if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) {
-                    monitoredPackage.mHasPassedHealthCheck = true;
-                    shouldUpdateFile = true;
-                }
-            }
-        }
-        if (shouldUpdateFile) {
-            saveToFileAsync();
-        }
-    }
-
     // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file?
     // This currently adds about 7ms extra to shutdown thread
     /** Writes the package information to file during shutdown. */
     public void writeNow() {
         if (!mAllObservers.isEmpty()) {
-            mWorkerHandler.removeCallbacks(this::saveToFile);
+            mLongTaskHandler.removeCallbacks(this::saveToFile);
             pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
             saveToFile();
             Slog.i(TAG, "Last write to update package durations");
         }
     }
 
+    // TODO(b/120598832): Set depending on DeviceConfig flag
+    /**
+     * Enables or disables explicit health checks.
+     * <p> If explicit health checks are enabled, the health check service is started.
+     * <p> If explicit health checks are disabled, pending explicit health check requests are
+     * passed and the health check service is stopped.
+     */
+    public void setExplicitHealthCheckEnabled(boolean enabled) {
+        synchronized (mLock) {
+            mIsHealthCheckEnabled = enabled;
+            mHealthCheckController.setEnabled(enabled);
+        }
+    }
+
     /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
     @Retention(SOURCE)
     @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
@@ -371,6 +486,37 @@
         String getName();
     }
 
+    /**
+     * Updates the observers monitoring {@code packageName} that explicit health check has passed.
+     *
+     * <p> This update is strictly for registered observers at the time of the call
+     * Observers that register after this signal will have no knowledge of prior signals and will
+     * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
+     *
+     * <p> {@code packageName} can still be considered failed if reported by
+     * {@link #onPackageFailure} before the package expires.
+     *
+     * <p> Triggered by components outside the system server when they are fully functional after an
+     * update.
+     */
+    private void onHealthCheckPassed(String packageName) {
+        Slog.i(TAG, "Health check passed for package: " + packageName);
+        boolean shouldUpdateFile = false;
+        synchronized (mLock) {
+            for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
+                ObserverInternal observer = mAllObservers.valueAt(observerIdx);
+                MonitoredPackage monitoredPackage = observer.mPackages.get(packageName);
+                if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) {
+                    monitoredPackage.mHasPassedHealthCheck = true;
+                    shouldUpdateFile = true;
+                }
+            }
+        }
+        if (shouldUpdateFile) {
+            saveToFileAsync();
+        }
+    }
+
     /** Reschedules handler to prune expired packages from observers. */
     private void rescheduleCleanup() {
         synchronized (mLock) {
@@ -393,8 +539,8 @@
                     || nextDurationToScheduleMs < remainingDurationMs) {
                 // First schedule or an earlier reschedule
                 pruneObservers(elapsedDurationMs);
-                mTimerHandler.removeCallbacks(mPackageCleanup);
-                mTimerHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
+                mShortTaskHandler.removeCallbacks(mPackageCleanup);
+                mShortTaskHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
                 mDurationAtLastReschedule = nextDurationToScheduleMs;
                 mUptimeAtLastRescheduleMs = uptimeMs;
             }
@@ -437,7 +583,7 @@
                 List<MonitoredPackage> failedPackages =
                         observer.updateMonitoringDurations(elapsedMs);
                 if (!failedPackages.isEmpty()) {
-                    onExplicitHealthCheckFailed(observer, failedPackages);
+                    onHealthCheckFailed(observer, failedPackages);
                 }
                 if (observer.mPackages.isEmpty()) {
                     Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired");
@@ -445,12 +591,13 @@
                 }
             }
         }
+        updateHealthChecks();
         saveToFileAsync();
     }
 
-    private void onExplicitHealthCheckFailed(ObserverInternal observer,
+    private void onHealthCheckFailed(ObserverInternal observer,
             List<MonitoredPackage> failedPackages) {
-        mWorkerHandler.post(() -> {
+        mLongTaskHandler.post(() -> {
             synchronized (mLock) {
                 PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
                 if (registeredObserver != null) {
@@ -458,6 +605,7 @@
                     for (int i = 0; i < failedPackages.size(); i++) {
                         String packageName = failedPackages.get(i).mName;
                         long versionCode = 0;
+                        Slog.i(TAG, "Explicit health check failed for package " + packageName);
                         try {
                             versionCode = pm.getPackageInfo(
                                     packageName, 0 /* flags */).getLongVersionCode();
@@ -498,7 +646,7 @@
         } catch (FileNotFoundException e) {
             // Nothing to monitor
         } catch (IOException | NumberFormatException | XmlPullParserException e) {
-            Log.wtf(TAG, "Unable to read monitored packages, deleting file", e);
+            Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e);
             mPolicyFile.delete();
         } finally {
             IoUtils.closeQuietly(infile);
@@ -542,8 +690,8 @@
     }
 
     private void saveToFileAsync() {
-        mWorkerHandler.removeCallbacks(this::saveToFile);
-        mWorkerHandler.post(this::saveToFile);
+        mLongTaskHandler.removeCallbacks(this::saveToFile);
+        mLongTaskHandler.post(this::saveToFile);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f51589a..3eb7c03 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2135,6 +2135,8 @@
                 mService.mServices.systemServicesReady();
             } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                 mService.startBroadcastObservers();
+            } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+                mService.mPackageWatchdog.onPackagesReady();
             }
         }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index cc90182..8a462da 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -16,7 +16,6 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM;
 import static android.app.ActivityTaskManager.RESIZE_MODE_USER;
@@ -306,9 +305,7 @@
         mSamplingInterval = 0;
         mAutoStop = false;
         mStreaming = false;
-        mUserId = mInternal.mUserController.handleIncomingUser(Binder.getCallingPid(),
-            Binder.getCallingUid(), defUser, false, ALLOW_FULL_ONLY,
-            "ActivityManagerShellCommand", null);
+        mUserId = defUser;
         mDisplayId = INVALID_DISPLAY;
         mWindowingMode = WINDOWING_MODE_UNDEFINED;
         mActivityType = ACTIVITY_TYPE_UNDEFINED;
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index c385991..fe762c0 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -39,12 +39,16 @@
 import android.hardware.face.IFaceService;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.service.restricted_image.RestrictedImagesDumpProto;
+import android.service.restricted_image.RestrictedImageProto;
+import android.service.restricted_image.RestrictedImageSetProto;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -281,7 +285,9 @@
 
             final long ident = Binder.clearCallingIdentity();
             try {
-                if (args.length > 0 && "--proto".equals(args[0])) {
+                if (args.length == 1 && "--restricted_image".equals(args[0])) {
+                    dumpRestrictedImage(fd);
+                } else if (args.length > 0 && "--proto".equals(args[0])) {
                     dumpProto(fd);
                 } else {
                     dumpInternal(pw);
@@ -1063,4 +1069,74 @@
         mPerformanceMap.clear();
         mCryptoPerformanceMap.clear();
     }
+
+    private void dumpRestrictedImage(FileDescriptor fd) {
+        // WARNING: CDD restricts image data from leaving TEE unencrypted on
+        //          production devices:
+        // [C-1-10] MUST not allow unencrypted access to identifiable biometric
+        //          data or any data derived from it (such as embeddings) to the
+        //         Application Processor outside the context of the TEE.
+        //  As such, this API should only be enabled for testing purposes on
+        //  engineering and userdebug builds.  All modules in the software stack
+        //  MUST enforce final build products do NOT have this functionality.
+        //  Additionally, the following check MUST NOT be removed.
+        if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
+            return;
+        }
+
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        final long setToken = proto.start(RestrictedImagesDumpProto.SETS);
+
+        // Name of the service
+        proto.write(RestrictedImageSetProto.CATEGORY, "face");
+
+        // Individual images
+        for (int i = 0; i < 5; i++) {
+            final long imageToken = proto.start(RestrictedImageSetProto.IMAGES);
+            proto.write(RestrictedImageProto.MIME_TYPE, "image/png");
+            proto.write(RestrictedImageProto.IMAGE_DATA, new byte[] {
+                    // png image data
+                    -119,   80,   78,   71,   13,   10,   26,   10,
+                       0,    0,    0,   13,   73,   72,   68,   82,
+                       0,    0,    0,  100,    0,    0,    0,  100,
+                       1,    3,    0,    0,    0,   74,   44,    7,
+                      23,    0,    0,    0,    4,  103,   65,   77,
+                      65,    0,    0,  -79, -113,   11,   -4,   97,
+                       5,    0,    0,    0,    1,  115,   82,   71,
+                      66,    0,  -82,  -50,   28,  -23,    0,    0,
+                       0,    6,   80,   76,   84,   69,   -1,   -1,
+                      -1,    0,    0,    0,   85,  -62,  -45,  126,
+                       0,    0,    0, -115,   73,   68,   65,   84,
+                      56,  -53,  -19,  -46,  -79,   17, -128,   32,
+                      12,    5,  -48,  120,   22, -106, -116,  -32,
+                      40,  -84,  101, -121,  -93,   57,   10,   35,
+                      88,   82,  112,  126,    3,  -60,  104,    6,
+                    -112,   70,  127,  -59,  -69,  -53,   29,   33,
+                    -127,  -24,   79,  -49,  -52,  -15,   41,   36,
+                      34, -105,   85,  124,  -14,   88,   27,    6,
+                      28,   68,    1,   82,   62,   22,  -95, -108,
+                      55,  -95,   40,   -9, -110,  -12,   98, -107,
+                      76,  -41, -105,  -62,  -50,  111,  -60,   46,
+                     -14,   -4,   24,  -89,   42, -103,   16,   63,
+                     -72,  -11,  -15,   48,  -62,  102,  -44,  102,
+                     -73,  -56,   56,  -21, -128,   92,  -70, -124,
+                     117,  -46,  -67,  -77,   82,   80,  121,  -44,
+                     -56,  116,   93,  -45,  -90,   -5,  -29,  -24,
+                     -83,  -75,   52,  -34,   55,  -22,  102,  -21,
+                    -105, -124,  -23,   71,   87,   -7,  -25,  -59,
+                    -100,  -73,  -92, -122,   -7, -109,  -49,  -80,
+                     -89,    0,    0,    0,    0,   73,   69,   78,
+                      68,  -82,   66,   96, -126
+            });
+            // proto.write(RestrictedImageProto.METADATA, flattened_protobuf);
+            proto.end(imageToken);
+        }
+
+        // Face service metadata
+        // proto.write(RestrictedImageSetProto.METADATA, flattened_protobuf);
+
+        proto.end(setToken);
+        proto.flush();
+    }
 }
diff --git a/services/core/java/com/android/server/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java
index 5c69c1d..9989c1a 100644
--- a/services/core/java/com/android/server/incident/IncidentCompanionService.java
+++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java
@@ -23,7 +23,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
 import android.os.IIncidentAuthListener;
 import android.os.IIncidentCompanion;
 import android.os.IIncidentManager;
@@ -49,6 +52,12 @@
     static final String TAG = "IncidentCompanionService";
 
     /**
+     * Dump argument for proxying restricted image dumps to the services
+     * listed in the config.
+     */
+    private static String[] RESTRICTED_IMAGE_DUMP_ARGS = new String[] { "--restricted_image" };
+
+    /**
      * The two permissions, for sendBroadcastAsUserMultiplePermissions.
      */
     private static final String[] DUMP_AND_USAGE_STATS_PERMISSIONS = new String[] {
@@ -260,7 +269,42 @@
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) {
                 return;
             }
-            mPendingReports.dump(fd, writer, args);
+
+            if (args.length == 1 && "--restricted_image".equals(args[0])) {
+                // Does NOT clearCallingIdentity
+                dumpRestrictedImages(fd);
+            } else {
+                // Regular dump
+                mPendingReports.dump(fd, writer, args);
+            }
+        }
+
+        /**
+         * Proxy for the restricted images section.
+         */
+        private void dumpRestrictedImages(FileDescriptor fd) {
+            // Only supported on eng or userdebug.
+            if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
+                return;
+            }
+
+            final Resources res = getContext().getResources();
+            final String[] services = res.getStringArray(
+                    com.android.internal.R.array.config_restrictedImagesServices);
+            final int servicesCount = services.length;
+            for (int i = 0; i < servicesCount; i++) {
+                final String name = services[i];
+                Log.d(TAG, "Looking up service " + name);
+                final IBinder service = ServiceManager.getService(name);
+                if (service != null) {
+                    Log.d(TAG, "Calling dump on service: " + name);
+                    try {
+                        service.dump(fd, RESTRICTED_IMAGE_DUMP_ARGS);
+                    } catch (RemoteException ex) {
+                        Log.w(TAG, "dump --restricted_image of " + name + " threw", ex);
+                    }
+                }
+            }
         }
 
         /**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 622c49e..5abc73e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1809,6 +1809,10 @@
     }
 
     // Native callback.
+    private void onPointerDownOutsideFocus(IBinder touchedToken) {
+    }
+
+    // Native callback.
     private int getVirtualKeyQuietTimeMillis() {
         return mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_virtualKeyQuietTimeMillis);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 54ec4f2..bfab85b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,7 +17,11 @@
 package com.android.server.notification;
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
+import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
@@ -844,7 +848,7 @@
                 }
             }
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
-                    Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE,
+                    FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE,
                     true, userId, REASON_CANCEL, nv.rank, nv.count,null);
             nv.recycle();
         }
@@ -2339,7 +2343,7 @@
             // Don't allow client applications to cancel foreground service notis or autobundled
             // summaries.
             final int mustNotHaveFlags = isCallingUidSystem() ? 0 :
-                    (FLAG_FOREGROUND_SERVICE | Notification.FLAG_AUTOGROUP_SUMMARY);
+                    (FLAG_FOREGROUND_SERVICE | FLAG_AUTOGROUP_SUMMARY);
             cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0,
                     mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null);
         }
@@ -3143,7 +3147,7 @@
         private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
                 int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
-                    Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE,
+                    FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE,
                     true,
                     userId, REASON_LISTENER_CANCEL, info);
         }
@@ -4234,7 +4238,7 @@
                                 .setGroupSummary(true)
                                 .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
                                 .setGroup(GroupHelper.AUTOGROUP_KEY)
-                                .setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true)
+                                .setFlag(FLAG_AUTOGROUP_SUMMARY, true)
                                 .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
                                 .setColor(adjustedSbn.getNotification().color)
                                 .setLocalOnly(true)
@@ -4763,9 +4767,9 @@
         boolean canBubble = mPreferencesHelper.areBubblesAllowed(pkg, userId)
                 && r.getChannel().canBubble();
         if (notification.getBubbleMetadata() != null && canBubble) {
-            notification.flags |= Notification.FLAG_BUBBLE;
+            notification.flags |= FLAG_BUBBLE;
         } else {
-            notification.flags &= ~Notification.FLAG_BUBBLE;
+            notification.flags &= ~FLAG_BUBBLE;
         }
     }
 
@@ -5228,8 +5232,8 @@
                     // Ensure if this is a foreground service that the proper additional
                     // flags are set.
                     if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
-                        notification.flags |= Notification.FLAG_ONGOING_EVENT
-                                | Notification.FLAG_NO_CLEAR;
+                        notification.flags |= FLAG_ONGOING_EVENT
+                                | FLAG_NO_CLEAR;
                     }
 
                     mRankingHelper.extractSignals(r);
@@ -6686,8 +6690,11 @@
                             null, userId, 0, 0, reason, listenerName);
 
                     FlagChecker flagChecker = (int flags) -> {
-                        if ((flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR))
-                                != 0) {
+                        int flagsToCheck = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
+                        if (REASON_LISTENER_CANCEL_ALL == reason) {
+                            flagsToCheck |= FLAG_BUBBLE;
+                        }
+                        if ((flags & flagsToCheck) != 0) {
                             return false;
                         }
                         return true;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index c52e29b..e4cb283 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -498,10 +498,11 @@
             }
         }
 
-        if (callingUid == Process.SYSTEM_UID) {
+        if (Build.IS_DEBUGGABLE || isDowngradeAllowedForCaller(callingUid)) {
             params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
         } else {
             params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
+            params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
         }
 
         boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
@@ -621,6 +622,11 @@
         return sessionId;
     }
 
+    private boolean isDowngradeAllowedForCaller(int callingUid) {
+        return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID
+                || callingUid == Process.SHELL_UID;
+    }
+
     @Override
     public void updateSessionAppIcon(int sessionId, Bitmap appIcon) {
         synchronized (mSessions) {
@@ -813,7 +819,7 @@
 
         final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
                 statusReceiver, versionedPackage.getPackageName(),
-                !canSilentlyInstallPackage, userId);
+                canSilentlyInstallPackage, userId);
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
                     == PackageManager.PERMISSION_GRANTED) {
             // Sweet, call straight through!
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 9348806..d8f07fe 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -166,8 +166,7 @@
      * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
      */
     public void startObservingHealth(List<String> packages, long durationMs) {
-        PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs,
-                false /* withExplicitHealthCheck */);
+        PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
     }
 
     /** Verifies the rollback state after a reboot. */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c2a4339..d52ba16 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2671,7 +2671,10 @@
                 wallpaper.connection.mReply = null;
             }
             try {
-                wallpaper.connection.mService.detach();
+                // It can be null if user switching happens before service connection.
+                if (wallpaper.connection.mService != null) {
+                    wallpaper.connection.mService.detach();
+                }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed detaching wallpaper service ", e);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a93bdba..91ec4a0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2730,30 +2730,40 @@
         final int appHeight = resolvedAppBounds.height();
         final int parentAppWidth = parentAppBounds.width();
         final int parentAppHeight = parentAppBounds.height();
+        if (parentAppWidth == appWidth && parentAppHeight == appHeight) {
+            // Matched the parent bounds.
+            return false;
+        }
+        if (parentAppWidth > appWidth && parentAppHeight > appHeight) {
+            // Both sides are smaller than the parent.
+            return true;
+        }
         if (parentAppWidth < appWidth || parentAppHeight < appHeight) {
             // One side is larger than the parent.
             return true;
         }
 
-        if (info.hasFixedAspectRatio()) {
+        // The rest of the condition is that only one side is smaller than the parent, but it still
+        // needs to exclude the cases where the size is limited by the fixed aspect ratio.
+        if (info.maxAspectRatio > 0) {
             final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
                     / Math.min(appWidth, appHeight);
-            final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight))
-                    / Math.min(parentAppWidth, parentAppHeight);
-            // Check if the parent still has available space in long side.
-            if (aspectRatio < parentAspectRatio
-                    && (aspectRatio < info.maxAspectRatio || info.minAspectRatio > 0)) {
-                return true;
+            if (aspectRatio >= info.maxAspectRatio) {
+                // The current size has reached the max aspect ratio.
+                return false;
             }
         }
-
-        final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
-        // If the width or height is the same as parent, it is already the best fit of the override
-        // bounds, therefore this condition is considered as not size compatibility mode. Here uses
-        // right and bottom as width and height of parent because the bounds may contain decor
-        // insets which has been accounted in override bounds. See {@link #computeBounds}.
-        return parentAppBounds.right != resolvedBounds.width()
-                && parentAppBounds.bottom != resolvedBounds.height();
+        if (info.minAspectRatio > 0) {
+            // The activity should have at least the min aspect ratio, so this checks if the parent
+            // still has available space to provide larger aspect ratio.
+            final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight))
+                    / Math.min(parentAppWidth, parentAppHeight);
+            if (parentAspectRatio <= info.minAspectRatio) {
+                // The long side has reached the parent.
+                return false;
+            }
+        }
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index 3bbe28d..90bb494 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -51,6 +51,7 @@
     private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1;
 
     protected final String mTag;
+    protected final int mDisplayId;
     private final int mTransientFlag;
     private final int mUnhideFlag;
     private final int mTranslucentFlag;
@@ -74,9 +75,10 @@
 
     private OnBarVisibilityChangedListener mVisibilityChangeListener;
 
-    BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag,
+    BarController(String tag, int displayId, int transientFlag, int unhideFlag, int translucentFlag,
             int statusBarManagerId, int translucentWmFlag, int transparentFlag) {
         mTag = "BarController." + tag;
+        mDisplayId = displayId;
         mTransientFlag = transientFlag;
         mUnhideFlag = unhideFlag;
         mTranslucentFlag = translucentFlag;
@@ -230,7 +232,7 @@
                 public void run() {
                     StatusBarManagerInternal statusbar = getStatusBarInternal();
                     if (statusbar != null) {
-                        statusbar.setWindowState(mWin.getDisplayId(), mStatusBarManagerId, state);
+                        statusbar.setWindowState(mDisplayId, mStatusBarManagerId, state);
                     }
                 }
             });
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index cb069fa..bbb857f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -26,6 +26,8 @@
 import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
 import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
 import static android.view.InsetsState.TYPE_TOP_BAR;
+import static android.view.InsetsState.TYPE_TOP_GESTURES;
+import static android.view.InsetsState.TYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
 import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
@@ -40,6 +42,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
@@ -152,6 +155,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ScreenShapeHelper;
 import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.function.TriConsumer;
 import com.android.internal.widget.PointerLocationView;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
@@ -215,6 +219,12 @@
     private final Object mServiceAcquireLock = new Object();
     private StatusBarManagerInternal mStatusBarManagerInternal;
 
+    @Px
+    private int mBottomGestureAdditionalInset;
+    @Px
+    private int mSideGestureInset;
+    private boolean mNavigationBarLetsThroughTaps;
+
     private StatusBarManagerInternal getStatusBarManagerInternal() {
         synchronized (mServiceAcquireLock) {
             if (mStatusBarManagerInternal == null) {
@@ -261,15 +271,9 @@
     /** Cached value of {@link ScreenShapeHelper#getWindowOutsetBottomPx} */
     @Px private int mWindowOutsetBottom;
 
-    private final StatusBarController mStatusBarController = new StatusBarController();
+    private final StatusBarController mStatusBarController;
 
-    private final BarController mNavigationBarController = new BarController("NavigationBar",
-            View.NAVIGATION_BAR_TRANSIENT,
-            View.NAVIGATION_BAR_UNHIDE,
-            View.NAVIGATION_BAR_TRANSLUCENT,
-            StatusBarManager.WINDOW_NAVIGATION_BAR,
-            FLAG_TRANSLUCENT_NAVIGATION,
-            View.NAVIGATION_BAR_TRANSPARENT);
+    private final BarController mNavigationBarController;
 
     private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
             new BarController.OnBarVisibilityChangedListener() {
@@ -416,6 +420,17 @@
         mDisplayContent = displayContent;
         mLock = service.getWindowManagerLock();
 
+        final int displayId = displayContent.getDisplayId();
+        mStatusBarController = new StatusBarController(displayId);
+        mNavigationBarController = new BarController("NavigationBar",
+                displayId,
+                View.NAVIGATION_BAR_TRANSIENT,
+                View.NAVIGATION_BAR_UNHIDE,
+                View.NAVIGATION_BAR_TRANSLUCENT,
+                StatusBarManager.WINDOW_NAVIGATION_BAR,
+                FLAG_TRANSLUCENT_NAVIGATION,
+                View.NAVIGATION_BAR_TRANSPARENT);
+
         final Resources r = mContext.getResources();
         mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
         mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer);
@@ -527,7 +542,6 @@
             if (mWindowSleepToken != null) {
                 return;
             }
-            final int displayId = displayContent.getDisplayId();
             mWindowSleepToken = service.mAtmInternal.acquireSleepToken(
                     "WindowSleepTokenOnDisplay" + displayId, displayId);
         };
@@ -857,11 +871,14 @@
                 if (mDisplayContent.isDefaultDisplay) {
                     mService.mPolicy.setKeyguardCandidateLw(win);
                 }
-                mDisplayContent.setInsetProvider(TYPE_TOP_BAR, win,
+                final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider =
                         (displayFrames, windowState, rect) -> {
                             rect.top = 0;
                             rect.bottom = getStatusBarHeight(displayFrames);
-                        });
+                        };
+                mDisplayContent.setInsetProvider(TYPE_TOP_BAR, win, frameProvider);
+                mDisplayContent.setInsetProvider(TYPE_TOP_GESTURES, win, frameProvider);
+                mDisplayContent.setInsetProvider(TYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider);
                 break;
             case TYPE_NAVIGATION_BAR:
                 mContext.enforceCallingOrSelfPermission(
@@ -878,6 +895,31 @@
                         mNavBarVisibilityListener, true);
                 mDisplayContent.setInsetProvider(InsetsState.TYPE_NAVIGATION_BAR,
                         win, null /* frameProvider */);
+                mDisplayContent.setInsetProvider(InsetsState.TYPE_BOTTOM_GESTURES, win,
+                        (displayFrames, windowState, inOutFrame) -> {
+                            inOutFrame.top -= mBottomGestureAdditionalInset;
+                        });
+                mDisplayContent.setInsetProvider(InsetsState.TYPE_LEFT_GESTURES, win,
+                        (displayFrames, windowState, inOutFrame) -> {
+                            inOutFrame.left = 0;
+                            inOutFrame.top = 0;
+                            inOutFrame.bottom = displayFrames.mDisplayHeight;
+                            inOutFrame.right = displayFrames.mUnrestricted.left + mSideGestureInset;
+                        });
+                mDisplayContent.setInsetProvider(InsetsState.TYPE_RIGHT_GESTURES, win,
+                        (displayFrames, windowState, inOutFrame) -> {
+                            inOutFrame.left = displayFrames.mUnrestricted.right - mSideGestureInset;
+                            inOutFrame.top = 0;
+                            inOutFrame.bottom = displayFrames.mDisplayHeight;
+                            inOutFrame.right = displayFrames.mDisplayWidth;
+                        });
+                mDisplayContent.setInsetProvider(InsetsState.TYPE_BOTTOM_TAPPABLE_ELEMENT, win,
+                        (displayFrames, windowState, inOutFrame) -> {
+                            if ((windowState.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0
+                                    || mNavigationBarLetsThroughTaps) {
+                                inOutFrame.setEmpty();
+                            }
+                        });
                 if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
                 break;
             case TYPE_NAVIGATION_BAR_PANEL:
@@ -2607,11 +2649,20 @@
         }
 
         mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
+        mSideGestureInset = res.getDimensionPixelSize(R.dimen.config_backGestureInset);
+        mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough);
 
         // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed
         mExperiments.onConfigurationChanged(uiContext);
         // EXPERIMENT END
 
+        // EXPERIMENT: TODO(b/113952590): Replace with real code after experiment.
+        // This should calculate how much above the frame we accept gestures. Currently,
+        // we extend the frame to capture the gestures, so this is 0.
+        mBottomGestureAdditionalInset = mExperiments.getNavigationBarFrameHeight()
+                - mExperiments.getNavigationBarFrameHeight();
+        // EXPERIMENT END
+
         updateConfigurationAndScreenSizeDependentBehaviors();
         mWindowOutsetBottom = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
     }
diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java
index 6db606d..f4260d3 100644
--- a/services/core/java/com/android/server/wm/StatusBarController.java
+++ b/services/core/java/com/android/server/wm/StatusBarController.java
@@ -36,22 +36,22 @@
 
         private Runnable mAppTransitionPending = () -> {
             StatusBarManagerInternal statusBar = getStatusBarInternal();
-            if (statusBar != null && mWin != null) {
-                statusBar.appTransitionPending(mWin.getDisplayId());
+            if (statusBar != null) {
+                statusBar.appTransitionPending(mDisplayId);
             }
         };
 
         private Runnable mAppTransitionCancelled = () -> {
             StatusBarManagerInternal statusBar = getStatusBarInternal();
-            if (statusBar != null && mWin != null) {
-                statusBar.appTransitionCancelled(mWin.getDisplayId());
+            if (statusBar != null) {
+                statusBar.appTransitionCancelled(mDisplayId);
             }
         };
 
         private Runnable mAppTransitionFinished = () -> {
             StatusBarManagerInternal statusBar = getStatusBarInternal();
-            if (statusBar != null && mWin != null) {
-                statusBar.appTransitionFinished(mWin.getDisplayId());
+            if (statusBar != null) {
+                statusBar.appTransitionFinished(mDisplayId);
             }
         };
 
@@ -65,8 +65,8 @@
                 long statusBarAnimationStartTime, long statusBarAnimationDuration) {
             mHandler.post(() -> {
                 StatusBarManagerInternal statusBar = getStatusBarInternal();
-                if (statusBar != null && mWin != null) {
-                    statusBar.appTransitionStarting(mWin.getDisplayId(),
+                if (statusBar != null) {
+                    statusBar.appTransitionStarting(mDisplayId,
                             statusBarAnimationStartTime, statusBarAnimationDuration);
                 }
             });
@@ -84,8 +84,9 @@
         }
     };
 
-    StatusBarController() {
+    StatusBarController(int displayId) {
         super("StatusBar",
+                displayId,
                 View.STATUS_BAR_TRANSIENT,
                 View.STATUS_BAR_UNHIDE,
                 View.STATUS_BAR_TRANSLUCENT,
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3d84bd4..3d6c868 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -96,6 +96,7 @@
     jmethodID interceptKeyBeforeDispatching;
     jmethodID dispatchUnhandledKey;
     jmethodID checkInjectEventsPermission;
+    jmethodID onPointerDownOutsideFocus;
     jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
     jmethodID getInputPortAssociations;
@@ -259,6 +260,7 @@
     virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
     virtual bool checkInjectEventsPermissionNonReentrant(
             int32_t injectorPid, int32_t injectorUid);
+    virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken);
 
     /* --- PointerControllerPolicyInterface implementation --- */
 
@@ -1205,6 +1207,15 @@
     return result;
 }
 
+void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
+    ATRACE_CALL();
+    JNIEnv* env = jniEnv();
+
+    jobject touchedTokenObj = javaObjectForIBinder(env, touchedToken);
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDownOutsideFocus, touchedTokenObj);
+    checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus");
+}
+
 void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
@@ -1809,6 +1820,9 @@
     GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
             "checkInjectEventsPermission", "(II)Z");
 
+    GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
+            "onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
+
     GET_METHOD_ID(gServiceClassInfo.getVirtualKeyQuietTimeMillis, clazz,
             "getVirtualKeyQuietTimeMillis", "()I");
 
diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
index 6a9eae0..e769769 100644
--- a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -60,6 +60,13 @@
     public final Inet4Address assignedV4Address;
     private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
 
+    // The lease expiry timestamp of v4 address allocated from DHCP server, in milliseconds.
+    @Nullable
+    public final Long assignedV4AddressExpiry;
+    // lease expiry doesn't imply any correlation between "the same lease expiry value" and "the
+    // same L3 network".
+    private static final float WEIGHT_ASSIGNEDV4ADDREXPIRY = 0.0f;
+
     // Optionally supplied by the client if it has an opinion on L3 network. For example, this
     // could be a hash of the SSID + security type on WiFi.
     @Nullable
@@ -81,6 +88,7 @@
     /** @hide */
     @VisibleForTesting
     public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
+            + WEIGHT_ASSIGNEDV4ADDREXPIRY
             + WEIGHT_GROUPHINT
             + WEIGHT_DNSADDRESSES
             + WEIGHT_MTU;
@@ -89,11 +97,16 @@
     @VisibleForTesting
     public NetworkAttributes(
             @Nullable final Inet4Address assignedV4Address,
+            @Nullable final Long assignedV4AddressExpiry,
             @Nullable final String groupHint,
             @Nullable final List<InetAddress> dnsAddresses,
             @Nullable final Integer mtu) {
         if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+        if (assignedV4AddressExpiry != null && assignedV4AddressExpiry <= 0) {
+            throw new IllegalArgumentException("lease expiry can't be negative or zero");
+        }
         this.assignedV4Address = assignedV4Address;
+        this.assignedV4AddressExpiry = assignedV4AddressExpiry;
         this.groupHint = groupHint;
         this.dnsAddresses = null == dnsAddresses ? null :
                 Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
@@ -105,6 +118,8 @@
         // The call to the other constructor must be the first statement of this constructor,
         // so everything has to be inline
         this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
+                parcelable.assignedV4AddressExpiry > 0
+                        ? parcelable.assignedV4AddressExpiry : null,
                 parcelable.groupHint,
                 blobArrayToInetAddressList(parcelable.dnsAddresses),
                 parcelable.mtu >= 0 ? parcelable.mtu : null);
@@ -150,6 +165,8 @@
         final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
         parcelable.assignedV4Address =
                 (null == assignedV4Address) ? null : assignedV4Address.getAddress();
+        parcelable.assignedV4AddressExpiry =
+                (null == assignedV4AddressExpiry) ? 0 : assignedV4AddressExpiry;
         parcelable.groupHint = groupHint;
         parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
         parcelable.mtu = (null == mtu) ? -1 : mtu;
@@ -168,6 +185,8 @@
     public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
         final float samenessScore =
                 samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
+                + samenessContribution(WEIGHT_ASSIGNEDV4ADDREXPIRY, assignedV4AddressExpiry,
+                      o.assignedV4AddressExpiry)
                 + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
                 + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
                 + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
@@ -189,6 +208,8 @@
         @Nullable
         private Inet4Address mAssignedAddress;
         @Nullable
+        private Long mAssignedAddressExpiry;
+        @Nullable
         private String mGroupHint;
         @Nullable
         private List<InetAddress> mDnsAddresses;
@@ -206,6 +227,20 @@
         }
 
         /**
+         * Set the lease expiry timestamp of assigned v4 address.
+         * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address.
+         * @return This builder.
+         */
+        public Builder setAssignedV4AddressExpiry(
+                @Nullable final Long assignedV4AddressExpiry) {
+            if (null != assignedV4AddressExpiry && assignedV4AddressExpiry <= 0) {
+                throw new IllegalArgumentException("lease expiry can't be negative or zero");
+            }
+            mAssignedAddressExpiry = assignedV4AddressExpiry;
+            return this;
+        }
+
+        /**
          * Set the group hint.
          * @param groupHint The group hint.
          * @return This builder.
@@ -248,14 +283,15 @@
          * @return The built NetworkAttributes object.
          */
         public NetworkAttributes build() {
-            return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu);
+            return new NetworkAttributes(mAssignedAddress, mAssignedAddressExpiry,
+                  mGroupHint, mDnsAddresses, mMtu);
         }
     }
 
     /** @hide */
     public boolean isEmpty() {
-        return (null == assignedV4Address) && (null == groupHint)
-                && (null == dnsAddresses) && (null == mtu);
+        return (null == assignedV4Address) && (null == assignedV4AddressExpiry)
+                && (null == groupHint) && (null == dnsAddresses) && (null == mtu);
     }
 
     @Override
@@ -263,6 +299,7 @@
         if (!(o instanceof NetworkAttributes)) return false;
         final NetworkAttributes other = (NetworkAttributes) o;
         return Objects.equals(assignedV4Address, other.assignedV4Address)
+                && Objects.equals(assignedV4AddressExpiry, other.assignedV4AddressExpiry)
                 && Objects.equals(groupHint, other.groupHint)
                 && Objects.equals(dnsAddresses, other.dnsAddresses)
                 && Objects.equals(mtu, other.mtu);
@@ -270,7 +307,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
+        return Objects.hash(assignedV4Address, assignedV4AddressExpiry,
+                groupHint, dnsAddresses, mtu);
     }
 
     /** Pretty print */
@@ -286,6 +324,13 @@
             nullFields.add("assignedV4Addr");
         }
 
+        if (null != assignedV4AddressExpiry) {
+            resultJoiner.add("assignedV4AddressExpiry :");
+            resultJoiner.add(assignedV4AddressExpiry.toString());
+        } else {
+            nullFields.add("assignedV4AddressExpiry");
+        }
+
         if (null != groupHint) {
             resultJoiner.add("groupHint :");
             resultJoiner.add(groupHint);
diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
index 0894d72..997eb2b 100644
--- a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
+++ b/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -30,6 +30,7 @@
  */
 parcelable NetworkAttributesParcelable {
     byte[] assignedV4Address;
+    long assignedV4AddressExpiry;
     String groupHint;
     Blob[] dnsAddresses;
     int mtu;
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 4332fea..6f1bd87 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
@@ -123,6 +124,9 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
 import com.android.internal.R;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -133,7 +137,6 @@
 import com.android.server.lights.LightsManager;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
 import com.android.server.notification.NotificationManagerService.NotificationListeners;
-import com.android.server.pm.UserManagerService;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -159,9 +162,6 @@
 import java.util.Set;
 import java.util.function.Consumer;
 
-import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -4424,6 +4424,77 @@
                 sbn.getKey()).getNotification().isBubbleNotification());
     }
 
+    @Test
+    public void testCancelAllNotifications_cancelsBubble() throws Exception {
+        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        nr.sbn.getNotification().flags |= FLAG_BUBBLE;
+        mService.addNotification(nr);
+
+        mBinderService.cancelAllNotifications(PKG, nr.sbn.getUserId());
+        waitForIdle();
+
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifs.length);
+        assertEquals(0, mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testAppCancelNotifications_cancelsBubbles() throws Exception {
+        final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
+        nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
+
+        // Post the notification
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
+        waitForIdle();
+
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+
+        mBinderService.cancelNotificationWithTag(PKG, null, nrBubble.sbn.getId(),
+                nrBubble.sbn.getUserId());
+        waitForIdle();
+
+        StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifs2.length);
+        assertEquals(0, mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception {
+        final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
+        final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
+        nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
+
+        mService.addNotification(nrNormal);
+        mService.addNotification(nrBubble);
+
+        mService.getBinderService().cancelNotificationsFromListener(null, null);
+        waitForIdle();
+
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testCancelNotificationsFromListener_ignoresBubbles() throws Exception {
+        final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
+        final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
+        nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
+
+        mService.addNotification(nrNormal);
+        mService.addNotification(nrBubble);
+
+        String[] keys = {nrNormal.sbn.getKey(), nrBubble.sbn.getKey()};
+        mService.getBinderService().cancelNotificationsFromListener(null, keys);
+        waitForIdle();
+
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
 
     public void testGetAllowedAssistantCapabilities() throws Exception {
         List<String> capabilities = mBinderService.getAllowedAssistantCapabilities(null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index d580557..32e96a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -438,8 +438,11 @@
         doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
                 .when(mActivity.mAppWindowToken).getOrientationIgnoreVisibility();
         mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-        mActivity.info.maxAspectRatio = 1;
+        mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
         ensureActivityConfiguration();
+        // The parent configuration doesn't change since the first resolved configuration, so the
+        // activity shouldn't be in the size compatibility mode.
+        assertFalse(mActivity.inSizeCompatMode());
 
         final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
         // Ensure the app bounds keep the declared aspect ratio.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 14eac87..a933da7 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2587,8 +2587,7 @@
      *              {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED}.
      * @param executor The executor of where the callback will execute.
      * @param callback Callback will be triggered once it succeeds or failed.
-     *                 See {@link TelephonyManager.SetOpportunisticSubscriptionResult}
-     *                 for more details. Pass null if don't care about the result.
+     *                 Pass null if don't care about the result.
      *
      * @hide
      *
@@ -2596,7 +2595,8 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void setPreferredDataSubscriptionId(int subId, boolean needValidation,
-            @Nullable @CallbackExecutor Executor executor, @Nullable  Consumer<Integer> callback) {
+            @Nullable @CallbackExecutor Executor executor, @Nullable
+            @TelephonyManager.SetOpportunisticSubscriptionResult Consumer<Integer> callback) {
         if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId);
         try {
             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index d0c2612..28af7ce 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -19,11 +19,16 @@
 import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
 import android.content.pm.VersionedPackage;
+import android.os.Handler;
+import android.os.RemoteException;
 import android.os.test.TestLooper;
+import android.util.AtomicFile;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -36,11 +41,14 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
-// TODO(zezeozue): Write test without using PackageWatchdog#getPackages. Just rely on
+// TODO: Write test without using PackageWatchdog#getPackages. Just rely on
 // behavior of observers receiving crash notifications or not to determine if it's registered
+// TODO: Use Truth in tests.
 /**
  * Test PackageWatchdog.
  */
@@ -77,12 +85,11 @@
         TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
 
         // Start observing for observer1 which will be unregistered
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false);
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
         // Start observing for observer2 which will expire
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION,
-                false);
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
         // Start observing for observer3 which will have expiry duration reduced
-        watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION, false);
+        watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION);
 
         // Verify packages observed at start
         // 1
@@ -145,9 +152,8 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false);
-        watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION,
-                false);
+        watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
 
         // Verify 2 observers are registered and saved internally
         // 1
@@ -193,8 +199,8 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false);
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false);
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A below the threshold
         for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) {
@@ -220,8 +226,8 @@
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
 
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false);
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION, false);
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
 
         // Then fail APP_C (not observed) above the threshold
         for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -255,7 +261,7 @@
                 }
             };
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION, false);
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A (different version) above the threshold
         for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -288,13 +294,13 @@
 
         // Start observing for all impact observers
         watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
-                SHORT_DURATION, false);
+                SHORT_DURATION);
         watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
-                SHORT_DURATION, false);
+                SHORT_DURATION);
         watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
-                SHORT_DURATION, false);
+                SHORT_DURATION);
         watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
-                SHORT_DURATION, false);
+                SHORT_DURATION);
 
         // Then fail all apps above the threshold
         for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -346,8 +352,8 @@
                 PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
 
         // Start observing for observerFirst and observerSecond with failure handling
-        watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION, false);
-        watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION, false);
+        watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
 
         // Then fail APP_A above the threshold
         for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -424,8 +430,8 @@
                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
 
         // Start observing for observer1 and observer2 with failure handling
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false);
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false);
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A above the threshold
         for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -442,11 +448,12 @@
     }
 
     /**
-     * Test explicit health check status determines package failure or success on expiry
+     * Test package passing explicit health checks does not fail and vice versa.
      */
     @Test
-    public void testPackageFailureExplicitHealthCheck() throws Exception {
-        PackageWatchdog watchdog = createWatchdog();
+    public void testExplicitHealthChecks() throws Exception {
+        TestController controller = new TestController();
+        PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
@@ -457,21 +464,36 @@
 
         // Start observing with explicit health checks for APP_A and APP_B respectively
         // with observer1 and observer2
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, true);
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION, true);
-        // Explicit health check passed for APP_A (observer1 is aware)
-        watchdog.onExplicitHealthCheckPassed(APP_A);
-        // Start observing APP_A with explicit health checks for observer3.
+        controller.setSupportedPackages(Arrays.asList(APP_A, APP_B));
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+
+        // Run handler so requests are dispatched to the controller
+        mTestLooper.dispatchAll();
+
+        // Verify we requested health checks for APP_A and APP_B
+        List<String> requestedPackages = controller.getRequestedPackages();
+        assertEquals(2, requestedPackages.size());
+        assertEquals(APP_A, requestedPackages.get(0));
+        assertEquals(APP_B, requestedPackages.get(1));
+
+        // Then health check passed for APP_A (observer1 is aware)
+        controller.setPackagePassed(APP_A);
+
+        // Then start observing APP_A with explicit health checks for observer3.
         // Observer3 didn't exist when we got the explicit health check above, so
         // it starts out with a non-passing explicit health check and has to wait for a pass
         // otherwise it would be notified of APP_A failure on expiry
-        watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION, true);
+        watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then expire observers
         Thread.sleep(SHORT_DURATION);
         // Run handler so package failures are dispatched to observers
         mTestLooper.dispatchAll();
 
+        // Verify we cancelled all requests on expiry
+        assertEquals(0, controller.getRequestedPackages().size());
+
         // Verify observer1 is not notified
         assertEquals(0, observer1.mFailedPackages.size());
 
@@ -484,9 +506,96 @@
         assertEquals(APP_A, observer3.mFailedPackages.get(0));
     }
 
+    /**
+     * Test explicit health check state can be disabled and enabled correctly.
+     */
+    @Test
+    public void testExplicitHealthCheckStateChanges() throws Exception {
+        TestController controller = new TestController();
+        PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1,
+                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+
+        // Start observing with explicit health checks for APP_A and APP_B
+        controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
+
+        // Run handler so requests are dispatched to the controller
+        mTestLooper.dispatchAll();
+
+        // Verify we requested health checks for APP_A and APP_B
+        List<String> requestedPackages = controller.getRequestedPackages();
+        assertEquals(2, requestedPackages.size());
+        assertEquals(APP_A, requestedPackages.get(0));
+        assertEquals(APP_B, requestedPackages.get(1));
+
+        // Disable explicit health checks (marks APP_A and APP_B as passed)
+        watchdog.setExplicitHealthCheckEnabled(false);
+
+        // Run handler so requests/cancellations are dispatched to the controller
+        mTestLooper.dispatchAll();
+
+        // Verify all checks are cancelled
+        assertEquals(0, controller.getRequestedPackages().size());
+
+        // Then expire APP_A
+        Thread.sleep(SHORT_DURATION);
+        mTestLooper.dispatchAll();
+
+        // Verify APP_A is not failed (APP_B) is not expired yet
+        assertEquals(0, observer.mFailedPackages.size());
+
+        // Re-enable explicit health checks
+        watchdog.setExplicitHealthCheckEnabled(true);
+
+        // Run handler so requests/cancellations are dispatched to the controller
+        mTestLooper.dispatchAll();
+
+        // Verify no requests are made cos APP_A is expired and APP_B was marked as passed
+        assertEquals(0, controller.getRequestedPackages().size());
+
+        // Then set new supported packages
+        controller.setSupportedPackages(Arrays.asList(APP_C));
+        // Start observing APP_A and APP_C; only APP_C has support for explicit health checks
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
+
+        // Run handler so requests/cancellations are dispatched to the controller
+        mTestLooper.dispatchAll();
+
+        // Verify requests are only made for APP_C
+        requestedPackages = controller.getRequestedPackages();
+        assertEquals(1, requestedPackages.size());
+        assertEquals(APP_C, requestedPackages.get(0));
+
+        // Then expire APP_A and APP_C
+        Thread.sleep(SHORT_DURATION);
+        mTestLooper.dispatchAll();
+
+        // Verify only APP_C is failed because explicit health checks was not supported for APP_A
+        assertEquals(1, observer.mFailedPackages.size());
+        assertEquals(APP_C, observer.mFailedPackages.get(0));
+    }
+
     private PackageWatchdog createWatchdog() {
-        return new PackageWatchdog(InstrumentationRegistry.getContext(),
-                mTestLooper.getLooper());
+        return createWatchdog(new TestController(), true /* withPackagesReady */);
+    }
+
+    private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) {
+        Context context = InstrumentationRegistry.getContext();
+        AtomicFile policyFile =
+                new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
+        Handler handler = new Handler(mTestLooper.getLooper());
+        PackageWatchdog watchdog =
+                new PackageWatchdog(context, policyFile, handler, handler, controller);
+        // Verify controller is not automatically started
+        assertFalse(controller.mIsEnabled);
+        if (withPackagesReady) {
+            watchdog.onPackagesReady();
+            // Verify controller by default is started when packages are ready
+            assertTrue(controller.mIsEnabled);
+        }
+        return watchdog;
     }
 
     private static class TestObserver implements PackageHealthObserver {
@@ -517,4 +626,69 @@
             return mName;
         }
     }
+
+    private static class TestController extends ExplicitHealthCheckController {
+        TestController() {
+            super(null /* controller */);
+        }
+
+        private boolean mIsEnabled;
+        private List<String> mSupportedPackages = new ArrayList<>();
+        private List<String> mRequestedPackages = new ArrayList<>();
+        private Runnable mStateChangedRunnable;
+        private Consumer<String> mPassedConsumer;
+
+        @Override
+        public void request(String packageName) throws RemoteException {
+            if (!mRequestedPackages.contains(packageName)) {
+                mRequestedPackages.add(packageName);
+            }
+        }
+
+        @Override
+        public void cancel(String packageName) throws RemoteException {
+            mRequestedPackages.remove(packageName);
+        }
+
+        @Override
+        public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
+            consumer.accept(mIsEnabled ? mSupportedPackages : Collections.emptyList());
+        }
+
+        @Override
+        public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
+            // Pass copy to prevent ConcurrentModificationException during test
+            consumer.accept(
+                    mIsEnabled ? new ArrayList<>(mRequestedPackages) : Collections.emptyList());
+        }
+
+        @Override
+        public void setEnabled(boolean enabled) {
+            mIsEnabled = enabled;
+            mStateChangedRunnable.run();
+        }
+
+        @Override
+        public void setCallbacks(Runnable stateChangedRunnable, Consumer<String> passedConsumer) {
+            mStateChangedRunnable = stateChangedRunnable;
+            mPassedConsumer = passedConsumer;
+        }
+
+        public void setSupportedPackages(List<String> packages) {
+            mSupportedPackages.clear();
+            mSupportedPackages.addAll(packages);
+        }
+
+        public void setPackagePassed(String packageName) {
+            mPassedConsumer.accept(packageName);
+        }
+
+        public List<String> getRequestedPackages() {
+            if (mIsEnabled) {
+                return mRequestedPackages;
+            } else {
+                return Collections.emptyList();
+            }
+        }
+    }
 }
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
index 76cccc9..1a3ea609 100644
--- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -44,6 +44,8 @@
         assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
 
         builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        // lease will expire in two hours
+        builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
         // groupHint stays null this time around
         builder.setDnsAddresses(Collections.emptyList());
         builder.setMtu(18);
@@ -51,6 +53,7 @@
         assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
 
         builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9"));
+        builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 3_600_000);
         builder.setGroupHint("groupHint");
         builder.setDnsAddresses(Arrays.asList(
                 InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"),
@@ -66,7 +69,7 @@
         // Verify that this test does not miss any new field added later.
         // If any field is added to NetworkAttributes it must be tested here for parceling
         // roundtrip.
-        assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+        assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
                 .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
     }
 
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
index dc20185..fb84611 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -57,6 +57,7 @@
         final NetworkAttributes na =
                 new NetworkAttributes(
                         (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}),
+                        System.currentTimeMillis() + 7_200_000,
                         "some hint",
                         Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}),
                                 Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})),
diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
index fff9635..e2668bc 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -296,13 +296,13 @@
     @Override
     public void registerComponentCallbacks(ComponentCallbacks callback) {
         if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
-        super.registerComponentCallbacks(callback);
+        getBaseContext().registerComponentCallbacks(callback);
     }
 
     @Override
     public void unregisterComponentCallbacks(ComponentCallbacks callback) {
         if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
-        super.unregisterComponentCallbacks(callback);
+        getBaseContext().unregisterComponentCallbacks(callback);
     }
 
     public TestablePermissions getTestablePermissions() {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 5336141..8577921 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -217,6 +217,29 @@
   EXPECT_THAT(str->value->spans[1].last_char, Eq(13u));
 }
 
+TEST_F(ResourceParserTest, ParseStringTranslatableAttribute) {
+  // If there is no translate attribute the default is 'true'
+  EXPECT_TRUE(TestParse(R"(<string name="foo1">Translate</string>)"));
+  String* str = test::GetValue<String>(&table_, "string/foo1");
+  ASSERT_THAT(str, NotNull());
+  ASSERT_TRUE(str->IsTranslatable());
+
+  // Explicit 'true' translate attribute
+  EXPECT_TRUE(TestParse(R"(<string name="foo2" translatable="true">Translate</string>)"));
+  str = test::GetValue<String>(&table_, "string/foo2");
+  ASSERT_THAT(str, NotNull());
+  ASSERT_TRUE(str->IsTranslatable());
+
+  // Explicit 'false' translate attribute
+  EXPECT_TRUE(TestParse(R"(<string name="foo3" translatable="false">Do not translate</string>)"));
+  str = test::GetValue<String>(&table_, "string/foo3");
+  ASSERT_THAT(str, NotNull());
+  ASSERT_FALSE(str->IsTranslatable());
+
+  // Invalid value for the translate attribute, should be boolean ('true' or 'false')
+  EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)"));
+}
+
 TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
   std::string input = R"(
       <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 2ec1ab3..9b81369f 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -143,6 +143,8 @@
                          const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
                          const std::string& output_path) {
   TRACE_CALL();
+  // Filenames starting with "donottranslate" are not localizable
+  bool translatable_file = path_data.name.find("donottranslate") != 0;
   ResourceTable table;
   {
     auto fin = file->OpenInputStream();
@@ -157,9 +159,7 @@
 
     ResourceParserOptions parser_options;
     parser_options.error_on_positional_arguments = !options.legacy_mode;
-
-    // If the filename includes donottranslate, then the default translatable is false.
-    parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos;
+    parser_options.translatable = translatable_file;
 
     // If visibility was forced, we need to use it when creating a new resource and also error if
     // we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
@@ -172,7 +172,7 @@
     }
   }
 
-  if (options.pseudolocalize) {
+  if (options.pseudolocalize && translatable_file) {
     // Generate pseudo-localized strings (en-XA and ar-XB).
     // These are created as weak symbols, and are only generated from default
     // configuration
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index c0c05cd..5f637bd 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -27,6 +27,8 @@
 
 namespace aapt {
 
+using CompilerTest = CommandTestFixture;
+
 std::string BuildPath(std::vector<std::string> args) {
   std::string out;
   if (args.empty()) {
@@ -51,7 +53,7 @@
   return CompileCommand(&diag).Execute(args, &std::cerr);
 }
 
-TEST(CompilerTest, MultiplePeriods) {
+TEST_F(CompilerTest, MultiplePeriods) {
   StdErrDiagnostics diag;
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()),
@@ -108,7 +110,7 @@
   ASSERT_EQ(::android::base::utf8::unlink(path5_out.c_str()), 0);
 }
 
-TEST(CompilerTest, DirInput) {
+TEST_F(CompilerTest, DirInput) {
   StdErrDiagnostics diag;
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()),
@@ -138,7 +140,7 @@
   ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0);
 }
 
-TEST(CompilerTest, ZipInput) {
+TEST_F(CompilerTest, ZipInput) {
   StdErrDiagnostics diag;
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   const std::string kResZip =
@@ -169,4 +171,86 @@
   ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0);
 }
 
-}  // namespace aapt
\ No newline at end of file
+/*
+ * This tests the "protection" from pseudo-translation of
+ * non-translatable files (starting with 'donotranslate')
+ * and strings (with the translatable="false" attribute)
+ *
+ * We check 4 string files, 2 translatable, and 2 not (based on file name)
+ * Each file contains 2 strings, one translatable, one not (attribute based)
+ * Each of these files are compiled and linked into one .apk, then we load the
+ * strings from the apk and check if there are pseudo-translated strings.
+ */
+
+// Using 000 and 111 because they are not changed by pseudo-translation,
+// making our life easier.
+constexpr static const char sTranslatableXmlContent[] =
+    "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+    "<resources>"
+    "  <string name=\"normal\">000</string>"
+    "  <string name=\"non_translatable\" translatable=\"false\">111</string>"
+    "</resources>";
+
+static void AssertTranslations(CommandTestFixture *ctf, std::string file_name,
+    std::vector<std::string> expected) {
+
+  StdErrDiagnostics diag;
+
+  const std::string source_file = ctf->GetTestPath("/res/values/" + file_name + ".xml");
+  const std::string compiled_files_dir = ctf->GetTestPath("/compiled_" + file_name);
+  const std::string out_apk = ctf->GetTestPath("/" + file_name + ".apk");
+
+  CHECK(ctf->WriteFile(source_file, sTranslatableXmlContent));
+  CHECK(file::mkdirs(compiled_files_dir.data()));
+
+  ASSERT_EQ(CompileCommand(&diag).Execute({
+      source_file,
+      "-o", compiled_files_dir,
+      "-v",
+      "--pseudo-localize"
+  }, &std::cerr), 0);
+
+  ASSERT_TRUE(ctf->Link({
+      "--manifest", ctf->GetDefaultManifest(),
+      "-o", out_apk
+  }, compiled_files_dir, &diag));
+
+  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+
+  ResourceTable* table = apk->GetResourceTable();
+  ASSERT_NE(table, nullptr);
+  table->string_pool.Sort();
+
+  const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings =
+      table->string_pool.strings();
+
+  // The actual / expected vectors have the same size
+  const size_t pool_size = pool_strings.size();
+  ASSERT_EQ(pool_size, expected.size());
+
+  for (size_t i = 0; i < pool_size; i++) {
+    std::string actual = pool_strings[i]->value;
+    ASSERT_EQ(actual, expected[i]);
+  }
+}
+
+TEST_F(CompilerTest, DoNotTranslateTest) {
+  // The first string (000) is translatable, the second is not
+  // ar-XB uses "\u200F\u202E...\u202C\u200F"
+  std::vector<std::string> expected_translatable = {
+      "000", "111", // default locale
+      "[000 one]", // en-XA
+      "\xE2\x80\x8F\xE2\x80\xAE" "000" "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB
+  };
+  AssertTranslations(this, "foo", expected_translatable);
+  AssertTranslations(this, "foo_donottranslate", expected_translatable);
+
+  // No translatable strings because these are non-translatable files
+  std::vector<std::string> expected_not_translatable = {
+      "000", "111", // default locale
+  };
+  AssertTranslations(this, "donottranslate", expected_not_translatable);
+  AssertTranslations(this, "donottranslate_foo", expected_not_translatable);
+}
+
+}  // namespace aapt