Merge changes I44ba0f9a,I70afa777 into sc-dev

* changes:
  Fix the live caption tooltip, which was sad and broken.
  Clip caption container to outline so the ripple doesn't extend past the circular bounds.
diff --git a/boot/Android.bp b/boot/Android.bp
index 3caede4..e8d88a5 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -48,13 +48,57 @@
     // bootclasspath.
     fragments: [
         {
+            apex: "com.android.appsearch",
+            module: "com.android.appsearch-bootclasspath-fragment",
+        },
+        {
             apex: "com.android.art",
             module: "art-bootclasspath-fragment",
         },
         {
+            apex: "com.android.conscrypt",
+            module: "com.android.conscrypt-bootclasspath-fragment",
+        },
+        {
             apex: "com.android.i18n",
             module: "i18n-bootclasspath-fragment",
         },
+        {
+            apex: "com.android.ipsec",
+            module: "com.android.ipsec-bootclasspath-fragment",
+        },
+        {
+            apex: "com.android.media",
+            module: "com.android.media-bootclasspath-fragment",
+        },
+        {
+            apex: "com.android.mediaprovider",
+            module: "com.android.mediaprovider-bootclasspath-fragment",
+        },
+        {
+            apex: "com.android.os.statsd",
+            module: "com.android.os.statsd-bootclasspath-fragment",
+        },
+        {
+            apex: "com.android.permission",
+            module: "com.android.permission-bootclasspath-fragment",
+        },
+        {
+            apex: "com.android.scheduling",
+            module: "com.android.scheduling-bootclasspath-fragment",
+        },
+        {
+            apex: "com.android.sdkext",
+            module: "com.android.sdkext-bootclasspath-fragment",
+        },
+        {
+            apex: "com.android.tethering",
+            module: "com.android.tethering-bootclasspath-fragment",
+        },
+        {
+            apex: "com.android.wifi",
+            module: "com.android.wifi-bootclasspath-fragment",
+        },
     ],
 
     // Additional information needed by hidden api processing.
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index f0d410f..881e0cf 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -481,6 +481,7 @@
 
     boolean mFullscreenApplied;
     boolean mIsFullscreen;
+    private boolean mLastWasInFullscreenMode;
     @UnsupportedAppUsage
     View mExtractView;
     boolean mExtractViewHidden;
@@ -920,8 +921,17 @@
         if (mHandler == null) {
             mHandler = new Handler(getMainLooper());
         }
-        mImeSurfaceScheduledForRemoval = true;
-        mHandler.postDelayed(() -> removeImeSurface(), TIMEOUT_SURFACE_REMOVAL_MILLIS);
+
+        if (mLastWasInFullscreenMode) {
+            // Caching surface / delaying surface removal can cause mServedView to detach in certain
+            // cases in RecyclerView (b/187772544).
+            // TODO(b/188818557): Re-enable IME surface caching for fullscreen mode once detaching
+            //  view issues is resolved in RecyclerView.
+            removeImeSurface();
+        } else {
+            mImeSurfaceScheduledForRemoval = true;
+            mHandler.postDelayed(() -> removeImeSurface(), TIMEOUT_SURFACE_REMOVAL_MILLIS);
+        }
     }
 
     private void removeImeSurface() {
@@ -2350,6 +2360,7 @@
             onWindowHidden();
             mDecorViewWasVisible = false;
         }
+        mLastWasInFullscreenMode = mIsFullscreen;
         updateFullscreenMode();
     }
 
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 370052d..f2857ce 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -368,27 +368,19 @@
     };
 
     /** Returns a proto (as used for atoms.proto) corresponding to this BatteryUsageStats. */
-    public byte[] getStatsProto(long sessionEndTimestampMs) {
-
-        final long sessionStartMillis = getStatsStartTimestamp();
-        // TODO(b/187223764): Use the getStatsEndTimestamp() instead, once that is added.
-        final long sessionEndMillis = sessionEndTimestampMs;
-        final long sessionDurationMillis = sessionEndTimestampMs - getStatsStartTimestamp();
-
+    public byte[] getStatsProto() {
         final BatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
                 AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
 
-        final int sessionDischargePercentage = getDischargePercentage();
-
         final ProtoOutputStream proto = new ProtoOutputStream();
-        proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, sessionStartMillis);
-        proto.write(BatteryUsageStatsAtomsProto.SESSION_END_MILLIS, sessionEndMillis);
-        proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, sessionDurationMillis);
+        proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, getStatsStartTimestamp());
+        proto.write(BatteryUsageStatsAtomsProto.SESSION_END_MILLIS, getStatsEndTimestamp());
+        proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, getStatsDuration());
         deviceBatteryConsumer.writeStatsProto(proto,
                 BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
         writeUidBatteryConsumersProto(proto);
         proto.write(BatteryUsageStatsAtomsProto.SESSION_DISCHARGE_PERCENTAGE,
-                sessionDischargePercentage);
+                getDischargePercentage());
         return proto.getBytes();
     }
 
@@ -399,8 +391,8 @@
     private void writeUidBatteryConsumersProto(ProtoOutputStream proto) {
         final List<UidBatteryConsumer> consumers = getUidBatteryConsumers();
 
-        // TODO: Sort the list by power consumption. If during the for, proto.getRawSize() > 45kb,
-        //       truncate the remainder of the list.
+        // TODO(b/189225426): Sort the list by power consumption. If during the for,
+        //                    proto.getRawSize() > 45kb, truncate the remainder of the list.
         final int size = consumers.size();
         for (int i = 0; i < size; i++) {
             final UidBatteryConsumer consumer = consumers.get(i);
diff --git a/core/java/android/os/HidlMemory.java b/core/java/android/os/HidlMemory.java
index 26fc6f0..2539a6b 100644
--- a/core/java/android/os/HidlMemory.java
+++ b/core/java/android/os/HidlMemory.java
@@ -79,6 +79,7 @@
     public void close() throws IOException {
         if (mHandle != null) {
             mHandle.close();
+            mHandle = null;
         }
     }
 
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 01cece3..c82a516 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -94,6 +94,8 @@
      * VibrationEffect VibrationEffects} to be played on one or more vibrators.
      * </p>
      *
+     * <p>The app should be in foreground for the vibration to happen.</p>
+     *
      * @param effect a combination of effects to be performed by one or more vibrators.
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE)
@@ -109,6 +111,9 @@
      * VibrationEffect} to be played on one or more vibrators.
      * </p>
      *
