Merge "Log why presentation was not shown" into tm-dev
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c6f5920..cf141c62 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -298,6 +298,11 @@
     /** Use background GC policy and default JIT threshold. */
     private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
 
+    /** The delay time for retrying to request DirectActions. */
+    private static final long REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS = 200;
+    /** The max count for retrying to request DirectActions. */
+    private static final int REQUEST_DIRECT_ACTIONS_RETRY_MAX_COUNT = 3;
+
     /**
      * Denotes an invalid sequence number corresponding to a process state change.
      */
@@ -1864,7 +1869,8 @@
                 cancellationCallback.sendResult(cancellationResult);
             }
             mH.sendMessage(PooledLambda.obtainMessage(ActivityThread::handleRequestDirectActions,
-                    ActivityThread.this, activityToken, interactor, cancellationSignal, callback));
+                    ActivityThread.this, activityToken, interactor, cancellationSignal, callback,
+                    REQUEST_DIRECT_ACTIONS_RETRY_MAX_COUNT));
         }
 
         @Override
@@ -3970,7 +3976,7 @@
     /** Fetches the user actions for the corresponding activity */
     private void handleRequestDirectActions(@NonNull IBinder activityToken,
             @NonNull IVoiceInteractor interactor, @NonNull CancellationSignal cancellationSignal,
-            @NonNull RemoteCallback callback) {
+            @NonNull RemoteCallback callback, int retryCount) {
         final ActivityClientRecord r = mActivities.get(activityToken);
         if (r == null) {
             Log.w(TAG, "requestDirectActions(): no activity for " + activityToken);
@@ -3978,7 +3984,20 @@
             return;
         }
         final int lifecycleState = r.getLifecycleState();
-        if (lifecycleState < ON_START || lifecycleState >= ON_STOP) {
+        if (lifecycleState < ON_START) {
+            // TODO(b/234173463): requestDirectActions callback should indicate errors
+            if (retryCount > 0) {
+                mH.sendMessageDelayed(
+                        PooledLambda.obtainMessage(ActivityThread::handleRequestDirectActions,
+                                ActivityThread.this, activityToken, interactor, cancellationSignal,
+                                callback, retryCount - 1), REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS);
+                return;
+            }
+            Log.w(TAG, "requestDirectActions(" + r + "): wrong lifecycle: " + lifecycleState);
+            callback.sendResult(null);
+            return;
+        }
+        if (lifecycleState >= ON_STOP) {
             Log.w(TAG, "requestDirectActions(" + r + "): wrong lifecycle: " + lifecycleState);
             callback.sendResult(null);
             return;
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index ef46624..067a4c3 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -36,6 +36,7 @@
 import com.android.internal.graphics.palette.CelebiQuantizer;
 import com.android.internal.graphics.palette.Palette;
 import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
+import com.android.internal.util.ContrastColorUtil;
 
 import java.io.FileOutputStream;
 import java.lang.annotation.Retention;
@@ -93,10 +94,18 @@
     // using the area instead. This way our comparisons are aspect ratio independent.
     private static final int MAX_WALLPAPER_EXTRACTION_AREA = MAX_BITMAP_SIZE * MAX_BITMAP_SIZE;
 
-    // Decides when dark theme is optimal for this wallpaper.
-    // The midpoint of perceptual luminance, 50, is 18.42 in relative luminance.
-    // ColorUtils.calculateLuminance returns relative luminance on a scale from 0 to 1.
-    private static final float DARK_THEME_MEAN_LUMINANCE = 0.1842f;
+    // When extracting the main colors, only consider colors
+    // present in at least MIN_COLOR_OCCURRENCE of the image
+    private static final float MIN_COLOR_OCCURRENCE = 0.05f;
+
+    // Decides when dark theme is optimal for this wallpaper
+    private static final float DARK_THEME_MEAN_LUMINANCE = 0.3f;
+    // Minimum mean luminosity that an image needs to have to support dark text
+    private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.7f;
+    // We also check if the image has dark pixels in it,
+    // to avoid bright images with some dark spots.
+    private static final float DARK_PIXEL_CONTRAST = 5.5f;
+    private static final float MAX_DARK_AREA = 0.05f;
 
     private final List<Color> mMainColors;
     private final Map<Integer, Integer> mAllColors;
@@ -244,9 +253,12 @@
         this(primaryColor, secondaryColor, tertiaryColor, 0);
 
         // Calculate dark theme support based on primary color.
-        final double relativeLuminance = ColorUtils.calculateLuminance(primaryColor.toArgb());
-        final boolean wallpaperIsDark = relativeLuminance < DARK_THEME_MEAN_LUMINANCE;
-        mColorHints |= wallpaperIsDark ? HINT_SUPPORTS_DARK_THEME : HINT_SUPPORTS_DARK_TEXT;
+        final float[] tmpHsl = new float[3];
+        ColorUtils.colorToHSL(primaryColor.toArgb(), tmpHsl);
+        final float luminance = tmpHsl[2];
+        if (luminance < DARK_THEME_MEAN_LUMINANCE) {
+            mColorHints |= HINT_SUPPORTS_DARK_THEME;
+        }
     }
 
     /**
@@ -524,6 +536,9 @@
 
         dimAmount = MathUtils.saturate(dimAmount);
         int[] pixels = new int[source.getWidth() * source.getHeight()];
+        double totalLuminance = 0;
+        final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA);
+        int darkPixels = 0;
         source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */,
                 source.getWidth(), source.getHeight());
 
@@ -532,70 +547,42 @@
         int dimmingLayerAlpha = (int) (255 * dimAmount);
         int blackTransparent = ColorUtils.setAlphaComponent(Color.BLACK, dimmingLayerAlpha);
 
-        // The median luminance in the wallpaper will be used to decide if it is light or dark.
-        //
-        // Calculating the luminances, adding them to a data structure, then selecting
-        // the middle element would be expensive, the sort would be O(n), where n is the number
-        // of pixels.
-        //
-        // Instead, we create an integer array with 101 elements initialized to zero.
-        // Why 101? 0 through 100, inclusive, matching the range of luminance.
-        // Then, for each pixel, the luminance is calculated, and the integer at the array index
-        // equal to the rounded luminance is incremented.
-        //
-        // After processing the pixels, the median luminance is determined by iterating over the
-        // array containing the count for each luminance. Starting from 0, we adding the count at
-        // each index until pixels.length/2 is exceeded. When that occurs, it means the current
-        // array index contains the pixel of median luminance, thus the current array index is the
-        // median luminance.
-        int[] luminanceCounts = new int[101];
+        // This bitmap was already resized to fit the maximum allowed area.
+        // Let's just loop through the pixels, no sweat!
+        float[] tmpHsl = new float[3];
         for (int i = 0; i < pixels.length; i++) {
             int pixelColor = pixels[i];
+            ColorUtils.colorToHSL(pixelColor, tmpHsl);
             final int alpha = Color.alpha(pixelColor);
-            if (alpha == 0) {
-                continue;
+
+            // Apply composite colors where the foreground is a black layer with an alpha value of
+            // the dim amount and the background is the wallpaper pixel color.
+            int compositeColors = ColorUtils.compositeColors(blackTransparent, pixelColor);
+
+            // Calculate the adjusted luminance of the dimmed wallpaper pixel color.
+            double adjustedLuminance = ColorUtils.calculateLuminance(compositeColors);
+
+            // Make sure we don't have a dark pixel mass that will
+            // make text illegible.
+            final boolean satisfiesTextContrast = ContrastColorUtil
+                    .calculateContrast(pixelColor, Color.BLACK) > DARK_PIXEL_CONTRAST;
+            if (!satisfiesTextContrast && alpha != 0) {
+                darkPixels++;
+                if (DEBUG_DARK_PIXELS) {
+                    pixels[i] = Color.RED;
+                }
             }
-
-            // If the wallpaper is dimmed, apply dimming before calculating luminance.
-            int compositeColor = dimAmount <= 0 ? pixelColor :
-                    ColorUtils.compositeColors(blackTransparent, pixelColor);
-
-            // calculateLuminance will return relative luminance on a scale from 0 to 1. Intent
-            // is normalize to 0 to 100, and that is done by defensively normalizing to
-            // luminanceCounts.length, then flooring the result to defensively avoid any imprecision
-            // in the result of calculateLuminance that could cause it to exceed 1 and thus the
-            // array bounds.
-            float relativeLuminance = (float) ColorUtils.calculateLuminance(compositeColor)
-                    * luminanceCounts.length - 1;
-            int intRelativeLuminance = (int) Math.floor(relativeLuminance);
-            int clampedRelativeLuminance = (intRelativeLuminance < 0) ? 0 :
-                    (intRelativeLuminance > luminanceCounts.length - 1) ? luminanceCounts.length - 1
-                            : intRelativeLuminance;
-            luminanceCounts[clampedRelativeLuminance] += 1;
+            totalLuminance += adjustedLuminance;
         }
 
-        int criticalRelativeLuminance = 0;
-        int luminancesProcessed = 0;
-        int criticalLuminanceIndex = (int) Math.floor(pixels.length * 0.5);
-        for (int i = 0; i < 100; i++) {
-            luminancesProcessed += luminanceCounts[i];
-            if (luminancesProcessed > criticalLuminanceIndex) {
-                criticalRelativeLuminance = i;
-                break;
-            }
-        }
-
-        // Wallpaper is dark if the median pixel is less than mid-gray.
-        //
-        // Relative luminance places mid-gray at 18.42, 19 is used since luminances were rounded to
-        // ints to be stored in the array.
-        //
-        // Why not use perceptual luminance? It would require one more conversion step at each
-        // pixel, or adding a function to convert relative luminance to perceptual luminance just
-        // for this line.
-        boolean wallpaperIsDark = criticalRelativeLuminance < 19;
         int hints = 0;
-        hints |= wallpaperIsDark ? HINT_SUPPORTS_DARK_THEME : HINT_SUPPORTS_DARK_TEXT;
+        double meanLuminance = totalLuminance / pixels.length;
+        if (meanLuminance > BRIGHT_IMAGE_MEAN_LUMINANCE && darkPixels < maxDarkPixels) {
+            hints |= HINT_SUPPORTS_DARK_TEXT;
+        }
+        if (meanLuminance < DARK_THEME_MEAN_LUMINANCE) {
+            hints |= HINT_SUPPORTS_DARK_THEME;
+        }
 
         if (DEBUG_DARK_PIXELS) {
             try (FileOutputStream out = new FileOutputStream("/data/pixels.png")) {
@@ -605,8 +592,8 @@
             } catch (Exception e) {
                 e.printStackTrace();
             }
-            Log.d("WallpaperColors", "median relative L: " + criticalRelativeLuminance
-                    + ", numPixels: " + pixels.length);
+            Log.d("WallpaperColors", "l: " + meanLuminance + ", d: " + darkPixels +
+                    " maxD: " + maxDarkPixels + " numPixels: " + pixels.length);
         }
 
         return hints;
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 0b05d94..2922ad6 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -210,6 +210,7 @@
                 .append(", scaleFactor=").append(scaleFactor)
                 .append(", transform=").append(transform)
                 .append(", windowToken=").append(windowToken)
+                .append(", isClone=").append(isClone)
                 .toString();
 
     }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 61c844a..66abe30 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -300,6 +300,8 @@
 
     @VisibleForTesting
     protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
+    private final EnterTransitionAnimationDelegate mEnterTransitionAnimationDelegate =
+            new EnterTransitionAnimationDelegate();
 
     private boolean mRemoveSharedElements = false;
 
@@ -383,7 +385,7 @@
                         // transition animation.
                         getWindow().setWindowAnimations(0);
                     }
