diff options
51 files changed, 1084 insertions, 361 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f227bbdd2cc5..c0183ad48139 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3799,44 +3799,7 @@ public final class ViewRootImpl implements ViewParent, } if (mAdded) { - profileRendering(hasWindowFocus); - if (hasWindowFocus) { - if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { - mFullRedrawNeeded = true; - try { - final Rect surfaceInsets = mWindowAttributes.surfaceInsets; - mAttachInfo.mThreadedRenderer.initializeIfNeeded( - mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); - } catch (OutOfResourcesException e) { - Log.e(mTag, "OutOfResourcesException locking surface", e); - try { - if (!mWindowSession.outOfMemory(mWindow)) { - Slog.w(mTag, "No processes killed for memory;" - + " killing self"); - Process.killProcess(Process.myPid()); - } - } catch (RemoteException ex) { - } - // Retry in a bit. - mHandler.sendMessageDelayed(mHandler.obtainMessage( - MSG_WINDOW_FOCUS_CHANGED), 500); - return; - } - } - } - - mAttachInfo.mHasWindowFocus = hasWindowFocus; - mImeFocusController.updateImeFocusable(mWindowAttributes, true /* force */); - mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes); - - if (mView != null) { - mAttachInfo.mKeyDispatchState.reset(); - mView.dispatchWindowFocusChanged(hasWindowFocus); - mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); - if (mAttachInfo.mTooltipHost != null) { - mAttachInfo.mTooltipHost.hideTooltip(); - } - } + dispatchFocusEvent(hasWindowFocus); // Note: must be done after the focus change callbacks, // so all of the view state is set up correctly. @@ -3872,6 +3835,45 @@ public final class ViewRootImpl implements ViewParent, } } + private void dispatchFocusEvent(boolean hasWindowFocus) { + profileRendering(hasWindowFocus); + if (hasWindowFocus && mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { + mFullRedrawNeeded = true; + try { + final Rect surfaceInsets = mWindowAttributes.surfaceInsets; + mAttachInfo.mThreadedRenderer.initializeIfNeeded( + mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); + } catch (OutOfResourcesException e) { + Log.e(mTag, "OutOfResourcesException locking surface", e); + try { + if (!mWindowSession.outOfMemory(mWindow)) { + Slog.w(mTag, "No processes killed for memory;" + + " killing self"); + Process.killProcess(Process.myPid()); + } + } catch (RemoteException ex) { + } + // Retry in a bit. + mHandler.sendMessageDelayed(mHandler.obtainMessage( + MSG_WINDOW_FOCUS_CHANGED), 500); + return; + } + } + + mAttachInfo.mHasWindowFocus = hasWindowFocus; + mImeFocusController.updateImeFocusable(mWindowAttributes, true /* force */); + mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes); + + if (mView != null) { + mAttachInfo.mKeyDispatchState.reset(); + mView.dispatchWindowFocusChanged(hasWindowFocus); + mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); + if (mAttachInfo.mTooltipHost != null) { + mAttachInfo.mTooltipHost.hideTooltip(); + } + } + } + private void handleWindowTouchModeChanged() { final boolean inTouchMode; synchronized (this) { diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 18c29d196ada..bbbb79c79527 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5300,6 +5300,10 @@ <!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. --> <bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool> + <!-- Whether the specific behaviour for translucent activities letterboxing is enabled. + TODO(b/255532890) Enable when ignoreOrientationRequest is set --> + <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool> + <!-- Whether a camera compat controller is enabled to allow the user to apply or revert treatment for stretched issues in camera viewfinder. --> <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b9259218c2d3..b0f6ae6085e3 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4401,6 +4401,9 @@ <!-- Set to true to make assistant show in front of the dream/screensaver. --> <java-symbol type="bool" name="config_assistantOnTopOfDream"/> + <!-- Set to true to enable letterboxing on translucent activities. --> + <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" /> + <java-symbol type="string" name="config_overrideComponentUiPackage" /> <java-symbol type="string" name="notification_channel_network_status" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml index e64b586a3e6f..8497ff094c03 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml @@ -27,6 +27,7 @@ android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" + android:paddingTop="@dimen/keyguard_lock_padding" android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent from this view when bouncer is shown --> diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/drawable/controls_panel_background.xml index 1992c7733bd3..9092877fc6fa 100644 --- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml +++ b/packages/SystemUI/res/drawable/controls_panel_background.xml @@ -1,3 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2022 The Android Open Source Project ~ @@ -12,9 +13,10 @@ ~ 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. + ~ --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="@color/dream_overlay_aqi_unknown" /> - <corners android:radius="@dimen/dream_aqi_badge_corner_radius" /> + <solid android:color="#1F1F1F" /> + <corners android:radius="@dimen/notification_corner_radius" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index 9efad2269463..ee3adba00fe5 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -90,7 +90,7 @@ android:layout_weight="1" android:layout_marginLeft="@dimen/global_actions_side_margin" android:layout_marginRight="@dimen/global_actions_side_margin" - android:background="#ff0000" + android:background="@drawable/controls_panel_background" android:padding="@dimen/global_actions_side_margin" android:visibility="gone" /> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml b/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml deleted file mode 100644 index fcebb8d3f6c6..000000000000 --- a/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml +++ /dev/null @@ -1,26 +0,0 @@ -<!-- - ~ Copyright (C) 2022 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. - --> - -<TextView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/aqi_view" - style="@style/clock_subtitle" - android:visibility="gone" - android:background="@drawable/dream_aqi_badge_bg" - android:paddingHorizontal="@dimen/dream_aqi_badge_padding_horizontal" - android:paddingVertical="@dimen/dream_aqi_badge_padding_vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml b/packages/SystemUI/res/values-h411dp/dimens.xml index f05922fb395c..6b21353d0e55 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml +++ b/packages/SystemUI/res/values-h411dp/dimens.xml @@ -13,10 +13,7 @@ ~ 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. ---> -<TextView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/weather_view" - style="@style/clock_subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> + --> +<resources> + <dimen name="volume_row_slider_height">137dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml index 055308f17776..39777ab56847 100644 --- a/packages/SystemUI/res/values-h700dp/dimens.xml +++ b/packages/SystemUI/res/values-h700dp/dimens.xml @@ -17,4 +17,5 @@ <resources> <!-- Margin above the ambient indication container --> <dimen name="ambient_indication_container_margin_top">15dp</dimen> -</resources>
\ No newline at end of file + <dimen name="volume_row_slider_height">177dp</dimen> +</resources> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/values-h841dp/dimens.xml index efbdd1af3644..412da199f6b6 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml +++ b/packages/SystemUI/res/values-h841dp/dimens.xml @@ -13,12 +13,7 @@ ~ 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. ---> -<TextClock - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/date_view" - style="@style/clock_subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:format12Hour="@string/dream_date_complication_date_format" - android:format24Hour="@string/dream_date_complication_date_format"/> + --> +<resources> + <dimen name="volume_row_slider_height">237dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index bc88bee41df8..ca4217f64b60 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -236,15 +236,6 @@ <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color> - <!-- Air Quality --> - <color name="dream_overlay_aqi_good">#689F38</color> - <color name="dream_overlay_aqi_moderate">#FBC02D</color> - <color name="dream_overlay_aqi_unhealthy_sensitive">#F57C00</color> - <color name="dream_overlay_aqi_unhealthy">#C53929</color> - <color name="dream_overlay_aqi_very_unhealthy">#AD1457</color> - <color name="dream_overlay_aqi_hazardous">#880E4F</color> - <color name="dream_overlay_aqi_unknown">#BDC1C6</color> - <!-- Dream overlay text shadows --> <color name="dream_overlay_clock_key_text_shadow_color">#4D000000</color> <color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index f3d2638c4ee7..c10e75289cf2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1560,10 +1560,6 @@ <dimen name="dream_overlay_y_offset">80dp</dimen> <dimen name="dream_overlay_exit_y_offset">40dp</dimen> - <dimen name="dream_aqi_badge_corner_radius">28dp</dimen> - <dimen name="dream_aqi_badge_padding_vertical">6dp</dimen> - <dimen name="dream_aqi_badge_padding_horizontal">16dp</dimen> - <dimen name="status_view_margin_horizontal">0dp</dimen> <!-- Media output broadcast dialog QR code picture size --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 084a0e0cb62d..643f831c7f94 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2688,9 +2688,6 @@ <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is unknown --> <string name="bt_le_audio_broadcast_dialog_unknown_name">Unknown</string> - <!-- Date format for the Dream Date Complication [CHAR LIMIT=NONE] --> - <string name="dream_date_complication_date_format">EEE, MMM d</string> - <!-- Time format for the Dream Time Complication for 12-hour time format [CHAR LIMIT=NONE] --> <string name="dream_time_complication_12_hr_time_format">h:mm</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 860c8e3a9f77..7da27b1d6898 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -260,7 +260,8 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey if (reason != PROMPT_REASON_NONE) { int promtReasonStringRes = mView.getPromptReasonStringRes(reason); if (promtReasonStringRes != 0) { - mMessageAreaController.setMessage(promtReasonStringRes); + mMessageAreaController.setMessage( + mView.getResources().getString(promtReasonStringRes), false); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 2e9ad5868eba..d1c9a3090860 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -142,8 +142,11 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> } public void startAppearAnimation() { - if (TextUtils.isEmpty(mMessageAreaController.getMessage())) { - mMessageAreaController.setMessage(getInitialMessageResId()); + if (TextUtils.isEmpty(mMessageAreaController.getMessage()) + && getInitialMessageResId() != 0) { + mMessageAreaController.setMessage( + mView.getResources().getString(getInitialMessageResId()), + /* animate= */ false); } mView.startAppearAnimation(); } @@ -163,9 +166,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> } /** Determines the message to show in the bouncer when it first appears. */ - protected int getInitialMessageResId() { - return 0; - } + protected abstract int getInitialMessageResId(); /** Factory for a {@link KeyguardInputViewController}. */ public static class Factory { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 5d86ccd5409e..67e3400670ba 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -52,6 +52,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { private int mYTransOffset; private View mBouncerMessageView; @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN; + public static final long ANIMATION_DURATION = 650; public KeyguardPINView(Context context) { this(context, null); @@ -181,7 +182,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { if (mAppearAnimator.isRunning()) { mAppearAnimator.cancel(); } - mAppearAnimator.setDuration(650); + mAppearAnimator.setDuration(ANIMATION_DURATION); mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction())); mAppearAnimator.start(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index f7423ed12e68..8011efdc1ae7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -139,4 +139,9 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB super.startErrorAnimation(); mView.startErrorAnimation(); } + + @Override + protected int getInitialMessageResId() { + return R.string.keyguard_enter_your_pin; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index f51ac325c9c1..35b2db27d879 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -74,9 +74,4 @@ public class KeyguardPinViewController return mView.startDisappearAnimation( mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); } - - @Override - protected int getInitialMessageResId() { - return R.string.keyguard_enter_your_pin; - } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 8f3484a0c99b..5d7a6f122e69 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -36,8 +36,11 @@ import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; import static java.lang.Integer.max; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.app.Activity; import android.app.AlertDialog; import android.app.admin.DevicePolicyManager; @@ -967,11 +970,23 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } mUserSwitcherViewGroup.setAlpha(0f); - ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA, - 1f); - alphaAnim.setInterpolator(Interpolators.ALPHA_IN); - alphaAnim.setDuration(500); - alphaAnim.start(); + ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry); + animator.setInterpolator(Interpolators.STANDARD_DECELERATE); + animator.setDuration(650); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mUserSwitcherViewGroup.setAlpha(1f); + mUserSwitcherViewGroup.setTranslationY(0f); + } + }); + animator.addUpdateListener(animation -> { + float value = (float) animation.getAnimatedValue(); + mUserSwitcherViewGroup.setAlpha(value); + mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value); + }); + animator.start(); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java index a5c8c7881e3b..39b567fd21b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -156,5 +156,10 @@ public class KeyguardSecurityViewFlipperController @Override public void onStartingToHide() { } + + @Override + protected int getInitialMessageResId() { + return 0; + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 71d5bf57baf6..ec4b78065c63 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1660,7 +1660,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationFailed() { - requestActiveUnlock( + requestActiveUnlockDismissKeyguard( ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL, "fingerprintFailure"); handleFingerprintAuthFailed(); @@ -2591,6 +2591,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Attempts to trigger active unlock from trust agent with a request to dismiss the keyguard. + */ + public void requestActiveUnlockDismissKeyguard( + @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin, + String extraReason + ) { + requestActiveUnlock( + requestOrigin, + extraReason + "-dismissKeyguard", true); + } + + /** * Whether the UDFPS bouncer is showing. */ public void setUdfpsBouncerShowing(boolean showing) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt index 4aa597ef3d28..8d0edf829416 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt @@ -50,7 +50,12 @@ object ControlsAnimations { * Setup an activity to handle enter/exit animations. [view] should be the root of the content. * Fade and translate together. */ - fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver { + fun observerForAnimations( + view: ViewGroup, + window: Window, + intent: Intent, + animateY: Boolean = true + ): LifecycleObserver { return object : LifecycleObserver { var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false) @@ -61,8 +66,12 @@ object ControlsAnimations { view.transitionAlpha = 0.0f if (translationY == -1f) { - translationY = view.context.resources.getDimensionPixelSize( - R.dimen.global_actions_controls_y_translation).toFloat() + if (animateY) { + translationY = view.context.resources.getDimensionPixelSize( + R.dimen.global_actions_controls_y_translation).toFloat() + } else { + translationY = 0f + } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index 5d611c4c8212..d8d8c0ead06a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -70,7 +70,8 @@ class ControlsActivity @Inject constructor( ControlsAnimations.observerForAnimations( requireViewById<ViewGroup>(R.id.control_detail_root), window, - intent + intent, + !featureFlags.isEnabled(Flags.USE_APP_PANELS) ) ) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index fb678aa420bf..1e3e5cd1c31c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -186,7 +186,7 @@ class ControlsUiControllerImpl @Inject constructor ( val allStructures = controlsController.get().getFavorites() val selected = getPreferredSelectedItem(allStructures) val anyPanels = controlsListingController.get().getCurrentServices() - .none { it.panelActivity != null } + .any { it.panelActivity != null } return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) { ControlsActivity::class.java diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt index 7143be298a9d..f5764c2fdc04 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt @@ -24,6 +24,10 @@ import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.RoundRectShape +import com.android.systemui.R import com.android.systemui.util.boundsOnScreen import com.android.wm.shell.TaskView import java.util.concurrent.Executor @@ -64,6 +68,16 @@ class PanelTaskViewController( options.taskAlwaysOnTop = true taskView.post { + val roundedCorner = + activityContext.resources.getDimensionPixelSize( + R.dimen.notification_corner_radius + ) + val radii = FloatArray(8) { roundedCorner.toFloat() } + taskView.background = + ShapeDrawable(RoundRectShape(radii, null, null)).apply { + setTint(Color.TRANSPARENT) + } + taskView.clipToOutline = true taskView.startActivity( pendingIntent, fillInIntent, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index ee0051220787..1065b94508f8 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -136,8 +136,15 @@ public class DreamHomeControlsComplication implements Complication { final boolean hasFavorites = mControlsComponent.getControlsController() .map(c -> !c.getFavorites().isEmpty()) .orElse(false); + boolean hasPanels = false; + for (int i = 0; i < controlsServices.size(); i++) { + if (controlsServices.get(i).getPanelActivity() != null) { + hasPanels = true; + break; + } + } final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility(); - return hasFavorites && visibility != UNAVAILABLE; + return (hasFavorites || hasPanels) && visibility != UNAVAILABLE; } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt index 2558fab216a0..394426df5552 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt @@ -130,6 +130,7 @@ constructor( state( isFeatureEnabled = component.isEnabled(), hasFavorites = favorites?.isNotEmpty() == true, + hasPanels = serviceInfos.any { it.panelActivity != null }, hasServiceInfos = serviceInfos.isNotEmpty(), iconResourceId = component.getTileImageId(), visibility = component.getVisibility(), @@ -148,13 +149,14 @@ constructor( private fun state( isFeatureEnabled: Boolean, hasFavorites: Boolean, + hasPanels: Boolean, hasServiceInfos: Boolean, visibility: ControlsComponent.Visibility, @DrawableRes iconResourceId: Int?, ): KeyguardQuickAffordanceConfig.LockScreenState { return if ( isFeatureEnabled && - hasFavorites && + (hasFavorites || hasPanels) && hasServiceInfos && iconResourceId != null && visibility == ControlsComponent.Visibility.AVAILABLE diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index ee3b13091d00..1ed18c3df332 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -363,6 +363,9 @@ public class InternetDialog extends SystemUIDialog implements if (!isChecked && shouldShowMobileDialog()) { showTurnOffMobileDialog(); } else if (!shouldShowMobileDialog()) { + if (mInternetDialogController.isMobileDataEnabled() == isChecked) { + return; + } mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId, isChecked, false); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index e33248cc5a2b..bfc810be85df 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2325,7 +2325,7 @@ public final class NotificationPanelViewController implements Dumpable { private boolean handleQsTouch(MotionEvent event) { - if (mSplitShadeEnabled && touchXOutsideOfQs(event.getX())) { + if (isSplitShadeAndTouchXOutsideQs(event.getX())) { return false; } final int action = event.getActionMasked(); @@ -2382,12 +2382,14 @@ public final class NotificationPanelViewController implements Dumpable { return false; } - private boolean touchXOutsideOfQs(float touchX) { - return touchX < mQsFrame.getX() || touchX > mQsFrame.getX() + mQsFrame.getWidth(); + /** Returns whether split shade is enabled and an x coordinate is outside of the QS frame. */ + private boolean isSplitShadeAndTouchXOutsideQs(float touchX) { + return mSplitShadeEnabled && (touchX < mQsFrame.getX() + || touchX > mQsFrame.getX() + mQsFrame.getWidth()); } private boolean isInQsArea(float x, float y) { - if (touchXOutsideOfQs(x)) { + if (isSplitShadeAndTouchXOutsideQs(x)) { return false; } // Let's reject anything at the very bottom around the home handle in gesture nav @@ -4720,6 +4722,7 @@ public final class NotificationPanelViewController implements Dumpable { if (!openingWithTouch || !mHasVibratedOnOpen) { mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); mHasVibratedOnOpen = true; + mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true"); } } } @@ -5316,7 +5319,7 @@ public final class NotificationPanelViewController implements Dumpable { @Override public void flingTopOverscroll(float velocity, boolean open) { // in split shade mode we want to expand/collapse QS only when touch happens within QS - if (mSplitShadeEnabled && touchXOutsideOfQs(mInitialTouchX)) { + if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) { return; } mLastOverscroll = 0f; @@ -6120,6 +6123,7 @@ public final class NotificationPanelViewController implements Dumpable { if (isFullyCollapsed()) { // If panel is fully collapsed, reset haptic effect before adding movement. mHasVibratedOnOpen = false; + mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction); } addMovement(event); if (!isFullyCollapsed()) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 0b59af3435ca..5fedbeb556c2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -140,6 +140,15 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { }) } + fun logHasVibrated(hasVibratedOnOpen: Boolean, fraction: Float) { + log(LogLevel.VERBOSE, { + bool1 = hasVibratedOnOpen + double1 = fraction.toDouble() + }, { + "hasVibratedOnOpen=$bool1, expansionFraction=$double1" + }) + } + fun logQsExpansionChanged( message: String, qsExpanded: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 556431114ff1..5e98f5419e84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -161,7 +161,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private KeyguardViewController mKeyguardViewController; private DozeScrimController mDozeScrimController; private KeyguardViewMediator mKeyguardViewMediator; - private ScrimController mScrimController; private PendingAuthenticated mPendingAuthenticated = null; private boolean mHasScreenTurnedOnSinceAuthenticating; private boolean mFadedAwayAfterWakeAndUnlock; @@ -261,7 +260,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp @Inject public BiometricUnlockController( DozeScrimController dozeScrimController, - KeyguardViewMediator keyguardViewMediator, ScrimController scrimController, + KeyguardViewMediator keyguardViewMediator, ShadeController shadeController, NotificationShadeWindowController notificationShadeWindowController, KeyguardStateController keyguardStateController, Handler handler, @@ -293,7 +292,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mNotificationShadeWindowController = notificationShadeWindowController; mDozeScrimController = dozeScrimController; mKeyguardViewMediator = keyguardViewMediator; - mScrimController = scrimController; mKeyguardStateController = keyguardStateController; mHandler = handler; mConsecutiveFpFailureThreshold = resources.getInteger( @@ -375,12 +373,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Trace.endSection(); } - private boolean pulsingOrAod() { - final ScrimState scrimState = mScrimController.getState(); - return scrimState == ScrimState.AOD - || scrimState == ScrimState.PULSING; - } - @Override public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, boolean isStrongBiometric) { @@ -425,7 +417,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive(); mMode = mode; mHasScreenTurnedOnSinceAuthenticating = false; - if (mMode == MODE_WAKE_AND_UNLOCK_PULSING && pulsingOrAod()) { + if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) { // If we are waking the device up while we are pulsing the clock and the // notifications would light up first, creating an unpleasant animation. // Defer changing the screen brightness by forcing doze brightness on our window diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index 8bbaf3dff1e5..10595439200a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -19,6 +19,7 @@ package com.android.keyguard; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -87,6 +88,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true); when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area)) .thenReturn(mKeyguardMessageArea); + when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources()); mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector, @@ -99,6 +101,11 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { public void onResume(int reason) { super.onResume(reason); } + + @Override + protected int getInitialMessageResId() { + return 0; + } }; mKeyguardAbsKeyInputViewController.init(); reset(mKeyguardMessageAreaController); // Clear out implicit call to init. @@ -125,4 +132,22 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { verifyZeroInteractions(mKeyguardSecurityCallback); verifyZeroInteractions(mKeyguardMessageAreaController); } + + @Test + public void onPromptReasonNone_doesNotSetMessage() { + mKeyguardAbsKeyInputViewController.showPromptReason(0); + verify(mKeyguardMessageAreaController, never()).setMessage( + getContext().getResources().getString(R.string.kg_prompt_reason_restart_password), + false); + } + + @Test + public void onPromptReason_setsMessage() { + when(mAbsKeyInputView.getPromptReasonStringRes(1)).thenReturn( + R.string.kg_prompt_reason_restart_password); + mKeyguardAbsKeyInputViewController.showPromptReason(1); + verify(mKeyguardMessageAreaController).setMessage( + getContext().getResources().getString(R.string.kg_prompt_reason_restart_password), + false); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index d20be56d6c6b..d91279399341 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -30,64 +30,54 @@ import com.android.systemui.util.concurrency.DelayableExecutor 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.Mock import org.mockito.Mockito -import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class KeyguardPasswordViewControllerTest : SysuiTestCase() { - @Mock - private lateinit var keyguardPasswordView: KeyguardPasswordView - @Mock - private lateinit var passwordEntry: EditText - @Mock - lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock - lateinit var securityMode: KeyguardSecurityModel.SecurityMode - @Mock - lateinit var lockPatternUtils: LockPatternUtils - @Mock - lateinit var keyguardSecurityCallback: KeyguardSecurityCallback - @Mock - lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory - @Mock - lateinit var latencyTracker: LatencyTracker - @Mock - lateinit var inputMethodManager: InputMethodManager - @Mock - lateinit var emergencyButtonController: EmergencyButtonController - @Mock - lateinit var mainExecutor: DelayableExecutor - @Mock - lateinit var falsingCollector: FalsingCollector - @Mock - lateinit var keyguardViewController: KeyguardViewController - @Mock - private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea - @Mock - private lateinit var mKeyguardMessageAreaController: - KeyguardMessageAreaController<BouncerKeyguardMessageArea> + @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView + @Mock private lateinit var passwordEntry: EditText + @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode + @Mock lateinit var lockPatternUtils: LockPatternUtils + @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback + @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory + @Mock lateinit var latencyTracker: LatencyTracker + @Mock lateinit var inputMethodManager: InputMethodManager + @Mock lateinit var emergencyButtonController: EmergencyButtonController + @Mock lateinit var mainExecutor: DelayableExecutor + @Mock lateinit var falsingCollector: FalsingCollector + @Mock lateinit var keyguardViewController: KeyguardViewController + @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea + @Mock + private lateinit var mKeyguardMessageAreaController: + KeyguardMessageAreaController<BouncerKeyguardMessageArea> - private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController + private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - Mockito.`when`( - keyguardPasswordView - .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area) - ).thenReturn(mKeyguardMessageArea) - Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea)) - .thenReturn(mKeyguardMessageAreaController) - Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry) - Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry) - ).thenReturn(passwordEntry) - keyguardPasswordViewController = KeyguardPasswordViewController( + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + Mockito.`when`( + keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>( + R.id.bouncer_message_area)) + .thenReturn(mKeyguardMessageArea) + Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea)) + .thenReturn(mKeyguardMessageAreaController) + Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry) + Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry)) + .thenReturn(passwordEntry) + `when`(keyguardPasswordView.resources).thenReturn(context.resources) + keyguardPasswordViewController = + KeyguardPasswordViewController( keyguardPasswordView, keyguardUpdateMonitor, securityMode, @@ -100,51 +90,48 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { mainExecutor, mContext.resources, falsingCollector, - keyguardViewController - ) - } + keyguardViewController) + } - @Test - fun testFocusWhenBouncerIsShown() { - Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true) - Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true) - keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED) - keyguardPasswordView.post { - verify(keyguardPasswordView).requestFocus() - verify(keyguardPasswordView).showKeyboard() - } + @Test + fun testFocusWhenBouncerIsShown() { + Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true) + Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true) + keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED) + keyguardPasswordView.post { + verify(keyguardPasswordView).requestFocus() + verify(keyguardPasswordView).showKeyboard() } + } - @Test - fun testDoNotFocusWhenBouncerIsHidden() { - Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false) - Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true) - keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED) - verify(keyguardPasswordView, never()).requestFocus() - } + @Test + fun testDoNotFocusWhenBouncerIsHidden() { + Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false) + Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true) + keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED) + verify(keyguardPasswordView, never()).requestFocus() + } - @Test - fun testHideKeyboardWhenOnPause() { - keyguardPasswordViewController.onPause() - keyguardPasswordView.post { - verify(keyguardPasswordView).clearFocus() - verify(keyguardPasswordView).hideKeyboard() - } + @Test + fun testHideKeyboardWhenOnPause() { + keyguardPasswordViewController.onPause() + keyguardPasswordView.post { + verify(keyguardPasswordView).clearFocus() + verify(keyguardPasswordView).hideKeyboard() } + } - @Test - fun startAppearAnimation() { - keyguardPasswordViewController.startAppearAnimation() - verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password) - } + @Test + fun startAppearAnimation() { + keyguardPasswordViewController.startAppearAnimation() + verify(mKeyguardMessageAreaController) + .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false) + } - @Test - fun startAppearAnimation_withExistingMessage() { - `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.") - keyguardPasswordViewController.startAppearAnimation() - verify( - mKeyguardMessageAreaController, - never() - ).setMessage(R.string.keyguard_enter_your_password) - } + @Test + fun startAppearAnimation_withExistingMessage() { + `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.") + keyguardPasswordViewController.startAppearAnimation() + verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean()) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index b3d1c8f909d8..85dbdb8330a3 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -30,97 +30,93 @@ import com.android.systemui.statusbar.policy.DevicePostureController 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.Mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` -import org.mockito.Mockito.never import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class KeyguardPatternViewControllerTest : SysuiTestCase() { - @Mock - private lateinit var mKeyguardPatternView: KeyguardPatternView + @Mock private lateinit var mKeyguardPatternView: KeyguardPatternView - @Mock - private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock - private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode + @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode - @Mock - private lateinit var mLockPatternUtils: LockPatternUtils + @Mock private lateinit var mLockPatternUtils: LockPatternUtils - @Mock - private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback + @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback - @Mock - private lateinit var mLatencyTracker: LatencyTracker - private var mFalsingCollector: FalsingCollector = FalsingCollectorFake() + @Mock private lateinit var mLatencyTracker: LatencyTracker + private var mFalsingCollector: FalsingCollector = FalsingCollectorFake() - @Mock - private lateinit var mEmergencyButtonController: EmergencyButtonController + @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController - @Mock - private lateinit - var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory + @Mock + private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory - @Mock - private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea + @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea - @Mock - private lateinit var mKeyguardMessageAreaController: - KeyguardMessageAreaController<BouncerKeyguardMessageArea> + @Mock + private lateinit var mKeyguardMessageAreaController: + KeyguardMessageAreaController<BouncerKeyguardMessageArea> - @Mock - private lateinit var mLockPatternView: LockPatternView + @Mock private lateinit var mLockPatternView: LockPatternView - @Mock - private lateinit var mPostureController: DevicePostureController + @Mock private lateinit var mPostureController: DevicePostureController - private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController + private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true) - `when`(mKeyguardPatternView - .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area)) - .thenReturn(mKeyguardMessageArea) - `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView)) - .thenReturn(mLockPatternView) - `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea)) - .thenReturn(mKeyguardMessageAreaController) - mKeyguardPatternViewController = KeyguardPatternViewController( + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true) + `when`( + mKeyguardPatternView.requireViewById<BouncerKeyguardMessageArea>( + R.id.bouncer_message_area)) + .thenReturn(mKeyguardMessageArea) + `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView)) + .thenReturn(mLockPatternView) + `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea)) + .thenReturn(mKeyguardMessageAreaController) + `when`(mKeyguardPatternView.resources).thenReturn(context.resources) + mKeyguardPatternViewController = + KeyguardPatternViewController( mKeyguardPatternView, - mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, - mLatencyTracker, mFalsingCollector, mEmergencyButtonController, - mKeyguardMessageAreaControllerFactory, mPostureController - ) - } - - @Test - fun onPause_resetsText() { - mKeyguardPatternViewController.init() - mKeyguardPatternViewController.onPause() - verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern) - } - - - @Test - fun startAppearAnimation() { - mKeyguardPatternViewController.startAppearAnimation() - verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern) - } - - @Test - fun startAppearAnimation_withExistingMessage() { - `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.") - mKeyguardPatternViewController.startAppearAnimation() - verify( - mKeyguardMessageAreaController, - never() - ).setMessage(R.string.keyguard_enter_your_password) - } + mKeyguardUpdateMonitor, + mSecurityMode, + mLockPatternUtils, + mKeyguardSecurityCallback, + mLatencyTracker, + mFalsingCollector, + mEmergencyButtonController, + mKeyguardMessageAreaControllerFactory, + mPostureController) + } + + @Test + fun onPause_resetsText() { + mKeyguardPatternViewController.init() + mKeyguardPatternViewController.onPause() + verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern) + } + + @Test + fun startAppearAnimation() { + mKeyguardPatternViewController.startAppearAnimation() + verify(mKeyguardMessageAreaController) + .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false) + } + + @Test + fun startAppearAnimation_withExistingMessage() { + `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.") + mKeyguardPatternViewController.startAppearAnimation() + verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean()) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index ce1101f389c0..b7421001b57e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -113,4 +115,9 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON); verify(mPasswordEntry).requestFocus(); } + + @Test + public void testGetInitialMessageResId() { + assertThat(mKeyguardPinViewController.getInitialMessageResId()).isNotEqualTo(0); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 8bcfe6f2b6f5..cdb7bbb9f823 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -31,10 +31,13 @@ import com.android.systemui.statusbar.policy.DevicePostureController 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.Mock import org.mockito.Mockito import org.mockito.Mockito.any import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -79,6 +82,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java)) ) .thenReturn(keyguardMessageAreaController) + `when`(keyguardPinView.resources).thenReturn(context.resources) pinViewController = KeyguardPinViewController( keyguardPinView, @@ -98,14 +102,14 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun startAppearAnimation() { pinViewController.startAppearAnimation() - verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin) + verify(keyguardMessageAreaController) + .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false) } @Test fun startAppearAnimation_withExistingMessage() { Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.") pinViewController.startAppearAnimation() - verify(keyguardMessageAreaController, Mockito.never()) - .setMessage(R.string.keyguard_enter_your_password) + verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean()) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 40542d25689d..849ff08ac84c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -2084,6 +2084,96 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); } + @Test + public void fingerprintFailure_requestActiveUnlock_dismissKeyguard() + throws RemoteException { + // GIVEN shouldTriggerActiveUnlock + bouncerFullyVisible(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // GIVEN active unlock triggers on biometric failures + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) + .thenReturn(true); + + // WHEN fingerprint fails + mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback.onAuthenticationFailed(); + + // ALWAYS request unlock with a keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(true)); + } + + @Test + public void faceNonBypassFailure_requestActiveUnlock_doesNotDismissKeyguard() + throws RemoteException { + // GIVEN shouldTriggerActiveUnlock + when(mAuthController.isUdfpsFingerDown()).thenReturn(false); + keyguardIsVisible(); + keyguardNotGoingAway(); + statusBarShadeIsNotLocked(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // GIVEN active unlock triggers on biometric failures + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) + .thenReturn(true); + + // WHEN face fails & bypass is not allowed + lockscreenBypassIsNotAllowed(); + mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed(); + + // THEN request unlock with NO keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(false)); + } + + @Test + public void faceBypassFailure_requestActiveUnlock_dismissKeyguard() + throws RemoteException { + // GIVEN shouldTriggerActiveUnlock + when(mAuthController.isUdfpsFingerDown()).thenReturn(false); + keyguardIsVisible(); + keyguardNotGoingAway(); + statusBarShadeIsNotLocked(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // GIVEN active unlock triggers on biometric failures + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) + .thenReturn(true); + + // WHEN face fails & bypass is not allowed + lockscreenBypassIsAllowed(); + mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed(); + + // THEN request unlock with a keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(true)); + } + + @Test + public void faceNonBypassFailure_requestActiveUnlock_dismissKeyguard() + throws RemoteException { + // GIVEN shouldTriggerActiveUnlock + when(mAuthController.isUdfpsFingerDown()).thenReturn(false); + lockscreenBypassIsNotAllowed(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // GIVEN active unlock triggers on biometric failures + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) + .thenReturn(true); + + // WHEN face fails & on the bouncer + bouncerFullyVisible(); + mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed(); + + // THEN request unlock with a keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(true)); + } + private void userDeviceLockDown() { when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId)) @@ -2101,6 +2191,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void mockCanBypassLockscreen(boolean canBypass) { + // force update the isFaceEnrolled cache: + mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser()); + mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController); when(mKeyguardBypassController.canBypass()).thenReturn(canBypass); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt index 779788aa0075..d172c9a2e630 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.management.ControlsProviderSelectorActivity import com.android.systemui.controls.settings.FakeControlsSettingsRepository import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter @@ -53,6 +54,7 @@ import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.android.wm.shell.TaskView import com.android.wm.shell.TaskViewFactory @@ -322,6 +324,45 @@ class ControlsUiControllerImplTest : SysuiTestCase() { .isFalse() } + @Test + fun testResolveActivityWhileSeeding_ControlsActivity() { + whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true) + assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java) + } + + @Test + fun testResolveActivityNotSeedingNoFavoritesNoPanels_ControlsProviderSelectorActivity() { + whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(false) + whenever(controlsController.getFavorites()).thenReturn(emptyList()) + + val selectedItems = + listOf( + SelectedItem.StructureItem( + StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList()) + ), + ) + sharedPreferences + .edit() + .putString("controls_component", selectedItems[0].componentName.flattenToString()) + .putString("controls_structure", selectedItems[0].name.toString()) + .commit() + + assertThat(underTest.resolveActivity()) + .isEqualTo(ControlsProviderSelectorActivity::class.java) + } + + @Test + fun testResolveActivityNotSeedingNoDefaultNoFavoritesPanel_ControlsActivity() { + val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls")) + val activity = ComponentName("pkg", "activity") + val csi = ControlsServiceInfo(panel.componentName, panel.appName, activity) + whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true) + whenever(controlsController.getFavorites()).thenReturn(emptyList()) + whenever(controlsListingController.getCurrentServices()).thenReturn(listOf(csi)) + + assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java) + } + private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo { val activity = ComponentName("pkg", "activity") sharedPreferences diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt index 5cd2ace4604a..de04ef810dd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt @@ -75,6 +75,7 @@ class PanelTaskViewControllerTest : SysuiTestCase() { uiExecutor.execute(it.arguments[0] as Runnable) true } + whenever(activityContext.resources).thenReturn(context.resources) uiExecutor = FakeExecutor(FakeSystemClock()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java index e6d3a69593cd..89c728082cc5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Context; import android.testing.AndroidTestingRunner; import android.view.View; @@ -54,6 +55,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -147,6 +149,19 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { } @Test + public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() { + final DreamHomeControlsComplication.Registrant registrant = + new DreamHomeControlsComplication.Registrant(mComplication, + mDreamOverlayStateController, mControlsComponent); + registrant.start(); + + setHaveFavorites(false); + setServiceWithPanel(); + + verify(mDreamOverlayStateController).addComplication(mComplication); + } + + @Test public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, @@ -232,6 +247,15 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { triggerControlsListingCallback(serviceInfos); } + private void setServiceWithPanel() { + final List<ControlsServiceInfo> serviceInfos = new ArrayList<>(); + ControlsServiceInfo csi = mock(ControlsServiceInfo.class); + serviceInfos.add(csi); + when(csi.getPanelActivity()).thenReturn(new ComponentName("a", "b")); + when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos); + triggerControlsListingCallback(serviceInfos); + } + private void setDreamOverlayActive(boolean value) { when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value); verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt index 322014a61a73..f8cb40885d21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt @@ -20,13 +20,14 @@ package com.android.systemui.keyguard.data.quickaffordance import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import java.util.* +import java.util.Optional import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -50,20 +51,22 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes companion object { @Parameters( name = - "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" + - " while locked = {3}, visibility is AVAILABLE {4} - expected visible = {5}" + "feature enabled = {0}, has favorites = {1}, has panels = {2}, " + + "has service infos = {3}, can show while locked = {4}, " + + "visibility is AVAILABLE {5} - expected visible = {6}" ) @JvmStatic fun data() = - (0 until 32) + (0 until 64) .map { combination -> arrayOf( - /* isFeatureEnabled= */ combination and 0b10000 != 0, - /* hasFavorites= */ combination and 0b01000 != 0, - /* hasServiceInfos= */ combination and 0b00100 != 0, - /* canShowWhileLocked= */ combination and 0b00010 != 0, - /* visibilityAvailable= */ combination and 0b00001 != 0, - /* isVisible= */ combination == 0b11111, + /* isFeatureEnabled= */ combination and 0b100000 != 0, + /* hasFavorites = */ combination and 0b010000 != 0, + /* hasPanels = */ combination and 0b001000 != 0, + /* hasServiceInfos= */ combination and 0b000100 != 0, + /* canShowWhileLocked= */ combination and 0b000010 != 0, + /* visibilityAvailable= */ combination and 0b000001 != 0, + /* isVisible= */ combination in setOf(0b111111, 0b110111, 0b101111), ) } .toList() @@ -72,6 +75,7 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes @Mock private lateinit var component: ControlsComponent @Mock private lateinit var controlsController: ControlsController @Mock private lateinit var controlsListingController: ControlsListingController + @Mock private lateinit var controlsServiceInfo: ControlsServiceInfo @Captor private lateinit var callbackCaptor: ArgumentCaptor<ControlsListingController.ControlsListingCallback> @@ -80,10 +84,11 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false @JvmField @Parameter(1) var hasFavorites: Boolean = false - @JvmField @Parameter(2) var hasServiceInfos: Boolean = false - @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false - @JvmField @Parameter(4) var isVisibilityAvailable: Boolean = false - @JvmField @Parameter(5) var isVisibleExpected: Boolean = false + @JvmField @Parameter(2) var hasPanels: Boolean = false + @JvmField @Parameter(3) var hasServiceInfos: Boolean = false + @JvmField @Parameter(4) var canShowWhileLocked: Boolean = false + @JvmField @Parameter(5) var isVisibilityAvailable: Boolean = false + @JvmField @Parameter(6) var isVisibleExpected: Boolean = false @Before fun setUp() { @@ -93,10 +98,13 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) whenever(component.getControlsListingController()) .thenReturn(Optional.of(controlsListingController)) + if (hasPanels) { + whenever(controlsServiceInfo.panelActivity).thenReturn(mock()) + } whenever(controlsListingController.getCurrentServices()) .thenReturn( if (hasServiceInfos) { - listOf(mock(), mock()) + listOf(controlsServiceInfo, mock()) } else { emptyList() } @@ -134,10 +142,15 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes val job = underTest.lockScreenState.onEach(values::add).launchIn(this) if (canShowWhileLocked) { + val serviceInfoMock: ControlsServiceInfo = mock { + if (hasPanels) { + whenever(panelActivity).thenReturn(mock()) + } + } verify(controlsListingController).addCallback(callbackCaptor.capture()) callbackCaptor.value.onServicesUpdated( if (hasServiceInfos) { - listOf(mock()) + listOf(serviceInfoMock) } else { emptyList() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index 8c8fdc5bf126..be0ad6e19a02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -84,7 +84,8 @@ public class InternetDialogTest extends SysuiTestCase { private View mDialogView; private View mSubTitle; private LinearLayout mEthernet; - private LinearLayout mMobileDataToggle; + private LinearLayout mMobileDataLayout; + private Switch mMobileToggleSwitch; private LinearLayout mWifiToggle; private Switch mWifiToggleSwitch; private TextView mWifiToggleSummary; @@ -133,7 +134,8 @@ public class InternetDialogTest extends SysuiTestCase { mDialogView = mInternetDialog.mDialogView; mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle); mEthernet = mDialogView.requireViewById(R.id.ethernet_layout); - mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout); + mMobileDataLayout = mDialogView.requireViewById(R.id.mobile_network_layout); + mMobileToggleSwitch = mDialogView.requireViewById(R.id.mobile_toggle); mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout); mWifiToggleSwitch = mDialogView.requireViewById(R.id.wifi_toggle); mWifiToggleSummary = mDialogView.requireViewById(R.id.wifi_toggle_summary); @@ -234,7 +236,7 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialog.updateDialog(true); - assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE); + assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE); } @Test @@ -246,7 +248,7 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialog.updateDialog(true); - assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE); + assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE); // Carrier network should be visible if airplane mode ON and Wi-Fi is ON. when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); @@ -255,7 +257,7 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialog.updateDialog(true); - assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE); } @Test @@ -265,7 +267,7 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialog.updateDialog(true); - assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE); + assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE); } @Test @@ -277,7 +279,7 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialog.updateDialog(true); - assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE); } @@ -314,6 +316,30 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() { + doReturn(true).when(mInternetDialogController).hasActiveSubId(); + when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); + when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true); + mMobileToggleSwitch.setChecked(false); + + mInternetDialog.updateDialog(true); + + assertThat(mMobileToggleSwitch.isChecked()).isTrue(); + } + + @Test + public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() { + doReturn(true).when(mInternetDialogController).hasActiveSubId(); + when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); + when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false); + mMobileToggleSwitch.setChecked(false); + + mInternetDialog.updateDialog(true); + + assertThat(mMobileToggleSwitch.isChecked()).isFalse(); + } + + @Test public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() { mInternetDialog.dismissDialog(); doReturn(true).when(mInternetDialogController).hasActiveSubId(); @@ -694,7 +720,7 @@ public class InternetDialogTest extends SysuiTestCase { private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible, boolean connectedWifiVisible) { mEthernet.setVisibility(ethernetVisible ? View.VISIBLE : View.GONE); - mMobileDataToggle.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE); + mMobileDataLayout.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE); mConnectedWifi.setVisibility(connectedWifiVisible ? View.VISIBLE : View.GONE); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index d1957acb9fd5..74f8c61ad186 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -89,8 +89,6 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { @Mock private KeyguardViewMediator mKeyguardViewMediator; @Mock - private ScrimController mScrimController; - @Mock private BiometricUnlockController.BiometricModeListener mBiometricModeListener; @Mock private ShadeController mShadeController; @@ -140,7 +138,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { when(mVibratorHelper.hasVibrator()).thenReturn(true); mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager); mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController, - mKeyguardViewMediator, mScrimController, mShadeController, + mKeyguardViewMediator, mShadeController, mNotificationShadeWindowController, mKeyguardStateController, mHandler, mUpdateMonitor, res.getResources(), mKeyguardBypassController, mMetricsLogger, mDumpManager, mPowerManager, mLogger, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b7b332621e7f..1ea09849cf61 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3211,6 +3211,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL); } + // TODO(b/261957226): centralise this logic in DPM boolean isPackageDeviceAdmin(String packageName, int userId) { final IDevicePolicyManager dpm = getDevicePolicyManager(); try { @@ -3237,6 +3238,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (dpm.packageHasActiveAdmins(packageName, users[i])) { return true; } + if (isDeviceManagementRoleHolder(packageName, users[i])) { + return true; + } } } } catch (RemoteException e) { @@ -3244,6 +3248,24 @@ public class PackageManagerService implements PackageSender, TestUtilityService return false; } + private boolean isDeviceManagementRoleHolder(String packageName, int userId) { + return Objects.equals(packageName, getDevicePolicyManagementRoleHolderPackageName(userId)); + } + + @Nullable + private String getDevicePolicyManagementRoleHolderPackageName(int userId) { + return Binder.withCleanCallingIdentity(() -> { + RoleManager roleManager = mContext.getSystemService(RoleManager.class); + List<String> roleHolders = + roleManager.getRoleHoldersAsUser( + RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, UserHandle.of(userId)); + if (roleHolders.isEmpty()) { + return null; + } + return roleHolders.get(0); + }); + } + /** Returns the device policy manager interface. */ private IDevicePolicyManager getDevicePolicyManager() { if (mDevicePolicyManager == null) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index dd870a8905cc..79a4acf55fef 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -599,6 +599,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * for display. */ void generateCrop(WallpaperData wallpaper) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("WPMS.generateCrop"); + generateCropInternal(wallpaper); + t.traceEnd(); + } + + private void generateCropInternal(WallpaperData wallpaper) { boolean success = false; // Only generate crop for default display. diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index cdc8c017b823..e639866a6bab 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -671,7 +671,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private boolean mCurrentLaunchCanTurnScreenOn = true; /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */ - private boolean mLastSurfaceShowing = true; + private boolean mLastSurfaceShowing; /** * The activity is opaque and fills the entire space of this task. @@ -1239,8 +1239,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "supportsEnterPipOnTaskSwitch: " + supportsEnterPipOnTaskSwitch); } - if (info.getMaxAspectRatio() != 0) { - pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio()); + if (getMaxAspectRatio() != 0) { + pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio()); } final float minAspectRatio = getMinAspectRatio(); if (minAspectRatio != 0) { @@ -1590,6 +1590,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A newParent.setResumedActivity(this, "onParentChanged"); mImeInsetsFrozenUntilStartInput = false; } + mLetterboxUiController.onActivityParentChanged(newParent); } if (rootTask != null && rootTask.topRunningActivity() == this) { @@ -5499,7 +5500,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // no animation but there will still be a transition set. // We still need to delay hiding the surface such that it // can be synchronized with showing the next surface in the transition. - if (!isVisible() && !delayed && !displayContent.mAppTransition.isTransitionSet()) { + if (!usingShellTransitions && !isVisible() && !delayed + && !displayContent.mAppTransition.isTransitionSet()) { SurfaceControl.openTransaction(); try { forAllWindows(win -> { @@ -7438,6 +7440,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Override + boolean showSurfaceOnCreation() { + return false; + } + + @Override void prepareSurfaces() { final boolean show = isVisible() || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS); @@ -7676,6 +7683,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Configuration.Orientation @Override int getRequestedConfigurationOrientation(boolean forDisplay) { + if (mLetterboxUiController.hasInheritedOrientation()) { + final RootDisplayArea root = getRootDisplayArea(); + if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { + return ActivityInfo.reverseOrientation( + mLetterboxUiController.getInheritedOrientation()); + } else { + return mLetterboxUiController.getInheritedOrientation(); + } + } if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) { // We use Task here because we want to be consistent with what happens in // multi-window mode where other tasks orientations are ignored. @@ -7803,6 +7819,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable CompatDisplayInsets getCompatDisplayInsets() { + if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { + return mLetterboxUiController.getInheritedCompatDisplayInsets(); + } return mCompatDisplayInsets; } @@ -7885,6 +7904,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateCompatDisplayInsets() { + if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { + mCompatDisplayInsets = mLetterboxUiController.getInheritedCompatDisplayInsets(); + return; + } if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) { // The override configuration is set only once in size compatibility mode. return; @@ -7946,6 +7969,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override float getCompatScale() { + if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { + return mLetterboxUiController.getInheritedSizeCompatScale(); + } return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale(); } @@ -8055,6 +8081,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * @return The orientation to use to understand if reachability is enabled. + */ + @ActivityInfo.ScreenOrientation + int getOrientationForReachability() { + return mLetterboxUiController.hasInheritedLetterboxBehavior() + ? mLetterboxUiController.getInheritedOrientation() + : getRequestedConfigurationOrientation(); + } + + /** * Returns whether activity bounds are letterboxed. * * <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link @@ -8094,6 +8130,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!ignoreVisibility && !mVisibleRequested) { return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; } + // TODO(b/256564921): Investigate if we need new metrics for translucent activities + if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { + return mLetterboxUiController.getInheritedAppCompatState(); + } if (mInSizeCompatModeForBounds) { return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; } @@ -8564,6 +8604,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { + if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { + // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity + // is letterboxed. + return false; + } final int appWidth = appBounds.width(); final int appHeight = appBounds.height(); final int containerAppWidth = containerBounds.width(); @@ -8584,10 +8629,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The rest of the condition is that only one side is smaller than the container, but it // still needs to exclude the cases where the size is limited by the fixed aspect ratio. - if (info.getMaxAspectRatio() > 0) { + final float maxAspectRatio = getMaxAspectRatio(); + if (maxAspectRatio > 0) { final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) / Math.min(appWidth, appHeight); - if (aspectRatio >= info.getMaxAspectRatio()) { + if (aspectRatio >= maxAspectRatio) { // The current size has reached the max aspect ratio. return false; } @@ -8809,7 +8855,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds, Rect containingBounds, float desiredAspectRatio) { - final float maxAspectRatio = info.getMaxAspectRatio(); + final float maxAspectRatio = getMaxAspectRatio(); final Task rootTask = getRootTask(); final float minAspectRatio = getMinAspectRatio(); final TaskFragment organizedTf = getOrganizedTaskFragment(); @@ -8916,6 +8962,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Returns the min aspect ratio of this activity. */ float getMinAspectRatio() { + if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { + return mLetterboxUiController.getInheritedMinAspectRatio(); + } if (info.applicationInfo == null) { return info.getMinAspectRatio(); } @@ -8960,11 +9009,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN; } + float getMaxAspectRatio() { + if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { + return mLetterboxUiController.getInheritedMaxAspectRatio(); + } + return info.getMaxAspectRatio(); + } + /** * Returns true if the activity has maximum or minimum aspect ratio. */ private boolean hasFixedAspectRatio() { - return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; + return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; } /** diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index c19353cb2676..127a7bf1c9a5 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Color; +import android.provider.DeviceConfig; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -103,6 +104,10 @@ final class LetterboxConfiguration { final Context mContext; + // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier + @NonNull + private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; + // Aspect ratio of letterbox for fixed orientation, values <= // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored. private float mFixedOrientationLetterboxAspectRatio; @@ -165,9 +170,12 @@ final class LetterboxConfiguration { // Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled; - // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier - @NonNull - private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; + // Whether letterboxing strategy is enabled for translucent activities. If {@value false} + // all the feature is disabled + private boolean mTranslucentLetterboxingEnabled; + + // Allows to enable letterboxing strategy for translucent activities ignoring flags. + private boolean mTranslucentLetterboxingOverrideEnabled; LetterboxConfiguration(Context systemUiContext) { this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext, @@ -206,6 +214,8 @@ final class LetterboxConfiguration { R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps)); mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled); + mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean( + R.bool.config_letterboxIsEnabledForTranslucentActivities); mLetterboxConfigurationPersister = letterboxConfigurationPersister; mLetterboxConfigurationPersister.start(); } @@ -817,6 +827,32 @@ final class LetterboxConfiguration { R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled); } + boolean isTranslucentLetterboxingEnabled() { + return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled + && isTranslucentLetterboxingAllowed()); + } + + void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) { + mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled; + } + + void setTranslucentLetterboxingOverrideEnabled( + boolean translucentLetterboxingOverrideEnabled) { + mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled; + setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled); + } + + /** + * Resets whether we use the constraints override strategy for letterboxing when dealing + * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}. + */ + void resetTranslucentLetterboxingEnabled() { + final boolean newValue = mContext.getResources().getBoolean( + R.bool.config_letterboxIsEnabledForTranslucentActivities); + setTranslucentLetterboxingEnabled(newValue); + setTranslucentLetterboxingOverrideEnabled(false); + } + /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */ private void updatePositionForHorizontalReachability( Function<Integer, Integer> newHorizonalPositionFun) { @@ -839,4 +875,9 @@ final class LetterboxConfiguration { nextVerticalPosition); } + // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener + static boolean isTranslucentLetterboxingAllowed() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, + "enable_translucent_activity_letterbox", false); + } } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index bcea6f4db1dc..a53a5fc00b0c 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; @@ -27,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION; +import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER; import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM; import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT; @@ -82,13 +84,44 @@ final class LetterboxUiController { private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; + private static final float UNDEFINED_ASPECT_RATIO = 0f; + private final Point mTmpPoint = new Point(); private final LetterboxConfiguration mLetterboxConfiguration; + private final ActivityRecord mActivityRecord; + /* + * WindowContainerListener responsible to make translucent activities inherit + * constraints from the first opaque activity beneath them. It's null for not + * translucent activities. + */ + @Nullable + private WindowContainerListener mLetterboxConfigListener; + private boolean mShowWallpaperForLetterboxBackground; + // In case of transparent activities we might need to access the aspectRatio of the + // first opaque activity beneath. + private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; + private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; + + @Configuration.Orientation + private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; + + // The app compat state for the opaque activity if any + private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; + + // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode. + private boolean mIsInheritedInSizeCompatMode; + + // This is the SizeCompatScale of the opaque activity beneath a translucent one + private float mInheritedSizeCompatScale; + + // The CompatDisplayInsets of the opaque activity beneath the translucent one. + private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; + @Nullable private Letterbox mLetterbox; @@ -220,7 +253,9 @@ final class LetterboxUiController { : mActivityRecord.inMultiWindowMode() ? mActivityRecord.getTask().getBounds() : mActivityRecord.getRootTask().getParent().getBounds(); - mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint); + final Rect innerFrame = hasInheritedLetterboxBehavior() + ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame(); + mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); } else if (mLetterbox != null) { mLetterbox.hide(); } @@ -305,7 +340,9 @@ final class LetterboxUiController { } private void handleHorizontalDoubleTap(int x) { - if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) { + // TODO(b/260857308): Investigate if enabling reachability for translucent activity + if (hasInheritedLetterboxBehavior() || !isHorizontalReachabilityEnabled() + || mActivityRecord.isInTransition()) { return; } @@ -341,7 +378,9 @@ final class LetterboxUiController { } private void handleVerticalDoubleTap(int y) { - if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) { + // TODO(b/260857308): Investigate if enabling reachability for translucent activity + if (hasInheritedLetterboxBehavior() || !isVerticalReachabilityEnabled() + || mActivityRecord.isInTransition()) { return; } @@ -390,7 +429,7 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE - && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT); + && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT); } private boolean isHorizontalReachabilityEnabled() { @@ -412,7 +451,7 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (parentConfiguration.orientation == ORIENTATION_PORTRAIT - && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_LANDSCAPE); + && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE); } private boolean isVerticalReachabilityEnabled() { @@ -576,9 +615,7 @@ final class LetterboxUiController { // Rounded corners should be displayed above the taskbar. bounds.bottom = Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top); - if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) { - bounds.scale(1.0f / mActivityRecord.getCompatScale()); - } + scaleIfNeeded(bounds); } private int getInsetsStateCornerRadius( @@ -788,4 +825,144 @@ final class LetterboxUiController { w.mAttrs.insetsFlags.appearance ); } + + /** + * Handles translucent activities letterboxing inheriting constraints from the + * first opaque activity beneath. + * @param parent The parent container. + */ + void onActivityParentChanged(WindowContainer<?> parent) { + if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { + return; + } + if (mLetterboxConfigListener != null) { + mLetterboxConfigListener.onRemoved(); + clearInheritedConfig(); + } + // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the + // opaque activity constraints because we're expecting the activity is already letterboxed. + if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null + || mActivityRecord.fillsParent()) { + return; + } + final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( + ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */, + true /* traverseTopToBottom */); + if (firstOpaqueActivityBeneath == null + || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) { + // We skip letterboxing if the translucent activity doesn't have any opaque + // activities beneath of if it's launched from a different user (e.g. notification) + return; + } + inheritConfiguration(firstOpaqueActivityBeneath); + mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( + mActivityRecord, firstOpaqueActivityBeneath, + (opaqueConfig, transparentConfig) -> { + final Configuration mutatedConfiguration = new Configuration(); + final Rect parentBounds = parent.getWindowConfiguration().getBounds(); + final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds(); + final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); + // We cannot use letterboxBounds directly here because the position relies on + // letterboxing. Using letterboxBounds directly, would produce a double offset. + bounds.set(parentBounds.left, parentBounds.top, + parentBounds.left + letterboxBounds.width(), + parentBounds.top + letterboxBounds.height()); + // We need to initialize appBounds to avoid NPE. The actual value will + // be set ahead when resolving the Configuration for the activity. + mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); + return mutatedConfiguration; + }); + } + + /** + * @return {@code true} if the current activity is translucent with an opaque activity + * beneath. In this case it will inherit bounds, orientation and aspect ratios from + * the first opaque activity beneath. + */ + boolean hasInheritedLetterboxBehavior() { + return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds(); + } + + /** + * @return {@code true} if the current activity is translucent with an opaque activity + * beneath and needs to inherit its orientation. + */ + boolean hasInheritedOrientation() { + // To force a different orientation, the transparent one needs to have an explicit one + // otherwise the existing one is fine and the actual orientation will depend on the + // bounds. + // To avoid wrong behaviour, we're not forcing orientation for activities with not + // fixed orientation (e.g. permission dialogs). + return hasInheritedLetterboxBehavior() + && mActivityRecord.mOrientation != SCREEN_ORIENTATION_UNSPECIFIED; + } + + float getInheritedMinAspectRatio() { + return mInheritedMinAspectRatio; + } + + float getInheritedMaxAspectRatio() { + return mInheritedMaxAspectRatio; + } + + int getInheritedAppCompatState() { + return mInheritedAppCompatState; + } + + float getInheritedSizeCompatScale() { + return mInheritedSizeCompatScale; + } + + @Configuration.Orientation + int getInheritedOrientation() { + return mInheritedOrientation; + } + + public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { + return mInheritedCompatDisplayInsets; + } + + private void inheritConfiguration(ActivityRecord firstOpaque) { + // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities + // which are not already providing one (e.g. permission dialogs) and presumably also + // not resizable. + if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) { + mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio(); + } + if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) { + mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio(); + } + mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); + mInheritedAppCompatState = firstOpaque.getAppCompatState(); + mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode(); + mInheritedSizeCompatScale = firstOpaque.getCompatScale(); + mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); + } + + private void clearInheritedConfig() { + mLetterboxConfigListener = null; + mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; + mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; + mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; + mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; + mIsInheritedInSizeCompatMode = false; + mInheritedSizeCompatScale = 1f; + mInheritedCompatDisplayInsets = null; + } + + private void scaleIfNeeded(Rect bounds) { + if (boundsNeedToScale()) { + bounds.scale(1.0f / mActivityRecord.getCompatScale()); + } + } + + private boolean boundsNeedToScale() { + if (hasInheritedLetterboxBehavior()) { + return mIsInheritedInSizeCompatMode + && mInheritedSizeCompatScale < 1.0f; + } else { + return mActivityRecord.inSizeCompatMode() + && mActivityRecord.getCompatScale() < 1.0f; + } + } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 7b16cccb928b..5c893de6b920 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -635,7 +635,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (showSurfaceOnCreation()) { getSyncTransaction().show(mSurfaceControl); } - onSurfaceShown(getSyncTransaction()); updateSurfacePositionNonOrganized(); } @@ -687,13 +686,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< scheduleAnimation(); } - /** - * Called when the surface is shown for the first time. - */ - void onSurfaceShown(Transaction t) { - // do nothing - } - // Temp. holders for a chain of containers we are currently processing. private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList<>(); private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList<>(); @@ -3946,27 +3938,54 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< unregisterConfigurationChangeListener(listener); } + static void overrideConfigurationPropagation(WindowContainer<?> receiver, + WindowContainer<?> supplier) { + overrideConfigurationPropagation(receiver, supplier, null /* configurationMerger */); + } + /** * Forces the receiver container to always use the configuration of the supplier container as * its requested override configuration. It allows to propagate configuration without changing * the relationship between child and parent. + * + * @param receiver The {@link WindowContainer<?>} which will receive the {@link + * Configuration} result of the merging operation. + * @param supplier The {@link WindowContainer<?>} which provides the initial {@link + * Configuration}. + * @param configurationMerger A {@link ConfigurationMerger} which combines the {@link + * Configuration} of the receiver and the supplier. */ - static void overrideConfigurationPropagation(WindowContainer<?> receiver, - WindowContainer<?> supplier) { + static WindowContainerListener overrideConfigurationPropagation(WindowContainer<?> receiver, + WindowContainer<?> supplier, @Nullable ConfigurationMerger configurationMerger) { final ConfigurationContainerListener listener = new ConfigurationContainerListener() { @Override public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) { - receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration()); + final Configuration mergedConfiguration = + configurationMerger != null + ? configurationMerger.merge(mergedOverrideConfig, + receiver.getConfiguration()) + : supplier.getConfiguration(); + receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration); } }; supplier.registerConfigurationChangeListener(listener); - receiver.registerWindowContainerListener(new WindowContainerListener() { + final WindowContainerListener wcListener = new WindowContainerListener() { @Override public void onRemoved() { receiver.unregisterWindowContainerListener(this); supplier.unregisterConfigurationChangeListener(listener); } - }); + }; + receiver.registerWindowContainerListener(wcListener); + return wcListener; + } + + /** + * Abstraction for functions merging two {@link Configuration} objects into one. + */ + @FunctionalInterface + interface ConfigurationMerger { + Configuration merge(Configuration first, Configuration second); } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index b8cf0ad2e774..4e692e2d212a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -963,6 +963,29 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } + private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) { + String arg = getNextArg(); + final boolean enabled; + switch (arg) { + case "true": + case "1": + enabled = true; + break; + case "false": + case "0": + enabled = false; + break; + default: + getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg); + return -1; + } + + synchronized (mInternal.mGlobalLock) { + mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled); + } + return 0; + } + private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException { if (peekNextArg() == null) { getErrPrintWriter().println("Error: No arguments provided."); @@ -1018,6 +1041,9 @@ public class WindowManagerShellCommand extends ShellCommand { case "--isSplitScreenAspectRatioForUnresizableAppsEnabled": runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw); break; + case "--isTranslucentLetterboxingEnabled": + runSetTranslucentLetterboxingEnabled(pw); + break; default: getErrPrintWriter().println( "Error: Unrecognized letterbox style option: " + arg); @@ -1081,6 +1107,9 @@ public class WindowManagerShellCommand extends ShellCommand { mLetterboxConfiguration .getIsSplitScreenAspectRatioForUnresizableAppsEnabled(); break; + case "isTranslucentLetterboxingEnabled": + mLetterboxConfiguration.resetTranslucentLetterboxingEnabled(); + break; default: getErrPrintWriter().println( "Error: Unrecognized letterbox style option: " + arg); @@ -1181,6 +1210,7 @@ public class WindowManagerShellCommand extends ShellCommand { mLetterboxConfiguration.resetDefaultPositionForVerticalReachability(); mLetterboxConfiguration.resetIsEducationEnabled(); mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled(); + mLetterboxConfiguration.resetTranslucentLetterboxingEnabled(); } } @@ -1217,7 +1247,6 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: " + mLetterboxConfiguration .getIsSplitScreenAspectRatioForUnresizableAppsEnabled()); - pw.println("Background type: " + LetterboxConfiguration.letterboxBackgroundTypeToString( mLetterboxConfiguration.getLetterboxBackgroundType())); @@ -1227,6 +1256,12 @@ public class WindowManagerShellCommand extends ShellCommand { + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius()); pw.println(" Wallpaper dark scrim alpha: " + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha()); + + if (mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { + pw.println("Letterboxing for translucent activities: enabled"); + } else { + pw.println("Letterboxing for translucent activities: disabled"); + } } return 0; } @@ -1419,12 +1454,16 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]"); pw.println(" Whether using split screen aspect ratio as a default aspect ratio for"); pw.println(" unresizable apps."); + pw.println(" --isTranslucentLetterboxingEnabled [true|1|false|0]"); + pw.println(" Whether letterboxing for translucent activities is enabled."); + pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType"); pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha"); pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier"); pw.println(" |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled"); - pw.println(" isEducationEnabled||defaultPositionMultiplierForHorizontalReachability"); - pw.println(" ||defaultPositionMultiplierForVerticalReachability]"); + pw.println(" |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability"); + pw.println(" |isTranslucentLetterboxingEnabled"); + pw.println(" |defaultPositionMultiplierForVerticalReachability]"); pw.println(" Resets overrides to default values for specified properties separated"); pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'."); pw.println(" If no arguments provided, all values will be reset."); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index babad4d4d744..94c33f27f651 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -164,6 +164,114 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testApplyStrategyToTranslucentActivities() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + mActivity.info.setMinAspectRatio(1.2f); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .setMinAspectRatio(1.1f) + .setMaxAspectRatio(3f) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // We check bounds + final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); + final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); + assertEquals(opaqueBounds, translucentRequestedBounds); + // We check orientation + final int translucentOrientation = + translucentActivity.getRequestedConfigurationOrientation(); + assertEquals(ORIENTATION_PORTRAIT, translucentOrientation); + // We check aspect ratios + assertEquals(1.2f, translucentActivity.getMinAspectRatio(), 0.00001f); + assertEquals(1.5f, translucentActivity.getMaxAspectRatio(), 0.00001f); + } + + @Test + public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + mActivity.info.setMinAspectRatio(1.2f); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .setMinAspectRatio(1.1f) + .setMaxAspectRatio(3f) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // We check bounds + final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); + final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); + assertNotEquals(opaqueBounds, translucentRequestedBounds); + } + + @Test + public void testApplyStrategyToMultipleTranslucentActivities() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + mActivity.info.setMinAspectRatio(1.2f); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .setMinAspectRatio(1.1f) + .setMaxAspectRatio(3f) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // We check bounds + final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); + final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); + assertEquals(opaqueBounds, translucentRequestedBounds); + // Launch another translucent activity + final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .build(); + doReturn(false).when(translucentActivity2).fillsParent(); + mTask.addChild(translucentActivity2); + // We check bounds + final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds(); + assertEquals(opaqueBounds, translucent2RequestedBounds); + } + + @Test + public void testTranslucentActivitiesDontGoInSizeCompactMode() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + assertTrue(mActivity.inSizeCompatMode()); + // Rotate back + rotateDisplay(mActivity.mDisplayContent, ROTATION_0); + assertFalse(mActivity.inSizeCompatMode()); + // We launch a transparent activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + doReturn(true).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // It should not be in SCM + assertFalse(translucentActivity.inSizeCompatMode()); + // We rotate again + rotateDisplay(translucentActivity.mDisplayContent, ROTATION_90); + assertFalse(translucentActivity.inSizeCompatMode()); + } + + @Test public void testRestartProcessIfVisible() { setUpDisplaySizeWithApp(1000, 2500); doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity); |