+     * <p>The app should be in foreground for the vibration to happen. Background apps should
+     * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+     *
      * @param effect a combination of effects to be performed by one or more vibrators.
      * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
      *                   specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index bee0a0b..333eebb 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -48,9 +48,8 @@
 
     @Test
     public void testGetStatsProto() {
-        final long sessionEndTimestampMs = 1050;
         final BatteryUsageStats bus = buildBatteryUsageStats();
-        final byte[] bytes = bus.getStatsProto(sessionEndTimestampMs);
+        final byte[] bytes = bus.getStatsProto();
         BatteryUsageStatsAtomsProto proto;
         try {
             proto = BatteryUsageStatsAtomsProto.parseFrom(bytes);
@@ -60,9 +59,9 @@
         }
 
         assertEquals(bus.getStatsStartTimestamp(), proto.sessionStartMillis);
-        assertEquals(sessionEndTimestampMs, proto.sessionEndMillis);
+        assertEquals(bus.getStatsEndTimestamp(), proto.sessionEndMillis);
         assertEquals(
-                sessionEndTimestampMs - bus.getStatsStartTimestamp(),
+                bus.getStatsEndTimestamp() - bus.getStatsStartTimestamp(),
                 proto.sessionDurationMillis);
         assertEquals(bus.getDischargePercentage(), proto.sessionDischargePercentage);
 
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 87acfd0..52132e8 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -22,6 +22,7 @@
     android:layout_height="wrap_content"
     android:clipChildren="false"
     android:clipToPadding="false"
+    android:forceHasOverlappingRendering="false"
     android:theme="@style/MediaPlayer">
     <com.android.systemui.media.MediaScrollView
         android:id="@+id/media_carousel_scroller"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index c16f13e..09d4685 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -129,7 +129,21 @@
             android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
             android:text="@string/report_rejected_touch"
             android:visibility="gone" />
-
+        <com.android.systemui.statusbar.phone.TapAgainView
+            android:id="@+id/shade_falsing_tap_again"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            systemui:layout_constraintLeft_toLeftOf="parent"
+            systemui:layout_constraintRight_toRightOf="parent"
+            systemui:layout_constraintBottom_toBottomOf="parent"
+            android:layout_marginBottom="20dp"
+            android:paddingLeft="20dp"
+            android:paddingRight="20dp"
+            android:paddingTop="10dp"
+            android:paddingBottom="10dp"
+            android:elevation="10dp"
+            android:visibility="gone"
+        />
     </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
 
     <FrameLayout
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b2ab5f7..5c1e935 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1418,10 +1418,6 @@
     <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
     <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
 
-    <!-- Delay after which the media will start transitioning to the full shade on
-         the lockscreen -->
-    <dimen name="lockscreen_shade_media_transition_start_delay">40dp</dimen>
-
     <!-- Distance that the full shade transition takes in order for qs to fully transition to the
          shade -->
     <dimen name="lockscreen_shade_qs_transition_distance">200dp</dimen>
@@ -1430,13 +1426,16 @@
          the shade (in alpha) -->
     <dimen name="lockscreen_shade_scrim_transition_distance">80dp</dimen>
 
-    <!-- Extra inset for the notifications when accounting for media during the lockscreen to
-         shade transition to compensate for the disappearing media -->
-    <dimen name="lockscreen_shade_transition_extra_media_inset">-48dp</dimen>
+    <!-- Distance that the full shade transition takes in order for media to fully transition to
+         the shade -->
+    <dimen name="lockscreen_shade_media_transition_distance">140dp</dimen>
 
     <!-- Maximum overshoot for the topPadding of notifications when transitioning to the full
          shade -->
-    <dimen name="lockscreen_shade_max_top_overshoot">32dp</dimen>
+    <dimen name="lockscreen_shade_notification_movement">24dp</dimen>
+
+    <!-- Maximum overshoot for the pulse expansion -->
+    <dimen name="pulse_expansion_max_top_overshoot">16dp</dimen>
 
     <dimen name="people_space_widget_radius">28dp</dimen>
     <dimen name="people_space_image_radius">20dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
index ee69e27..dba530e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -40,6 +40,7 @@
     private boolean mIsFalseRobustTap;
 
     private final List<FalsingBeliefListener> mFalsingBeliefListeners = new ArrayList<>();
+    private final List<FalsingTapListener> mTapListeners = new ArrayList<>();
 
     @Override
     public void onSuccessfulUnlock() {
@@ -148,11 +149,15 @@
 
     @Override
     public void addTapListener(FalsingTapListener falsingTapListener) {
-
+        mTapListeners.add(falsingTapListener);
     }
 
     @Override
     public void removeTapListener(FalsingTapListener falsingTapListener) {
+        mTapListeners.remove(falsingTapListener);
+    }
 
+    public List<FalsingTapListener> getTapListeners() {
+        return mTapListeners;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 73dfe5e..075bc70 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -26,21 +26,23 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
 import javax.inject.Inject
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 
 /**
  * Similarly to isShown but also excludes views that have 0 alpha
@@ -80,6 +82,7 @@
     wakefulnessLifecycle: WakefulnessLifecycle,
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
 ) {
+
     /**
      * The root overlay of the hierarchy. This is where the media notification is attached to
      * whenever the view is transitioning from one host to another. It also make sure that the
@@ -90,6 +93,30 @@
     private var rootView: View? = null
     private var currentBounds = Rect()
     private var animationStartBounds: Rect = Rect()
+
+    /**
+     * The cross fade progress at the start of the animation. 0.5f means it's just switching between
+     * the start and the end location and the content is fully faded, while 0.75f means that we're
+     * halfway faded in again in the target state.
+     */
+    private var animationStartCrossFadeProgress = 0.0f
+
+    /**
+     * The starting alpha of the animation
+     */
+    private var animationStartAlpha = 0.0f
+
+    /**
+     * The starting location of the cross fade if an animation is running right now.
+     */
+    @MediaLocation
+    private var crossFadeAnimationStartLocation = -1
+
+    /**
+     * The end location of the cross fade if an animation is running right now.
+     */
+    @MediaLocation
+    private var crossFadeAnimationEndLocation = -1
     private var targetBounds: Rect = Rect()
     private val mediaFrame
         get() = mediaCarouselController.mediaFrame
@@ -98,9 +125,22 @@
         interpolator = Interpolators.FAST_OUT_SLOW_IN
         addUpdateListener {
             updateTargetState()
-            interpolateBounds(animationStartBounds, targetBounds, animatedFraction,
+            val currentAlpha: Float
+            var boundsProgress = animatedFraction
+            if (isCrossFadeAnimatorRunning) {
+                animationCrossFadeProgress = MathUtils.lerp(animationStartCrossFadeProgress, 1.0f,
+                    animatedFraction)
+                // When crossfading, let's keep the bounds at the right location during fading
+                boundsProgress = if (animationCrossFadeProgress < 0.5f) 0.0f else 1.0f
+                currentAlpha = calculateAlphaFromCrossFade(animationCrossFadeProgress,
+                    instantlyShowAtEnd = false)
+            } else {
+                // If we're not crossfading, let's interpolate from the start alpha to 1.0f
+                currentAlpha = MathUtils.lerp(animationStartAlpha, 1.0f, animatedFraction)
+            }
+            interpolateBounds(animationStartBounds, targetBounds, boundsProgress,
                     result = currentBounds)
-            applyState(currentBounds)
+            applyState(currentBounds, currentAlpha)
         }
         addListener(object : AnimatorListenerAdapter() {
             private var cancelled: Boolean = false
@@ -112,6 +152,7 @@
             }
 
             override fun onAnimationEnd(animation: Animator?) {
+                isCrossFadeAnimatorRunning = false
                 if (!cancelled) {
                     applyTargetStateIfNotAnimating()
                 }
@@ -192,11 +233,6 @@
     private var distanceForFullShadeTransition = 0
 
     /**
-     * Delay after which the media will start transitioning to the full shade on the lockscreen.
-     */
-    private var fullShadeTransitionDelay = 0
-
-    /**
      * The amount of progress we are currently in if we're transitioning to the full shade.
      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
      * shade.
@@ -207,18 +243,33 @@
                 return
             }
             field = value
-            if (bypassController.bypassEnabled) {
+            if (bypassController.bypassEnabled || statusbarState != StatusBarState.KEYGUARD) {
+                // No need to do all the calculations / updates below if we're not on the lockscreen
+                // or if we're bypassing.
                 return
             }
-            updateDesiredLocation()
+            updateDesiredLocation(forceNoAnimation = isCurrentlyFading())
             if (value >= 0) {
                 updateTargetState()
+                // Setting the alpha directly, as the below call will use it to update the alpha
+                carouselAlpha = calculateAlphaFromCrossFade(field, instantlyShowAtEnd = true)
                 applyTargetStateIfNotAnimating()
             }
         }
 
+    /**
+     * Is there currently a cross-fade animation running driven by an animator?
+     */
+    private var isCrossFadeAnimatorRunning = false
+
+    /**
+     * Are we currently transitionioning from the lockscreen to the full shade
+     * [StatusBarState.SHADE_LOCKED] or [StatusBarState.SHADE]. Once the user has dragged down and
+     * the transition starts, this will no longer return true.
+     */
     private val isTransitioningToFullShade: Boolean
-        get() = fullShadeTransitionProgress != 0f && !bypassController.bypassEnabled
+        get() = fullShadeTransitionProgress != 0f && !bypassController.bypassEnabled &&
+            statusbarState == StatusBarState.KEYGUARD
 
     /**
      * Set the amount of pixels we have currently dragged down if we're transitioning to the full
@@ -227,14 +278,8 @@
     fun setTransitionToFullShadeAmount(value: Float) {
         // If we're transitioning starting on the shade_locked, we don't want any delay and rather
         // have it aligned with the rest of the animation
-        val delay = if (statusbarState == StatusBarState.KEYGUARD) {
-            fullShadeTransitionDelay
-        } else {
-            0
-        }
-        val progress = MathUtils.saturate((value - delay) /
-                (distanceForFullShadeTransition - delay))
-        fullShadeTransitionProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(progress)
+        val progress = MathUtils.saturate(value / distanceForFullShadeTransition)
+        fullShadeTransitionProgress = progress
     }
 
     /**
@@ -296,6 +341,49 @@
             }
         }
 
+    /**
+     * The current cross fade progress. 0.5f means it's just switching
+     * between the start and the end location and the content is fully faded, while 0.75f means
+     * that we're halfway faded in again in the target state.
+     * This is only valid while [isCrossFadeAnimatorRunning] is true.
+     */
+    private var animationCrossFadeProgress = 1.0f
+
+    /**
+     * The current carousel Alpha.
+     */
+    private var carouselAlpha: Float = 1.0f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            CrossFadeHelper.fadeIn(mediaFrame, value)
+        }
+
+    /**
+     * Calculate the alpha of the view when given a cross-fade progress.
+     *
+     * @param crossFadeProgress The current cross fade progress. 0.5f means it's just switching
+     * between the start and the end location and the content is fully faded, while 0.75f means
+     * that we're halfway faded in again in the target state.
+     *
+     * @param instantlyShowAtEnd should the view be instantly shown at the end. This is needed
+     * to avoid fadinging in when the target was hidden anyway.
+     */
+    private fun calculateAlphaFromCrossFade(
+        crossFadeProgress: Float,
+        instantlyShowAtEnd: Boolean
+    ): Float {
+        if (crossFadeProgress <= 0.5f) {
+            return 1.0f - crossFadeProgress / 0.5f
+        } else if (instantlyShowAtEnd) {
+            return 1.0f
+        } else {
+            return (crossFadeProgress - 0.5f) / 0.5f
+        }
+    }
+
     init {
         updateConfiguration()
         configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
@@ -375,9 +463,7 @@
 
     private fun updateConfiguration() {
         distanceForFullShadeTransition = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_qs_transition_distance)
-        fullShadeTransitionDelay = context.resources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_media_transition_start_delay)
+                R.dimen.lockscreen_shade_media_transition_distance)
     }
 
     /**
@@ -449,8 +535,13 @@
                     shouldAnimateTransition(desiredLocation, previousLocation)
             val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
             val host = getHost(desiredLocation)
-            mediaCarouselController.onDesiredLocationChanged(desiredLocation, host, animate,
-                    animDuration, delay)
+            val willFade = calculateTransformationType() == TRANSFORMATION_TYPE_FADE
+            if (!willFade || isCurrentlyInGuidedTransformation() || !animate) {
+                // if we're fading, we want the desired location / measurement only to change
+                // once fully faded. This is happening in the host attachment
+                mediaCarouselController.onDesiredLocationChanged(desiredLocation, host,
+                    animate, animDuration, delay)
+            }
             performTransitionToNewLocation(isNewView, animate)
         }
     }
@@ -470,6 +561,8 @@
         if (isCurrentlyInGuidedTransformation()) {
             applyTargetStateIfNotAnimating()
         } else if (animate) {
+            val wasCrossFading = isCrossFadeAnimatorRunning
+            val previewsCrossFadeProgress = animationCrossFadeProgress
             animator.cancel()
             if (currentAttachmentLocation != previousLocation ||
                     !previousHost.hostView.isAttachedToWindow) {
@@ -482,6 +575,42 @@
                 // be outdated
                 animationStartBounds.set(previousHost.currentBounds)
             }
+            val transformationType = calculateTransformationType()
+            var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE
+            var crossFadeStartProgress = 0.0f
+            // The alpha is only relevant when not cross fading
+            var newCrossFadeStartLocation = previousLocation
+            if (wasCrossFading) {
+                if (currentAttachmentLocation == crossFadeAnimationEndLocation) {
+                    if (needsCrossFade) {
+                        // We were previously crossFading and we've already reached
+                        // the end view, Let's start crossfading from the same position there
+                        crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
+                    }
+                    // Otherwise let's fade in from the current alpha, but not cross fade
+                } else {
+                    // We haven't reached the previous location yet, let's still cross fade from
+                    // where we were.
+                    newCrossFadeStartLocation = crossFadeAnimationStartLocation
+                    if (newCrossFadeStartLocation == desiredLocation) {
+                        // we're crossFading back to where we were, let's start at the end position
+                        crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
+                    } else {
+                        // Let's start from where we are right now
+                        crossFadeStartProgress = previewsCrossFadeProgress
+                        // We need to force cross fading as we haven't reached the end location yet
+                        needsCrossFade = true
+                    }
+                }
+            } else if (needsCrossFade) {
+                // let's not flicker and start with the same alpha
+                crossFadeStartProgress = (1.0f - carouselAlpha) / 2.0f
+            }
+            isCrossFadeAnimatorRunning = needsCrossFade
+            crossFadeAnimationStartLocation = newCrossFadeStartLocation
+            crossFadeAnimationEndLocation = desiredLocation
+            animationStartAlpha = carouselAlpha
+            animationStartCrossFadeProgress = crossFadeStartProgress
             adjustAnimatorForTransition(desiredLocation, previousLocation)
             if (!animationPending) {
                 rootView?.let {
@@ -518,6 +647,17 @@
             // non-trivial reattaching logic happening that will make the view not-shown earlier
             return true
         }
+
+        if (statusbarState == StatusBarState.KEYGUARD) {
+            if (currentLocation == LOCATION_LOCKSCREEN &&
+                previousLocation == LOCATION_QS ||
+                (currentLocation == LOCATION_QS &&
+                    previousLocation == LOCATION_LOCKSCREEN)) {
+                // We're always fading from lockscreen to keyguard in situations where the player
+                // is already fully hidden
+                return false
+            }
+        }
         return mediaFrame.isShownNotFaded || animator.isRunning || animationPending
     }
 
@@ -538,7 +678,7 @@
                     keyguardStateController.isKeyguardFadingAway) {
                 delay = keyguardStateController.keyguardFadingAwayDelay
             }
-            animDuration = StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE.toLong()
+            animDuration = (StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE / 2f).toLong()
         } else if (previousLocation == LOCATION_QQS && desiredLocation == LOCATION_LOCKSCREEN) {
             animDuration = StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR.toLong()
         }
@@ -550,7 +690,7 @@
             // Let's immediately apply the target state (which is interpolated) if there is
             // no animation running. Otherwise the animation update will already update
             // the location
-            applyState(targetBounds)
+            applyState(targetBounds, carouselAlpha)
         }
     }
 
@@ -558,7 +698,7 @@
      * Updates the bounds that the view wants to be in at the end of the animation.
      */
     private fun updateTargetState() {
-        if (isCurrentlyInGuidedTransformation()) {
+        if (isCurrentlyInGuidedTransformation() && !isCurrentlyFading()) {
             val progress = getTransformationProgress()
             var endHost = getHost(desiredLocation)!!
             var starthost = getHost(previousLocation)!!
@@ -606,12 +746,33 @@
     }
 
     /**
+     * Calculate the transformation type for the current animation
+     */
+    @VisibleForTesting
+    @TransformationType
+    fun calculateTransformationType(): Int {
+        if (isTransitioningToFullShade) {
+            return TRANSFORMATION_TYPE_FADE
+        }
+        if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS ||
+            previousLocation == LOCATION_QS && desiredLocation == LOCATION_LOCKSCREEN) {
+            // animating between ls and qs should fade, as QS is clipped.
+            return TRANSFORMATION_TYPE_FADE
+        }
+        if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
+            // animating between ls and qqs should fade when dragging down via e.g. expand button
+            return TRANSFORMATION_TYPE_FADE
+        }
+        return TRANSFORMATION_TYPE_TRANSITION
+    }
+
+    /**
      * @return the current transformation progress if we're in a guided transformation and -1
      * otherwise
      */
     private fun getTransformationProgress(): Float {
         val progress = getQSTransformationProgress()
-        if (progress >= 0) {
+        if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) {
             return progress
         }
         if (isTransitioningToFullShade) {
@@ -643,19 +804,20 @@
     private fun cancelAnimationAndApplyDesiredState() {
         animator.cancel()
         getHost(desiredLocation)?.let {
-            applyState(it.currentBounds, immediately = true)
+            applyState(it.currentBounds, alpha = 1.0f, immediately = true)
         }
     }
 
     /**
      * Apply the current state to the view, updating it's bounds and desired state
      */
-    private fun applyState(bounds: Rect, immediately: Boolean = false) {
+    private fun applyState(bounds: Rect, alpha: Float, immediately: Boolean = false) {
         currentBounds.set(bounds)
-        val currentlyInGuidedTransformation = isCurrentlyInGuidedTransformation()
-        val startLocation = if (currentlyInGuidedTransformation) previousLocation else -1
-        val progress = if (currentlyInGuidedTransformation) getTransformationProgress() else 1.0f
-        val endLocation = desiredLocation
+        carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f
+        val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading()
+        val startLocation = if (onlyUseEndState) -1 else previousLocation
+        val progress = if (onlyUseEndState) 1.0f else getTransformationProgress()
+        val endLocation = resolveLocationForFading()
         mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately)
         updateHostAttachment()
         if (currentAttachmentLocation == IN_OVERLAY) {
@@ -668,8 +830,19 @@
     }
 
     private fun updateHostAttachment() {
-        val inOverlay = isTransitionRunning() && rootOverlay != null
-        val newLocation = if (inOverlay) IN_OVERLAY else desiredLocation
+        var newLocation = resolveLocationForFading()
+        var canUseOverlay = !isCurrentlyFading()
+        if (isCrossFadeAnimatorRunning) {
+            if (getHost(newLocation)?.visible == true &&
+                getHost(newLocation)?.hostView?.isShown == false &&
+                newLocation != desiredLocation) {
+                // We're crossfading but the view is already hidden. Let's move to the overlay
+                // instead. This happens when animating to the full shade using a button click.
+                canUseOverlay = true
+            }
+        }
+        val inOverlay = isTransitionRunning() && rootOverlay != null && canUseOverlay
+        newLocation = if (inOverlay) IN_OVERLAY else newLocation
         if (currentAttachmentLocation != newLocation) {
             currentAttachmentLocation = newLocation
 
@@ -677,10 +850,10 @@
             (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame)
 
             // Add it to the new one
-            val targetHost = getHost(desiredLocation)!!.hostView
             if (inOverlay) {
                 rootOverlay!!.add(mediaFrame)
             } else {
+                val targetHost = getHost(newLocation)!!.hostView
                 // When adding back to the host, let's make sure to reset the bounds.
                 // Usually adding the view will trigger a layout that does this automatically,
                 // but we sometimes suppress this.
@@ -693,9 +866,39 @@
                         left + currentBounds.width(),
                         top + currentBounds.height())
             }
+            if (isCrossFadeAnimatorRunning) {
+                // When cross-fading with an animation, we only notify the media carousel of the
+                // location change, once the view is reattached to the new place and not immediately
+                // when the desired location changes. This callback will update the measurement
+                // of the carousel, only once we've faded out at the old location and then reattach
+                // to fade it in at the new location.
+                mediaCarouselController.onDesiredLocationChanged(
+                    newLocation,
+                    getHost(newLocation),
+                    animate = false
+                )
+            }
         }
     }
 
+    /**
+     * Calculate the location when cross fading between locations. While fading out,
+     * the content should remain in the previous location, while after the switch it should
+     * be at the desired location.
+     */
+    private fun resolveLocationForFading(): Int {
+        if (isCrossFadeAnimatorRunning) {
+            // When animating between two hosts with a fade, let's keep ourselves in the old
+            // location for the first half, and then switch over to the end location
+            if (animationCrossFadeProgress > 0.5 || previousLocation == -1) {
+                return crossFadeAnimationEndLocation
+            } else {
+                return crossFadeAnimationStartLocation
+            }
+        }
+        return desiredLocation
+    }
+
     private fun isTransitionRunning(): Boolean {
         return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f ||
                 animator.isRunning || animationPending
@@ -708,29 +911,29 @@
             return desiredLocation
         }
         val onLockscreen = (!bypassController.bypassEnabled &&
-                (statusbarState == StatusBarState.KEYGUARD ||
-                        statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+            (statusbarState == StatusBarState.KEYGUARD ||
+                statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
         val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
         val location = when {
             qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
             qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
-            onLockscreen && isTransitioningToFullShade -> LOCATION_QQS
+            onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
             onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
             else -> LOCATION_QQS
         }
         // When we're on lock screen and the player is not active, we should keep it in QS.
         // Otherwise it will try to animate a transition that doesn't make sense.
         if (location == LOCATION_LOCKSCREEN && getHost(location)?.visible != true &&
-                !statusBarStateController.isDozing) {
+            !statusBarStateController.isDozing) {
             return LOCATION_QS
         }
         if (location == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS &&
-                collapsingShadeFromQS) {
+            collapsingShadeFromQS) {
             // When collapsing on the lockscreen, we want to remain in QS
             return LOCATION_QS
         }
         if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN &&
-                !fullyAwake) {
+            !fullyAwake) {
             // When unlocking from dozing / while waking up, the media shouldn't be transitioning
             // in an animated way. Let's keep it in the lockscreen until we're fully awake and
             // reattach it without an animation
@@ -740,6 +943,26 @@
     }
 
     /**
+     * Are we currently transforming to the full shade and already in QQS
+     */
+    private fun isTransformingToFullShadeAndInQQS(): Boolean {
+        if (!isTransitioningToFullShade) {
+            return false
+        }
+        return fullShadeTransitionProgress > 0.5f
+    }
+
+    /**
+     * Is the current transformationType fading
+     */
+    private fun isCurrentlyFading(): Boolean {
+        if (isTransitioningToFullShade) {
+            return true
+        }
+        return isCrossFadeAnimatorRunning
+    }
+
+    /**
      * Returns true when the media card could be visible to the user if existed.
      */
     private fun isVisibleToUser(): Boolean {
@@ -789,9 +1012,27 @@
          * Attached at the root of the hierarchy in an overlay
          */
         const val IN_OVERLAY = -1000
+
+        /**
+         * The default transformation type where the hosts transform into each other using a direct
+         * transition
+         */
+        const val TRANSFORMATION_TYPE_TRANSITION = 0
+
+        /**
+         * A transformation type where content fades from one place to another instead of
+         * transitioning
+         */
+        const val TRANSFORMATION_TYPE_FADE = 1
     }
 }
 
+@IntDef(prefix = ["TRANSFORMATION_TYPE_"], value = [
+    MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION,
+    MediaHierarchyManager.TRANSFORMATION_TYPE_FADE])
+@Retention(AnnotationRetention.SOURCE)
+private annotation class TransformationType
+
 @IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
     MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
 @Retention(AnnotationRetention.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 34c654c..1c5fa43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -95,7 +95,7 @@
     private boolean mLastKeyguardAndExpanded;
     /**
      * The last received state from the controller. This should not be used directly to check if
-     * we're on keyguard but use {@link #isKeyguardShowing()} instead since that is more accurate
+     * we're on keyguard but use {@link #isKeyguardState()} instead since that is more accurate
      * during state transitions which often call into us.
      */
     private int mState;
@@ -326,7 +326,7 @@
                 || mHeaderAnimating;
         mQSPanelController.setExpanded(mQsExpanded);
         mQSDetail.setExpanded(mQsExpanded);
-        boolean keyguardShowing = isKeyguardShowing();
+        boolean keyguardShowing = isKeyguardState();
         mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
                 || mShowCollapsedOnKeyguard)
                 ? View.VISIBLE
@@ -344,7 +344,7 @@
                 !mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE);
     }
 
-    private boolean isKeyguardShowing() {
+    private boolean isKeyguardState() {
         // We want the freshest state here since otherwise we'll have some weirdness if earlier
         // listeners trigger updates
         return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
@@ -366,7 +366,7 @@
             if (mQSAnimator != null) {
                 mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed);
             }
-            if (!showCollapsed && isKeyguardShowing()) {
+            if (!showCollapsed && isKeyguardState()) {
                 setQsExpansion(mLastQSExpansion, 0);
             }
         }
@@ -457,7 +457,7 @@
         mContainer.setExpansion(expansion);
         final float translationScaleY = (mTranslateWhileExpanding
                 ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
-        boolean onKeyguardAndExpanded = isKeyguardShowing() && !mShowCollapsedOnKeyguard;
+        boolean onKeyguardAndExpanded = isKeyguardState() && !mShowCollapsedOnKeyguard;
         if (!mHeaderAnimating && !headerWillBeAnimating()) {
             getView().setTranslationY(
                     onKeyguardAndExpanded
@@ -531,7 +531,6 @@
             // The Media can be scrolled off screen by default, let's offset it
             float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY()
                     + mQSPanelScrollView.getScrollRange();
-            // The expanded media host should never move below the laid out position
             pinToBottom(expandedMediaPosition, mQsMediaHost, true /* expanded */);
             // The expanded media host should never move above the laid out position
             pinToBottom(absoluteBottomPosition, mQqsMediaHost, false /* expanded */);
@@ -540,7 +539,8 @@
 
     private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) {
         View hostView = mediaHost.getHostView();
-        if (mLastQSExpansion > 0) {
+        // on keyguard we cross-fade to expanded, so no need to pin it.
+        if (mLastQSExpansion > 0 && !isKeyguardState()) {
             float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView)
                     - hostView.getHeight();
             float currentPosition = mediaHost.getCurrentBounds().top
@@ -573,7 +573,7 @@
 
     private boolean headerWillBeAnimating() {
         return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard
-                && !isKeyguardShowing();
+                && !isKeyguardState();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index be7adbc..74d3425 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -430,12 +430,14 @@
             } else {
                 state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
             }
-        } else if (cb.mNoDefaultNetwork && cb.mNoNetworksAvailable) {
-            state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
-            state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
-        } else if (cb.mNoValidatedNetwork && !cb.mNoNetworksAvailable) {
-            state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
-            state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
+        } else if (cb.mNoDefaultNetwork) {
+            if (cb.mNoNetworksAvailable) {
+                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+                state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
+            } else {
+                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
+                state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
+            }
         } else if (cb.mIsTransient) {
             state.icon = ResourceIcon.get(
                 com.android.internal.R.drawable.ic_signal_wifi_transient_animation);
@@ -488,12 +490,14 @@
             state.state = Tile.STATE_INACTIVE;
             state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
             state.secondaryLabel = r.getString(R.string.status_bar_airplane);
-        } else if (cb.mNoDefaultNetwork && cb.mNoNetworksAvailable) {
-            state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
-            state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
-        } else if (cb.mNoValidatedNetwork && !cb.mNoNetworksAvailable) {
-            state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
-            state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
+        } else if (cb.mNoDefaultNetwork) {
+            if (cb.mNoNetworksAvailable) {
+                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+                state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
+            } else {
+                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
+                state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
+            }
         } else {
             state.icon = new SignalIcon(cb.mMobileSignalIconId);
             state.secondaryLabel = appendMobileDataType(cb.mDataSubscriptionName,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index b6357b8..045a197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -36,6 +36,12 @@
      */
     default void registerCallback(StatusBarWindowCallback callback) {}
 
+    /**
+     * Unregisters a {@link StatusBarWindowCallback previous registered with
+     * {@link #registerCallback(StatusBarWindowCallback)}}
+     */
+    default void unregisterCallback(StatusBarWindowCallback callback) {}
+
     /** Notifies the registered {@link StatusBarWindowCallback} instances. */
     default void notifyStateChangedCallbacks() {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 655ed41..33aa7c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -26,14 +26,18 @@
 import android.provider.DeviceConfig
 import android.util.Log
 import android.view.View
+import com.android.systemui.Dumpable
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.phone.StatusBarWindowController
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.util.Assert
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.time.SystemClock
+import java.io.FileDescriptor
+import java.io.PrintWriter
 
 import javax.inject.Inject
 
@@ -59,9 +63,10 @@
     private val coordinator: SystemEventCoordinator,
     private val chipAnimationController: SystemEventChipAnimationController,
     private val statusBarWindowController: StatusBarWindowController,
+    private val dumpManager: DumpManager,
     private val systemClock: SystemClock,
     @Main private val executor: DelayableExecutor
-) : CallbackController<SystemStatusAnimationCallback> {
+) : CallbackController<SystemStatusAnimationCallback>, Dumpable {
 
     companion object {
         private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
@@ -71,10 +76,6 @@
                 PROPERTY_ENABLE_IMMERSIVE_INDICATOR, true)
     }
 
-    /** True from the time a scheduled event starts until it's animation finishes */
-    var isActive = false
-        private set
-
     @SystemAnimationState var animationState: Int = IDLE
         private set
 
@@ -88,6 +89,7 @@
 
     init {
         coordinator.attachScheduler(this)
+        dumpManager.registerDumpable(TAG, this)
     }
 
     fun onStatusEvent(event: StatusEvent) {
@@ -293,6 +295,20 @@
         anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState)
     }
 
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("Scheduled event: $scheduledEvent")
+        pw.println("Has persistent privacy dot: $hasPersistentDot")
+        pw.println("Animation state: $animationState")
+        pw.println("Listeners:")
+        if (listeners.isEmpty()) {
+            pw.println("(none)")
+        } else {
+            listeners.forEach {
+                pw.println("  $it")
+            }
+        }
+    }
+
     inner class ChipAnimatorAdapter(
         @SystemAnimationState val endState: Int,
         val viewCreator: (context: Context) -> View
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 21d8164..94edbd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5145,8 +5145,8 @@
     }
 
     /**
-     * Sets the extra top inset for the full shade transition. This is needed to compensate for
-     * media transitioning to quick settings
+     * Sets the extra top inset for the full shade transition. This moves notifications down
+     * during the drag down.
      */
     public void setExtraTopInsetForFullShadeTransition(float inset) {
         mExtraTopInsetForFullShadeTransition = inset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d23a309..4432f54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -185,8 +185,17 @@
 
     private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener;
 
+    /**
+     * The total distance in pixels that the full shade transition takes to transition entirely to
+     * the full shade.
+     */
     private int mTotalDistanceForFullShadeTransition;
-    private int mTotalExtraMediaInsetFullShadeTransition;
+
+    /**
+     * The amount of movement the notifications do when transitioning to the full shade before
+     * reaching the overstrech
+     */
+    private int mNotificationDragDownMovement;
 
     @VisibleForTesting
     final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
@@ -255,8 +264,8 @@
     };
 
     private void updateResources() {
-        mTotalExtraMediaInsetFullShadeTransition = mResources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_transition_extra_media_inset);
+        mNotificationDragDownMovement = mResources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_notification_movement);
         mTotalDistanceForFullShadeTransition = mResources.getDimensionPixelSize(
                 R.dimen.lockscreen_shade_qs_transition_distance);
     }
@@ -1410,15 +1419,13 @@
      * shade. 0.0f means we're not transitioning yet.
      */
     public void setTransitionToFullShadeAmount(float amount) {
-        float extraTopInset;
-        MediaHeaderView view = mKeyguardMediaController.getSinglePaneContainer();
-        if (view == null || view.getHeight() == 0
-                || mStatusBarStateController.getState() != KEYGUARD) {
-            extraTopInset = 0;
-        } else {
-            extraTopInset = MathUtils.saturate(amount / mTotalDistanceForFullShadeTransition);
-            extraTopInset = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(extraTopInset);
-            extraTopInset = extraTopInset * mTotalExtraMediaInsetFullShadeTransition;
+        float extraTopInset = 0.0f;
+        if (mStatusBarStateController.getState() == KEYGUARD) {
+            float overallProgress = MathUtils.saturate(amount / mView.getHeight());
+            float transitionProgress = Interpolators.getOvershootInterpolation(overallProgress,
+                    0.6f,
+                    (float) mTotalDistanceForFullShadeTransition / (float) mView.getHeight());
+            extraTopInset = transitionProgress * mNotificationDragDownMovement;
         }
         mView.setExtraTopInsetForFullShadeTransition(extraTopInset);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index f4710f4..7c2723d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -325,7 +325,9 @@
      */
     private float getClockAlpha(int y) {
         float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f, mDarkAmount)));
-        alphaKeyguard *= (1f - mQsExpansion);
+        float qsAlphaFactor = MathUtils.saturate(mQsExpansion / 0.3f);
+        qsAlphaFactor = 1f - qsAlphaFactor;
+        alphaKeyguard *= qsAlphaFactor;
         alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
         return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index d84bb90..68e2070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -142,7 +142,6 @@
         Animator yTranslate =
                 ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, 0, -getYTranslationPixels());
         yTranslate.setDuration(getFadeOutDuration());
-        fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
         animatorSet.playTogether(fadeOut, yTranslate);
 
         return animatorSet;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 0f3af09..d9ba494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -24,6 +24,8 @@
 import android.graphics.PorterDuffXfermode;
 import android.util.AttributeSet;
 
+import com.android.systemui.R;
+
 public class NotificationPanelView extends PanelView {
 
     private static final boolean DEBUG = false;
@@ -92,6 +94,10 @@
         mRtlChangeListener = listener;
     }
 
+    public TapAgainView getTapAgainView() {
+        return findViewById(R.id.shade_falsing_tap_again);
+    }
+
     interface RtlChangeListener {
         void onRtlPropertielsChanged(int layoutDirection);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 1263b10..7d4707e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -97,8 +97,8 @@
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
-import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
@@ -306,6 +306,7 @@
     private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
     private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
     private final QSDetailDisplayer mQSDetailDisplayer;
+    private final FragmentService mFragmentService;
     private final FeatureFlags mFeatureFlags;
     private final ScrimController mScrimController;
     private final PrivacyDotViewController mPrivacyDotViewController;
@@ -314,6 +315,7 @@
     // If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications
     private final int mMaxKeyguardNotifications;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    private final TapAgainViewController mTapAgainViewController;
     private boolean mShouldUseSplitNotificationShade;
     // Current max allowed keyguard notifications determined by measuring the panel
     private int mMaxAllowedKeyguardNotifications;
@@ -508,6 +510,13 @@
     private float mSectionPadding;
 
     /**
+     * The padding between the start of notifications and the qs boundary on the lockscreen.
+     * On lockscreen, notifications aren't inset this extra amount, but we still want the
+     * qs boundary to be padded.
+     */
+    private int mLockscreenNotificationQSPadding;
+
+    /**
      * The amount of progress we are currently in if we're transitioning to the full shade.
      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
      * shade. This value can also go beyond 1.1 when we're overshooting!
@@ -529,7 +538,7 @@
     /**
      * The maximum overshoot allowed for the top padding for the full shade transition
      */
-    private int mMaxOverscrollAmountForDragDown;
+    private int mMaxOverscrollAmountForPulse;
 
     /**
      * Should we animate the next bounds update
@@ -573,6 +582,7 @@
     private int mScrimCornerRadius;
     private int mScreenCornerRadius;
     private int mNotificationScrimPadding;
+    private boolean mQSAnimatingHiddenFromCollapsed;
 
     private final QuickAccessWalletClient mQuickAccessWalletClient;
     private final Executor mUiExecutor;
@@ -604,7 +614,12 @@
     private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() {
         @Override
         public void onDoubleTapRequired() {
-            showTransientIndication(R.string.notification_tap_again);
+            if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
+                mTapAgainViewController.show();
+            } else {
+                mKeyguardIndicationController.showTransientIndication(
+                        R.string.notification_tap_again);
+            }
             mVibratorHelper.vibrate(VibrationEffect.EFFECT_STRENGTH_MEDIUM);
         }
     };
@@ -653,6 +668,8 @@
             QuickAccessWalletClient quickAccessWalletClient,
             KeyguardMediaController keyguardMediaController,
             PrivacyDotViewController privacyDotViewController,
+            TapAgainViewController tapAgainViewController,
+            FragmentService fragmentService,
             @Main Executor uiExecutor,
             SecureSettings secureSettings) {
         super(view, falsingManager, dozeLog, keyguardStateController,
@@ -679,6 +696,7 @@
         mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
         mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
         mQSDetailDisplayer = qsDetailDisplayer;
+        mFragmentService = fragmentService;
         mKeyguardUserSwitcherEnabled = mResources.getBoolean(
                 com.android.internal.R.bool.config_keyguardUserSwitcher);
         mKeyguardQsUserSwitchEnabled =
@@ -705,6 +723,7 @@
         mUserManager = userManager;
         mMediaDataManager = mediaDataManager;
         mQuickAccessWalletClient = quickAccessWalletClient;
+        mTapAgainViewController = tapAgainViewController;
         mUiExecutor = uiExecutor;
         mSecureSettings = secureSettings;
         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -812,7 +831,7 @@
                 amount -> {
                     float progress = amount / mView.getHeight();
                     float overstretch = Interpolators.getOvershootInterpolation(progress,
-                            (float) mMaxOverscrollAmountForDragDown / mView.getHeight(),
+                            (float) mMaxOverscrollAmountForPulse / mView.getHeight(),
                             0.2f);
                     setOverStrechAmount(overstretch);
                 });
@@ -843,6 +862,8 @@
         if (mShouldUseSplitNotificationShade) {
             updateResources();
         }
+
+        mTapAgainViewController.init();
     }
 
     @Override
@@ -873,14 +894,16 @@
                 R.dimen.heads_up_status_bar_padding);
         mDistanceForQSFullShadeTransition = mResources.getDimensionPixelSize(
                 R.dimen.lockscreen_shade_qs_transition_distance);
-        mMaxOverscrollAmountForDragDown = mResources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_max_top_overshoot);
+        mMaxOverscrollAmountForPulse = mResources.getDimensionPixelSize(
+                R.dimen.pulse_expansion_max_top_overshoot);
         mScrimCornerRadius = mResources.getDimensionPixelSize(
                 R.dimen.notification_scrim_corner_radius);
         mScreenCornerRadius = mResources.getDimensionPixelSize(
                 com.android.internal.R.dimen.rounded_corner_radius);
         mNotificationScrimPadding = mResources.getDimensionPixelSize(
                 R.dimen.notification_side_paddings);
+        mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
+                R.dimen.notification_side_paddings);
     }
 
     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -1421,7 +1444,7 @@
         }
         mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
                 true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-        if (animate) {
+        if (animate && !isFullyCollapsed()) {
             animateCloseQs(true /* animateAway */);
         } else {
             closeQs();
@@ -1705,6 +1728,11 @@
     }
 
     private float computeQsExpansionFraction() {
+        if (mQSAnimatingHiddenFromCollapsed) {
+            // When hiding QS from collapsed state, the expansion can sometimes temporarily
+            // be larger than 0 because of the timing, leading to flickers.
+            return 0.0f;
+        }
         return Math.min(
                 1f, (mQsExpansionHeight - mQsMinExpansionHeight) / (mQsMaxExpansionHeight
                         - mQsMinExpansionHeight));
@@ -2367,7 +2395,6 @@
     public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
         mAnimateNextNotificationBounds = animate && !mShouldUseSplitNotificationShade;
         mNotificationBoundsAnimationDelay = delay;
-        float progress = MathUtils.saturate(pxAmount / mView.getHeight());
 
         float endPosition = 0;
         if (pxAmount > 0.0f) {
@@ -2375,29 +2402,28 @@
                     && !mMediaDataManager.hasActiveMedia()) {
                 // No notifications are visible, let's animate to the height of qs instead
                 if (mQs != null) {
-                    // Let's interpolate to the header height
-                    endPosition = mQs.getHeader().getHeight();
+                    // Let's interpolate to the header height instead of the top padding,
+                    // because the toppadding is way too low because of the large clock.
+                    // we still want to take into account the edgePosition though as that nicely
+                    // overshoots in the stackscroller
+                    endPosition = getQSEdgePosition()
+                            - mNotificationStackScrollLayoutController.getTopPadding()
+                            + mQs.getHeader().getHeight();
                 }
             } else {
                 // Interpolating to the new bottom edge position!
-                endPosition = getQSEdgePosition() - mOverStretchAmount;
-
-                // If we have media, we need to put the boundary below it, as the media header
-                // still uses the space during the transition.
-                endPosition +=
-                        mNotificationStackScrollLayoutController.getFullShadeTransitionInset();
+                endPosition = getQSEdgePosition()
+                        + mNotificationStackScrollLayoutController.getFullShadeTransitionInset();
+                if (isOnKeyguard()) {
+                    endPosition -= mLockscreenNotificationQSPadding;
+                }
             }
         }
 
         // Calculate the overshoot amount such that we're reaching the target after our desired
         // distance, but only reach it fully once we drag a full shade length.
-        float transitionProgress = 0;
-        if (endPosition != 0 && progress != 0) {
-            transitionProgress = Interpolators.getOvershootInterpolation(progress,
-                    mMaxOverscrollAmountForDragDown / endPosition,
-                    (float) mDistanceForQSFullShadeTransition / (float) mView.getHeight());
-        }
-        mTransitioningToFullShadeProgress = transitionProgress;
+        mTransitioningToFullShadeProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+                MathUtils.saturate(pxAmount / mDistanceForQSFullShadeTransition));
 
         int position = (int) MathUtils.lerp((float) 0, endPosition,
                 mTransitioningToFullShadeProgress);
@@ -2405,8 +2431,6 @@
             // we want at least 1 pixel otherwise the panel won't be clipped
             position = Math.max(1, position);
         }
-        float overStretchAmount = Math.max(position - endPosition, 0.0f);
-        setOverStrechAmount(overStretchAmount);
         mTransitionToFullShadeQSPosition = position;
         updateQsExpansion();
     }
@@ -2509,6 +2533,7 @@
 
             @Override
             public void onAnimationEnd(Animator animation) {
+                mQSAnimatingHiddenFromCollapsed = false;
                 mAnimatingQS = false;
                 notifyExpandingFinished();
                 mNotificationStackScrollLayoutController.resetCheckSnoozeLeavebehind();
@@ -2525,6 +2550,7 @@
         animator.start();
         mQsExpansionAnimator = animator;
         mQsAnimatorExpand = expanding;
+        mQSAnimatingHiddenFromCollapsed = computeQsExpansionFraction() == 0.0f && target == 0;
     }
 
     /**
@@ -3675,10 +3701,6 @@
         updateMaxDisplayedNotifications(true);
     }
 
-    public void showTransientIndication(int id) {
-        mKeyguardIndicationController.showTransientIndication(id);
-    }
-
     public void setAlpha(float alpha) {
         mView.setAlpha(alpha);
     }
@@ -4264,7 +4286,8 @@
     private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener {
         @Override
         public void onViewAttachedToWindow(View v) {
-            FragmentHostManager.get(mView).addTagListener(QS.TAG, mFragmentListener);
+            mFragmentService.getFragmentHostManager(mView)
+                            .addTagListener(QS.TAG, mFragmentListener);
             mStatusBarStateController.addCallback(mStatusBarStateListener);
             mConfigurationController.addCallback(mConfigurationListener);
             mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
@@ -4278,7 +4301,8 @@
 
         @Override
         public void onViewDetachedFromWindow(View v) {
-            FragmentHostManager.get(mView).removeTagListener(QS.TAG, mFragmentListener);
+            mFragmentService.getFragmentHostManager(mView)
+                            .removeTagListener(QS.TAG, mFragmentListener);
             mStatusBarStateController.removeCallback(mStatusBarStateListener);
             mConfigurationController.removeCallback(mConfigurationListener);
             mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index ae018ba..52f9aca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -152,6 +152,16 @@
         mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback));
     }
 
+    @Override
+    public void unregisterCallback(StatusBarWindowCallback callback) {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            if (mCallbacks.get(i).get() == callback) {
+                mCallbacks.remove(i);
+                return;
+            }
+        }
+    }
+
     /**
      * Register a listener to monitor scrims visibility
      * @param listener A listener to monitor scrims visibility
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 ba2340e..3f889e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4386,6 +4386,7 @@
         } else {
             mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
         }
+        updateLightRevealScrimVisibility();
         Trace.endSection();
     }
 
@@ -4818,6 +4819,11 @@
             return;
         }
 
+        if (mDozeServiceHost.isPulsing()) {
+            mLightRevealScrim.setVisibility(View.GONE);
+            return;
+        }
+
         if (mFeatureFlags.useNewLockscreenAnimations()
                 && (mDozeParameters.getAlwaysOn() || mDozeParameters.isQuickPickupEnabled())) {
             mLightRevealScrim.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 8befe80..edcf261 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -23,6 +23,8 @@
 import android.view.DisplayCutout
 import android.view.View.LAYOUT_DIRECTION_RTL
 import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
@@ -118,17 +120,8 @@
         val chipWidth = rotatedResources.getDimensionPixelSize(
                 R.dimen.ongoing_appops_chip_max_width)
 
-        return if (context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL) {
-            Rect(insets.left - dotWidth,
-                    insets.top,
-                    insets.left + chipWidth,
-                    insets.bottom)
-        } else {
-            Rect(insets.right - chipWidth,
-                    insets.top,
-                    insets.right + dotWidth,
-                    insets.bottom)
-        }
+        val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
+        return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl)
     }
 
     /**
@@ -139,8 +132,7 @@
         var insets = insetsByCorner[rotation]
         if (insets == null) {
             val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context)
-            insets = getCalculatedInsetsForRotation(rotation, rotatedResources)
-            insetsByCorner[rotation] = insets
+            insets = getAndSetInsetsForRotation(rotation, rotatedResources)
         }
 
         return insets
@@ -157,13 +149,19 @@
     }
 
     private fun getCalculatedInsetsForRotation(
-        @Rotation rotation: Int,
+        @Rotation targetRotation: Int,
         rotatedResources: Resources
     ): Rect {
         val dc = context.display.cutout
+        val currentRotation = RotationUtils.getExactRotation(context)
 
         return calculateInsetsForRotationWithRotatedResources(
-                rotation, rotatedResources, dc, windowManager, context)
+                currentRotation,
+                targetRotation,
+                dc,
+                windowManager.maximumWindowMetrics,
+                rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height),
+                rotatedResources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding))
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
@@ -179,8 +177,8 @@
 
 private const val TAG = "StatusBarInsetsProvider"
 
-private fun getRotationZeroDisplayBounds(wm: WindowManager, @Rotation exactRotation: Int): Rect {
-    val bounds = wm.maximumWindowMetrics.bounds
+private fun getRotationZeroDisplayBounds(wm: WindowMetrics, @Rotation exactRotation: Int): Rect {
+    val bounds = wm.bounds
 
     if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) {
         return bounds
@@ -190,9 +188,24 @@
     return Rect(0, 0, bounds.bottom, bounds.right)
 }
 
-private fun getCurrentDisplayBounds(wm: WindowManager): Rect {
-    val bounds = wm.maximumWindowMetrics.bounds
-    return bounds
+@VisibleForTesting
+fun getPrivacyChipBoundingRectForInsets(
+    contentRect: Rect,
+    dotWidth: Int,
+    chipWidth: Int,
+    isRtl: Boolean
+): Rect {
+    return if (isRtl) {
+        Rect(contentRect.left - dotWidth,
+                contentRect.top,
+                contentRect.left + chipWidth,
+                contentRect.bottom)
+    } else {
+        Rect(contentRect.right - chipWidth,
+                contentRect.top,
+                contentRect.right + dotWidth,
+                contentRect.bottom)
+    }
 }
 
 /**
@@ -206,41 +219,32 @@
  * @see [RotationUtils#getResourcesForRotation]
  */
 fun calculateInsetsForRotationWithRotatedResources(
+    @Rotation currentRotation: Int,
     @Rotation targetRotation: Int,
-    rotatedResources: Resources,
     displayCutout: DisplayCutout?,
-    windowmanager: WindowManager,
-    context: Context
+    windowMetrics: WindowMetrics,
+    statusBarHeight: Int,
+    roundedCornerPadding: Int
 ): Rect {
-    val rtl = rotatedResources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
-
-    val exactRotation = RotationUtils.getExactRotation(context)
-    val height = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height)
-
     /*
     TODO: Check if this is ever used for devices with no rounded corners
-    val paddingStart = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_start)
-    val paddingEnd = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_end)
-    val left = if (rtl) paddingEnd else paddingStart
-    val right = if(rtl) paddingStart else paddingEnd
+    val left = if (isRtl) paddingEnd else paddingStart
+    val right = if (isRtl) paddingStart else paddingEnd
      */
 
-    val roundedCornerPadding = rotatedResources.getDimensionPixelSize(
-            R.dimen.rounded_corner_content_padding)
-
-    val rotZeroBounds = getRotationZeroDisplayBounds(windowmanager, exactRotation)
-    val currentBounds = getCurrentDisplayBounds(windowmanager)
+    val rotZeroBounds = getRotationZeroDisplayBounds(windowMetrics, currentRotation)
+    val currentBounds = windowMetrics.bounds
 
     val sbLeftRight = getStatusBarLeftRight(
             displayCutout,
-            height,
+            statusBarHeight,
             rotZeroBounds.right,
             rotZeroBounds.bottom,
             currentBounds.width(),
             currentBounds.height(),
             roundedCornerPadding,
             targetRotation,
-            exactRotation)
+            currentRotation)
 
     return sbLeftRight
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index b10e841..d3953df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -175,7 +175,7 @@
                 && !mIsAirplaneMode) {
             newState.visible = true;
             newState.resId = R.drawable.ic_qs_no_internet_unavailable;
-        } else if (mWifiIconState.noValidatedNetwork && !mWifiIconState.noNetworksAvailable
+        } else if (mWifiIconState.noDefaultNetwork && !mWifiIconState.noNetworksAvailable
                 && (!mIsAirplaneMode || (mIsAirplaneMode && mIsWifiEnabled))) {
             newState.visible = true;
             newState.resId = R.drawable.ic_qs_no_internet_available;
@@ -380,7 +380,7 @@
         if (noDefaultNetwork && noNetworksAvailable && !mIsAirplaneMode) {
             newState.visible = true;
             newState.resId = R.drawable.ic_qs_no_internet_unavailable;
-        } else if (noValidatedNetwork && !noNetworksAvailable
+        } else if (noDefaultNetwork && !noNetworksAvailable
                 && (!mIsAirplaneMode || (mIsAirplaneMode && mIsWifiEnabled))) {
             newState.visible = true;
             newState.resId = R.drawable.ic_qs_no_internet_available;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java
new file mode 100644
index 0000000..9856795
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+import com.android.wm.shell.animation.Interpolators;
+
+/**
+ * View to show a toast-like popup on the notification shade and quick settings.
+ */
+public class TapAgainView extends FrameLayout {
+    public TapAgainView(
+            @NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        updateBgColor();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        TextView text = new TextView(mContext);
+        text.setText(R.string.notification_tap_again);
+        addView(text);
+    }
+
+    void updateBgColor() {
+        setBackgroundResource(R.drawable.rounded_bg_full);
+    }
+
+    /** Make the view visible. */
+    public void animateIn() {
+        int yTranslation = mContext.getResources().getDimensionPixelSize(
+                R.dimen.keyguard_indication_y_translation);
+
+        AnimatorSet animatorSet = new AnimatorSet();
+        ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, View.ALPHA, 1f);
+        fadeIn.setStartDelay(150);  // From KeyguardIndicationTextView#getFadeInDelay
+        fadeIn.setDuration(317);  // From KeyguardIndicationTextView#getFadeInDuration
+        fadeIn.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+
+        Animator yTranslate =
+                ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, yTranslation, 0);
+        yTranslate.setDuration(600);  // From KeyguardIndicationTextView#getYInDuration
+        yTranslate.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                setTranslationY(0);
+            }
+        });
+        animatorSet.playTogether(yTranslate, fadeIn);
+        animatorSet.start();
+        setVisibility(View.VISIBLE);
+    }
+
+    /** Make the view gone. */
+    public void animateOut() {
+        long fadeOutDuration = 167L;  // From KeyguardIndicationTextView#getFadeOutDuration
+        int yTranslation = mContext.getResources().getDimensionPixelSize(
+                com.android.systemui.R.dimen.keyguard_indication_y_translation);
+
+        AnimatorSet animatorSet = new AnimatorSet();
+        ObjectAnimator fadeOut = ObjectAnimator.ofFloat(this, View.ALPHA, 0f);
+        fadeOut.setDuration(fadeOutDuration);
+        fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+
+        Animator yTranslate =
+                ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, 0, -yTranslation);
+        yTranslate.setDuration(fadeOutDuration);
+        animatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                setVisibility(GONE);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                setVisibility(GONE);
+            }
+        });
+        animatorSet.playTogether(yTranslate, fadeOut);
+        animatorSet.start();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
new file mode 100644
index 0000000..bb53bad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static com.android.systemui.classifier.FalsingModule.DOUBLE_TAP_TIMEOUT_MS;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.util.ViewController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Controller for {@link TapAgainView}.
+ */
+@StatusBarComponent.StatusBarScope
+public class TapAgainViewController extends ViewController<TapAgainView> {
+    private final DelayableExecutor mDelayableExecutor;
+    private final ConfigurationController mConfigurationController;
+    private final long mDoubleTapTimeMs;
+
+    private Runnable mHideCanceler;
+
+    @VisibleForTesting
+    final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+        @Override
+        public void onOverlayChanged() {
+            mView.updateBgColor();
+        }
+
+        @Override
+        public void onUiModeChanged() {
+            mView.updateBgColor();
+        }
+
+        @Override
+        public void onThemeChanged() {
+            mView.updateBgColor();
+        }
+    };
+
+    @Inject
+    protected TapAgainViewController(TapAgainView view,
+            @Main DelayableExecutor delayableExecutor,
+            ConfigurationController configurationController,
+            @Named(DOUBLE_TAP_TIMEOUT_MS) long doubleTapTimeMs) {
+        super(view);
+        mDelayableExecutor = delayableExecutor;
+        mConfigurationController = configurationController;
+        mDoubleTapTimeMs = doubleTapTimeMs;
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mConfigurationController.addCallback(mConfigurationListener);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mConfigurationController.removeCallback(mConfigurationListener);
+    }
+
+    /** Shows the associated view, possibly animating it. */
+    public void show() {
+        if (mHideCanceler != null) {
+            mHideCanceler.run();
+        }
+        mView.animateIn();
+        mHideCanceler = mDelayableExecutor.executeDelayed(this::hide, mDoubleTapTimeMs);
+    }
+
+    /** Hides the associated view, possibly animating it. */
+    public void hide() {
+        mHideCanceler = null;
+        mView.animateOut();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 008c0ae..27d71ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -23,6 +23,7 @@
 import com.android.systemui.biometrics.AuthRippleView;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
+import com.android.systemui.statusbar.phone.TapAgainView;
 
 import dagger.Module;
 import dagger.Provides;
@@ -53,4 +54,11 @@
             NotificationShadeWindowView notificationShadeWindowView) {
         return notificationShadeWindowView.findViewById(R.id.auth_ripple);
     }