-                    startPostponedEnterTransition();
+                    mEnterTransitionAnimationDelegate.markImagePreviewReady();
                     return true;
                 }
             });
@@ -431,7 +433,7 @@
                     mHideParentOnFail = false;
                 }
                 mRemoveSharedElements = true;
-                startPostponedEnterTransition();
+                mEnterTransitionAnimationDelegate.markImagePreviewReady();
             }
         }
 
@@ -724,7 +726,7 @@
                 mRemoveSharedElements = false;
             }
         });
-        postponeEnterTransition();
+        mEnterTransitionAnimationDelegate.postponeTransition();
     }
 
     @Override
@@ -1242,6 +1244,9 @@
         if (layout != null) {
             adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
         }
+        if (previewType != CONTENT_PREVIEW_IMAGE) {
+            mEnterTransitionAnimationDelegate.markImagePreviewReady();
+        }
 
         return layout;
     }
@@ -2514,90 +2519,96 @@
                 if (mResolverDrawerLayout == null || gridAdapter == null) {
                     return;
                 }
-
-                final int bottomInset = mSystemWindowInsets != null
-                                            ? mSystemWindowInsets.bottom : 0;
-                int offset = bottomInset;
-                int rowsToShow = gridAdapter.getSystemRowCount()
-                        + gridAdapter.getProfileRowCount()
-                        + gridAdapter.getServiceTargetRowCount()
-                        + gridAdapter.getCallerAndRankedTargetRowCount();
-
-                // then this is most likely not a SEND_* action, so check
-                // the app target count
-                if (rowsToShow == 0) {
-                    rowsToShow = gridAdapter.getRowCount();
-                }
-
-                // still zero? then use a default height and leave, which
-                // can happen when there are no targets to show
-                if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
-                    offset += getResources().getDimensionPixelSize(
-                            R.dimen.chooser_max_collapsed_height);
-                    mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
-                    return;
-                }
-
-                View stickyContentPreview = findViewById(R.id.content_preview_container);
-                if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
-                    offset += stickyContentPreview.getHeight();
-                }
-
-                if (shouldShowTabs()) {
-                    offset += findViewById(R.id.tabs).getHeight();
-                }
-
-                if (recyclerView.getVisibility() == View.VISIBLE) {
-                    int directShareHeight = 0;
-                    rowsToShow = Math.min(4, rowsToShow);
-                    boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow);
-                    mLastNumberOfChildren = recyclerView.getChildCount();
-                    for (int i = 0, childCount = recyclerView.getChildCount();
-                            i < childCount && rowsToShow > 0; i++) {
-                        View child = recyclerView.getChildAt(i);
-                        if (((GridLayoutManager.LayoutParams)
-                                child.getLayoutParams()).getSpanIndex() != 0) {
-                            continue;
-                        }
-                        int height = child.getHeight();
-                        offset += height;
-                        if (shouldShowExtraRow) {
-                            offset += height;
-                        }
-
-                        if (gridAdapter.getTargetType(
-                                recyclerView.getChildAdapterPosition(child))
-                                == ChooserListAdapter.TARGET_SERVICE) {
-                            directShareHeight = height;
-                        }
-                        rowsToShow--;
-                    }
-
-                    boolean isExpandable = getResources().getConfiguration().orientation
-                            == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
-                    if (directShareHeight != 0 && isSendAction(getTargetIntent())
-                            && isExpandable) {
-                        // make sure to leave room for direct share 4->8 expansion
-                        int requiredExpansionHeight =
-                                (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
-                        int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
-                        int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
-                                - requiredExpansionHeight - topInset - bottomInset;
-
-                        offset = Math.min(offset, minHeight);
-                    }
-                } else {
-                    ViewGroup currentEmptyStateView = getActiveEmptyStateView();
-                    if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
-                        offset += currentEmptyStateView.getHeight();
-                    }
-                }
-
-                mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
+                int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter);
+                mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
+                mEnterTransitionAnimationDelegate.markOffsetCalculated();
             });
         }
     }
 
