diff options
37 files changed, 723 insertions, 284 deletions
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 2ae721648656..895dde760058 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -1507,8 +1507,9 @@ public class ActivityOptions extends ComponentOptions { } /** @hide */ - public void setRemoteTransition(@Nullable RemoteTransition remoteTransition) { + public ActivityOptions setRemoteTransition(@Nullable RemoteTransition remoteTransition) { mRemoteTransition = remoteTransition; + return this; } /** @hide */ diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index b7ec7b55d7db..d66fca8945f1 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -22,6 +22,7 @@ import android.os.PooledStringWriter; import android.os.RemoteException; import android.os.SystemClock; import android.service.autofill.FillRequest; +import android.text.Spanned; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -1557,6 +1558,10 @@ public class AssistStructure implements Parcelable { /** * Returns any text associated with the node that is displayed to the user, or null * if there is none. + * + * <p> The text will be stripped of any spans that could potentially contain reference to + * the activity context, to avoid memory leak. If the text contained a span, a plain + * string version of the text will be returned. */ @Nullable public CharSequence getText() { @@ -1996,14 +2001,16 @@ public class AssistStructure implements Parcelable { @Override public void setText(CharSequence text) { ViewNodeText t = getNodeText(); - t.mText = TextUtils.trimNoCopySpans(text); + // Strip spans from the text to avoid memory leak + t.mText = TextUtils.trimToParcelableSize(stripAllSpansFromText(text)); t.mTextSelectionStart = t.mTextSelectionEnd = -1; } @Override public void setText(CharSequence text, int selectionStart, int selectionEnd) { ViewNodeText t = getNodeText(); - t.mText = TextUtils.trimNoCopySpans(text); + // Strip spans from the text to avoid memory leak + t.mText = stripAllSpansFromText(text); t.mTextSelectionStart = selectionStart; t.mTextSelectionEnd = selectionEnd; } @@ -2222,6 +2229,13 @@ public class AssistStructure implements Parcelable { public void setHtmlInfo(@NonNull HtmlInfo htmlInfo) { mNode.mHtmlInfo = htmlInfo; } + + private CharSequence stripAllSpansFromText(CharSequence text) { + if (text instanceof Spanned) { + return text.toString(); + } + return text; + } } private static final class HtmlInfoNode extends HtmlInfo implements Parcelable { diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java index 24d1c9577f82..1b8d925ec1cd 100644 --- a/core/java/android/inputmethodservice/InkWindow.java +++ b/core/java/android/inputmethodservice/InkWindow.java @@ -104,7 +104,11 @@ final class InkWindow extends PhoneWindow { */ void hide(boolean remove) { if (getDecorView() != null) { - getDecorView().setVisibility(remove ? View.GONE : View.INVISIBLE); + if (remove) { + mWindowManager.removeViewImmediate(getDecorView()); + } else { + getDecorView().setVisibility(View.INVISIBLE); + } } } diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java index 3b8298ed3627..dda399357d8c 100644 --- a/core/java/android/view/WindowLayout.java +++ b/core/java/android/view/WindowLayout.java @@ -124,16 +124,16 @@ public class WindowLayout { || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) { final Insets systemBarsInsets = state.calculateInsets( displayFrame, systemBars(), requestedVisibleTypes); - if (systemBarsInsets.left > 0) { + if (systemBarsInsets.left >= cutout.getSafeInsetLeft()) { displayCutoutSafeExceptMaybeBars.left = MIN_X; } - if (systemBarsInsets.top > 0) { + if (systemBarsInsets.top >= cutout.getSafeInsetTop()) { displayCutoutSafeExceptMaybeBars.top = MIN_Y; } - if (systemBarsInsets.right > 0) { + if (systemBarsInsets.right >= cutout.getSafeInsetRight()) { displayCutoutSafeExceptMaybeBars.right = MAX_X; } - if (systemBarsInsets.bottom > 0) { + if (systemBarsInsets.bottom >= cutout.getSafeInsetBottom()) { displayCutoutSafeExceptMaybeBars.bottom = MAX_Y; } } diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java index 065089f53633..53c73c6bf161 100644 --- a/core/java/android/widget/AdapterViewFlipper.java +++ b/core/java/android/widget/AdapterViewFlipper.java @@ -16,10 +16,7 @@ package android.widget; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.TypedArray; import android.os.Message; import android.util.AttributeSet; @@ -48,7 +45,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator { private boolean mRunning = false; private boolean mStarted = false; private boolean mVisible = false; - private boolean mUserPresent = true; private boolean mAdvancedByHost = false; public AdapterViewFlipper(Context context) { @@ -82,40 +78,10 @@ public class AdapterViewFlipper extends AdapterViewAnimator { a.recycle(); } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mUserPresent = false; - updateRunning(); - } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - mUserPresent = true; - updateRunning(false); - } - } - }; - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - // Listen for broadcasts related to user-presence - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_USER_PRESENT); - - // OK, this is gross but needed. This class is supported by the - // remote views machanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For exmaple, when adding widgets from a user profile to the - // home screen. Therefore, we register the receiver as the current - // user not the one the context is for. - getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(), - filter, null, getHandler()); - - if (mAutoStart) { // Automatically start when requested startFlipping(); @@ -126,8 +92,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mVisible = false; - - getContext().unregisterReceiver(mReceiver); updateRunning(); } @@ -235,8 +199,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator { * true. */ private void updateRunning(boolean flipNow) { - boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent - && mAdapter != null; + boolean running = !mAdvancedByHost && mVisible && mStarted && mAdapter != null; if (running != mRunning) { if (running) { showOnly(mWhichChild, flipNow); @@ -248,7 +211,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator { } if (LOGD) { Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted - + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning); + + ", mRunning=" + mRunning); } } diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index 1f0e95ea305a..e01583322979 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -23,7 +23,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.BlendMode; @@ -37,6 +36,9 @@ import android.view.RemotableViewMethod; import android.view.View; import android.view.inspector.InspectableProperty; import android.widget.RemoteViews.RemoteView; +import android.widget.TextClock.ClockEventDelegate; + +import com.android.internal.util.Preconditions; import java.time.Clock; import java.time.DateTimeException; @@ -112,6 +114,7 @@ public class AnalogClock extends View { public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mClockEventDelegate = new ClockEventDelegate(context); mSecondsHandFps = AppGlobals.getIntCoreSetting( WidgetFlags.KEY_ANALOG_CLOCK_SECONDS_HAND_FPS, context.getResources() @@ -584,21 +587,9 @@ public class AnalogClock extends View { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - IntentFilter filter = new IntentFilter(); if (!mReceiverAttached) { - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - - // OK, this is gross but needed. This class is supported by the - // remote views mechanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For example, when adding widgets from a user profile to the - // home screen. Therefore, we register the receiver as the current - // user not the one the context is for. - getContext().registerReceiverAsUser(mIntentReceiver, - android.os.Process.myUserHandle(), filter, null, getHandler()); + mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler()); mReceiverAttached = true; } @@ -615,12 +606,23 @@ public class AnalogClock extends View { @Override protected void onDetachedFromWindow() { if (mReceiverAttached) { - getContext().unregisterReceiver(mIntentReceiver); + mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver); mReceiverAttached = false; } super.onDetachedFromWindow(); } + /** + * Sets a delegate to handle clock event registration. This must be called before the view is + * attached to the window + * + * @hide + */ + public void setClockEventDelegate(ClockEventDelegate delegate) { + Preconditions.checkState(!mReceiverAttached, "Clock events already registered"); + mClockEventDelegate = delegate; + } + private void onVisible() { if (!mVisible) { mVisible = true; @@ -797,6 +799,7 @@ public class AnalogClock extends View { } }; private boolean mReceiverAttached; + private ClockEventDelegate mClockEventDelegate; private final Runnable mTick = new Runnable() { @Override diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java index e48afb2a3cc8..255bd679dc35 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -16,6 +16,7 @@ package android.widget; +import static android.os.Process.myUserHandle; import static android.view.ViewDebug.ExportedProperty; import static android.widget.RemoteViews.RemoteView; @@ -24,7 +25,6 @@ import android.annotation.TestApi; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -43,6 +43,7 @@ import android.view.ViewHierarchyEncoder; import android.view.inspector.InspectableProperty; import com.android.internal.R; +import com.android.internal.util.Preconditions; import java.time.Duration; import java.time.Instant; @@ -141,6 +142,8 @@ public class TextClock extends TextView { private boolean mRegistered; private boolean mShouldRunTicker; + private ClockEventDelegate mClockEventDelegate; + private Calendar mTime; private String mTimeZone; @@ -178,8 +181,7 @@ public class TextClock extends TextView { if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { final String timeZone = intent.getStringExtra(Intent.EXTRA_TIMEZONE); createTime(timeZone); - } else if (!mShouldRunTicker && (Intent.ACTION_TIME_TICK.equals(intent.getAction()) - || Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))) { + } else if (!mShouldRunTicker && Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) { return; } onTimeChanged(); @@ -282,6 +284,7 @@ public class TextClock extends TextView { if (mFormat24 == null) { mFormat24 = getBestDateTimePattern("Hm"); } + mClockEventDelegate = new ClockEventDelegate(getContext()); createTime(mTimeZone); chooseFormat(); @@ -431,6 +434,17 @@ public class TextClock extends TextView { } /** + * Sets a delegate to handle clock event registration. This must be called before the view is + * attached to the window + * + * @hide + */ + public void setClockEventDelegate(ClockEventDelegate delegate) { + Preconditions.checkState(!mRegistered, "Clock events already registered"); + mClockEventDelegate = delegate; + } + + /** * Update the displayed time if necessary and invalidate the view. */ public void refreshTime() { @@ -557,7 +571,7 @@ public class TextClock extends TextView { if (!mRegistered) { mRegistered = true; - registerReceiver(); + mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler()); registerObserver(); createTime(mTimeZone); @@ -582,7 +596,7 @@ public class TextClock extends TextView { super.onDetachedFromWindow(); if (mRegistered) { - unregisterReceiver(); + mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver); unregisterObserver(); mRegistered = false; @@ -598,56 +612,27 @@ public class TextClock extends TextView { mStopTicking = true; } - private void registerReceiver() { - final IntentFilter filter = new IntentFilter(); - - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - - // OK, this is gross but needed. This class is supported by the - // remote views mechanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For example, when adding widgets from a managed profile to the - // home screen. Therefore, we register the receiver as the user - // the app is running as not the one the context is for. - getContext().registerReceiverAsUser(mIntentReceiver, android.os.Process.myUserHandle(), - filter, null, getHandler()); - } - private void registerObserver() { if (mRegistered) { if (mFormatChangeObserver == null) { mFormatChangeObserver = new FormatChangeObserver(getHandler()); } - final ContentResolver resolver = getContext().getContentResolver(); - Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24); - if (mShowCurrentUserTime) { - resolver.registerContentObserver(uri, true, - mFormatChangeObserver, UserHandle.USER_ALL); - } else { - // UserHandle.myUserId() is needed. This class is supported by the - // remote views mechanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For example, when adding widgets from a managed profile to the - // home screen. Therefore, we register the ContentObserver with the user - // the app is running (e.g. the launcher) and not the user of the - // context (e.g. the widget's profile). - resolver.registerContentObserver(uri, true, - mFormatChangeObserver, UserHandle.myUserId()); - } + // UserHandle.myUserId() is needed. This class is supported by the + // remote views mechanism and as a part of that the remote views + // can be inflated by a context for another user without the app + // having interact users permission - just for loading resources. + // For example, when adding widgets from a managed profile to the + // home screen. Therefore, we register the ContentObserver with the user + // the app is running (e.g. the launcher) and not the user of the + // context (e.g. the widget's profile). + int userHandle = mShowCurrentUserTime ? UserHandle.USER_ALL : UserHandle.myUserId(); + mClockEventDelegate.registerFormatChangeObserver(mFormatChangeObserver, userHandle); } } - private void unregisterReceiver() { - getContext().unregisterReceiver(mIntentReceiver); - } - private void unregisterObserver() { if (mFormatChangeObserver != null) { - final ContentResolver resolver = getContext().getContentResolver(); - resolver.unregisterContentObserver(mFormatChangeObserver); + mClockEventDelegate.unregisterFormatChangeObserver(mFormatChangeObserver); } } @@ -674,4 +659,59 @@ public class TextClock extends TextView { stream.addProperty("format", mFormat == null ? null : mFormat.toString()); stream.addProperty("hasSeconds", mHasSeconds); } + + /** + * Utility class to delegate some system event handling to allow overring the default behavior + * + * @hide + */ + public static class ClockEventDelegate { + + private final Context mContext; + + public ClockEventDelegate(Context context) { + mContext = context; + } + + /** + * Registers a receiver for actions {@link Intent#ACTION_TIME_CHANGED} and + * {@link Intent#ACTION_TIMEZONE_CHANGED} + * + * OK, this is gross but needed. This class is supported by the remote views mechanism and + * as a part of that the remote views can be inflated by a context for another user without + * the app having interact users permission - just for loading resources. For example, + * when adding widgets from a managed profile to the home screen. Therefore, we register + * the receiver as the user the app is running as not the one the context is for. + */ + public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) { + final IntentFilter filter = new IntentFilter(); + + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + + mContext.registerReceiverAsUser(receiver, myUserHandle(), filter, null, handler); + } + + /** + * Unregisters a previously registered receiver + */ + public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) { + mContext.unregisterReceiver(receiver); + } + + /** + * Registers an observer for time format changes + */ + public void registerFormatChangeObserver(ContentObserver observer, int userHandle) { + Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24); + mContext.getContentResolver().registerContentObserver(uri, true, observer, userHandle); + } + + /** + * Unregisters a previously registered observer + */ + public void unregisterFormatChangeObserver(ContentObserver observer) { + mContext.getContentResolver().unregisterContentObserver(observer); + } + } } diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java index 5abb6e1637e7..eaf037e68976 100644 --- a/core/java/android/widget/ViewFlipper.java +++ b/core/java/android/widget/ViewFlipper.java @@ -18,10 +18,7 @@ package android.widget; import android.annotation.IntRange; import android.compat.annotation.UnsupportedAppUsage; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.TypedArray; import android.os.Build; import android.os.Message; @@ -51,8 +48,6 @@ public class ViewFlipper extends ViewAnimator { private boolean mRunning = false; private boolean mStarted = false; private boolean mVisible = false; - @UnsupportedAppUsage - private boolean mUserPresent = true; public ViewFlipper(Context context) { super(context); @@ -70,39 +65,10 @@ public class ViewFlipper extends ViewAnimator { a.recycle(); } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mUserPresent = false; - updateRunning(); - } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - mUserPresent = true; - updateRunning(false); - } - } - }; - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - // Listen for broadcasts related to user-presence - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_USER_PRESENT); - - // OK, this is gross but needed. This class is supported by the - // remote views machanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For exmaple, when adding widgets from a user profile to the - // home screen. Therefore, we register the receiver as the current - // user not the one the context is for. - getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(), - filter, null, getHandler()); - if (mAutoStart) { // Automatically start when requested startFlipping(); @@ -113,8 +79,6 @@ public class ViewFlipper extends ViewAnimator { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mVisible = false; - - getContext().unregisterReceiver(mReceiver); updateRunning(); } @@ -186,7 +150,7 @@ public class ViewFlipper extends ViewAnimator { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void updateRunning(boolean flipNow) { - boolean running = mVisible && mStarted && mUserPresent; + boolean running = mVisible && mStarted; if (running != mRunning) { if (running) { showOnly(mWhichChild, flipNow); @@ -198,7 +162,7 @@ public class ViewFlipper extends ViewAnimator { } if (LOGD) { Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted - + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning); + + ", mRunning=" + mRunning); } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 2b39bb4eb7a5..0a726d99723a 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -3023,28 +3023,31 @@ public class ChooserActivity extends ResolverActivity implements return shouldShowTabs() && (mMultiProfilePagerAdapter.getListAdapterForUserHandle( UserHandle.of(UserHandle.myUserId())).getCount() > 0 - || shouldShowContentPreviewWhenEmpty()) + || shouldShowStickyContentPreviewWhenEmpty()) && shouldShowContentPreview(); } /** - * This method could be used to override the default behavior when we hide the preview area - * when the current tab doesn't have any items. + * This method could be used to override the default behavior when we hide the sticky preview + * area when the current tab doesn't have any items. * - * @return true if we want to show the content preview area even if the tab for the current - * user is empty + * @return {@code true} if we want to show the sticky content preview area even if the tab for + * the current user is empty */ - protected boolean shouldShowContentPreviewWhenEmpty() { + protected boolean shouldShowStickyContentPreviewWhenEmpty() { return false; } - /** - * @return true if we want to show the content preview area - */ - protected boolean shouldShowContentPreview() { + @Override + public boolean shouldShowContentPreview() { return isSendAction(getTargetIntent()); } + @Override + public boolean shouldShowServiceTargets() { + return shouldShowContentPreview() && !ActivityManager.isLowRamDeviceStatic(); + } + private void updateStickyContentPreview() { if (shouldShowStickyContentPreviewNoOrientationCheck()) { // The sticky content preview is only shown when we show the work and personal tabs. @@ -3406,11 +3409,7 @@ public class ChooserActivity extends ResolverActivity implements // There can be at most one row in the listview, that is internally // a ViewGroup with 2 rows public int getServiceTargetRowCount() { - if (shouldShowContentPreview() - && !ActivityManager.isLowRamDeviceStatic()) { - return 1; - } - return 0; + return shouldShowServiceTargets() ? 1 : 0; } public int getAzLabelRowCount() { diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index 1eecb413adcb..36038ae15853 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -19,7 +19,6 @@ package com.android.internal.app; import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER; -import android.app.ActivityManager; import android.app.prediction.AppPredictor; import android.content.ComponentName; import android.content.Context; @@ -425,11 +424,9 @@ public class ChooserListAdapter extends ResolverListAdapter { } public int getServiceTargetCount() { - if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent()) - && !ActivityManager.isLowRamDeviceStatic()) { + if (mChooserListCommunicator.shouldShowServiceTargets()) { return Math.min(mServiceTargets.size(), mChooserListCommunicator.getMaxRankedTargets()); } - return 0; } @@ -771,6 +768,10 @@ public class ChooserListAdapter extends ResolverListAdapter { void sendListViewUpdateMessage(UserHandle userHandle); boolean isSendAction(Intent targetIntent); + + boolean shouldShowContentPreview(); + + boolean shouldShowServiceTargets(); } /** diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index 52ffc984c41e..a513ca535620 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -21,6 +21,7 @@ import static android.content.res.Resources.ID_NULL; import android.annotation.IdRes; import android.content.Context; +import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; @@ -53,10 +54,13 @@ public class ResolverDrawerLayout extends ViewGroup { private static final String TAG = "ResolverDrawerLayout"; private MetricsLogger mMetricsLogger; + + /** - * Max width of the whole drawer layout + * Max width of the whole drawer layout and its res id */ - private final int mMaxWidth; + private int mMaxWidthResId; + private int mMaxWidth; /** * Max total visible height of views not marked always-show when in the closed/initial state @@ -152,6 +156,7 @@ public class ResolverDrawerLayout extends ViewGroup { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ResolverDrawerLayout, defStyleAttr, 0); + mMaxWidthResId = a.getResourceId(R.styleable.ResolverDrawerLayout_maxWidth, -1); mMaxWidth = a.getDimensionPixelSize(R.styleable.ResolverDrawerLayout_maxWidth, -1); mMaxCollapsedHeight = a.getDimensionPixelSize( R.styleable.ResolverDrawerLayout_maxCollapsedHeight, 0); @@ -1042,6 +1047,18 @@ public class ResolverDrawerLayout extends ViewGroup { return mAlwaysShowHeight; } + /** + * Max width of the drawer needs to be updated after the configuration is changed. + * For example, foldables have different layout width when the device is folded and unfolded. + */ + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (mMaxWidthResId > 0) { + mMaxWidth = getResources().getDimensionPixelSize(mMaxWidthResId); + } + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int width = getWidth(); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt index 2eeaf53ef146..1c9f04a299cc 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt +++ b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt @@ -19,8 +19,12 @@ package com.android.internal.app import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo +import android.content.pm.ShortcutInfo +import android.graphics.drawable.Icon import android.os.Bundle import android.os.UserHandle import android.service.chooser.ChooserTarget @@ -32,12 +36,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.R import com.android.internal.app.ChooserListAdapter.LoadDirectShareIconTask +import com.android.internal.app.chooser.DisplayResolveInfo import com.android.internal.app.chooser.SelectableTargetInfo import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator import com.android.internal.app.chooser.TargetInfo import com.android.server.testutils.any import com.android.server.testutils.mock import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyInt @@ -46,22 +53,25 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class ChooserListAdapterTest { - private val packageManager = mock<PackageManager> { - whenever(resolveActivity(any(), anyInt())).thenReturn(mock()) - } + private val packageManager = + mock<PackageManager> { whenever(resolveActivity(any(), anyInt())).thenReturn(mock()) } private val context = InstrumentationRegistry.getInstrumentation().getContext() private val resolverListController = mock<ResolverListController>() - private val chooserListCommunicator = mock<ChooserListAdapter.ChooserListCommunicator> { - whenever(maxRankedTargets).thenReturn(0) - } - private val selectableTargetInfoCommunicator = - mock<SelectableTargetInfoCommunicator> { - whenever(targetIntent).thenReturn(mock()) + private val chooserListCommunicator = + mock<ChooserListAdapter.ChooserListCommunicator> { + whenever(maxRankedTargets).thenReturn(0) } + private val selectableTargetInfoCommunicator = + mock<SelectableTargetInfoCommunicator> { whenever(targetIntent).thenReturn(mock()) } private val chooserActivityLogger = mock<ChooserActivityLogger>() + @Before + fun setUp() { + whenever(resolverListController.userHandle).thenReturn(UserHandle.CURRENT) + } + private fun createChooserListAdapter( - taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask + taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask = createTaskProvider() ) = ChooserListAdapterOverride( context, @@ -98,9 +108,8 @@ class ChooserListAdapterTest { view.tag = viewHolderOne val targetInfo = createSelectableTargetInfo() val iconTaskOne = mock<LoadDirectShareIconTask>() - val testTaskProvider = mock<() -> LoadDirectShareIconTask> { - whenever(invoke()).thenReturn(iconTaskOne) - } + val testTaskProvider = + mock<() -> LoadDirectShareIconTask> { whenever(invoke()).thenReturn(iconTaskOne) } val testSubject = createChooserListAdapter { testTaskProvider.invoke() } testSubject.testViewBind(view, targetInfo, 0) @@ -114,6 +123,65 @@ class ChooserListAdapterTest { verify(testTaskProvider, times(1)).invoke() } + @Test + fun getServiceTargetCount_shouldNotShowServiceTargets_returnsZero() { + whenever(chooserListCommunicator.shouldShowServiceTargets()).thenReturn(false) + val adapter = createChooserListAdapter() + whenever(chooserListCommunicator.maxRankedTargets).thenReturn(10) + addServiceTargets(adapter, targetCount = 50) + + assertThat(adapter.serviceTargetCount).isEqualTo(0) + } + + private fun createTaskProvider(): (SelectableTargetInfo?) -> LoadDirectShareIconTask { + val iconTaskOne = mock<LoadDirectShareIconTask>() + val testTaskProvider = + mock<() -> LoadDirectShareIconTask> { whenever(invoke()).thenReturn(iconTaskOne) } + return { testTaskProvider.invoke() } + } + + private fun addServiceTargets(adapter: ChooserListAdapter, targetCount: Int) { + val origTarget = + DisplayResolveInfo( + Intent(), + createResolveInfo(), + Intent(), + ResolverListAdapter.ResolveInfoPresentationGetter(context, 200, createResolveInfo()) + ) + val targets = mutableListOf<ChooserTarget>() + for (i in 1..targetCount) { + val score = 1f + val componentName = ComponentName("chooser.list.adapter", "Test$i") + val extras = Bundle() + val icon: Icon? = null + targets += ChooserTarget("Title $i", icon, score, componentName, extras) + } + val directShareToShortcutInfos = mapOf<ChooserTarget, ShortcutInfo>() + adapter.addServiceResults( + origTarget, + targets, + ChooserActivity.TARGET_TYPE_DEFAULT, + directShareToShortcutInfos + ) + } + + private fun createResolveInfo(): ResolveInfo { + val applicationInfo = + ApplicationInfo().apply { + packageName = "chooser.list.adapter" + name = "ChooserListAdapterTestApplication" + } + val activityInfo = + ActivityInfo().apply { + packageName = applicationInfo.packageName + name = "ChooserListAdapterTest" + } + activityInfo.applicationInfo = applicationInfo + val resolveInfo = ResolveInfo() + resolveInfo.activityInfo = activityInfo + return resolveInfo + } + private fun createSelectableTargetInfo(): SelectableTargetInfo = SelectableTargetInfo( context, @@ -125,13 +193,7 @@ class ChooserListAdapterTest { ) private fun createChooserTarget(): ChooserTarget = - ChooserTarget( - "Title", - null, - 1f, - ComponentName("package", "package.Class"), - Bundle() - ) + ChooserTarget("Title", null, 1f, ComponentName("package", "package.Class"), Bundle()) private fun createView(): View { val view = FrameLayout(context) @@ -164,23 +226,23 @@ private class ChooserListAdapterOverride( chooserActivityLogger: ChooserActivityLogger?, initialIntentsUserHandle: UserHandle?, private val taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask -) : ChooserListAdapter( - context, - payloadIntents, - initialIntents, - rList, - filterLastUsed, - resolverListController, - chooserListCommunicator, - selectableTargetInfoCommunicator, - packageManager, - chooserActivityLogger, - initialIntentsUserHandle, -) { +) : + ChooserListAdapter( + context, + payloadIntents, + initialIntents, + rList, + filterLastUsed, + resolverListController, + chooserListCommunicator, + selectableTargetInfoCommunicator, + packageManager, + chooserActivityLogger, + initialIntentsUserHandle, + ) { override fun createLoadDirectShareIconTask( info: SelectableTargetInfo? - ): LoadDirectShareIconTask = - taskProvider.invoke(info) + ): LoadDirectShareIconTask = taskProvider.invoke(info) fun testViewBind(view: View?, info: TargetInfo?, position: Int) { onBindView(view, info, position) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index bb65da3deade..50ba8975802b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -738,8 +738,17 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { final boolean wasClosing = closingIdx >= 0; t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash()); t.setLayer(target.leash, layer); - // Hide the animation leash if not already visible, let listener show it - t.setVisibility(target.leash, !wasClosing); + if (wasClosing) { + // App was previously visible and is closing + t.show(target.leash); + t.setAlpha(target.leash, 1f); + // Also override the task alpha as it was set earlier when dispatching + // the transition and setting up the leash to hide the + t.setAlpha(change.getLeash(), 1f); + } else { + // Hide the animation leash, let the listener show it + t.hide(target.leash); + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " opening new leaf taskId=%d wasClosing=%b", target.taskId, wasClosing); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index 5d7a3d432dcb..23f50eaba7e7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -34,6 +34,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.ActivityStarter import com.android.systemui.wallet.controller.QuickAccessWalletController +import com.android.systemui.wallet.util.getPaymentCards import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -60,7 +61,7 @@ constructor( val callback = object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback { override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) { - val hasCards = response?.walletCards?.isNotEmpty() == true + val hasCards = getPaymentCards(response.walletCards)?.isNotEmpty() == true trySendWithFailureLogging( state( isFeatureEnabled = isWalletAvailable(), @@ -135,7 +136,7 @@ constructor( object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback { override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) { continuation.resumeWith( - Result.success(response?.walletCards ?: emptyList()) + Result.success(getPaymentCards(response.walletCards) ?: emptyList()) ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index 60fd10492628..cf64a838c2bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -274,7 +274,9 @@ class MediaProjectionAppSelectorActivity( recentsViewController.hasRecentTasks } - override fun shouldShowContentPreviewWhenEmpty() = shouldShowContentPreview() + override fun shouldShowStickyContentPreviewWhenEmpty() = shouldShowContentPreview() + + override fun shouldShowServiceTargets() = false private fun hasWorkProfile() = mMultiProfilePagerAdapter.count > 1 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 544e6ad295ff..e382eca17369 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -20,6 +20,7 @@ import static android.graphics.drawable.Icon.TYPE_URI; import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; +import static com.android.systemui.wallet.util.WalletCardUtilsKt.getPaymentCards; import android.content.Intent; import android.content.pm.PackageManager; @@ -210,7 +211,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) { Log.i(TAG, "Successfully retrieved wallet cards."); mIsWalletUpdating = false; - List<WalletCard> cards = response.getWalletCards(); + List<WalletCard> cards = getPaymentCards(response.getWalletCards()); if (cards.isEmpty()) { Log.d(TAG, "No wallet cards exist."); mCardViewDrawable = null; diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index 81d04d4458c0..6fd0a4db5a14 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -16,6 +16,8 @@ package com.android.systemui.wallet.ui; +import static com.android.systemui.wallet.util.WalletCardUtilsKt.getPaymentCards; + import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -47,10 +49,10 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.KeyguardStateController; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** Controller for the wallet card carousel screen. */ public class WalletScreenController implements @@ -126,22 +128,11 @@ public class WalletScreenController implements return; } Log.i(TAG, "Successfully retrieved wallet cards."); - List<WalletCard> walletCards = response.getWalletCards(); - - boolean allUnknown = true; - for (WalletCard card : walletCards) { - if (card.getCardType() != WalletCard.CARD_TYPE_UNKNOWN) { - allUnknown = false; - break; - } - } + List<WalletCard> walletCards = getPaymentCards(response.getWalletCards()); - List<WalletCardViewInfo> paymentCardData = new ArrayList<>(); - for (WalletCard card : walletCards) { - if (allUnknown || card.getCardType() == WalletCard.CARD_TYPE_PAYMENT) { - paymentCardData.add(new QAWalletCardViewInfo(mContext, card)); - } - } + List<WalletCardViewInfo> paymentCardData = walletCards.stream().map( + card -> new QAWalletCardViewInfo(mContext, card) + ).collect(Collectors.toList()); // Get on main thread for UI updates. mHandler.post(() -> { diff --git a/packages/SystemUI/src/com/android/systemui/wallet/util/WalletCardUtils.kt b/packages/SystemUI/src/com/android/systemui/wallet/util/WalletCardUtils.kt new file mode 100644 index 000000000000..077b80bde953 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallet/util/WalletCardUtils.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.wallet.util + +import android.service.quickaccesswallet.WalletCard + +/** + * Filters wallet cards to only those of [WalletCard.CARD_TYPE_PAYMENT], or returns all cards if + * they are all of [WalletCard.CARD_TYPE_UNKNOWN] (maintaining pre-U behavior). Used by the wallet + * card carousel, quick settings tile, and lock screen. + */ +fun getPaymentCards(walletCards: List<WalletCard>): List<WalletCard> { + val atLeastOneKnownCardType = walletCards.any { it.cardType != WalletCard.CARD_TYPE_UNKNOWN } + + return if (atLeastOneKnownCardType) { + walletCards.filter { it.cardType == WalletCard.CARD_TYPE_PAYMENT } + } else { + walletCards + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index d36e77889810..b9c0b7fe35d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.data.quickaffordance import android.graphics.drawable.Drawable import android.service.quickaccesswallet.GetWalletCardsResponse import android.service.quickaccesswallet.QuickAccessWalletClient +import android.service.quickaccesswallet.WalletCard import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.R @@ -38,6 +39,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.test.runTest import org.junit.Before @@ -92,6 +94,40 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } @Test + fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() = + runTest(UnconfinedTestDispatcher()) { + setUpState(cardType = WalletCard.CARD_TYPE_NON_PAYMENT) + var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null + + val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + job.cancel() + } + + @Test + fun affordance_keyguardShowing_hasPaymentCard_visibleModel() = + runTest(UnconfinedTestDispatcher()) { + setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT) + var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null + + val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this) + + val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible + assertThat(visibleModel.icon) + .isEqualTo( + Icon.Loaded( + drawable = ICON, + contentDescription = + ContentDescription.Resource( + res = R.string.accessibility_wallet_button, + ), + ) + ) + job.cancel() + } + + @Test fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest { setUpState(isWalletFeatureAvailable = false) var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null @@ -187,6 +223,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { isWalletServiceAvailable: Boolean = true, isWalletQuerySuccessful: Boolean = true, hasSelectedCard: Boolean = true, + cardType: Int = WalletCard.CARD_TYPE_UNKNOWN ) { val walletClient: QuickAccessWalletClient = mock() whenever(walletClient.tileIcon).thenReturn(ICON) @@ -202,7 +239,19 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { if (isWalletQuerySuccessful) { onWalletCardsRetrieved( if (hasSelectedCard) { - GetWalletCardsResponse(listOf(mock()), 0) + GetWalletCardsResponse( + listOf( + WalletCard.Builder( + /*cardId= */ CARD_ID, + /*cardType= */ cardType, + /*cardImage= */ mock(), + /*contentDescription= */ CARD_DESCRIPTION, + /*pendingIntent= */ mock() + ) + .build() + ), + 0 + ) } else { GetWalletCardsResponse(emptyList(), 0) } @@ -216,5 +265,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { companion object { private val ICON: Drawable = mock() + private const val CARD_ID: String = "Id" + private const val CARD_DESCRIPTION: String = "Description" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index b00ae399af21..dd55baddc2d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -395,7 +395,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); WalletCard walletCard = new WalletCard.Builder( - CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build(); + CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build(); GetWalletCardsResponse response = new GetWalletCardsResponse(Collections.singletonList(walletCard), 0); @@ -410,6 +410,22 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { } @Test + public void testQueryCards_cardDataPayment_updateSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_PAYMENT); + + assertNotNull(mTile.getState().sideViewCustomDrawable); + } + + @Test + public void testQueryCards_cardDataNonPayment_updateSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_NON_PAYMENT); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + + @Test public void testQueryCards_noCards_notUpdateSideViewDrawable() { setUpWalletCard(/* hasCard= */ false); @@ -438,6 +454,29 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { verifyZeroInteractions(mQuickAccessWalletClient); } + private WalletCard createWalletCardWithType(Context context, int cardType) { + PendingIntent pendingIntent = + PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + return new WalletCard.Builder(CARD_ID, cardType, CARD_IMAGE, CARD_DESCRIPTION, + pendingIntent).build(); + } + + private void setUpWalletCardWithType(boolean hasCard, int cardType) { + GetWalletCardsResponse response = + new GetWalletCardsResponse( + hasCard + ? Collections.singletonList( + createWalletCardWithType(mContext, cardType)) + : Collections.EMPTY_LIST, 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + } + private void setUpWalletCard(boolean hasCard) { GetWalletCardsResponse response = new GetWalletCardsResponse( diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java index 692af6a9a37b..c1d11aa1eb89 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java @@ -459,7 +459,7 @@ public class WalletScreenControllerTest extends SysuiTestCase { WalletCard.CARD_TYPE_UNKNOWN), createWalletCardWithType(mContext, WalletCard.CARD_TYPE_PAYMENT), createWalletCardWithType(mContext, WalletCard.CARD_TYPE_NON_PAYMENT) - ); + ); GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0); mController.onWalletCardsRetrieved(response); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt new file mode 100644 index 000000000000..e46c1f554dd6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 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.wallet.util + +import android.service.quickaccesswallet.WalletCard +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +/** Test class for WalletCardUtils */ +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +@SmallTest +class WalletCardUtilsTest : SysuiTestCase() { + + private val paymentCard = createWalletCardWithType(WalletCard.CARD_TYPE_PAYMENT) + private val nonPaymentCard = createWalletCardWithType(WalletCard.CARD_TYPE_NON_PAYMENT) + private val unknownCard = createWalletCardWithType(WalletCard.CARD_TYPE_UNKNOWN) + + @Test + fun paymentCards_cardTypesAllUnknown_getsAllCards() { + val walletCardList = + mutableListOf( + createWalletCardWithType(WalletCard.CARD_TYPE_UNKNOWN), + createWalletCardWithType(WalletCard.CARD_TYPE_UNKNOWN), + createWalletCardWithType(WalletCard.CARD_TYPE_UNKNOWN) + ) + + assertThat(walletCardList).isEqualTo(getPaymentCards(walletCardList)) + } + + @Test + fun paymentCards_cardTypesDifferent_onlyGetsPayment() { + val walletCardList = mutableListOf(paymentCard, nonPaymentCard, unknownCard) + + assertThat(getPaymentCards(walletCardList)).isEqualTo(mutableListOf(paymentCard)) + } + + private fun createWalletCardWithType(cardType: Int): WalletCard { + return WalletCard.Builder( + /*cardId= */ CARD_ID, + /*cardType= */ cardType, + /*cardImage= */ mock(), + /*contentDescription= */ CARD_DESCRIPTION, + /*pendingIntent= */ mock() + ) + .build() + } + + companion object { + private const val CARD_ID: String = "ID" + private const val CARD_DESCRIPTION: String = "Description" + } +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 60af280614e2..e08fdd65ad94 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1437,6 +1437,7 @@ public class AudioDeviceInventory { } }); new MediaMetrics.Item(mMetricsId + "disconnectA2dp") + .set(MediaMetrics.Property.EVENT, "disconnectA2dp") .record(); if (toRemove.size() > 0) { final int delay = checkSendBecomingNoisyIntentInt( @@ -1459,6 +1460,7 @@ public class AudioDeviceInventory { } }); new MediaMetrics.Item(mMetricsId + "disconnectA2dpSink") + .set(MediaMetrics.Property.EVENT, "disconnectA2dpSink") .record(); toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress)); } @@ -1474,6 +1476,7 @@ public class AudioDeviceInventory { } }); new MediaMetrics.Item(mMetricsId + "disconnectHearingAid") + .set(MediaMetrics.Property.EVENT, "disconnectHearingAid") .record(); if (toRemove.size() > 0) { final int delay = checkSendBecomingNoisyIntentInt( @@ -1531,6 +1534,7 @@ public class AudioDeviceInventory { } }); new MediaMetrics.Item(mMetricsId + "disconnectLeAudio") + .set(MediaMetrics.Property.EVENT, "disconnectLeAudio") .record(); if (toRemove.size() > 0) { final int delay = checkSendBecomingNoisyIntentInt(device, diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index 52b92c4c7ca6..378cdba09520 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -31,7 +31,12 @@ class RampAnimator<T> { private final FloatProperty<T> mProperty; private float mCurrentValue; - private float mTargetValue; + + // target in HLG space + private float mTargetHlgValue; + + // target in linear space + private float mTargetLinearValue; private float mRate; private float mAnimationIncreaseMaxTimeSecs; private float mAnimationDecreaseMaxTimeSecs; @@ -78,7 +83,8 @@ class RampAnimator<T> { if (mFirstTime || target != mCurrentValue) { mFirstTime = false; mRate = 0; - mTargetValue = target; + mTargetHlgValue = target; + mTargetLinearValue = targetLinear; mCurrentValue = target; setPropertyValue(target); mAnimating = false; @@ -105,13 +111,14 @@ class RampAnimator<T> { // Otherwise, continue at the previous rate. if (!mAnimating || rate > mRate - || (target <= mCurrentValue && mCurrentValue <= mTargetValue) - || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) { + || (target <= mCurrentValue && mCurrentValue <= mTargetHlgValue) + || (mTargetHlgValue <= mCurrentValue && mCurrentValue <= target)) { mRate = rate; } - final boolean changed = (mTargetValue != target); - mTargetValue = target; + final boolean changed = (mTargetHlgValue != target); + mTargetHlgValue = target; + mTargetLinearValue = targetLinear; // Start animating. if (!mAnimating && target != mCurrentValue) { @@ -135,7 +142,11 @@ class RampAnimator<T> { * into linear space. */ private void setPropertyValue(float val) { - final float linearVal = BrightnessUtils.convertGammaToLinear(val); + // To avoid linearVal inconsistency when converting to HLG and back to linear space + // used original target linear value for final animation step + float linearVal = + val == mTargetHlgValue ? mTargetLinearValue : BrightnessUtils.convertGammaToLinear( + val); mProperty.setValue(mObject, linearVal); } @@ -150,13 +161,13 @@ class RampAnimator<T> { final float scale = ValueAnimator.getDurationScale(); if (scale == 0) { // Animation off. - mAnimatedValue = mTargetValue; + mAnimatedValue = mTargetHlgValue; } else { final float amount = timeDelta * mRate / scale; - if (mTargetValue > mCurrentValue) { - mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue); + if (mTargetHlgValue > mCurrentValue) { + mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetHlgValue); } else { - mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue); + mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetHlgValue); } } final float oldCurrentValue = mCurrentValue; @@ -164,7 +175,7 @@ class RampAnimator<T> { if (oldCurrentValue != mCurrentValue) { setPropertyValue(mCurrentValue); } - if (mTargetValue == mCurrentValue) { + if (mTargetHlgValue == mCurrentValue) { mAnimating = false; } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 7aea63255dc8..a1d28da5f65e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -119,7 +119,6 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; -import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputDevice; import android.view.MotionEvent; @@ -127,7 +126,6 @@ import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; -import android.view.WindowManagerGlobal; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputBinding; @@ -3073,9 +3071,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture, "Waiting for the lazy init of mImeDrawsImeNavBarRes"); } + // Whether the current display has a navigation bar. When this is false (e.g. emulator), + // the IME should not draw the IME navigation bar. + final boolean hasNavigationBar = mWindowManagerInternal + .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY + ? mCurTokenDisplayId : DEFAULT_DISPLAY); final boolean canImeDrawsImeNavBar = - mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() - && hasNavigationBarOnCurrentDisplay(); + mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar; final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked( InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE); return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0) @@ -3083,21 +3085,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0); } - /** - * Whether the current display has a navigation bar. When this is {@code false} (e.g. emulator), - * the IME should <em>not</em> draw the IME navigation bar. - */ - @GuardedBy("ImfLock.class") - private boolean hasNavigationBarOnCurrentDisplay() { - final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - try { - return wm.hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY - ? mCurTokenDisplayId : DEFAULT_DISPLAY); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - @GuardedBy("ImfLock.class") private boolean shouldShowImeSwitcherLocked(int visibility) { if (!mShowOngoingImeSwitcherForPhones) return false; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 72c6111cba9d..dfd317c58e47 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -976,9 +976,6 @@ public final class MediaProjectionManagerService extends SystemService throw new SecurityException("Media projections require a foreground service" + " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION"); } - - mCallback = callback; - registerCallback(mCallback); try { mToken = callback.asBinder(); mDeathEater = () -> { @@ -1023,6 +1020,11 @@ public final class MediaProjectionManagerService extends SystemService } } startProjectionLocked(this); + + // Register new callbacks after stop has been dispatched to previous session. + mCallback = callback; + registerCallback(mCallback); + // Mark this token as used when the app gets the MediaProjection instance. mCountStarts++; } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 01786becda61..3639e1b9cb47 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1126,11 +1126,25 @@ final class LetterboxUiController { return computeAspectRatio(bounds); } + /** + * Whether we should enable users to resize the current app. + */ + boolean shouldEnableUserAspectRatioSettings() { + // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has + // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null, + // the current app doesn't opt-out so the first part of the predicate is true. + return !FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride) + && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() + && mActivityRecord.mDisplayContent != null + && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest(); + } + + /** + * Whether we should apply the user aspect ratio override to the min aspect ratio for the + * current app. + */ boolean shouldApplyUserMinAspectRatioOverride() { - if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride) - || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() - || mActivityRecord.mDisplayContent == null - || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { + if (!shouldEnableUserAspectRatioSettings()) { return false; } diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index c914fa10687f..c8635335a445 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -148,7 +148,8 @@ public class SafeActivityOptions { .setPendingIntentBackgroundActivityStartMode( options.getPendingIntentBackgroundActivityStartMode()) .setPendingIntentCreatorBackgroundActivityStartMode( - options.getPendingIntentCreatorBackgroundActivityStartMode()); + options.getPendingIntentCreatorBackgroundActivityStartMode()) + .setRemoteTransition(options.getRemoteTransition()); } /** diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9f2aff28cb11..60b6a8879ecf 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3476,9 +3476,9 @@ class Task extends TaskFragment { } } // User Aspect Ratio Settings is enabled if the app is not in SCM - info.topActivityEligibleForUserAspectRatioButton = - mWmService.mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() - && top != null && !info.topActivityInSizeCompat; + info.topActivityEligibleForUserAspectRatioButton = top != null + && !info.topActivityInSizeCompat + && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings(); info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 805e7ffe7d76..9f1bccb9a27a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -954,4 +954,11 @@ public abstract class WindowManagerInternal { /** Returns the SurfaceControl accessibility services should use for accessibility overlays. */ public abstract SurfaceControl getA11yOverlayLayer(int displayId); + + /** + * Device has a software navigation bar (separate from the status bar) on specific display. + * + * @param displayId the id of display to check if there is a software navigation bar. + */ + public abstract boolean hasNavigationBar(int displayId); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b20be551c114..e6a341fe37e7 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8380,6 +8380,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public boolean hasNavigationBar(int displayId) { + return WindowManagerService.this.hasNavigationBar(displayId); + } + + @Override public void setInputMethodTargetChangeListener(@NonNull ImeTargetChangeListener listener) { synchronized (mGlobalLock) { mImeTargetChangeListener = listener; diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index bfe055354b9c..74a0bafd3a4c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -1279,6 +1279,7 @@ public class WindowManagerShellCommand extends ShellCommand { mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx(); mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); + mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier(); mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled(); mLetterboxConfiguration.resetIsVerticalReachabilityEnabled(); mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java new file mode 100644 index 000000000000..2820da7c49c1 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static org.junit.Assert.assertEquals; + +import android.util.FloatProperty; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; + +@SmallTest +public class RampAnimatorTest { + + private RampAnimator<TestObject> mRampAnimator; + + private final TestObject mTestObject = new TestObject(); + + private final FloatProperty<TestObject> mTestProperty = new FloatProperty<>("mValue") { + @Override + public void setValue(TestObject object, float value) { + object.mValue = value; + } + + @Override + public Float get(TestObject object) { + return object.mValue; + } + }; + + @Before + public void setUp() { + mRampAnimator = new RampAnimator<>(mTestObject, mTestProperty); + } + + @Test + public void testInitialValueUsedInLastAnimationStep() { + mRampAnimator.setAnimationTarget(0.67f, 0.1f); + + assertEquals(0.67f, mTestObject.mValue, 0); + } + + private static class TestObject { + private float mValue; + } +} diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index ddd1221e4c91..d85768dd7588 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -79,6 +79,9 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + /** * Tests for the {@link MediaProjectionManagerService} class. * @@ -202,6 +205,29 @@ public class MediaProjectionManagerServiceTest { } @Test + public void testCreateProjection_priorProjectionGrant() throws + NameNotFoundException, InterruptedException { + // Create a first projection. + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + FakeIMediaProjectionCallback callback1 = new FakeIMediaProjectionCallback(); + projection.start(callback1); + + // Create a second projection. + MediaProjectionManagerService.MediaProjection secondProjection = + startProjectionPreconditions(); + FakeIMediaProjectionCallback callback2 = new FakeIMediaProjectionCallback(); + secondProjection.start(callback2); + + // Check that the first projection get stopped, but not the second projection. + final int timeout = 5; + boolean stoppedCallback1 = callback1.mLatch.await(timeout, TimeUnit.SECONDS); + boolean stoppedCallback2 = callback2.mLatch.await(timeout, TimeUnit.SECONDS); + + assertThat(stoppedCallback1).isTrue(); + assertThat(stoppedCallback2).isFalse(); + } + + @Test public void testCreateProjection_attemptReuse_noPriorProjectionGrant() throws NameNotFoundException { // Create a first projection. @@ -785,8 +811,10 @@ public class MediaProjectionManagerServiceTest { } private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub { + CountDownLatch mLatch = new CountDownLatch(1); @Override public void onStop() throws RemoteException { + mLatch.countDown(); } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 2ad9fa0e5b13..0566f460c655 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -862,6 +862,39 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test + public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse() + throws Exception { + prepareActivityThatShouldApplyUserMinAspectRatioOverride(); + mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + + assertFalse(mController.shouldEnableUserAspectRatioSettings()); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue() + throws Exception { + prepareActivityThatShouldApplyUserMinAspectRatioOverride(); + mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); + + mController = new LetterboxUiController(mWm, mActivity); + + assertTrue(mController.shouldEnableUserAspectRatioSettings()); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_noIgnoreOrientaion_returnsFalse() + throws Exception { + prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false); + mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); + + mController = new LetterboxUiController(mWm, mActivity); + + assertFalse(mController.shouldEnableUserAspectRatioSettings()); + } + + @Test public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse() throws Exception { prepareActivityThatShouldApplyUserMinAspectRatioOverride(); @@ -898,13 +931,26 @@ public class LetterboxUiControllerTest extends WindowTestsBase { assertTrue(mController.shouldApplyUserMinAspectRatioOverride()); } - private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() { + @Test + public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientationreturnsFalse() { + prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false); + + assertFalse(mController.shouldApplyUserMinAspectRatioOverride()); + } + + private void prepareActivityForShouldApplyUserMinAspectRatioOverride( + boolean orientationRequest) { spyOn(mController); - doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled(); + doReturn(orientationRequest).when( + mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled(); mDisplayContent.setIgnoreOrientationRequest(true); doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode(); } + private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() { + prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ true); + } + private void prepareActivityThatShouldApplyUserFullscreenOverride() { spyOn(mController); doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled(); 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 f332b6988da0..09b56f450955 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -571,26 +571,28 @@ public class TaskTests extends WindowTestsBase { final Task task = rootTask.getBottomMostTask(); final ActivityRecord root = task.getTopNonFinishingActivity(); spyOn(mWm.mLetterboxConfiguration); - - // When device config flag is disabled the button is not enabled - doReturn(false).when(mWm.mLetterboxConfiguration) - .isUserAppAspectRatioSettingsEnabled(); - doReturn(false).when(mWm.mLetterboxConfiguration) - .isTranslucentLetterboxingEnabled(); - assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); - - // The flag is enabled - doReturn(true).when(mWm.mLetterboxConfiguration) - .isUserAppAspectRatioSettingsEnabled(); spyOn(root); - doReturn(task).when(root).getOrganizedTask(); - // When the flag is enabled and the top activity is not in size compat mode. + spyOn(root.mLetterboxUiController); + + doReturn(true).when(root.mLetterboxUiController) + .shouldEnableUserAspectRatioSettings(); doReturn(false).when(root).inSizeCompatMode(); + doReturn(task).when(root).getOrganizedTask(); + + // The button should be eligible to be displayed assertTrue(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + // When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled + doReturn(false).when(root.mLetterboxUiController) + .shouldEnableUserAspectRatioSettings(); + assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + doReturn(true).when(root.mLetterboxUiController) + .shouldEnableUserAspectRatioSettings(); + // When in size compat mode the button is not enabled doReturn(true).when(root).inSizeCompatMode(); assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + doReturn(false).when(root).inSizeCompatMode(); } /** diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 60d640c71d3a..fbb2eb99a1ba 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10182,7 +10182,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0); sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false); - sDefaults.putBoolean(KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL, true); + sDefaults.putBoolean(KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL, false); sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA}); sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true); |