+
+    /** */
+    @Provides
+    @StatusBarComponent.StatusBarScope
+    public static TapAgainView getTapAgainView(NotificationPanelView npv) {
+        return npv.getTapAgainView();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 07e9fed..4ab07af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -1023,6 +1023,11 @@
         mValidatedTransports.clear();
         if (mLastDefaultNetworkCapabilities != null) {
             for (int transportType : mLastDefaultNetworkCapabilities.getTransportTypes()) {
+                if (transportType != NetworkCapabilities.TRANSPORT_CELLULAR
+                        && transportType != NetworkCapabilities.TRANSPORT_WIFI
+                        && transportType != NetworkCapabilities.TRANSPORT_ETHERNET) {
+                    continue;
+                }
                 if (transportType == NetworkCapabilities.TRANSPORT_CELLULAR
                         && Utils.tryGetWifiInfoForVcn(mLastDefaultNetworkCapabilities) != null) {
                     mConnectedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
@@ -1045,11 +1050,15 @@
             Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports);
         }
 
-        mInetCondition = !mValidatedTransports.isEmpty();
+        mInetCondition = mValidatedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
+                || mValidatedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
+                || mValidatedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
 
         pushConnectivityToSignals();
         if (mProviderModel) {
-            mNoDefaultNetwork = mConnectedTransports.isEmpty();
+            mNoDefaultNetwork = !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
+                && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
+                && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
             mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
                     mNoNetworksAvailable);
             for (int i = 0; i < mMobileSignalControllers.size(); i++) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index c6aef4a..bf87a4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -20,6 +20,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
@@ -33,6 +34,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
+import junit.framework.Assert
 import org.junit.Assert.assertNotNull
 import org.junit.Before
 import org.junit.Rule
@@ -65,8 +67,6 @@
     @Mock
     private lateinit var bypassController: KeyguardBypassController
     @Mock
-    private lateinit var mediaFrame: ViewGroup
-    @Mock
     private lateinit var keyguardStateController: KeyguardStateController
     @Mock
     private lateinit var statusBarStateController: SysuiStatusBarStateController
@@ -90,9 +90,11 @@
     @Rule
     val mockito = MockitoJUnit.rule()
     private lateinit var mediaHiearchyManager: MediaHierarchyManager
+    private lateinit var mediaFrame: ViewGroup
 
     @Before
     fun setup() {
+        mediaFrame = FrameLayout(context)
         `when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
         mediaHiearchyManager = MediaHierarchyManager(
                 context,
@@ -112,6 +114,9 @@
         `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
         `when`(mediaCarouselController.mediaCarouselScrollHandler)
                 .thenReturn(mediaCarouselScrollHandler)
+        val observer = wakefullnessObserver.value
+        assertNotNull("lifecycle observer wasn't registered", observer)
+        observer.onFinishedWakingUp()
         // We'll use the viewmanager to verify a few calls below, let's reset this.
         clearInvocations(mediaCarouselController)
     }
@@ -120,6 +125,7 @@
         `when`(host.location).thenReturn(location)
         `when`(host.currentBounds).thenReturn(Rect())
         `when`(host.hostView).thenReturn(UniqueObjectHostView(context))
+        `when`(host.visible).thenReturn(true)
         mediaHiearchyManager.register(host)
     }
 
@@ -160,6 +166,73 @@
     }
 
     @Test