+    private int calculateDrawerOffset(
+            int top, int bottom, RecyclerView recyclerView, ChooserGridAdapter gridAdapter) {
+
+        final int bottomInset = mSystemWindowInsets != null
+                ? mSystemWindowInsets.bottom : 0;
+        int offset = bottomInset;
+        int rowsToShow = gridAdapter.getSystemRowCount()
+                + gridAdapter.getProfileRowCount()
+                + gridAdapter.getServiceTargetRowCount()
+                + gridAdapter.getCallerAndRankedTargetRowCount();
+
+        // then this is most likely not a SEND_* action, so check
+        // the app target count
+        if (rowsToShow == 0) {
+            rowsToShow = gridAdapter.getRowCount();
+        }
+
+        // still zero? then use a default height and leave, which
+        // can happen when there are no targets to show
+        if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
+            offset += getResources().getDimensionPixelSize(
+                    R.dimen.chooser_max_collapsed_height);
+            return offset;
+        }
+
+        View stickyContentPreview = findViewById(R.id.content_preview_container);
+        if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
+            offset += stickyContentPreview.getHeight();
+        }
+
+        if (shouldShowTabs()) {
+            offset += findViewById(R.id.tabs).getHeight();
+        }
+
+        if (recyclerView.getVisibility() == View.VISIBLE) {
+            int directShareHeight = 0;
+            rowsToShow = Math.min(4, rowsToShow);
+            boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow);
+            mLastNumberOfChildren = recyclerView.getChildCount();
+            for (int i = 0, childCount = recyclerView.getChildCount();
+                    i < childCount && rowsToShow > 0; i++) {
+                View child = recyclerView.getChildAt(i);
+                if (((GridLayoutManager.LayoutParams)
+                        child.getLayoutParams()).getSpanIndex() != 0) {
+                    continue;
+                }
+                int height = child.getHeight();
+                offset += height;
+                if (shouldShowExtraRow) {
+                    offset += height;
+                }
+
+                if (gridAdapter.getTargetType(
+                        recyclerView.getChildAdapterPosition(child))
+                        == ChooserListAdapter.TARGET_SERVICE) {
+                    directShareHeight = height;
+                }
+                rowsToShow--;
+            }
+
+            boolean isExpandable = getResources().getConfiguration().orientation
+                    == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
+            if (directShareHeight != 0 && isSendAction(getTargetIntent())
+                    && isExpandable) {
+                // make sure to leave room for direct share 4->8 expansion
+                int requiredExpansionHeight =
+                        (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
+                int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
+                int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
+                        - requiredExpansionHeight - topInset - bottomInset;
+
+                offset = Math.min(offset, minHeight);
+            }
+        } else {
+            ViewGroup currentEmptyStateView = getActiveEmptyStateView();
+            if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
+                offset += currentEmptyStateView.getHeight();
+            }
+        }
+
+        return Math.min(offset, bottom - top);
+    }
+
     /**
      * If we have a tabbed view and are showing 1 row in the current profile and an empty
      * state screen in the other profile, to prevent cropping of the empty state screen we show
@@ -3930,6 +3941,51 @@
         }
     }
 
+    /**
+     * A helper class to track app's readiness for the scene transition animation.
+     * The app is ready when both the image is laid out and the drawer offset is calculated.
+     */
+    private class EnterTransitionAnimationDelegate implements View.OnLayoutChangeListener {
+        private boolean mPreviewReady = false;
+        private boolean mOffsetCalculated = false;
+
+        void postponeTransition() {
+            postponeEnterTransition();
+        }
+
+        void markImagePreviewReady() {
+            if (!mPreviewReady) {
+                mPreviewReady = true;
+                maybeStartListenForLayout();
+            }
+        }
+
+        void markOffsetCalculated() {
+            if (!mOffsetCalculated) {
+                mOffsetCalculated = true;
+                maybeStartListenForLayout();
+            }
+        }
+
+        private void maybeStartListenForLayout() {
+            if (mPreviewReady && mOffsetCalculated && mResolverDrawerLayout != null) {
+                if (mResolverDrawerLayout.isInLayout()) {
+                    startPostponedEnterTransition();
+                } else {
+                    mResolverDrawerLayout.addOnLayoutChangeListener(this);
+                    mResolverDrawerLayout.requestLayout();
+                }
+            }
+        }
+
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+                int oldTop, int oldRight, int oldBottom) {
+            v.removeOnLayoutChangeListener(this);
+            startPostponedEnterTransition();
+        }
+    }
+
     @Override
     protected void maybeLogProfileChange() {
         getChooserActivityLogger().logShareheetProfileChanged();
diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
index 18f870d..3d78489 100644
--- a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
+++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
@@ -24,6 +24,7 @@
         android:layout_height="wrap_content"
         android:layout_marginTop="40dp"
         android:layout_marginStart="4dp"
+        android:textColor="?android:attr/textColorSecondary"
         app:layout_constraintStart_toStartOf="@id/done_button"
         app:layout_constraintTop_toBottomOf="@id/done_button" />
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index b54b832..570d11b 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.text.Editable;
 import android.util.Log;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
@@ -93,7 +94,9 @@
     }
 
     private void saveToClipboard() {
-        ClipData clip = ClipData.newPlainText("text", mEditText.getText());
+        Editable editedText = mEditText.getText();
+        editedText.clearSpans();
+        ClipData clip = ClipData.newPlainText("text", editedText);
         PersistableBundle extras = new PersistableBundle();
         extras.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, mSensitive);
         clip.getDescription().setExtras(extras);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 8f06546..9fb4a60 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -35,6 +35,7 @@
 
 import androidx.core.graphics.drawable.IconCompat;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.qrcode.QrCodeGenerator;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastSender;
