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,