+    fun testGoingToFullShade() {
+        // Let's set it onto Lock screen
+        `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
+            true)
+        statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
+        clearInvocations(mediaCarouselController)
+
+        // Let's transition all the way to full shade
+        mediaHiearchyManager.setTransitionToFullShadeAmount(100000f)
+        verify(mediaCarouselController).onDesiredLocationChanged(
+            eq(MediaHierarchyManager.LOCATION_QQS),
+            any(MediaHostState::class.java),
+            eq(false),
+            anyLong(),
+            anyLong())
+        clearInvocations(mediaCarouselController)
+
+        // Let's go back to the lock screen
+        mediaHiearchyManager.setTransitionToFullShadeAmount(0.0f)
+        verify(mediaCarouselController).onDesiredLocationChanged(
+            eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
+            any(MediaHostState::class.java),
+            eq(false),
+            anyLong(),
+            anyLong())
+
+        // Let's make sure alpha is set
+        mediaHiearchyManager.setTransitionToFullShadeAmount(2.0f)
+        Assert.assertTrue("alpha should not be 1.0f when cross fading", mediaFrame.alpha != 1.0f)
+    }
+
+    @Test
+    fun testTransformationOnLockScreenIsFading() {
+        // Let's set it onto Lock screen
+        `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
+            true)
+        statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
+        clearInvocations(mediaCarouselController)
+
+        // Let's transition from lockscreen to qs
+        mediaHiearchyManager.qsExpansion = 1.0f
+        val transformType = mediaHiearchyManager.calculateTransformationType()
+        Assert.assertTrue("media isn't transforming to qs with a fade",
+            transformType == MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+    }
+
+    @Test
+    fun testTransformationOnLockScreenToQQSisFading() {
+        // Let's set it onto Lock screen
+        `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
+            true)
+        statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
+        clearInvocations(mediaCarouselController)
+
+        // Let's transition from lockscreen to qs
+        `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+        statusBarCallback.value.onStatePreChange(StatusBarState.KEYGUARD,
+            StatusBarState.SHADE_LOCKED)
+        val transformType = mediaHiearchyManager.calculateTransformationType()
+        Assert.assertTrue("media isn't transforming to qqswith a fade",
+            transformType == MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+    }
+
+    @Test
     fun testCloseGutsRelayToCarousel() {
         mediaHiearchyManager.closeGuts()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 6b4797f..83a1872 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -20,12 +20,15 @@
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
+import static com.android.systemui.statusbar.notification.ViewGroupFadeHelper.reset;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -75,13 +78,17 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.doze.DozeLog;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -252,11 +259,21 @@
     private PrivacyDotViewController mPrivacyDotViewController;
     @Mock
     private SecureSettings mSecureSettings;
+    @Mock
+    private TapAgainViewController mTapAgainViewController;
+    @Mock
+    private KeyguardIndicationController mKeyguardIndicationController;
+    @Mock
+    private FragmentService mFragmentService;
+    @Mock
+    private FragmentHostManager mFragmentHostManager;
 
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
+    private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
+    private FalsingManagerFake mFalsingManager = new FalsingManagerFake();
 
     @Before
     public void setup() {
@@ -297,6 +314,7 @@
         mNotificationContainerParent.addView(newViewWithId(R.id.keyguard_status_view));
         when(mView.findViewById(R.id.notification_container_parent))
                 .thenReturn(mNotificationContainerParent);
+        when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager);
         FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
                 mDisplayMetrics);
 
@@ -317,7 +335,7 @@
                 mKeyguardBypassController, mHeadsUpManager,
                 mock(NotificationRoundnessManager.class),
                 mStatusBarStateController,
-                new FalsingManagerFake(),
+                mFalsingManager,
                 mLockscreenShadeTransitionController,
                 new FalsingCollectorFake());
         when(mKeyguardStatusViewComponentFactory.build(any()))