@@ -176,7 +177,8 @@
         refreshUi();
     }
 
-    private void refreshUi() {
+    @VisibleForTesting
+    void refreshUi() {
         setQrCodeView();
 
         mCurrentBroadcastName = getBroadcastMetadataInfo(METADATA_BROADCAST_NAME);
@@ -215,7 +217,8 @@
         mIsPasswordHide = !mIsPasswordHide;
     }
 
-    private void launchBroadcastUpdatedDialog(boolean isBroadcastCode, String editString) {
+    @VisibleForTesting
+    void launchBroadcastUpdatedDialog(boolean isBroadcastCode, String editString) {
         final View layout = LayoutInflater.from(mContext).inflate(
                 R.layout.media_output_broadcast_update_dialog, null);
         final EditText editText = layout.requireViewById(R.id.broadcast_edit_text);
@@ -242,7 +245,8 @@
         return mMediaOutputController.getBroadcastMetadata();
     }
 
-    private void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) {
+    @VisibleForTesting
+    void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) {
         Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
         if (positiveBtn != null) {
             positiveBtn.setEnabled(false);
@@ -345,4 +349,9 @@
             mBroadcastErrorMessage.setText(R.string.media_output_broadcast_last_update_error);
         }
     }
+
+    @VisibleForTesting
+    int getRetryCount() {
+        return mRetryCount;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
new file mode 100644
index 0000000..1767ccd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.LocalMediaManager;
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
+
+    private static final String TEST_PACKAGE = "test_package";
+    private static final String BROADCAST_NAME_TEST = "Broadcast_name_test";
+    private static final String BROADCAST_CODE_TEST = "112233";
+    private static final String BROADCAST_CODE_UPDATE_TEST = "11223344";
+
+    // Mock
+    private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
+    private MediaController mMediaController = mock(MediaController.class);
+    private PlaybackState mPlaybackState = mock(PlaybackState.class);
+    private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
+    private final LocalBluetoothProfileManager mLocalBluetoothProfileManager = mock(
+            LocalBluetoothProfileManager.class);
+    private final LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast = mock(
+            LocalBluetoothLeBroadcast.class);
+    private final ActivityStarter mStarter = mock(ActivityStarter.class);
+    private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
+    private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
+    private final MediaDevice mMediaDevice = mock(MediaDevice.class);
+    private final NotificationEntryManager mNotificationEntryManager =
+            mock(NotificationEntryManager.class);
+    private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
+            NearbyMediaDevicesManager.class);
+    private final LocalBluetoothLeBroadcastMetadata mLocalBluetoothLeBroadcastMetadata =
+            mock(LocalBluetoothLeBroadcastMetadata.class);
+
+    private List<MediaController> mMediaControllers = new ArrayList<>();
+    private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog;
+    private MediaOutputController mMediaOutputController;
+    private final List<String> mFeatures = new ArrayList<>();
+
+    @Before
+    public void setUp() {
+        when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile())
+                .thenReturn(mLocalBluetoothLeBroadcast);
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+        when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_NONE);
+        when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE);
+        mMediaControllers.add(mMediaController);
+        when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
+        when(mLocalBluetoothLeBroadcast.getLocalBluetoothLeBroadcastMetaData()).thenReturn(
+                mLocalBluetoothLeBroadcastMetadata);
+        when(mLocalBluetoothLeBroadcastMetadata.convertToQrCodeString())
+                .thenReturn("metadata_test_convert");
+        when(mLocalBluetoothLeBroadcast.getProgramInfo()).thenReturn(BROADCAST_NAME_TEST);
+        when(mLocalBluetoothLeBroadcast.getBroadcastCode())
+                .thenReturn(BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8));
+
+        mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
+                mMediaSessionManager, mLocalBluetoothManager, mStarter,
+                mNotificationEntryManager, mDialogLaunchAnimator,
+                Optional.of(mNearbyMediaDevicesManager));
+        mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
+        mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
+                mBroadcastSender, mMediaOutputController);
+        mMediaOutputBroadcastDialog.show();
+
+        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
+        when(mMediaDevice.getFeatures()).thenReturn(mFeatures);
+    }
+
+    @After
+    public void tearDown() {
+        mMediaOutputBroadcastDialog.dismissDialog();
+    }
+
+    @Test
+    public void refreshUi_checkBroadcastQrCodeView() {
+        mMediaOutputBroadcastDialog.refreshUi();
+        final ImageView broadcastQrCodeView = mMediaOutputBroadcastDialog.mDialogView
+                .requireViewById(R.id.qrcode_view);
+
+        assertThat(broadcastQrCodeView.getDrawable()).isNotNull();
+    }
+
+    @Test
+    public void refreshUi_checkBroadcastName() {
+        mMediaOutputBroadcastDialog.refreshUi();
+        final TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView
+                .requireViewById(R.id.broadcast_name_summary);
+
+        assertThat(broadcastName.getText().toString()).isEqualTo(BROADCAST_NAME_TEST);
+    }
+
+    @Test
+    public void refreshUi_checkBroadcastCode() {
+        mMediaOutputBroadcastDialog.refreshUi();
+        final TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView
+                .requireViewById(R.id.broadcast_code_summary);
+
+        assertThat(broadcastCode.getText().toString()).isEqualTo(BROADCAST_CODE_TEST);
+    }
+
+    @Test
+    public void updateBroadcastInfo_stopBroadcastFailed_handleFailedUI() {
+        mMediaOutputBroadcastDialog.launchBroadcastUpdatedDialog(true, BROADCAST_CODE_UPDATE_TEST);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile())
+                .thenReturn(null);
+
+        mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
+        assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(1);
+
+        mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
+        assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(2);
+
+        // It will be the MAX Retry Count = 3
+        mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
+        assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(0);
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.kt
new file mode 100644
index 0000000..d03b0a4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+
+import com.android.settingslib.media.MediaOutputConstants
+import com.android.systemui.SysuiTestCase
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MediaOutputDialogReceiverTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
+    @Mock
+    private lateinit var mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory
+
+    private lateinit var receiver: MediaOutputDialogReceiver
+    private lateinit var intent: Intent
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        receiver = MediaOutputDialogReceiver(
+                mediaOutputDialogFactory, mediaOutputBroadcastDialogFactory)
+    }
+
+    @Test
+    fun onReceive_intentWithPackageName_LaunchBroadcastDialog() {
+        intent = Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG).apply {
+            putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, "test_pkg") }
+
+        receiver.onReceive(mContext, intent)
+
+        verify(mediaOutputBroadcastDialogFactory, times(1)).create(anyString(), anyBoolean(), any())
+    }
+
+    @Test
+    fun onReceive_intentWithoutPackageName_doNotLaunchBroadcastDialog() {
+        intent = Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG).apply {
+            putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, "") }
+
+        receiver.onReceive(mContext, intent)
+
+        verify(mediaOutputBroadcastDialogFactory, never()).create(anyString(), anyBoolean(), any())
+    }
+}
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index bd2a97d..229799a 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -29,6 +29,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.CollectionUtils;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -267,6 +268,21 @@
         }
     }
 