@@ -331,11 +349,12 @@
         when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
                 .thenReturn(mKeyguardStatusBarViewController);
 
+        reset(mView);
         mNotificationPanelViewController = new NotificationPanelViewController(mView,
                 mResources,
                 mLayoutInflater,
                 coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
-                new FalsingManagerFake(), new FalsingCollectorFake(),
+                mFalsingManager, new FalsingCollectorFake(),
                 mNotificationLockscreenUserManager, mNotificationEntryManager,
                 mKeyguardStateController, mStatusBarStateController, mDozeLog,
                 mDozeParameters, mCommandQueue, mVibratorHelper,
@@ -364,6 +383,8 @@
                 mQuickAccessWalletClient,
                 mKeyguardMediaController,
                 mPrivacyDotViewController,
+                mTapAgainViewController,
+                mFragmentService,
                 new FakeExecutor(new FakeSystemClock()),
                 mSecureSettings);
         mNotificationPanelViewController.initDependencies(
@@ -371,6 +392,13 @@
                 mNotificationShelfController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
         mNotificationPanelViewController.setBar(mPanelBar);
+        mNotificationPanelViewController.setKeyguardIndicationController(
+                mKeyguardIndicationController);
+        ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+        verify(mView, atLeast(1)).addOnAttachStateChangeListener(
+                onAttachStateChangeListenerArgumentCaptor.capture());
+        mOnAttachStateChangeListeners = onAttachStateChangeListenerArgumentCaptor.getAllValues();
 
         ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
                 ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
@@ -616,6 +644,34 @@
         verify(mKeyguardStateController).notifyPanelFlingEnd();
     }
 
+    @Test
+    public void testDoubleTapRequired_Keyguard() {
+        FalsingManager.FalsingTapListener listener = getFalsingTapListener();
+        mStatusBarStateController.setState(KEYGUARD);
+
+        listener.onDoubleTapRequired();
+
+        verify(mKeyguardIndicationController).showTransientIndication(anyInt());
+    }
+
+    @Test
+    public void testDoubleTapRequired_ShadeLocked() {
+        FalsingManager.FalsingTapListener listener = getFalsingTapListener();
+        mStatusBarStateController.setState(SHADE_LOCKED);
+
+        listener.onDoubleTapRequired();
+
+        verify(mTapAgainViewController).show();
+    }
+
+    private FalsingManager.FalsingTapListener getFalsingTapListener() {
+        for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
+            listener.onViewAttachedToWindow(mView);
+        }
+        assertThat(mFalsingManager.getTapListeners().size()).isEqualTo(1);
+        return mFalsingManager.getTapListeners().get(0);
+    }
+
     private View newViewWithId(int id) {
         View view = new View(mContext);
         view.setId(id);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
new file mode 100644
index 0000000..4796cd7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Rect
+import android.test.suitebuilder.annotation.SmallTest
+import android.view.DisplayCutout
+import android.view.WindowMetrics
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.leak.RotationUtils
+import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
+import com.android.systemui.util.leak.RotationUtils.Rotation
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class StatusBarContentInsetsProviderTest : SysuiTestCase() {
+    @Mock private lateinit var dc: DisplayCutout
+    @Mock private lateinit var windowMetrics: WindowMetrics
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun testGetBoundingRectForPrivacyChipForRotation_noCutout() {
+        val screenBounds = Rect(0, 0, 1080, 2160)
+        val roundedCornerPadding = 20
+        val sbHeightPortrait = 100
+        val sbHeightLandscape = 60
+        val currentRotation = ROTATION_NONE
+        val chipWidth = 30
+        val dotWidth = 10
+
+        `when`(windowMetrics.bounds).thenReturn(screenBounds)
+
+        var isRtl = false
+        var targetRotation = ROTATION_NONE
+        var bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                null,
+                windowMetrics,
+                sbHeightPortrait,
+                roundedCornerPadding)
+
+        var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
+        /* 1080 - 20 (rounded corner) - 30 (chip),
+        *  0 (sb top)
+        *  1080 - 20 (rounded corner) + 10 ( dot),
+        *  100 (sb height portrait)
+        */
+        var expected = Rect(1030, 0, 1070, 100)
+        assertRects(expected, chipBounds, currentRotation, targetRotation)
+        isRtl = true
+        chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
+        /* 0 + 20 (rounded corner) - 10 (dot),
+        *  0 (sb top)
+        *  0 + 20 (rounded corner) + 30 (chip),
+        *  100 (sb height portrait)
+        */
+        expected = Rect(10, 0, 50, 100)
+        assertRects(expected, chipBounds, currentRotation, targetRotation)
+
+        isRtl = false
+        targetRotation = ROTATION_LANDSCAPE
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightLandscape,
+                roundedCornerPadding)
+
+        chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
+        /* 2160 - 20 (rounded corner) - 30 (chip),
+        *  0 (sb top)
+        *  2160 - 20 (rounded corner) + 10 ( dot),
+        *  60 (sb height landscape)
+        */
+        expected = Rect(2110, 0, 2150, 60)
+        assertRects(expected, chipBounds, currentRotation, targetRotation)
+        isRtl = true
+        chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
+        /* 0 + 20 (rounded corner) - 10 (dot),
+        *  0 (sb top)
+        *  0 + 20 (rounded corner) + 30 (chip),
+        *  60 (sb height landscape)
+        */
+        expected = Rect(10, 0, 50, 60)
+        assertRects(expected, chipBounds, currentRotation, targetRotation)
+    }
+
+    @Test
+    fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() {
+        // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+        val screenBounds = Rect(0, 0, 1080, 2160)
+        val dcBounds = Rect(0, 0, 100, 100)
+        val roundedCornerPadding = 20
+        val sbHeightPortrait = 100
+        val sbHeightLandscape = 60
+        val currentRotation = ROTATION_NONE
+
+        `when`(windowMetrics.bounds).thenReturn(screenBounds)
+        `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+        // THEN rotations which share a short side should use the greater value between rounded
+        // corner padding and the display cutout's size
+        var targetRotation = ROTATION_NONE
+        var expectedBounds = Rect(dcBounds.right,
+                0,
+                screenBounds.right - roundedCornerPadding,
+                sbHeightPortrait)
+
+        var bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightPortrait,
+                roundedCornerPadding)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_LANDSCAPE
+        expectedBounds = Rect(dcBounds.height(),
+                0,
+                screenBounds.height() - roundedCornerPadding,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightLandscape,
+                roundedCornerPadding)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        // THEN the side that does NOT share a short side with the display cutout ignores the
+        // display cutout bounds
+        targetRotation = ROTATION_UPSIDE_DOWN
+        expectedBounds = Rect(roundedCornerPadding,
+                0,
+                screenBounds.width() - roundedCornerPadding,
+                sbHeightPortrait)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightPortrait,
+                roundedCornerPadding)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        // Phone in portrait, seascape (rot_270) bounds
+        targetRotation = ROTATION_SEASCAPE
+        expectedBounds = Rect(roundedCornerPadding,
+                0,
+                screenBounds.height() - dcBounds.height(),
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightLandscape,
+                roundedCornerPadding)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+    }
+
+    @Test
+    fun testCalculateInsetsForRotationWithRotatedResources_nonCornerCutout() {
+        // GIVEN phone in portrait mode, where width < height and the cutout is not in the corner
+        // the assumption here is that if the cutout does NOT touch the corner then we have room to
+        // layout the status bar in the given space.
+
+        val screenBounds = Rect(0, 0, 1080, 2160)
+        // cutout centered at the top
+        val dcBounds = Rect(490, 0, 590, 100)
+        val roundedCornerPadding = 20
+        val sbHeightPortrait = 100
+        val sbHeightLandscape = 60
+        val currentRotation = ROTATION_NONE
+
+        `when`(windowMetrics.bounds).thenReturn(screenBounds)
+        `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+        // THEN only the landscape/seascape rotations should avoid the cutout area because of the
+        // potential letterboxing
+        var targetRotation = ROTATION_NONE
+        var expectedBounds = Rect(roundedCornerPadding,
+                0,
+                screenBounds.right - roundedCornerPadding,
+                sbHeightPortrait)
+
+        var bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightPortrait,
+                roundedCornerPadding)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_LANDSCAPE
+        expectedBounds = Rect(dcBounds.height(),
+                0,
+                screenBounds.height() - roundedCornerPadding,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightLandscape,
+                roundedCornerPadding)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_UPSIDE_DOWN
+        expectedBounds = Rect(roundedCornerPadding,
+                0,
+                screenBounds.right - roundedCornerPadding,
+                sbHeightPortrait)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightPortrait,
+                roundedCornerPadding)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_SEASCAPE
+        expectedBounds = Rect(roundedCornerPadding,
+                0,
+                screenBounds.height() - dcBounds.height(),
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightLandscape,
+                roundedCornerPadding)
+
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+    }
+
+    @Test
+    fun testCalculateInsetsForRotationWithRotatedResources_noCutout() {
+        // GIVEN device in portrait mode, where width < height and no cutout
+        val currentRotation = ROTATION_NONE
+        val screenBounds = Rect(0, 0, 1080, 2160)
+        val roundedCornerPadding = 20
+        val sbHeightPortrait = 100
+        val sbHeightLandscape = 60
+
+        `when`(windowMetrics.bounds).thenReturn(screenBounds)
+
+        // THEN content insets should only use rounded corner padding
+        var targetRotation = ROTATION_NONE
+        var expectedBounds = Rect(roundedCornerPadding,
+                0,
+                screenBounds.right - roundedCornerPadding,
+                sbHeightPortrait)
+
+        var bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                null, /* no cutout */
+                windowMetrics,
+                sbHeightPortrait,
+                roundedCornerPadding)
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_LANDSCAPE
+        expectedBounds = Rect(roundedCornerPadding,
+                0,
+                screenBounds.height() - roundedCornerPadding,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                null, /* no cutout */
+                windowMetrics,
+                sbHeightLandscape,
+                roundedCornerPadding)
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_UPSIDE_DOWN
+        expectedBounds = Rect(roundedCornerPadding,
+                0,
+                screenBounds.width() - roundedCornerPadding,
+                sbHeightPortrait)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                null, /* no cutout */
+                windowMetrics,
+                sbHeightPortrait,
+                roundedCornerPadding)
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+        targetRotation = ROTATION_LANDSCAPE
+        expectedBounds = Rect(roundedCornerPadding,
+                0,
+                screenBounds.height() - roundedCornerPadding,
+                sbHeightLandscape)
+
+        bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                null, /* no cutout */
+                windowMetrics,
+                sbHeightLandscape,
+                roundedCornerPadding)
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+    }
+
+    private fun assertRects(
+        expected: Rect,
+        actual: Rect,
+        @Rotation currentRotation: Int,
+        @Rotation targetRotation: Int
+    ) {
+        assertTrue(
+                "Rects must match. currentRotation=${RotationUtils.toString(currentRotation)}" +
+                " targetRotation=${RotationUtils.toString(targetRotation)}" +
+                " expected=$expected actual=$actual",
+                expected.equals(actual))
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 6cb374a..4e6e91a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -771,9 +771,7 @@
                 default:
                     throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
             }