+    /**
+     * Dumps current companion device association states.
+     */
+    public void dump(@NonNull PrintWriter out) {
+        out.append("Companion Device Associations: ");
+        if (getAssociations().isEmpty()) {
+            out.append("<empty>\n");
+        } else {
+            out.append("\n");
+            for (AssociationInfo a : getAssociations()) {
+                out.append("  ").append(a.toString()).append('\n');
+            }
+        }
+    }
+
     private void broadcastChange(@ChangeType int changeType, AssociationInfo association) {
         synchronized (mListeners) {
             for (OnChangeListener listener : mListeners) {
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 6eb8e26..64729a2 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -33,6 +33,7 @@
 import com.android.internal.infra.PerUser;
 import com.android.internal.util.CollectionUtils;
 
+import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -234,6 +235,28 @@
         primaryServiceConnector.postOnDeviceDisappeared(association);
     }
 
+    void dump(@NonNull PrintWriter out) {
+        out.append("Companion Device Application Controller: \n");
+
+        synchronized (mBoundCompanionApplications) {
+            out.append("  Bound Companion Applications: ");
+            if (mBoundCompanionApplications.size() == 0) {
+                out.append("<empty>\n");
+            } else {
+                out.append("\n");
+                mBoundCompanionApplications.dump(out);
+            }
+        }
+
+        out.append("  Companion Applications Scheduled For Rebinding: ");
+        if (mScheduledForRebindingCompanionApplications.size() == 0) {
+            out.append("<empty>\n");
+        } else {
+            out.append("\n");
+            mScheduledForRebindingCompanionApplications.dump(out);
+        }
+    }
+
     private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) {
         if (DEBUG) Log.i(TAG, "onPrimaryServiceBindingDied() u" + userId + "/" + packageName);
 
@@ -333,5 +356,23 @@
                 }
             }
         }