-            // TODO(b/187223764): busTime won't be needed once end_session is a field in BUS.
-            final long busTime = System.currentTimeMillis();
-            final byte[] statsProto = bus.getStatsProto(busTime);
+            final byte[] statsProto = bus.getStatsProto();
 
             data.add(FrameworkStatsLog.buildStatsEvent(atomTag, statsProto));
 
diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java
index d6060fa..88c9850 100644
--- a/services/core/java/com/android/server/power/WakeLockLog.java
+++ b/services/core/java/com/android/server/power/WakeLockLog.java
@@ -16,16 +16,12 @@
 
 package com.android.server.power;
 
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
 import android.os.PowerManager;
 import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.SomeArgs;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -74,8 +70,6 @@
 
     private static final boolean DEBUG = false;
 
-    private static final int MSG_ON_WAKE_LOCK_EVENT = 1;
-
     private static final int TYPE_TIME_RESET = 0x0;
     private static final int TYPE_ACQUIRE = 0x1;
     private static final int TYPE_RELEASE = 0x2;
@@ -130,7 +124,6 @@
     private final Injector mInjector;
     private final TheLog mLog;
     private final TagDatabase mTagDatabase;
-    private final Handler mHandler;
     private final SimpleDateFormat mDumpsysDateFormat;
 
     WakeLockLog() {
@@ -140,7 +133,6 @@
     @VisibleForTesting
     WakeLockLog(Injector injector) {
         mInjector = injector;
-        mHandler = new WakeLockLogHandler(injector.getLooper());
         mTagDatabase = new TagDatabase(injector);
         EntryByteTranslator translator = new EntryByteTranslator(mTagDatabase);
         mLog = new TheLog(injector, translator, mTagDatabase);
@@ -172,7 +164,7 @@
      * Dumps all the wake lock data currently saved in the wake lock log to the specified
      * {@code PrintWriter}.
      *
-     * @param The {@code PrintWriter} to write to.
+     * @param pw The {@code PrintWriter} to write to.
      */
     public void dump(PrintWriter pw) {
         dump(pw, false);
@@ -221,9 +213,6 @@
     /**
      * Adds a new entry to the log based on the specified wake lock parameters.
      *
-     * Grabs the current time for the event and then posts the rest of the logic (actually
-     * adding it to the log) to a background thread.
-     *
      * @param eventType The type of event (ACQUIRE, RELEASE);
      * @param tag The wake lock's identifying tag.
      * @param ownerUid The owner UID of the wake lock.
@@ -239,15 +228,11 @@
         }
 
         final long time = mInjector.currentTimeMillis();
-
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = tagNameReducer(tag);
-        args.argi1 = eventType;
-        args.argi2 = ownerUid;
-        args.argi3 = eventType == TYPE_ACQUIRE ? translateFlagsFromPowerManager(flags) : 0;
-        args.argi4 = (int) ((time >> 32) & 0xFFFFFFFFL);
-        args.argi5 = (int) (time & 0xFFFFFFFFL);
-        mHandler.obtainMessage(MSG_ON_WAKE_LOCK_EVENT, args).sendToTarget();
+        final int translatedFlags = eventType == TYPE_ACQUIRE
+                ? translateFlagsFromPowerManager(flags)
+                : 0;
+        handleWakeLockEventInternal(eventType, tagNameReducer(tag), ownerUid, translatedFlags,
+                time);
     }
 
     /**
@@ -273,8 +258,8 @@
      * flags, {@code WakeLockLog.FLAG_*}, and a log-level, {@code WakeLockLog.LEVEL_*}.
      *
      * @param flags Wake lock flags including {@code PowerManager.*_WAKE_LOCK}
-     *              {@link PowerManager.ACQUIRE_CAUSES_WAKEUP}, and
-     *              {@link PowerManager.ON_AFTER_RELEASE}.
+     *              {@link PowerManager#ACQUIRE_CAUSES_WAKEUP}, and
+     *              {@link PowerManager#ON_AFTER_RELEASE}.
      * @return The compressed flags value.
      */
     int translateFlagsFromPowerManager(int flags) {
@@ -328,9 +313,9 @@
         }
 
         String reduciblePrefix = null;
-        for (int tp = 0; tp < REDUCED_TAG_PREFIXES.length; tp++) {
-            if (tag.startsWith(REDUCED_TAG_PREFIXES[tp])) {
-                reduciblePrefix = REDUCED_TAG_PREFIXES[tp];
+        for (String reducedTagPrefix : REDUCED_TAG_PREFIXES) {
+            if (tag.startsWith(reducedTagPrefix)) {
+                reduciblePrefix = reducedTagPrefix;
                 break;
             }
         }
@@ -339,7 +324,7 @@
             final StringBuilder sb = new StringBuilder();
 
             // add prefix first
-            sb.append(tag.substring(0, reduciblePrefix.length()));
+            sb.append(tag, 0, reduciblePrefix.length());
 
             // Stop looping on final marker
             final int end = Math.max(tag.lastIndexOf("/"), tag.lastIndexOf("."));
@@ -604,7 +589,7 @@
          * @return The number of bytes written to buffer, or required to write to the buffer.
          */
         int toBytes(LogEntry entry, byte[] bytes, long timeReference) {
-            int sizeNeeded = -1;
+            final int sizeNeeded;
             switch (entry.type) {
                 case TYPE_ACQUIRE: {
                     sizeNeeded = 3;
@@ -696,8 +681,9 @@
      * {@link EntryByteTranslator} to convert byte {@link LogEntry} to bytes within the buffer.
      *
      * This class also implements the logic around TIME_RESET events. Since the LogEntries store
-     * their time (8-bit) relative to the previous event, this class can add {@link TYPE_TIME_RESET}
-     * LogEntries as necessary to allow a LogEntry's relative time to fit within that range.
+     * their time (8-bit) relative to the previous event, this class can add
+     * {@link #TYPE_TIME_RESET} LogEntries as necessary to allow a LogEntry's relative time to fit
+     * within that range.
      */
     static class TheLog {
         private final EntryByteTranslator mTranslator;
@@ -711,7 +697,7 @@
         /**
          * Second temporary buffer used when reading and writing bytes from the buffer.
          * A second temporary buffer is necessary since additional items can be read concurrently
-         * from {@link mTempBuffer}. E.g., Adding an entry to a full buffer requires removing
+         * from {@link #mTempBuffer}. E.g., Adding an entry to a full buffer requires removing
          * other entries from the buffer.
          */
         private final byte[] mReadWriteTempBuffer = new byte[MAX_LOG_ENTRY_BYTE_SIZE];
@@ -832,7 +818,7 @@
          * Returns an {@link Iterator} of {@link LogEntry}s for all the entries in the log.
          *
          * If the log is modified while the entries are being read, the iterator will throw a
-         * {@link ConcurrentModificationExceptoin}.
+         * {@link ConcurrentModificationException}.
          *
          * @param tempEntry A temporary {@link LogEntry} instance to use so that new instances
          *                  aren't allocated with every call to {@code Iterator.next}.
@@ -1063,7 +1049,7 @@
      * instanced into bytes.
      *
      * If a new tag is added when the database is full, the oldest tag is removed. The oldest tag
-     * is calcualted using {@link TagData.lastUsedTime}.
+     * is calculated using {@link TagData#lastUsedTime}.
      */
     static class TagDatabase {
         private final int mInvalidIndex;
@@ -1086,9 +1072,9 @@
             int byteEstimate = 0;
             int tagSize = 0;
             int tags = 0;
-            for (int i = 0; i < mArray.length; i++) {
+            for (TagData tagData : mArray) {
                 byteEstimate += 8;  // reference pointer
-                TagData data = mArray[i];
+                TagData data = tagData;
                 if (data != null) {
                     entries++;
                     byteEstimate += data.getByteSize();
@@ -1280,10 +1266,6 @@
 
         @Override
         public String toString() {
-            StringBuilder sb = new StringBuilder();
-            if (DEBUG) {
-                sb.append("(").append(index).append(")");
-            }
             return "[" + ownerUid + " ; " + tag + "]";
         }
 
@@ -1308,10 +1290,6 @@
      * Injector used by {@link WakeLockLog} for testing purposes.
      */
     public static class Injector {
-        public Looper getLooper() {
-            return BackgroundThread.get().getLooper();
-        }
-
         public int getTagDatabaseSize() {
             return TAG_DATABASE_SIZE;
         }
@@ -1328,28 +1306,4 @@
             return DATE_FORMAT;
         }
     }
-
-    private class WakeLockLogHandler extends Handler {
-        WakeLockLogHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message message) {
-            switch(message.what) {
-                case MSG_ON_WAKE_LOCK_EVENT:
-                    final SomeArgs args = (SomeArgs) message.obj;
-                    final String tag = (String) args.arg1;
-                    final int eventType = args.argi1;
-                    final int ownerUid = args.argi2;
-                    final int flags = args.argi3;
-                    final long time = (((long) args.argi4) << 32) + (args.argi5 & 0xFFFFFFFFL);
-                    args.recycle();
-                    handleWakeLockEventInternal(eventType, tag, ownerUid, flags, time);
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index a82c91e..dc868b3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -126,6 +126,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.connectivity.WifiActivityEnergyInfo;
+import android.os.incremental.IncrementalManager;
 import android.os.storage.DiskInfo;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
@@ -426,6 +427,7 @@
     private final Object mHealthHalLock = new Object();
     private final Object mAttributedAppOpsLock = new Object();
     private final Object mSettingsStatsLock = new Object();
+    private final Object mInstalledIncrementalPackagesLock = new Object();
 
     public StatsPullAtomService(Context context) {
         super(context);
@@ -695,6 +697,10 @@
                         synchronized (mSettingsStatsLock) {
                             return pullSettingsStatsLocked(atomTag, data);
                         }
+                    case FrameworkStatsLog.INSTALLED_INCREMENTAL_PACKAGE:
+                        synchronized (mInstalledIncrementalPackagesLock) {
+                            return pullInstalledIncrementalPackagesLocked(atomTag, data);
+                        }
                     default:
                         throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
                 }
@@ -877,6 +883,7 @@
         registerBatteryVoltage();
         registerBatteryCycleCount();
         registerSettingsStats();
+        registerInstalledIncrementalPackages();
     }
 
     private void initAndRegisterNetworkStatsPullers() {
@@ -3949,6 +3956,31 @@
         return StatsManager.PULL_SUCCESS;
     }
 
+    private void registerInstalledIncrementalPackages() {
+        int tagId = FrameworkStatsLog.INSTALLED_INCREMENTAL_PACKAGE;
+        mStatsManager.setPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                DIRECT_EXECUTOR,
+                mStatsCallbackImpl
+        );
+    }
+
+    int pullInstalledIncrementalPackagesLocked(int atomTag, List<StatsEvent> pulledData) {
+        final PackageManager pm = mContext.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY)) {
+            // Incremental is not enabled on this device. The result list will be empty.
+            return StatsManager.PULL_SUCCESS;
+        }
+        List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
+        for (PackageInfo pi : installedPackages) {
+            if (IncrementalManager.isIncrementalPath(pi.applicationInfo.getBaseCodePath())) {
+                pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, pi.applicationInfo.uid));
+            }
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
     // Thermal event received from vendor thermal management subsystem
     private static final class ThermalEventListener extends IThermalEventListener.Stub {
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
index a03ba9c..09612e3 100644
--- a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
@@ -20,12 +20,8 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import android.os.Looper;
 import android.os.PowerManager;
-import android.os.test.TestLooper;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.PrintWriter;
@@ -38,17 +34,6 @@
  */
 public class WakeLockLogTest {
 
-    private TestLooper mTestLooper;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-    }
-
     @Test
     public void testAddTwoItems() {
         final int tagDatabaseSize = 128;
@@ -70,7 +55,7 @@
                 + "  -\n"
                 + "  Events: 2, Time-Resets: 0\n"
                 + "  Buffer, Bytes used: 6\n",
-                dispatchAndDump(log, false));
+                dumpLog(log, false));
     }
 
     @Test
@@ -92,7 +77,7 @@
                 + "  -\n"
                 + "  Events: 2, Time-Resets: 1\n"
                 + "  Buffer, Bytes used: 15\n",
-                dispatchAndDump(log, false));
+                dumpLog(log, false));
     }
 
     @Test
@@ -114,7 +99,7 @@
                 + "  -\n"
                 + "  Events: 2, Time-Resets: 0\n"
                 + "  Buffer, Bytes used: 6\n",
-                dispatchAndDump(log, false));
+                dumpLog(log, false));
     }
 
     @Test
@@ -142,7 +127,7 @@
                 + "  -\n"
                 + "  Events: 3, Time-Resets: 0\n"
                 + "  Buffer, Bytes used: 9\n",
-                dispatchAndDump(log, false));
+                dumpLog(log, false));
     }
 
     @Test
@@ -160,7 +145,7 @@
                 + "  -\n"
                 + "  Events: 0, Time-Resets: 0\n"
                 + "  Buffer, Bytes used: 0\n",
-                dispatchAndDump(log, false));
+                dumpLog(log, false));
     }
 
     @Test
@@ -179,7 +164,7 @@
                 + "  -\n"
                 + "  Events: 1, Time-Resets: 0\n"
                 + "  Buffer, Bytes used: 3\n",
-                dispatchAndDump(log, false));
+                dumpLog(log, false));
     }
 
     @Test
@@ -201,7 +186,7 @@
                 + "  Events: 2, Time-Resets: 0\n"
                 + "  Buffer, Bytes used: 5\n"
                 + "  Tag Database: size(5), entries: 1, Bytes used: 80\n",
-                dispatchAndDump(log, true));
+                dumpLog(log, true));
     }
 
     @Test
@@ -223,11 +208,10 @@
                 + "  -\n"
                 + "  Events: 1, Time-Resets: 0\n"
                 + "  Buffer, Bytes used: 3\n",
-                dispatchAndDump(log, false));
+                dumpLog(log, false));
     }
 
-    private String dispatchAndDump(WakeLockLog log, boolean includeTagDb) {
-        mTestLooper.dispatchAll();
+    private String dumpLog(WakeLockLog log, boolean includeTagDb) {
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
         log.dump(pw, includeTagDb);
@@ -244,11 +228,6 @@
         }
 
         @Override
-        public Looper getLooper() {
-            return mTestLooper.getLooper();
-        }
-
-        @Override
         public int getTagDatabaseSize() {
             return mTagDatabaseSize;
         }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c527e66..6b7fc2f 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3682,6 +3682,13 @@
             "emergency_number_prefix_string_array";
 
     /**
+     * Indicates whether carrier treats "*67" or "*82" as a temporary mode CLIR.
+     * @hide
+     */
+    public static final String KEY_CARRIER_SUPPORTS_CALLER_ID_VERTICAL_SERVICE_CODES_BOOL =
+            "carrier_supports_caller_id_vertical_service_codes_bool";
+
+    /**
      * Smart forwarding config. Smart forwarding is a feature to configure call forwarding to a
      * different SIM in the device when one SIM is not reachable. The config here specifies a smart
      * forwarding component that will launch UI for changing the configuration. An empty string
@@ -5496,6 +5503,7 @@
                         1 /* Roaming Indicator Off */
                 });
         sDefaults.putStringArray(KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY, new String[0]);
+        sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_CALLER_ID_VERTICAL_SERVICE_CODES_BOOL, false);
         sDefaults.putBoolean(KEY_USE_USIM_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL, false);
         sDefaults.putBoolean(KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION, true);