+
+        private void dump(@NonNull PrintWriter out) {
+            for (int i = 0; i < size(); i++) {
+                final int userId = keyAt(i);
+                final Map<String, T> forUser = get(userId);
+                if (forUser.isEmpty()) {
+                    out.append("    u").append(String.valueOf(userId)).append(": <empty>\n");
+                }
+
+                for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
+                    final String packageName = packageValue.getKey();
+                    final T value = packageValue.getValue();
+                    out.append("    u").append(String.valueOf(userId)).append("\\")
+                            .append(packageName).append(" -> ")
+                            .append(value.toString()).append('\n');
+                }
+            }
+        }
     }
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0bfe282..3f7cba6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -764,14 +764,9 @@
                 return;
             }
 
-            // TODO(b/218615185): mAssociationStore.dump() instead
-            out.append("Companion Device Associations:").append('\n');
-            for (AssociationInfo a : mAssociationStore.getAssociations()) {
-                out.append("  ").append(a.toString()).append('\n');
-            }
-
-            // TODO(b/218615185): mDevicePresenceMonitor.dump()
-            // TODO(b/218615185): mCompanionAppController.dump()
+            mAssociationStore.dump(out);
+            mDevicePresenceMonitor.dump(out);
+            mCompanionAppController.dump(out);
         }
     }
 
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 89ed301e..0e4870a 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -33,6 +33,7 @@
 
 import com.android.server.companion.AssociationStore;
 
+import java.io.PrintWriter;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -293,6 +294,54 @@
         throw new SecurityException("Caller is neither Shell nor Root");
     }
 
+    /**
+     * Dumps system information about devices that are marked as "present".
+     */
+    public void dump(@NonNull PrintWriter out) {
+        out.append("Companion Device Present: ");
+        if (mConnectedBtDevices.isEmpty()
+                && mNearbyBleDevices.isEmpty()
+                && mReportedSelfManagedDevices.isEmpty()) {
+            out.append("<empty>\n");
+            return;
+        } else {
+            out.append("\n");
+        }
+
+        out.append("  Connected Bluetooth Devices: ");
+        if (mConnectedBtDevices.isEmpty()) {
+            out.append("<empty>\n");
+        } else {
+            out.append("\n");
+            for (int associationId : mConnectedBtDevices) {
+                AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+                out.append("    ").append(a.toShortString()).append('\n');
+            }
+        }
+
+        out.append("  Nearby BLE Devices: ");
+        if (mNearbyBleDevices.isEmpty()) {
+            out.append("<empty>\n");
+        } else {
+            out.append("\n");
+            for (int associationId : mNearbyBleDevices) {
+                AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+                out.append("    ").append(a.toShortString()).append('\n');
+            }
+        }
+
+        out.append("  Self-Reported Devices: ");
+        if (mReportedSelfManagedDevices.isEmpty()) {
+            out.append("<empty>\n");
+        } else {
+            out.append("\n");
+            for (int associationId : mReportedSelfManagedDevices) {
+                AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+                out.append("    ").append(a.toShortString()).append('\n');
+            }
+        }
+    }
+
     private class SimulatedDevicePresenceSchedulerHelper extends Handler {
         SimulatedDevicePresenceSchedulerHelper() {
             super(Looper.getMainLooper());
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index ca7b789..7448611 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -102,13 +102,15 @@
             return;
         }
         synchronized (mSyncRoot) {
-            if (brightness == mBrightness) {
-                return;
+            // If the brightness is the same, we still need to update any listeners as the act of
+            // setting the brightness alone has side effects, like clearing any temporary
+            // brightness. We can skip persisting to disk, however, since it hasn't actually
+            // changed.
+            if (brightness != mBrightness) {
+                mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
+                        brightness);
             }
-
             mBrightness = brightness;
-            mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
-                    brightness);
             int toSend = Float.floatToIntBits(mBrightness);
             Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED, toSend, 0);
             mHandler.sendMessage(msg);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ec7ccc4..6285ef1 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -3349,7 +3349,7 @@
                 synchronized (mSyncRoot) {
                     DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
                     if (dpc != null) {
-                        dpc.putScreenBrightnessSetting(brightness);
+                        dpc.setBrightness(brightness);
                     }
                     mPersistentDataStore.saveIfNeeded();
                 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d13a9a3..f39c412 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1345,7 +1345,7 @@
                 if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
                     slowChange = true; // slowly adapt to auto-brightness
                 }
-                updateScreenBrightnessSetting = true;
+                updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
                 mAppliedAutoBrightness = true;
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
             } else {
@@ -1415,7 +1415,7 @@
             // before applying the low power or dim transformations so that the slider
             // accurately represents the full possible range, even if they range changes what
             // it means in absolute terms.
-            putScreenBrightnessSetting(brightnessState, /* updateCurrent */ true);
+            updateScreenBrightnessSetting(brightnessState);
         }
 
         // Apply dimming by at least some minimum amount when user activity
@@ -2288,17 +2288,18 @@
         return clampScreenBrightnessForVr(brightnessFloat);
     }
 
-    void putScreenBrightnessSetting(float brightnessValue) {
-        putScreenBrightnessSetting(brightnessValue, false);
+    void setBrightness(float brightnessValue) {
+        // Update the setting, which will eventually call back into DPC to have us actually update
+        // the display with the new value.
+        mBrightnessSetting.setBrightness(brightnessValue);
     }
 
-    private void putScreenBrightnessSetting(float brightnessValue, boolean updateCurrent) {
-        if (!isValidBrightnessValue(brightnessValue)) {
+    private void updateScreenBrightnessSetting(float brightnessValue) {
+        if (!isValidBrightnessValue(brightnessValue)
+                || brightnessValue == mCurrentScreenBrightnessSetting) {
             return;
         }
-        if (updateCurrent) {
-            setCurrentScreenBrightness(brightnessValue);
-        }
+        setCurrentScreenBrightness(brightnessValue);
         mBrightnessSetting.setBrightness(brightnessValue);
     }
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 2e1aaf8..aba7572 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -599,6 +599,7 @@
                     SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS);
         } catch (RuntimeException e) {
             Log.e(TAG, "Failed to request network.", e);
+            mSuplConnectivityCallback = null;
             handleReleaseSuplConnection(GPS_AGPS_DATA_CONN_FAILED);
         }
     }
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index 6e5011d..3b4aa8e 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -154,7 +154,7 @@
 
         for (InputWindowHandle window : windowHandles) {
             final boolean visible = (window.inputConfig & InputConfig.NOT_VISIBLE) == 0;
-            if (visible && window.getWindow() != null) {
+            if (visible && window.getWindow() != null && !window.isClone) {
                 tempVisibleWindows.add(window);
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 7723a46..fb87576 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -192,6 +192,7 @@
 
     private Task mInTask;
     private TaskFragment mInTaskFragment;
+    private TaskFragment mAddingToTaskFragment;
     @VisibleForTesting
     boolean mAddingToTask;
 
@@ -2254,20 +2255,27 @@
             // In this situation we want to remove all activities from the task up to the one
             // being started. In most cases this means we are resetting the task to its initial
             // state.
-            final ActivityRecord top = targetTask.performClearTop(mStartActivity, mLaunchFlags);
+            final ActivityRecord clearTop = targetTask.performClearTop(mStartActivity,
+                    mLaunchFlags);
 
-            if (top != null) {
-                if (top.isRootOfTask()) {
+            if (clearTop != null && !clearTop.finishing) {
+                if (clearTop.isRootOfTask()) {
                     // Activity aliases may mean we use different intents for the top activity,
                     // so make sure the task now has the identity of the new intent.
-                    top.getTask().setIntent(mStartActivity);
+                    clearTop.getTask().setIntent(mStartActivity);
                 }
-                deliverNewIntent(top, intentGrants);
+                deliverNewIntent(clearTop, intentGrants);
             } else {
                 // A special case: we need to start the activity because it is not currently
                 // running, and the caller has asked to clear the current task to have this
                 // activity at the top.
                 mAddingToTask = true;
+                // Adding the new activity to the same embedded TF of the clear-top activity if
+                // possible.
+                if (clearTop != null && clearTop.getTaskFragment() != null
+                        && clearTop.getTaskFragment().isEmbedded()) {
+                    mAddingToTaskFragment = clearTop.getTaskFragment();
+                }
                 if (targetTask.getRootTask() == null) {
                     // Target root task got cleared when we all activities were removed above.
                     // Go ahead and reset it.
@@ -2892,14 +2900,19 @@
                 newParent = mInTaskFragment;
             }
         } else {
-            final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */,
-                    false /* includingEmbeddedTask */);
-            final TaskFragment taskFragment = top != null ? top.getTaskFragment() : null;
-            if (taskFragment != null && taskFragment.isEmbedded()
-                    && canEmbedActivity(taskFragment, mStartActivity, false /* newTask */, task)) {
+            TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
+            if (candidateTf == null) {
+                final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */,
+                        false /* includingEmbeddedTask */);
+                if (top != null) {
+                    candidateTf = top.getTaskFragment();
+                }
+            }
+            if (candidateTf != null && candidateTf.isEmbedded()
+                    && canEmbedActivity(candidateTf, mStartActivity, false /* newTask */, task)) {
                 // Use the embedded TaskFragment of the top activity as the new parent if the
                 // activity can be embedded.
-                newParent = top.getTaskFragment();
+                newParent = candidateTf;
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1bec2a5..bf5246f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1644,7 +1644,7 @@
      * activities on top of it and return the instance.
      *
      * @param newR Description of the new activity being started.
-     * @return Returns the old activity that should be continued to be used,
+     * @return Returns the existing activity in the task that performs the clear-top operation,
      * or {@code null} if none was found.
      */
     private ActivityRecord clearTopActivities(ActivityRecord newR, int launchFlags) {
@@ -1663,7 +1663,6 @@
                 && !ActivityStarter.isDocumentLaunchesIntoExisting(launchFlags)) {
             if (!r.finishing) {
                 r.finishIfPossible("clear-task-top", false /* oomAdj */);
-                return null;
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 324f029..96811ef 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2061,9 +2061,15 @@
                 final boolean inPipTransition = windowingMode == WINDOWING_MODE_PINNED
                         && !mTmpFullBounds.isEmpty() && mTmpFullBounds.equals(parentBounds);
                 if (WindowConfiguration.isFloating(windowingMode) && !inPipTransition) {
-                    // For floating tasks, calculate the smallest width from the bounds of the task
+                    // For floating tasks, calculate the smallest width from the bounds of the
+                    // task, because they should not be affected by insets.
                     inOutConfig.smallestScreenWidthDp = (int) (
                             Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
+                } else if (isEmbedded()) {
+                    // For embedded TFs, the smallest width should be updated. Otherwise, inherit
+                    // from the parent task would result in applications loaded wrong resource.
+                    inOutConfig.smallestScreenWidthDp =
+                            Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
                 }
                 // otherwise, it will just inherit
             }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 9304761..202168b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -266,7 +266,7 @@
         // Detach from process so the activities can be removed from hierarchy when finishing.
         activity1.detachFromProcess();
         activity2.detachFromProcess();
-        assertNull(task.performClearTop(activity1, 0 /* launchFlags */));
+        assertTrue(task.performClearTop(activity1, 0 /* launchFlags */).finishing);
         assertFalse(task.hasChild());
         // In real case, the task should be preserved for adding new activity.
         assertTrue(task.isAttached());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 0558648..39a6868 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -70,6 +70,7 @@
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.wm.ActivityAssistInfo;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -86,10 +87,14 @@
 
     final static String CLOSE_REASON_VOICE_INTERACTION = "voiceinteraction";
 
+    /** The delay time for retrying to request DirectActions. */
+    private static final long REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS = 200;
+
     final boolean mValid;
 
     final Context mContext;
     final Handler mHandler;
+    final Handler mDirectActionsHandler;
     final VoiceInteractionManagerService.VoiceInteractionManagerServiceStub mServiceStub;
     final int mUser;
     final ComponentName mComponent;
@@ -184,6 +189,7 @@
             int userHandle, ComponentName service) {
         mContext = context;
         mHandler = handler;
+        mDirectActionsHandler = new Handler(true);
         mServiceStub = stub;
         mUser = userHandle;
         mComponent = service;
@@ -343,7 +349,10 @@
                 .getAttachedNonFinishingActivityForTask(taskId, null);
         if (tokens == null || tokens.getAssistToken() != assistToken) {
             Slog.w(TAG, "Unknown activity to query for direct actions");
-            callback.sendResult(null);
+            mDirectActionsHandler.sendMessageDelayed(PooledLambda.obtainMessage(
+                    VoiceInteractionManagerServiceImpl::retryRequestDirectActions,
+                    VoiceInteractionManagerServiceImpl.this, token, taskId, assistToken,
+                    cancellationCallback, callback), REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS);
         } else {
             try {
                 tokens.getApplicationThread().requestDirectActions(tokens.getActivityToken(),
@@ -355,6 +364,33 @@
         }
     }
 
+    private void retryRequestDirectActions(@NonNull IBinder token, int taskId,
+            @NonNull IBinder assistToken,  @Nullable RemoteCallback cancellationCallback,
+            @NonNull RemoteCallback callback) {
+        synchronized (mServiceStub) {
+            if (mActiveSession == null || token != mActiveSession.mToken) {
+                Slog.w(TAG, "retryRequestDirectActions does not match active session");
+                callback.sendResult(null);
+                return;
+            }
+            final ActivityTokens tokens = LocalServices.getService(
+                            ActivityTaskManagerInternal.class)
+                    .getAttachedNonFinishingActivityForTask(taskId, null);
+            if (tokens == null || tokens.getAssistToken() != assistToken) {
+                Slog.w(TAG, "Unknown activity to query for direct actions during retrying");
+                callback.sendResult(null);
+            } else {
+                try {
+                    tokens.getApplicationThread().requestDirectActions(tokens.getActivityToken(),
+                            mActiveSession.mInteractor, cancellationCallback, callback);
+                } catch (RemoteException e) {
+                    Slog.w("Unexpected remote error", e);
+                    callback.sendResult(null);
+                }
+            }
+        }
+    }
+
     void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
             @Nullable Bundle arguments, int taskId, IBinder assistToken,
             @Nullable RemoteCallback cancellationCallback,