diff options
52 files changed, 776 insertions, 593 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index e8571757c6f7..c4ffa340dccd 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -30,7 +30,7 @@ hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclu ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES} # This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py. -flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH} +flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PROJECT} [Tool Paths] ktfmt = ${REPO_ROOT}/prebuilts/build-tools/common/framework/ktfmt.jar diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index cb82278ca577..54ee37519546 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -7504,8 +7504,6 @@ public final class ViewRootImpl implements ViewParent, final KeyEvent event = (KeyEvent)q.mEvent; if (mView.dispatchKeyEventPreIme(event)) { return FINISH_HANDLED; - } else if (q.forPreImeOnly()) { - return FINISH_NOT_HANDLED; } return FORWARD; } @@ -10002,7 +10000,6 @@ public final class ViewRootImpl implements ViewParent, public static final int FLAG_RESYNTHESIZED = 1 << 4; public static final int FLAG_UNHANDLED = 1 << 5; public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6; - public static final int FLAG_PRE_IME_ONLY = 1 << 7; public QueuedInputEvent mNext; @@ -10010,13 +10007,6 @@ public final class ViewRootImpl implements ViewParent, public InputEventReceiver mReceiver; public int mFlags; - public boolean forPreImeOnly() { - if ((mFlags & FLAG_PRE_IME_ONLY) != 0) { - return true; - } - return false; - } - public boolean shouldSkipIme() { if ((mFlags & FLAG_DELIVER_POST_IME) != 0) { return true; @@ -10043,7 +10033,6 @@ public final class ViewRootImpl implements ViewParent, hasPrevious = flagToString("FINISHED_HANDLED", FLAG_FINISHED_HANDLED, hasPrevious, sb); hasPrevious = flagToString("RESYNTHESIZED", FLAG_RESYNTHESIZED, hasPrevious, sb); hasPrevious = flagToString("UNHANDLED", FLAG_UNHANDLED, hasPrevious, sb); - hasPrevious = flagToString("FLAG_PRE_IME_ONLY", FLAG_PRE_IME_ONLY, hasPrevious, sb); if (!hasPrevious) { sb.append("0"); } @@ -10100,7 +10089,7 @@ public final class ViewRootImpl implements ViewParent, } @UnsupportedAppUsage - QueuedInputEvent enqueueInputEvent(InputEvent event, + void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) { QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); @@ -10139,7 +10128,6 @@ public final class ViewRootImpl implements ViewParent, } else { scheduleProcessInputEvents(); } - return q; } private void scheduleProcessInputEvents() { @@ -12461,45 +12449,29 @@ public final class ViewRootImpl implements ViewParent, + "IWindow:%s Session:%s", mOnBackInvokedDispatcher, mBasePackageName, mWindow, mWindowSession)); } - mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow, this, + mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow, mImeBackAnimationController); } - /** - * Sends {@link KeyEvent#ACTION_DOWN ACTION_DOWN} and {@link KeyEvent#ACTION_UP ACTION_UP} - * back key events - * - * @param preImeOnly whether the back events should be sent to the pre-ime stage only - * @return whether the event was handled (i.e. onKeyPreIme consumed it if preImeOnly=true) - */ - public boolean injectBackKeyEvents(boolean preImeOnly) { - boolean consumed; - try { - processingBackKey(true); - sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly); - consumed = sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly); - } finally { - processingBackKey(false); - } - return consumed; - } - - private boolean sendBackKeyEvent(int action, boolean preImeOnly) { + private void sendBackKeyEvent(int action) { long when = SystemClock.uptimeMillis(); final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); - int flags = preImeOnly ? QueuedInputEvent.FLAG_PRE_IME_ONLY : 0; - QueuedInputEvent q = enqueueInputEvent(ev, null /* receiver */, flags, - true /* processImmediately */); - return (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0; + enqueueInputEvent(ev, null /* receiver */, 0 /* flags */, true /* processImmediately */); } private void registerCompatOnBackInvokedCallback() { mCompatOnBackInvokedCallback = () -> { - injectBackKeyEvents(/* preImeOnly */ false); + try { + processingBackKey(true); + sendBackKeyEvent(KeyEvent.ACTION_DOWN); + sendBackKeyEvent(KeyEvent.ACTION_UP); + } finally { + processingBackKey(false); + } }; if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) { Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher"); diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 4c993c2544ce..b7f6f363dd04 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -37,7 +37,6 @@ import android.view.IWindow; import android.view.IWindowSession; import android.view.ImeBackAnimationController; import android.view.MotionEvent; -import android.view.ViewRootImpl; import androidx.annotation.VisibleForTesting; @@ -50,7 +49,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; import java.util.TreeMap; -import java.util.function.BooleanSupplier; import java.util.function.Supplier; /** @@ -70,7 +68,6 @@ import java.util.function.Supplier; public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private IWindowSession mWindowSession; private IWindow mWindow; - private ViewRootImpl mViewRoot; @VisibleForTesting public final BackTouchTracker mTouchTracker = new BackTouchTracker(); @VisibleForTesting @@ -137,12 +134,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { * is attached a window. */ public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window, - @Nullable ViewRootImpl viewRoot, @Nullable ImeBackAnimationController imeBackAnimationController) { synchronized (mLock) { mWindowSession = windowSession; mWindow = window; - mViewRoot = viewRoot; mImeBackAnimationController = imeBackAnimationController; if (!mAllCallbacks.isEmpty()) { setTopOnBackInvokedCallback(getTopCallback()); @@ -156,7 +151,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { clear(); mWindow = null; mWindowSession = null; - mViewRoot = null; mImeBackAnimationController = null; } } @@ -182,6 +176,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return; } if (callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) { + // Fall back to compat back key injection if legacy back behaviour should be used. + if (!isOnBackInvokedCallbackEnabled()) return; if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback && mImeBackAnimationController != null) { // register ImeBackAnimationController instead to play predictive back animation @@ -304,14 +300,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } - private boolean callOnKeyPreIme() { - if (mViewRoot != null && !isOnBackInvokedCallbackEnabled(mViewRoot.mContext)) { - return mViewRoot.injectBackKeyEvents(/*preImeOnly*/ true); - } else { - return false; - } - } - private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) { if (mWindowSession == null || mWindow == null) { return; @@ -320,8 +308,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { OnBackInvokedCallbackInfo callbackInfo = null; if (callback != null) { int priority = mAllCallbacks.get(callback); - final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback, - mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme); + final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper( + callback, mTouchTracker, mProgressAnimator, mHandler); callbackInfo = new OnBackInvokedCallbackInfo( iCallback, priority, @@ -411,20 +399,16 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private final BackTouchTracker mTouchTracker; @NonNull private final Handler mHandler; - @NonNull - private final BooleanSupplier mOnKeyPreIme; OnBackInvokedCallbackWrapper( @NonNull OnBackInvokedCallback callback, @NonNull BackTouchTracker touchTracker, @NonNull BackProgressAnimator progressAnimator, - @NonNull Handler handler, - @NonNull BooleanSupplier onKeyPreIme) { + @NonNull Handler handler) { mCallback = new WeakReference<>(callback); mTouchTracker = touchTracker; mProgressAnimator = progressAnimator; mHandler = handler; - mOnKeyPreIme = onKeyPreIme; } @Override @@ -467,7 +451,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public void onBackInvoked() throws RemoteException { mHandler.post(() -> { mTouchTracker.reset(); - if (consumedByOnKeyPreIme()) return; boolean isInProgress = mProgressAnimator.isBackAnimationInProgress(); final OnBackInvokedCallback callback = mCallback.get(); if (callback == null) { @@ -489,30 +472,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { }); } - private boolean consumedByOnKeyPreIme() { - final OnBackInvokedCallback callback = mCallback.get(); - if (callback instanceof ImeBackAnimationController - || callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) { - // call onKeyPreIme API if the current callback is an IME callback and the app has - // not set enableOnBackInvokedCallback="false" - try { - boolean consumed = mOnKeyPreIme.getAsBoolean(); - if (consumed) { - // back event intercepted by app in onKeyPreIme -> cancel the IME animation. - final OnBackAnimationCallback animationCallback = - getBackAnimationCallback(); - if (animationCallback != null) { - mProgressAnimator.onBackCancelled(animationCallback::onBackCancelled); - } - return true; - } - } catch (Exception e) { - Log.d(TAG, "Failed to call onKeyPreIme", e); - } - } - return false; - } - @Override public void setTriggerBack(boolean triggerBack) throws RemoteException { mTouchTracker.setTriggerBack(triggerBack); diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index daf2fe345ffd..b91f2d628359 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -106,3 +106,10 @@ flag { description: "Whether to apply Camera Compat treatment to fixed-orientation apps in desktop windowing mode" bug: "314952133" } + +flag { + name: "enable_task_stack_observer_in_shell" + namespace: "lse_desktop_experience" + description: "Introduces a new observer in shell to track the task stack." + bug: "341932484" +} diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 98d6ec6897a6..920981e2b8fe 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -923,7 +923,7 @@ public class ResolverActivity extends Activity implements mSystemWindowInsets = insets.getSystemWindowInsets(); mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, - mSystemWindowInsets.right, mSystemWindowInsets.bottom); + mSystemWindowInsets.right, 0); resetButtonBar(); @@ -952,7 +952,7 @@ public class ResolverActivity extends Activity implements if (mSystemWindowInsets != null) { mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, - mSystemWindowInsets.right, mSystemWindowInsets.bottom); + mSystemWindowInsets.right, 0); } } diff --git a/core/res/res/drawable/floating_popup_background_dark.xml b/core/res/res/drawable/floating_popup_background.xml index c4b44484d046..99acedf06e2d 100644 --- a/core/res/res/drawable/floating_popup_background_dark.xml +++ b/core/res/res/drawable/floating_popup_background.xml @@ -16,8 +16,9 @@ */ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> - <solid android:color="@color/background_floating_material_dark" /> + <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest"/> <corners android:radius="?android:attr/dialogCornerRadius" /> </shape> diff --git a/core/res/res/drawable/floating_popup_background_light.xml b/core/res/res/drawable/floating_popup_background_light.xml deleted file mode 100644 index 767140d9d5c1..000000000000 --- a/core/res/res/drawable/floating_popup_background_light.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2015, 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. -*/ ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid android:color="@color/background_floating_material_light" /> - <corners android:radius="?android:attr/dialogCornerRadius" /> -</shape> - diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml index 776a35d15ef0..96f0909eea10 100644 --- a/core/res/res/layout/floating_popup_container.xml +++ b/core/res/res/layout/floating_popup_container.xml @@ -24,4 +24,4 @@ android:elevation="@android:dimen/text_edit_floating_toolbar_elevation" android:focusable="true" android:focusableInTouchMode="true" - android:background="?attr/floatingToolbarPopupBackgroundDrawable"/> + android:background="@drawable/floating_popup_background"/> diff --git a/core/res/res/layout/floating_popup_menu_button.xml b/core/res/res/layout/floating_popup_menu_button.xml index e4c2a34b1af8..0b3861cad252 100644 --- a/core/res/res/layout/floating_popup_menu_button.xml +++ b/core/res/res/layout/floating_popup_menu_button.xml @@ -16,6 +16,7 @@ */ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" @@ -53,7 +54,7 @@ android:ellipsize="end" android:fontFamily="@*android:string/config_bodyFontFamily" android:textSize="@dimen/floating_toolbar_text_size" - android:textColor="?attr/floatingToolbarForegroundColor" + android:textColor="?androidprv:attr/materialColorOnSurface" android:background="@null" android:focusable="false" android:focusableInTouchMode="false" diff --git a/core/res/res/layout/floating_popup_overflow_button.xml b/core/res/res/layout/floating_popup_overflow_button.xml index 12e200088286..a51836b35057 100644 --- a/core/res/res/layout/floating_popup_overflow_button.xml +++ b/core/res/res/layout/floating_popup_overflow_button.xml @@ -16,6 +16,7 @@ */ --> <ImageButton xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/overflow" android:layout_width="@dimen/floating_toolbar_menu_image_button_width" android:layout_height="@dimen/floating_toolbar_height" @@ -25,4 +26,4 @@ android:paddingBottom="@dimen/floating_toolbar_menu_image_button_vertical_padding" android:scaleType="centerInside" android:background="?attr/actionBarItemBackground" - android:tint="?attr/floatingToolbarForegroundColor" /> + android:tint="?androidprv:attr/materialColorOnSurface" /> diff --git a/core/res/res/layout/text_edit_suggestion_container_material.xml b/core/res/res/layout/text_edit_suggestion_container_material.xml index 34e7bc8af8da..d6e1e9d61743 100644 --- a/core/res/res/layout/text_edit_suggestion_container_material.xml +++ b/core/res/res/layout/text_edit_suggestion_container_material.xml @@ -23,7 +23,7 @@ android:id="@+id/suggestionWindowContainer" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?android:attr/floatingToolbarPopupBackgroundDrawable" + android:background="@drawable/floating_popup_background" android:elevation="@android:dimen/text_edit_floating_toolbar_elevation" android:layout_margin="@android:dimen/text_edit_floating_toolbar_margin" android:orientation="vertical" diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 575573cb0ffb..df5cbb1fbc92 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -37,8 +37,7 @@ <item>@drawable/fastscroll_label_right_material</item> <item>@drawable/fastscroll_thumb_material</item> <item>@drawable/fastscroll_track_material</item> - <item>@drawable/floating_popup_background_dark</item> - <item>@drawable/floating_popup_background_light</item> + <item>@drawable/floating_popup_background</item> <item>@drawable/ic_ab_back_material</item> <item>@drawable/ic_ab_back_material_dark</item> <item>@drawable/ic_ab_back_material_light</item> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index c74bc9ba6e75..9846b710300f 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -606,11 +606,9 @@ <!-- ============ --> <eat-comment /> <attr name="floatingToolbarCloseDrawable" format="reference" /> - <attr name="floatingToolbarForegroundColor" format="reference|color" /> <attr name="floatingToolbarItemBackgroundBorderlessDrawable" format="reference" /> <attr name="floatingToolbarItemBackgroundDrawable" format="reference" /> <attr name="floatingToolbarOpenDrawable" format="reference" /> - <attr name="floatingToolbarPopupBackgroundDrawable" format="reference" /> <attr name="floatingToolbarDividerColor" format="reference" /> <!-- ============ --> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index bdbf96b97c1e..c3d304dc35e1 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -391,11 +391,9 @@ please see themes_device_defaults.xml. <!-- Floating toolbar styles --> <item name="floatingToolbarCloseDrawable">@drawable/ic_ab_back_material_dark</item> - <item name="floatingToolbarForegroundColor">@color/foreground_material_dark</item> <item name="floatingToolbarItemBackgroundBorderlessDrawable">@drawable/item_background_borderless_material_dark</item> <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_dark</item> <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_dark</item> - <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_dark</item> <item name="floatingToolbarDividerColor">@color/floating_popup_divider_dark</item> <!-- SearchView attributes --> @@ -579,11 +577,9 @@ please see themes_device_defaults.xml. <!-- Floating toolbar styles --> <item name="floatingToolbarCloseDrawable">@drawable/ic_ab_back_material_light</item> - <item name="floatingToolbarForegroundColor">@color/foreground_material_light</item> <item name="floatingToolbarItemBackgroundBorderlessDrawable">@drawable/item_background_borderless_material_light</item> <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_light</item> <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_light</item> - <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_light</item> <item name="floatingToolbarDividerColor">@color/floating_popup_divider_light</item> <!-- Tooltip popup colors --> diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index d4482f243939..b0190a5c8218 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -112,7 +112,7 @@ public class WindowOnBackInvokedDispatcherTest { doReturn(mApplicationInfo).when(mContext).getApplicationInfo(); mDispatcher = new WindowOnBackInvokedDispatcher(mContext, Looper.getMainLooper()); - mDispatcher.attachToWindow(mWindowSession, mWindow, null, mImeBackAnimationController); + mDispatcher.attachToWindow(mWindowSession, mWindow, mImeBackAnimationController); } private void waitForIdle() { @@ -455,26 +455,25 @@ public class WindowOnBackInvokedDispatcherTest { @Test public void registerImeCallbacks_onBackInvokedCallbackEnabled() throws RemoteException { - verifyImeCallackRegistrations(); - } - - @Test - public void registerImeCallbacks_onBackInvokedCallbackDisabled() throws RemoteException { - doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled(); - verifyImeCallackRegistrations(); - } - - private void verifyImeCallackRegistrations() throws RemoteException { - // verify default callback is replaced with ImeBackAnimationController - mDispatcher.registerOnBackInvokedCallbackUnchecked(mDefaultImeCallback, PRIORITY_DEFAULT); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback); assertCallbacksSize(/* default */ 1, /* overlay */ 0); assertSetCallbackInfo(); assertTopCallback(mImeBackAnimationController); - // verify regular ime callback is successfully registered - mDispatcher.registerOnBackInvokedCallbackUnchecked(mImeCallback, PRIORITY_DEFAULT); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback); assertCallbacksSize(/* default */ 2, /* overlay */ 0); assertSetCallbackInfo(); assertTopCallback(mImeCallback); } + + @Test + public void registerImeCallbacks_legacyBack() throws RemoteException { + doReturn(false).when(mApplicationInfo).isOnBackInvokedCallbackEnabled(); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mDefaultImeCallback); + assertNoSetCallbackInfo(); + + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mImeCallback); + assertNoSetCallbackInfo(); + } } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 4311e7968e91..29b57c95ee45 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1039,6 +1039,16 @@ flag { } flag { + name: "glanceable_hub_animate_timer_activity_starts" + namespace: "systemui" + description: "Properly animates activity starts from live timers on the glanceable hub" + bug: "345741071" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "new_touchpad_gestures_tutorial" namespace: "systemui" description: "Enables new interactive tutorial for learning touchpad gestures" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 776651558e48..60b6f62dfa46 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.unit.IntRect import com.android.compose.animation.scene.SceneScope import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.widgets.WidgetInteractionHandler import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines import com.android.systemui.keyguard.ui.composable.section.LockSection import com.android.systemui.statusbar.phone.SystemUIDialogFactory @@ -34,6 +35,7 @@ class CommunalContent @Inject constructor( private val viewModel: CommunalViewModel, + private val interactionHandler: WidgetInteractionHandler, private val dialogFactory: SystemUIDialogFactory, private val lockSection: LockSection, ) { @@ -45,6 +47,7 @@ constructor( content = { CommunalHub( viewModel = viewModel, + interactionHandler = interactionHandler, dialogFactory = dialogFactory, modifier = Modifier.element(Communal.Elements.Grid) ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 1f7f07bb072d..eccb0724a8d4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -16,13 +16,13 @@ package com.android.systemui.communal.ui.compose -import android.appwidget.AppWidgetHostView import android.graphics.drawable.Icon import android.os.Bundle import android.util.SizeF import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS import android.widget.FrameLayout +import android.widget.RemoteViews import androidx.annotation.VisibleForTesting import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope @@ -132,6 +132,7 @@ import com.android.compose.modifiers.thenIf import com.android.compose.theme.LocalAndroidColorScheme import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.internal.R.dimen.system_app_widget_background_radius +import com.android.systemui.Flags.glanceableHubAnimateTimerActivityStarts import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalScenes @@ -144,6 +145,7 @@ import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.PopupType +import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialogFactory @@ -154,6 +156,7 @@ import kotlinx.coroutines.launch fun CommunalHub( modifier: Modifier = Modifier, viewModel: BaseCommunalViewModel, + interactionHandler: RemoteViews.InteractionHandler? = null, dialogFactory: SystemUIDialogFactory? = null, widgetConfigurator: WidgetConfigurator? = null, onOpenWidgetPicker: (() -> Unit)? = null, @@ -262,6 +265,7 @@ fun CommunalHub( contentListState = contentListState, selectedKey = selectedKey, widgetConfigurator = widgetConfigurator, + interactionHandler = interactionHandler, ) } } @@ -391,6 +395,7 @@ private fun BoxScope.CommunalHubLazyGrid( setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit, updateDragPositionForRemove: (offset: Offset) -> Boolean, widgetConfigurator: WidgetConfigurator?, + interactionHandler: RemoteViews.InteractionHandler?, ) { var gridModifier = Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) } @@ -468,7 +473,8 @@ private fun BoxScope.CommunalHubLazyGrid( selected = selected && !isDragging, widgetConfigurator = widgetConfigurator, index = index, - contentListState = contentListState + contentListState = contentListState, + interactionHandler = interactionHandler, ) } } else { @@ -479,7 +485,8 @@ private fun BoxScope.CommunalHubLazyGrid( size = size, selected = false, index = index, - contentListState = contentListState + contentListState = contentListState, + interactionHandler = interactionHandler, ) } } @@ -759,6 +766,7 @@ private fun CommunalContent( widgetConfigurator: WidgetConfigurator? = null, index: Int, contentListState: ContentListState, + interactionHandler: RemoteViews.InteractionHandler?, ) { when (model) { is CommunalContentModel.WidgetContent.Widget -> @@ -778,7 +786,7 @@ private fun CommunalContent( is CommunalContentModel.WidgetContent.PendingWidget -> PendingWidgetPlaceholder(model, modifier) is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier) - is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier) + is CommunalContentModel.Smartspace -> SmartspaceContent(interactionHandler, model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) is CommunalContentModel.Umo -> Umo(viewModel, modifier) } @@ -1091,13 +1099,19 @@ fun PendingWidgetPlaceholder( @Composable private fun SmartspaceContent( + interactionHandler: RemoteViews.InteractionHandler?, model: CommunalContentModel.Smartspace, modifier: Modifier = Modifier, ) { AndroidView( modifier = modifier, factory = { context -> - AppWidgetHostView(context).apply { updateAppWidget(model.remoteViews) } + SmartspaceAppWidgetHostView(context).apply { + if (glanceableHubAnimateTimerActivityStarts()) { + interactionHandler?.let { setInteractionHandler(it) } + } + updateAppWidget(model.remoteViews) + } }, // For reusing composition in lazy lists. onReset = {}, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index 9e905ac11b1e..94018bbdbd22 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -24,6 +24,7 @@ import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.widgets.WidgetInteractionHandler import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene @@ -40,6 +41,7 @@ class CommunalScene constructor( private val viewModel: CommunalViewModel, private val dialogFactory: SystemUIDialogFactory, + private val interactionHandler: WidgetInteractionHandler, ) : ComposableScene { override val key = Scenes.Communal @@ -53,6 +55,6 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { - CommunalHub(modifier, viewModel, dialogFactory) + CommunalHub(modifier, viewModel, interactionHandler, dialogFactory) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 48a348b9d1c5..c2dd80375d5a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -109,8 +109,7 @@ internal fun CoroutineScope.animateToScene( layoutState.transitions.interruptionHandler.onInterruption( transitionState, target, - ) - ?: DefaultInterruptionHandler.onInterruption(transitionState, target) + ) ?: DefaultInterruptionHandler.onInterruption(transitionState, target) val animateFrom = interruptionResult.animateFrom if ( @@ -159,6 +158,7 @@ private fun CoroutineScope.animate( val transition = if (reversed) { OneOffTransition( + key = transitionKey, fromScene = targetScene, toScene = fromScene, currentScene = targetScene, @@ -167,6 +167,7 @@ private fun CoroutineScope.animate( ) } else { OneOffTransition( + key = transitionKey, fromScene = fromScene, toScene = targetScene, currentScene = targetScene, @@ -178,7 +179,7 @@ private fun CoroutineScope.animate( // Change the current layout state to start this new transition. This will compute the // TransformationSpec associated to this transition, which we need to initialize the Animatable // that will actually animate it. - layoutState.startTransition(transition, transitionKey, chain) + layoutState.startTransition(transition, chain) // The transition now contains the transformation spec that we should use to instantiate the // Animatable. @@ -207,6 +208,7 @@ private fun CoroutineScope.animate( } private class OneOffTransition( + override val key: TransitionKey?, fromScene: SceneKey, toScene: SceneKey, override val currentScene: SceneKey, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index e9633c2f6603..60d78feec8c2 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -257,7 +257,7 @@ private class DragControllerImpl( fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) { if (isDrivingTransition || force) { - layoutState.startTransition(newTransition, newTransition.key) + layoutState.startTransition(newTransition) } swipeTransition = newTransition @@ -555,7 +555,7 @@ private class SwipeTransition( val layoutImpl: SceneTransitionLayoutImpl, val layoutState: BaseSceneTransitionLayoutState, val coroutineScope: CoroutineScope, - val key: TransitionKey?, + override val key: TransitionKey?, val _fromScene: Scene, val _toScene: Scene, val userActionDistanceScope: UserActionDistanceScope, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index edf8943509df..980982a30926 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -280,7 +280,7 @@ internal class ElementNode( constraints: Constraints, ): MeasureResult { val transitions = currentTransitions - val transition = elementTransition(element, transitions) + val transition = elementTransition(layoutImpl, element, transitions) // If this element is not supposed to be laid out now, either because it is not part of any // ongoing transition or the other scene of its transition is overscrolling, then lay out @@ -318,13 +318,10 @@ internal class ElementNode( val targetOffsetInScene = lookaheadScopeCoordinates.localLookaheadPositionOf(coords) // No need to place the element in this scene if we don't want to draw it anyways. - if (!shouldPlaceElement(layoutImpl, scene, element, transition)) { + if (!shouldPlaceElement(layoutImpl, scene.key, element, transition)) { sceneState.lastOffset = Offset.Unspecified sceneState.lastScale = Scale.Unspecified sceneState.lastAlpha = Element.AlphaUnspecified - - sceneState.clearValuesBeforeInterruption() - sceneState.clearInterruptionDeltas() return } @@ -353,7 +350,17 @@ internal class ElementNode( getValueBeforeInterruption = { sceneState.offsetBeforeInterruption }, setValueBeforeInterruption = { sceneState.offsetBeforeInterruption = it }, getInterruptionDelta = { sceneState.offsetInterruptionDelta }, - setInterruptionDelta = { sceneState.offsetInterruptionDelta = it }, + setInterruptionDelta = { delta -> + setPlacementInterruptionDelta( + element = element, + sceneState = sceneState, + transition = transition, + delta = delta, + setter = { sceneState, delta -> + sceneState.offsetInterruptionDelta = delta + }, + ) + }, diff = { a, b -> a - b }, add = { a, b, bProgress -> a + b * bProgress }, ) @@ -363,7 +370,7 @@ internal class ElementNode( val offset = (interruptedOffset - currentOffset).round() if ( isElementOpaque(scene, element, transition) && - interruptedAlpha(layoutImpl, transition, sceneState, alpha = 1f) == 1f + interruptedAlpha(layoutImpl, element, transition, sceneState, alpha = 1f) == 1f ) { sceneState.lastAlpha = 1f @@ -374,13 +381,17 @@ internal class ElementNode( } else { placeable.placeWithLayer(offset) { // This layer might still run on its own (outside of the placement phase) even - // if this element is not placed anymore, so we need to double check again here - // before calling [elementAlpha] (which will update [SceneState.lastAlpha]). We - // also need to recompute the current transition to make sure that we are using - // the current transition and not a reference to an old one. See b/343138966 for - // details. - val transition = elementTransition(element, currentTransitions) - if (!shouldPlaceElement(layoutImpl, scene, element, transition)) { + // if this element is not placed or composed anymore, so we need to double check + // again here before calling [elementAlpha] (which will update + // [SceneState.lastAlpha]). We also need to recompute the current transition to + // make sure that we are using the current transition and not a reference to an + // old one. See b/343138966 for details. + if (_element == null) { + return@placeWithLayer + } + + val transition = elementTransition(layoutImpl, element, currentTransitions) + if (!shouldPlaceElement(layoutImpl, scene.key, element, transition)) { return@placeWithLayer } @@ -394,7 +405,7 @@ internal class ElementNode( override fun ContentDrawScope.draw() { element.wasDrawnInAnyScene = true - val transition = elementTransition(element, currentTransitions) + val transition = elementTransition(layoutImpl, element, currentTransitions) val drawScale = getDrawScale(layoutImpl, scene, element, transition, sceneState) if (drawScale == Scale.Default) { drawContent() @@ -435,6 +446,7 @@ internal class ElementNode( * its scenes contains the element. */ private fun elementTransition( + layoutImpl: SceneTransitionLayoutImpl, element: Element, transitions: List<TransitionState.Transition>, ): TransitionState.Transition? { @@ -448,7 +460,7 @@ private fun elementTransition( if (transition != previousTransition && transition != null && previousTransition != null) { // The previous transition was interrupted by another transition. - prepareInterruption(element, transition, previousTransition) + prepareInterruption(layoutImpl, element, transition, previousTransition) } else if (transition == null && previousTransition != null) { // The transition was just finished. element.sceneStates.values.forEach { @@ -461,18 +473,43 @@ private fun elementTransition( } private fun prepareInterruption( + layoutImpl: SceneTransitionLayoutImpl, element: Element, transition: TransitionState.Transition, previousTransition: TransitionState.Transition, ) { val sceneStates = element.sceneStates - sceneStates[previousTransition.fromScene]?.selfUpdateValuesBeforeInterruption() - sceneStates[previousTransition.toScene]?.selfUpdateValuesBeforeInterruption() - sceneStates[transition.fromScene]?.selfUpdateValuesBeforeInterruption() - sceneStates[transition.toScene]?.selfUpdateValuesBeforeInterruption() + fun updatedSceneState(key: SceneKey): Element.SceneState? { + return sceneStates[key]?.also { it.selfUpdateValuesBeforeInterruption() } + } + + val previousFromState = updatedSceneState(previousTransition.fromScene) + val previousToState = updatedSceneState(previousTransition.toScene) + val fromState = updatedSceneState(transition.fromScene) + val toState = updatedSceneState(transition.toScene) reconcileStates(element, previousTransition) reconcileStates(element, transition) + + // Remove the interruption values to all scenes but the scene(s) where the element will be + // placed, to make sure that interruption deltas are computed only right after this interruption + // is prepared. + fun maybeCleanPlacementValuesBeforeInterruption(sceneState: Element.SceneState) { + if (!shouldPlaceElement(layoutImpl, sceneState.scene, element, transition)) { + sceneState.offsetBeforeInterruption = Offset.Unspecified + sceneState.alphaBeforeInterruption = Element.AlphaUnspecified + sceneState.scaleBeforeInterruption = Scale.Unspecified + + sceneState.offsetInterruptionDelta = Offset.Zero + sceneState.alphaInterruptionDelta = 0f + sceneState.scaleInterruptionDelta = Scale.Zero + } + } + + previousFromState?.let { maybeCleanPlacementValuesBeforeInterruption(it) } + previousToState?.let { maybeCleanPlacementValuesBeforeInterruption(it) } + fromState?.let { maybeCleanPlacementValuesBeforeInterruption(it) } + toState?.let { maybeCleanPlacementValuesBeforeInterruption(it) } } /** @@ -579,9 +616,38 @@ private inline fun <T> computeInterruptedValue( } } +/** + * Set the interruption delta of a *placement/drawing*-related value (offset, alpha, scale). This + * ensures that the delta is also set on the other scene in the transition for shared elements, so + * that there is no jump cut if the scene where the element is placed has changed. + */ +private inline fun <T> setPlacementInterruptionDelta( + element: Element, + sceneState: Element.SceneState, + transition: TransitionState.Transition?, + delta: T, + setter: (Element.SceneState, T) -> Unit, +) { + // Set the interruption delta on the current scene. + setter(sceneState, delta) + + if (transition == null) { + return + } + + // If the element is shared, also set the delta on the other scene so that it is used by that + // scene if we start overscrolling it and change the scene where the element is placed. + val otherScene = + if (sceneState.scene == transition.fromScene) transition.toScene else transition.fromScene + val otherSceneState = element.sceneStates[otherScene] ?: return + if (isSharedElementEnabled(element.key, transition)) { + setter(otherSceneState, delta) + } +} + private fun shouldPlaceElement( layoutImpl: SceneTransitionLayoutImpl, - scene: Scene, + scene: SceneKey, element: Element, transition: TransitionState.Transition?, ): Boolean { @@ -592,7 +658,7 @@ private fun shouldPlaceElement( // Don't place the element in this scene if this scene is not part of the current element // transition. - if (scene.key != transition.fromScene && scene.key != transition.toScene) { + if (scene != transition.fromScene && scene != transition.toScene) { return false } @@ -610,7 +676,7 @@ private fun shouldPlaceElement( return shouldPlaceOrComposeSharedElement( layoutImpl, - scene.key, + scene, element.key, transition, ) @@ -740,13 +806,14 @@ private fun elementAlpha( element.sceneStates.forEach { it.value.alphaBeforeInterruption = 0f } } - val interruptedAlpha = interruptedAlpha(layoutImpl, transition, sceneState, alpha) + val interruptedAlpha = interruptedAlpha(layoutImpl, element, transition, sceneState, alpha) sceneState.lastAlpha = interruptedAlpha return interruptedAlpha } private fun interruptedAlpha( layoutImpl: SceneTransitionLayoutImpl, + element: Element, transition: TransitionState.Transition?, sceneState: Element.SceneState, alpha: Float, @@ -760,7 +827,15 @@ private fun interruptedAlpha( getValueBeforeInterruption = { sceneState.alphaBeforeInterruption }, setValueBeforeInterruption = { sceneState.alphaBeforeInterruption = it }, getInterruptionDelta = { sceneState.alphaInterruptionDelta }, - setInterruptionDelta = { sceneState.alphaInterruptionDelta = it }, + setInterruptionDelta = { delta -> + setPlacementInterruptionDelta( + element = element, + sceneState = sceneState, + transition = transition, + delta = delta, + setter = { sceneState, delta -> sceneState.alphaInterruptionDelta = delta }, + ) + }, diff = { a, b -> a - b }, add = { a, b, bProgress -> a + b * bProgress }, ) @@ -867,7 +942,15 @@ private fun ContentDrawScope.getDrawScale( getValueBeforeInterruption = { sceneState.scaleBeforeInterruption }, setValueBeforeInterruption = { sceneState.scaleBeforeInterruption = it }, getInterruptionDelta = { sceneState.scaleInterruptionDelta }, - setInterruptionDelta = { sceneState.scaleInterruptionDelta = it }, + setInterruptionDelta = { delta -> + setPlacementInterruptionDelta( + element = element, + sceneState = sceneState, + transition = transition, + delta = delta, + setter = { sceneState, delta -> sceneState.scaleInterruptionDelta = delta }, + ) + }, diff = { a, b -> Scale( scaleX = a.scaleX - b.scaleX, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 44affd968513..6a178c8b0c25 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -226,6 +226,12 @@ sealed interface TransitionState { val toScene: SceneKey, ) : TransitionState { /** + * The key of this transition. This should usually be null, but it can be specified to use a + * specific set of transformations associated to this transition. + */ + open val key: TransitionKey? = null + + /** * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or * when flinging quickly during a swipe gesture. @@ -455,11 +461,7 @@ internal abstract class BaseSceneTransitionLayoutState( * * Important: you *must* call [finishTransition] once the transition is finished. */ - internal fun startTransition( - transition: TransitionState.Transition, - transitionKey: TransitionKey? = null, - chain: Boolean = true, - ) { + internal fun startTransition(transition: TransitionState.Transition, chain: Boolean = true) { checkThread() // Compute the [TransformationSpec] when the transition starts. @@ -469,7 +471,9 @@ internal abstract class BaseSceneTransitionLayoutState( // Update the transition specs. transition.transformationSpec = - transitions.transitionSpec(fromScene, toScene, key = transitionKey).transformationSpec() + transitions + .transitionSpec(fromScene, toScene, key = transition.key) + .transformationSpec() if (orientation != null) { transition.updateOverscrollSpecs( fromSpec = transitions.overscrollSpec(fromScene, orientation), @@ -568,9 +572,10 @@ internal abstract class BaseSceneTransitionLayoutState( originalTransition = transitionState, fromScene = targetCurrentScene, toScene = matchingLink.targetTo, + key = matchingLink.targetTransitionKey, ) - stateLink.target.startTransition(linkedTransition, matchingLink.targetTransitionKey) + stateLink.target.startTransition(linkedTransition) activeTransitionLinks[stateLink] = linkedTransition } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt index 79f126d24561..ed9888560f05 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt @@ -17,6 +17,7 @@ package com.android.compose.animation.scene.transition.link import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.TransitionState import kotlinx.coroutines.Job @@ -25,6 +26,7 @@ internal class LinkedTransition( private val originalTransition: TransitionState.Transition, fromScene: SceneKey, toScene: SceneKey, + override val key: TransitionKey? = null, ) : TransitionState.Transition(fromScene, toScene) { override val currentScene: SceneKey diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 47c9b9cbfd10..41cacb4c71fc 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -47,7 +47,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.layout.approachLayout import androidx.compose.ui.platform.LocalViewConfiguration -import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertPositionInRootIsEqualTo @@ -639,10 +638,7 @@ class ElementTest { // Change the current transition. rule.runOnUiThread { - state.startTransition( - transition(from = SceneA, to = SceneB, progress = { 0.5f }), - transitionKey = null, - ) + state.startTransition(transition(from = SceneA, to = SceneB, progress = { 0.5f })) } // The size of Foo should still be 20dp given that the new state was not composed yet. @@ -1171,7 +1167,7 @@ class ElementTest { val offsetInAToB = lerp(offsetInA, offsetInB, aToBProgress) val sizeInAToB = lerp(sizeInA, sizeInB, aToBProgress) val valueInAToB = lerp(valueInA, valueInB, aToBProgress) - rule.runOnUiThread { state.startTransition(aToB, transitionKey = null) } + rule.runOnUiThread { state.startTransition(aToB) } rule .onNode(isElement(TestElements.Foo, SceneB)) .assertSizeIsEqualTo(sizeInAToB) @@ -1191,7 +1187,7 @@ class ElementTest { progress = { bToCProgress }, interruptionProgress = { interruptionProgress }, ) - rule.runOnUiThread { state.startTransition(bToC, transitionKey = null) } + rule.runOnUiThread { state.startTransition(bToC) } // The interruption deltas, which will be multiplied by the interruption progress then added // to the current transition offset and size. @@ -1333,9 +1329,9 @@ class ElementTest { interruptionProgress = { bToCInterruptionProgress }, onFinish = neverFinish(), ) - rule.runOnUiThread { state.startTransition(aToB, transitionKey = null) } + rule.runOnUiThread { state.startTransition(aToB) } rule.waitForIdle() - rule.runOnUiThread { state.startTransition(bToC, transitionKey = null) } + rule.runOnUiThread { state.startTransition(bToC) } // Foo is placed in both B and C given that the shared transition is disabled. In B, its // offset is impacted by the interruption but in C it is not. @@ -1371,7 +1367,7 @@ class ElementTest { progress = { 0.7f }, interruptionProgress = { 1f }, ) - rule.runOnUiThread { state.startTransition(bToA, transitionKey = null) } + rule.runOnUiThread { state.startTransition(bToA) } // Foo should have the position it had in B right before the interruption. rule @@ -1395,8 +1391,7 @@ class ElementTest { to = SceneB, progress = { -1f }, orientation = Orientation.Horizontal - ), - transitionKey = null, + ) ) } } @@ -1513,8 +1508,7 @@ class ElementTest { to = SceneB, progress = { 0.6f }, interruptionProgress = { interruptionProgress }, - ), - transitionKey = null + ) ) } rule.waitForIdle() @@ -1632,4 +1626,97 @@ class ElementTest { rule.onNode(hasText(fooInA)).assertIsDisplayed() rule.onNode(hasText(fooInB)).assertDoesNotExist() } + + @Test + fun interruptionThenOverscroll() = runTest { + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutStateImpl( + SceneA, + transitions { + overscroll(SceneB, Orientation.Vertical) { + translate(TestElements.Foo, y = 15.dp) + } + } + ) + } + + @Composable + fun SceneScope.SceneWithFoo(offset: DpOffset, modifier: Modifier = Modifier) { + Box(modifier.fillMaxSize()) { + Box(Modifier.offset(offset.x, offset.y).element(TestElements.Foo).size(100.dp)) + } + } + + rule.setContent { + SceneTransitionLayout(state, Modifier.size(200.dp)) { + scene(SceneA) { SceneWithFoo(offset = DpOffset.Zero) } + scene(SceneB) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 0.dp)) } + scene(SceneC) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 40.dp)) } + } + } + + // Start A => B at 75%. + rule.runOnUiThread { + state.startTransition( + transition( + from = SceneA, + to = SceneB, + progress = { 0.75f }, + onFinish = neverFinish(), + ) + ) + } + + // Foo should be at offset (30dp, 0dp) and placed in scene B. + rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed() + rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(30.dp, 0.dp) + rule.onNode(isElement(TestElements.Foo, SceneC)).assertIsNotDisplayed() + + // Interrupt A => B with B => C at 0%. + var progress by mutableStateOf(0f) + var interruptionProgress by mutableStateOf(1f) + rule.runOnUiThread { + state.startTransition( + transition( + from = SceneB, + to = SceneC, + progress = { progress }, + interruptionProgress = { interruptionProgress }, + orientation = Orientation.Vertical, + onFinish = neverFinish(), + ) + ) + } + + // Because interruption progress is at 100M, Foo should still be at offset (30dp, 0dp) but + // placed in scene C. + rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed() + rule.onNode(isElement(TestElements.Foo, SceneB)).assertIsNotDisplayed() + rule.onNode(isElement(TestElements.Foo, SceneC)).assertPositionInRootIsEqualTo(30.dp, 0.dp) + + // Overscroll B => C on scene B at -100%. Because overscrolling on B => C translates Foo + // vertically by -15dp and that interruptionProgress is still 100%, we should now be at + // (30dp, -15dp) + progress = -1f + rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed() + rule + .onNode(isElement(TestElements.Foo, SceneB)) + .assertPositionInRootIsEqualTo(30.dp, -15.dp) + rule.onNode(isElement(TestElements.Foo, SceneC)).assertIsNotDisplayed() + + // Finish the interruption, we should now be at (40dp, -15dp), still on scene B. + interruptionProgress = 0f + rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed() + rule + .onNode(isElement(TestElements.Foo, SceneB)) + .assertPositionInRootIsEqualTo(40.dp, -15.dp) + rule.onNode(isElement(TestElements.Foo, SceneC)).assertIsNotDisplayed() + + // Finish the transition, we should be at the final position (40dp, 40dp) on scene C. + progress = 1f + rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed() + rule.onNode(isElement(TestElements.Foo, SceneB)).assertIsNotDisplayed() + rule.onNode(isElement(TestElements.Foo, SceneC)).assertPositionInRootIsEqualTo(40.dp, 40.dp) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt index 85d4165b4bf6..09d1a827d0c7 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt @@ -40,7 +40,7 @@ class InterruptionHandlerTest { val state = MutableSceneTransitionLayoutState( SceneA, - transitions { /* default interruption handler */}, + transitions { /* default interruption handler */ }, ) state.setTargetScene(SceneB, coroutineScope = this) @@ -160,7 +160,7 @@ class InterruptionHandlerTest { progressVelocity = { progressVelocity }, onFinish = { launch {} }, ) - state.startTransition(aToB, transitionKey = null) + state.startTransition(aToB) // Animate back to A. The previous transition is reversed, i.e. it has the same (from, to) // pair, and its velocity is used when animating the progress back to 0. @@ -186,7 +186,7 @@ class InterruptionHandlerTest { progressVelocity = { progressVelocity }, onFinish = { launch {} }, ) - state.startTransition(aToB, transitionKey = null) + state.startTransition(aToB) // Animate to B. The previous transition is reversed, i.e. it has the same (from, to) pair, // and its velocity is used when animating the progress to 1. diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt index 2a75e13066df..55431354b693 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt @@ -135,7 +135,7 @@ class ObservableTransitionStateTest { var transitionCurrentScene by mutableStateOf(SceneA) val transition = transition(from = SceneA, to = SceneB, current = { transitionCurrentScene }) - state.startTransition(transition, transitionKey = null) + state.startTransition(transition) assertThat(currentScene.value).isEqualTo(SceneA) // Change the transition current scene. diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index d2c8bd6928ee..de6f1cc518f4 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -57,7 +57,7 @@ class SceneTransitionLayoutStateTest { @Test fun isTransitioningTo_transition() { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) - state.startTransition(transition(from = SceneA, to = SceneB), transitionKey = null) + state.startTransition(transition(from = SceneA, to = SceneB)) assertThat(state.isTransitioning()).isTrue() assertThat(state.isTransitioning(from = SceneA)).isTrue() @@ -175,7 +175,7 @@ class SceneTransitionLayoutStateTest { val childTransition = transition(SceneA, SceneB) - childState.startTransition(childTransition, null) + childState.startTransition(childTransition) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() @@ -211,7 +211,7 @@ class SceneTransitionLayoutStateTest { val childTransition = transition(SceneA, SceneB) - childState.startTransition(childTransition, null) + childState.startTransition(childTransition) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue() @@ -229,7 +229,7 @@ class SceneTransitionLayoutStateTest { var progress = 0f val childTransition = transition(SceneA, SceneB, progress = { progress }) - childState.startTransition(childTransition, null) + childState.startTransition(childTransition) assertThat(parentState.currentTransition?.progress).isEqualTo(0f) progress = .5f @@ -242,7 +242,7 @@ class SceneTransitionLayoutStateTest { val childTransition = transition(SceneB, SceneA) - childState.startTransition(childTransition, null) + childState.startTransition(childTransition) assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue() assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) @@ -256,7 +256,7 @@ class SceneTransitionLayoutStateTest { val (parentState, childState) = setupLinkedStates() val childTransition = transition(SceneA, SceneB) - childState.startTransition(childTransition, null) + childState.startTransition(childTransition) childState.finishTransition(childTransition, SceneA) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA)) @@ -268,7 +268,7 @@ class SceneTransitionLayoutStateTest { val (parentState, childState) = setupLinkedStates() val childTransition = transition(SceneA, SceneB) - childState.startTransition(childTransition, null) + childState.startTransition(childTransition) childState.finishTransition(childTransition, SceneD) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) @@ -283,16 +283,16 @@ class SceneTransitionLayoutStateTest { transition( SceneA, SceneB, - onFinish = { launch { /* Do nothing. */} }, + onFinish = { launch { /* Do nothing. */ } }, ) val parentTransition = transition( SceneC, SceneA, - onFinish = { launch { /* Do nothing. */} }, + onFinish = { launch { /* Do nothing. */ } }, ) - childState.startTransition(childTransition, null) - parentState.startTransition(parentTransition, null) + childState.startTransition(childTransition) + parentState.startTransition(parentTransition) childState.finishTransition(childTransition, SceneB) assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) @@ -341,10 +341,7 @@ class SceneTransitionLayoutStateTest { @Test fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) - state.startTransition( - transition(from = SceneA, to = SceneB, progress = { 0.2f }), - transitionKey = null - ) + state.startTransition(transition(from = SceneA, to = SceneB, progress = { 0.2f })) assertThat(state.isTransitioning()).isTrue() // Ignore the request if the progress is not close to 0 or 1, using the threshold. @@ -360,10 +357,7 @@ class SceneTransitionLayoutStateTest { @Test fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) - state.startTransition( - transition(from = SceneA, to = SceneB, progress = { 0.8f }), - transitionKey = null - ) + state.startTransition(transition(from = SceneA, to = SceneB, progress = { 0.8f })) assertThat(state.isTransitioning()).isTrue() // Ignore the request if the progress is not close to 0 or 1, using the threshold. @@ -385,13 +379,13 @@ class SceneTransitionLayoutStateTest { from = SceneA, to = SceneB, progress = { 0.5f }, - onFinish = { launch { /* do nothing */} }, + onFinish = { launch { /* do nothing */ } }, ) - state.startTransition(aToB, transitionKey = null) + state.startTransition(aToB) assertThat(state.currentTransitions).containsExactly(aToB).inOrder() val bToC = transition(from = SceneB, to = SceneC, progress = { 0.8f }) - state.startTransition(bToC, transitionKey = null) + state.startTransition(bToC) assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder() // Ignore the request if the progress is not close to 0 or 1, using the threshold. @@ -409,7 +403,7 @@ class SceneTransitionLayoutStateTest { val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD) val childTransition = transition(SceneA, SceneB) - childState.startTransition(childTransition, null) + childState.startTransition(childTransition) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() @@ -425,7 +419,7 @@ class SceneTransitionLayoutStateTest { val childTransition = transition(SceneA, SceneB) - childState.startTransition(childTransition, null) + childState.startTransition(childTransition) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() @@ -440,7 +434,7 @@ class SceneTransitionLayoutStateTest { setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD) val childTransition = transition(SceneA, SceneB) - childState.startTransition(childTransition, null) + childState.startTransition(childTransition) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse() } @@ -460,8 +454,7 @@ class SceneTransitionLayoutStateTest { to = SceneB, progress = progress, orientation = Orientation.Vertical, - ), - transitionKey = null + ) ) assertThat(state.isTransitioning()).isTrue() return state @@ -583,19 +576,19 @@ class SceneTransitionLayoutStateTest { assertThat(state.currentTransitions).isEmpty() // A => B. - state.startTransition(aToB, transitionKey = null) + state.startTransition(aToB) assertThat(finishingTransitions).isEmpty() assertThat(state.finishedTransitions).isEmpty() assertThat(state.currentTransitions).containsExactly(aToB).inOrder() // B => C. This should automatically call finish() on aToB. - state.startTransition(bToC, transitionKey = null) + state.startTransition(bToC) assertThat(finishingTransitions).containsExactly(aToB) assertThat(state.finishedTransitions).isEmpty() assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder() // C => A. This should automatically call finish() on bToC. - state.startTransition(cToA, transitionKey = null) + state.startTransition(cToA) assertThat(finishingTransitions).containsExactly(aToB, bToC) assertThat(state.finishedTransitions).isEmpty() assertThat(state.currentTransitions).containsExactly(aToB, bToC, cToA).inOrder() @@ -617,8 +610,8 @@ class SceneTransitionLayoutStateTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions) fun startTransition() { - val transition = transition(SceneA, SceneB, onFinish = { launch { /* do nothing */} }) - state.startTransition(transition, transitionKey = null) + val transition = transition(SceneA, SceneB, onFinish = { launch { /* do nothing */ } }) + state.startTransition(transition) } var hasLoggedWtf = false diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py index 95a25c58bc67..d78ef5a5f1bf 100755 --- a/packages/SystemUI/flag_check.py +++ b/packages/SystemUI/flag_check.py @@ -52,7 +52,7 @@ def main(): nargs='?', default='', help= - 'REPO_PATH in repo upload to determine whether the check should run for this project.') + 'REPO_PROJECT in repo upload to determine whether the check should run for this project.') # Parse the arguments args = parser.parse_args() @@ -112,16 +112,16 @@ def main(): sys.exit(0) -def should_run_path(path, files): +def should_run_path(project, files): """Returns a boolean if this check should run with these paths. If you want to check for a particular subdirectory under the path, add a check here, call should_run_files and check for a specific sub dir path in should_run_files. """ - if not path: + if not project: return False - if path == 'frameworks/base': + if project == 'platform/frameworks/base': return should_run_files(files) - # Default case, run for all other paths which calls this script. + # Default case, run for all other projects which calls this script. return True diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt index aad2e6001f1c..a0e7781a07b0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt @@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel import com.android.systemui.communal.shared.model.CommunalScenes @@ -27,8 +28,10 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -63,6 +66,21 @@ class CommunalSceneInteractorTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun snapToSceneWithDelay() = + testScope.runTest { + val currentScene by collectLastValue(underTest.currentScene) + assertThat(currentScene).isEqualTo(CommunalScenes.Blank) + underTest.snapToScene( + CommunalScenes.Communal, + ActivityTransitionAnimator.TIMINGS.totalDuration + ) + assertThat(currentScene).isEqualTo(CommunalScenes.Blank) + advanceTimeBy(ActivityTransitionAnimator.TIMINGS.totalDuration) + assertThat(currentScene).isEqualTo(CommunalScenes.Communal) + } + @Test fun transitionProgress_fullProgress() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt index 420b11c4bde3..df7b291c4bab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt @@ -27,23 +27,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.util.mockito.eq -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.refEq -import org.mockito.Mock -import org.mockito.Mockito.isNull -import org.mockito.Mockito.notNull -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.eq +import org.mockito.kotlin.isNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.notNull +import org.mockito.kotlin.refEq +import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) class WidgetInteractionHandlerTest : SysuiTestCase() { - @Mock private lateinit var activityStarter: ActivityStarter - - private lateinit var underTest: WidgetInteractionHandler + private val activityStarter = mock<ActivityStarter>() private val testIntent = PendingIntent.getActivity( @@ -54,10 +50,8 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { ) private val testResponse = RemoteResponse.fromPendingIntent(testIntent) - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - underTest = WidgetInteractionHandler(activityStarter) + private val underTest: WidgetInteractionHandler by lazy { + WidgetInteractionHandler(activityStarter) } @Test @@ -81,6 +75,26 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { } @Test + fun launchAnimatorIsUsedForSmartspaceView() { + val parent = FrameLayout(context) + val view = SmartspaceAppWidgetHostView(context) + parent.addView(view) + val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view) + + underTest.onInteraction(view, testIntent, testResponse) + + verify(activityStarter) + .startPendingIntentMaybeDismissingKeyguard( + eq(testIntent), + eq(false), + isNull(), + notNull(), + refEq(fillInIntent), + refEq(activityOptions.toBundle()), + ) + } + + @Test fun launchAnimatorIsNotUsedForRegularView() { val parent = FrameLayout(context) val view = View(context) diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml index 5ce2601d407d..08edf59000b8 100644 --- a/packages/SystemUI/res/layout-land/volume_dialog.xml +++ b/packages/SystemUI/res/layout-land/volume_dialog.xml @@ -13,9 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:sysui="http://schemas.android.com/apk/res-auto" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/volume_dialog_container" android:layout_width="wrap_content" @@ -97,16 +95,18 @@ android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding" android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding" android:paddingRight="@dimen/volume_dialog_ringer_rows_padding"> + <com.android.keyguard.AlphaOptimizedImageButton android:id="@+id/settings" - android:src="@drawable/horizontal_ellipsis" android:layout_width="@dimen/volume_dialog_tap_target_size" android:layout_height="@dimen/volume_dialog_tap_target_size" android:layout_gravity="center" - android:contentDescription="@string/accessibility_volume_settings" android:background="@drawable/ripple_drawable_20dp" - android:tint="?androidprv:attr/colorAccent" - android:soundEffectsEnabled="false" /> + android:contentDescription="@string/accessibility_volume_settings" + android:scaleType="centerInside" + android:soundEffectsEnabled="false" + android:src="@drawable/horizontal_ellipsis" + android:tint="?androidprv:attr/colorAccent" /> </FrameLayout> </LinearLayout> diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt index 260dcbad6201..7a4006d515f7 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt @@ -28,6 +28,7 @@ import com.android.systemui.scene.shared.model.SceneDataSource import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -51,7 +52,7 @@ interface CommunalSceneRepository { fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null) /** Immediately snaps to the desired scene. */ - fun snapToScene(toScene: SceneKey) + fun snapToScene(toScene: SceneKey, delayMillis: Long = 0) /** * Updates the transition state of the hub [SceneTransitionLayout]. @@ -92,10 +93,11 @@ constructor( } } - override fun snapToScene(toScene: SceneKey) { + override fun snapToScene(toScene: SceneKey, delayMillis: Long) { applicationScope.launch { // SceneTransitionLayout state updates must be triggered on the thread the STL was // created on. + delay(delayMillis) sceneDataSource.snapToScene(toScene) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index 5cfe9798420d..0dab67c98761 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -53,8 +53,8 @@ constructor( } /** Immediately snaps to the new scene. */ - fun snapToScene(newScene: SceneKey) { - communalSceneRepository.snapToScene(newScene) + fun snapToScene(newScene: SceneKey, delayMillis: Long = 0) { + communalSceneRepository.snapToScene(newScene, delayMillis) } /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/SmartspaceAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/SmartspaceAppWidgetHostView.kt new file mode 100644 index 000000000000..7f1146399f4e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/SmartspaceAppWidgetHostView.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.widgets + +import android.appwidget.AppWidgetHostView +import android.appwidget.AppWidgetProviderInfo +import android.content.Context +import com.android.systemui.animation.LaunchableView +import com.android.systemui.animation.LaunchableViewDelegate + +/** AppWidgetHostView that displays in communal hub to show smartspace content. */ +class SmartspaceAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView { + private val launchableViewDelegate = + LaunchableViewDelegate( + this, + superSetVisibility = { super.setVisibility(it) }, + ) + + override fun setAppWidget(appWidgetId: Int, info: AppWidgetProviderInfo?) { + super.setAppWidget(appWidgetId, info) + setPadding(0, 0, 0, 0) + } + + override fun getRemoteContextEnsuringCorrectCachedApkPath(): Context? { + // Silence errors + return null + } + + override fun setShouldBlockVisibilityChanges(block: Boolean) = + launchableViewDelegate.setShouldBlockVisibilityChanges(block) + + override fun setVisibility(visibility: Int) = launchableViewDelegate.setVisibility(visibility) +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt index 51a3a6d827dd..e88a8b5b7cdb 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal.widgets import android.app.ActivityOptions import android.app.PendingIntent +import android.appwidget.AppWidgetHostView import android.content.Intent import android.util.Pair import android.view.View @@ -26,9 +27,11 @@ import androidx.core.util.component1 import androidx.core.util.component2 import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.common.ui.view.getNearestParent +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter import javax.inject.Inject +@SysUISingleton class WidgetInteractionHandler @Inject constructor( @@ -55,7 +58,7 @@ constructor( pendingIntent: PendingIntent, launchOptions: Pair<Intent, ActivityOptions>, ): Boolean { - val hostView = view.getNearestParent<CommunalAppWidgetHostView>() + val hostView = view.getNearestParent<AppWidgetHostView>() val animationController = hostView?.let(ActivityTransitionAnimator.Controller::fromView) val (fillInIntent, activityOptions) = launchOptions diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index fc29eaba4b46..e5fc4e2968cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -555,7 +555,12 @@ constructor( override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { super.onTransitionAnimationStart(isExpandingFullyAbove) - + if (communalHub()) { + communalSceneInteractor.snapToScene( + CommunalScenes.Blank, + ActivityTransitionAnimator.TIMINGS.totalDuration + ) + } // Double check that the keyguard is still showing and not going // away, but if so set the keyguard occluded. Typically, WM will let // KeyguardViewMediator know directly, but we're overriding that to @@ -581,9 +586,6 @@ constructor( // collapse the shade (or at least run the post collapse runnables) // later on. centralSurfaces?.setIsLaunchingActivityOverLockscreen(false, false) - if (communalHub()) { - communalSceneInteractor.snapToScene(CommunalScenes.Blank) - } delegate.onTransitionAnimationEnd(isExpandingFullyAbove) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt index a7bf87def5be..d280be216f5e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt @@ -6,6 +6,7 @@ import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.shared.model.CommunalScenes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -13,20 +14,25 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** Fake implementation of [CommunalSceneRepository]. */ @OptIn(ExperimentalCoroutinesApi::class) class FakeCommunalSceneRepository( - applicationScope: CoroutineScope, + private val applicationScope: CoroutineScope, override val currentScene: MutableStateFlow<SceneKey> = MutableStateFlow(CommunalScenes.Default), ) : CommunalSceneRepository { + override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = - snapToScene(toScene) + snapToScene(toScene, 0) - override fun snapToScene(toScene: SceneKey) { - this.currentScene.value = toScene - this._transitionState.value = flowOf(ObservableTransitionState.Idle(toScene)) + override fun snapToScene(toScene: SceneKey, delayMillis: Long) { + applicationScope.launch { + delay(delayMillis) + currentScene.value = toScene + _transitionState.value = flowOf(ObservableTransitionState.Idle(toScene)) + } } private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default) diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 959300629cd7..f85d786f89c5 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -101,8 +101,10 @@ class UiAutomationManager { SystemActionPerformer systemActionPerformer, AccessibilityWindowManager awm, int flags) { accessibilityServiceInfo.setComponentName(COMPONENT_NAME); - Slogf.i(LOG_TAG, "Registering UiTestAutomationService (id=%s) when called by user %d", - accessibilityServiceInfo.getId(), Binder.getCallingUserHandle().getIdentifier()); + Slogf.i(LOG_TAG, "Registering UiTestAutomationService (id=%s, flags=0x%x) when" + + " called by user %d", + accessibilityServiceInfo.getId(), flags, + Binder.getCallingUserHandle().getIdentifier()); if (mUiAutomationService != null) { throw new IllegalStateException( "UiAutomationService " + mUiAutomationService.mServiceInterface diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2a724cdce456..d79d198b554e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -15274,15 +15274,50 @@ public class ActivityManagerService extends IActivityManager.Stub BackgroundStartPrivileges backgroundStartPrivileges, @Nullable int[] broadcastAllowList, @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) { - final int cookie = BroadcastQueue.traceBegin("broadcastIntentLockedTraced"); - final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId, - intent, resolvedType, resultToApp, resultTo, resultCode, resultData, resultExtras, - requiredPermissions, excludedPermissions, excludedPackages, appOp, - BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky, - callingPid, callingUid, realCallingUid, realCallingPid, userId, - backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver); - BroadcastQueue.traceEnd(cookie); - return res; + final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky, + callingUid, realCallingUid, userId); + try { + final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId, + intent, resolvedType, resultToApp, resultTo, resultCode, resultData, + resultExtras, requiredPermissions, excludedPermissions, excludedPackages, + appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky, + callingPid, callingUid, realCallingUid, realCallingPid, userId, + backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver); + return res; + } finally { + traceBroadcastIntentEnd(cookie); + } + } + + private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo, + boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) { + if (!Flags.traceReceiverRegistration()) { + return BroadcastQueue.traceBegin("broadcastIntentLockedTraced"); + } + if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { + final StringBuilder sb = new StringBuilder("broadcastIntent: "); + sb.append(callingUid); sb.append('/'); + final String action = intent.getAction(); + sb.append(action == null ? null : action); sb.append('/'); + sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/'); + sb.append(ordered ? "O" : "_"); + sb.append(sticky ? "S" : "_"); + sb.append(resultTo != null ? "C" : "_"); + sb.append('/'); + sb.append('u'); sb.append(userId); + if (callingUid != realCallingUid) { + sb.append('/'); + sb.append("sender="); sb.append(realCallingUid); + } + return BroadcastQueue.traceBegin(sb.toString()); + } + return 0; + } + + private static void traceBroadcastIntentEnd(int cookie) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { + BroadcastQueue.traceEnd(cookie); + } } @GuardedBy("this") diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java index f572845dc214..966be5318973 100644 --- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java +++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java @@ -32,6 +32,7 @@ import android.util.ArraySet; import android.util.Slog; import com.android.internal.widget.VerifyCredentialResponse; +import com.android.server.biometrics.BiometricHandlerProvider; import java.util.ArrayList; import java.util.List; @@ -132,9 +133,11 @@ public class BiometricDeferredQueue { mFaceResetLockoutTask = null; }; - BiometricDeferredQueue(@NonNull SyntheticPasswordManager spManager, @NonNull Handler handler) { + BiometricDeferredQueue(@NonNull SyntheticPasswordManager spManager) { mSpManager = spManager; - mHandler = handler; + + //Using a higher priority thread to avoid any delays and interruption of clients + mHandler = BiometricHandlerProvider.getInstance().getBiometricCallbackHandler(); mPendingResetLockoutsForFingerprint = new ArrayList<>(); mPendingResetLockoutsForFace = new ArrayList<>(); mPendingResetLockouts = new ArrayList<>(); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index ae3d36acdb7f..22b33ddcfa2b 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -687,7 +687,7 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager = injector.getSyntheticPasswordManager(mStorage); mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mKeyStore); - mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler); + mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager); mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(), mStorage); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 050d44eed2ea..b93dcdc93a82 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1423,7 +1423,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements DevicePolicyManagerInternal dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); final boolean canSilentlyInstallPackage = - dpmi != null && dpmi.canSilentlyInstallPackage(callerPackageName, callingUid); + (dpmi != null && dpmi.canSilentlyInstallPackage(callerPackageName, callingUid)) + || PackageInstallerSession.isEmergencyInstallerEnabled( + versionedPackage.getPackageName(), snapshot, userId, callingUid); final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, statusReceiver, versionedPackage.getPackageName(), @@ -1445,15 +1447,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements .createEvent(DevicePolicyEnums.UNINSTALL_PACKAGE) .setAdmin(callerPackageName) .write(); - } else if (PackageInstallerSession.isEmergencyInstallerEnabled(callerPackageName, snapshot, - userId, callingUid)) { - // Need to clear the calling identity to get DELETE_PACKAGES permission - final long ident = Binder.clearCallingIdentity(); - try { - mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags); - } finally { - Binder.restoreCallingIdentity(ident); - } } else { ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId); if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) { diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java index b05a421e6e87..e544ae64521c 100644 --- a/services/core/java/com/android/server/policy/TalkbackShortcutController.java +++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java @@ -117,6 +117,7 @@ class TalkbackShortcutController { } private boolean isTalkback(ServiceInfo info) { - return TALKBACK_LABEL.equals(info.loadLabel(mPackageManager).toString()); + return TALKBACK_LABEL.equals(info.loadLabel(mPackageManager).toString()) + && (info.applicationInfo.isSystemApp() || info.applicationInfo.isUpdatedSystemApp()); } } diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index 6710d02bee90..988e8fea70b9 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -56,10 +56,17 @@ final class VibratorController { private volatile boolean mIsUnderExternalControl; private volatile float mCurrentAmplitude; - /** Listener for vibration completion callbacks from native. */ + /** + * Listener for vibration completion callbacks from native. + * + * <p>Only the latest active native call to {@link VibratorController#on} will ever trigger this + * completion callback, to avoid race conditions during a vibration playback. If a new call to + * {@link #on} or {@link #off} happens before a previous callback was triggered then the + * previous callback will be disabled, even if the new command fails. + */ public interface OnVibrationCompleteListener { - /** Callback triggered when vibration is complete. */ + /** Callback triggered when an active vibration command is complete. */ void onComplete(int vibratorId, long vibrationId); } @@ -235,7 +242,7 @@ final class VibratorController { } /** - * Turn on the vibrator for {@code milliseconds} time, using {@code vibrationId} or completion + * Turn on the vibrator for {@code milliseconds} time, using {@code vibrationId} for completion * callback to {@link OnVibrationCompleteListener}. * * <p>This will affect the state of {@link #isVibrating()}. @@ -255,7 +262,7 @@ final class VibratorController { } /** - * Plays predefined vibration effect, using {@code vibrationId} or completion callback to + * Plays predefined vibration effect, using {@code vibrationId} for completion callback to * {@link OnVibrationCompleteListener}. * * <p>This will affect the state of {@link #isVibrating()}. @@ -276,8 +283,8 @@ final class VibratorController { } /** - * Plays a composition of vibration primitives, using {@code vibrationId} or completion callback - * to {@link OnVibrationCompleteListener}. + * Plays a composition of vibration primitives, using {@code vibrationId} for completion + * callback to {@link OnVibrationCompleteListener}. * * <p>This will affect the state of {@link #isVibrating()}. * @@ -299,7 +306,7 @@ final class VibratorController { } /** - * Plays a composition of pwle primitives, using {@code vibrationId} or completion callback + * Plays a composition of pwle primitives, using {@code vibrationId} for completion callback * to {@link OnVibrationCompleteListener}. * * <p>This will affect the state of {@link #isVibrating()}. @@ -321,7 +328,11 @@ final class VibratorController { } } - /** Turns off the vibrator. This will affect the state of {@link #isVibrating()}. */ + /** + * Turns off the vibrator and disables completion callback to any pending vibration. + * + * <p>This will affect the state of {@link #isVibrating()}. + */ public void off() { synchronized (mLock) { mNativeWrapper.off(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index fec1af47d6e6..d9e143403e2d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -661,7 +661,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private CompatDisplayInsets mCompatDisplayInsets; - private final TaskFragment.ConfigOverrideHint mResolveConfigHint; + @VisibleForTesting + final TaskFragment.ConfigOverrideHint mResolveConfigHint; private final boolean mOptOutEdgeToEdge; @@ -8533,6 +8534,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mIsEligibleForFixedOrientationLetterbox = false; mLetterboxBoundsForFixedOrientationAndAspectRatio = null; mLetterboxBoundsForAspectRatio = null; + mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration, + isFixedRotationTransforming()); // Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be // different from windowing mode of the task (PiP) during transition from fullscreen to PiP @@ -8647,10 +8650,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig); + mResolveConfigHint.resetTmpOverrides(); logAppCompatState(); } + @Nullable Rect getParentAppBoundsOverride() { + return Rect.copyOrNull(mResolveConfigHint.mTmpParentAppBoundsOverride); + } + /** * If necessary, override configuration fields related to app bounds. * This will happen when the app is targeting SDK earlier than 35. @@ -8674,8 +8682,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A rotation = mDisplayContent.getRotation(); } if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig - || getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets() - || isFloating(parentWindowingMode) || rotation == ROTATION_UNDEFINED)) { + || getCompatDisplayInsets() != null || isFloating(parentWindowingMode) + || rotation == ROTATION_UNDEFINED)) { // If the insets configuration decoupled logic is not enabled for the app, or the app // already has a compat override, or the context doesn't contain enough info to // calculate the override, skip the override. @@ -8697,7 +8705,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A : mDisplayContent.mBaseDisplayWidth; final int dh = rotated ? mDisplayContent.mBaseDisplayWidth : mDisplayContent.mBaseDisplayHeight; - final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy() + final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy() .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets; // This should be the only place override the configuration for ActivityRecord. Override // the value if not calculated yet. @@ -8713,12 +8721,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f); - inOutConfig.screenWidthDp = overrideScreenWidthDp; + inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f); } if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - final int overrideScreenHeightDp = (int) (outAppBounds.height() / density + 0.5f); - inOutConfig.screenHeightDp = overrideScreenHeightDp; + inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f); } if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED @@ -8829,7 +8835,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } final Rect screenResolvedBounds = mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds; - final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds(); + final Rect parentAppBounds = mResolveConfigHint.mTmpParentAppBoundsOverride; final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); final float screenResolvedBoundsWidth = screenResolvedBounds.width(); final float parentAppBoundsWidth = parentAppBounds.width(); @@ -9238,7 +9244,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private void resolveAspectRatioRestriction(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); - final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds(); + final Rect parentAppBounds = mResolveConfigHint.mTmpParentAppBoundsOverride; final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use @@ -9267,19 +9273,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @NonNull CompatDisplayInsets compatDisplayInsets) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); - final Insets insets; - if (mResolveConfigHint.mUseOverrideInsetsForConfig) { - // TODO(b/343197837): Add test to verify SCM behaviour with new bound configuration - // Insets are decoupled from configuration by default from V+, use legacy - // compatibility behaviour for apps targeting SDK earlier than 35 - // (see applySizeOverrideIfNeeded). - insets = Insets.of(mDisplayContent.getDisplayPolicy() - .getDecorInsetsInfo(mDisplayContent.mDisplayFrames.mRotation, - mDisplayContent.mDisplayFrames.mWidth, - mDisplayContent.mDisplayFrames.mHeight).mOverrideNonDecorInsets); - } else { - insets = Insets.NONE; - } // When an activity needs to be letterboxed because of fixed orientation, use fixed // orientation bounds (stored in resolved bounds) instead of parent bounds since the @@ -9290,22 +9283,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect containerBounds = useResolvedBounds ? new Rect(resolvedBounds) : newParentConfiguration.windowConfiguration.getBounds(); - final Rect parentAppBounds = - newParentConfiguration.windowConfiguration.getAppBounds(); - parentAppBounds.inset(insets); final Rect containerAppBounds = useResolvedBounds ? new Rect(resolvedConfig.windowConfiguration.getAppBounds()) - : parentAppBounds; + : mResolveConfigHint.mTmpParentAppBoundsOverride; final int requestedOrientation = getRequestedConfigurationOrientation(); final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; + final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig + ? mResolveConfigHint.mTmpOverrideConfigOrientation + : newParentConfiguration.orientation; final int orientation = orientationRequested ? requestedOrientation // We should use the original orientation of the activity when possible to avoid // forcing the activity in the opposite orientation. : compatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED ? compatDisplayInsets.mOriginalRequestedOrientation - : newParentConfiguration.orientation; + : parentOrientation; int rotation = newParentConfiguration.windowConfiguration.getRotation(); final boolean isFixedToUserRotation = mDisplayContent == null || mDisplayContent.getDisplayRotation().isFixedToUserRotation(); @@ -9347,7 +9340,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside // the parent bounds appropriately. if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) { - resolvedConfig.orientation = newParentConfiguration.orientation; + resolvedConfig.orientation = parentOrientation; } // Below figure is an example that puts an activity which was launched in a larger container diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 194771f6b387..5e93e8930bab 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1288,6 +1288,9 @@ final class LetterboxUiController { if (!allowHorizontalReachabilityForThinLetterbox()) { return false; } + final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride(); + final Rect parentAppBounds = parentAppBoundsOverride != null + ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds(); // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty final Rect opaqueActivityBounds = mActivityRecord.mTransparentPolicy @@ -1297,10 +1300,8 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN // Check whether the activity fills the parent vertically. - && parentConfiguration.windowConfiguration.getAppBounds().height() - <= opaqueActivityBounds.height() - && parentConfiguration.windowConfiguration.getAppBounds().width() - > opaqueActivityBounds.width(); + && parentAppBounds.height() <= opaqueActivityBounds.height() + && parentAppBounds.width() > opaqueActivityBounds.width(); } @VisibleForTesting @@ -1326,6 +1327,9 @@ final class LetterboxUiController { if (!allowVerticalReachabilityForThinLetterbox()) { return false; } + final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride(); + final Rect parentAppBounds = parentAppBoundsOverride != null + ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds(); // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty. final Rect opaqueActivityBounds = mActivityRecord.mTransparentPolicy @@ -1335,10 +1339,8 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN // Check whether the activity fills the parent horizontally. - && parentConfiguration.windowConfiguration.getAppBounds().width() - <= opaqueActivityBounds.width() - && parentConfiguration.windowConfiguration.getAppBounds().height() - > opaqueActivityBounds.height(); + && parentAppBounds.width() <= opaqueActivityBounds.width() + && parentAppBounds.height() > opaqueActivityBounds.height(); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index b8b746a3de7f..187b1057b6fc 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -40,6 +40,8 @@ import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static android.os.UserHandle.USER_NULL; import static android.view.Display.INVALID_DISPLAY; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; @@ -87,6 +89,7 @@ import android.content.PermissionChecker; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; @@ -2225,7 +2228,43 @@ class TaskFragment extends WindowContainer<WindowContainer> { static class ConfigOverrideHint { @Nullable DisplayInfo mTmpOverrideDisplayInfo; @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets; + @Nullable Rect mTmpParentAppBoundsOverride; + int mTmpOverrideConfigOrientation; boolean mUseOverrideInsetsForConfig; + + void resolveTmpOverrides(DisplayContent dc, Configuration parentConfig, + boolean isFixedRotationTransforming) { + mTmpParentAppBoundsOverride = new Rect(parentConfig.windowConfiguration.getAppBounds()); + final Insets insets; + if (mUseOverrideInsetsForConfig && dc != null) { + // Insets are decoupled from configuration by default from V+, use legacy + // compatibility behaviour for apps targeting SDK earlier than 35 + // (see applySizeOverrideIfNeeded). + int rotation = parentConfig.windowConfiguration.getRotation(); + if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming) { + rotation = dc.getRotation(); + } + final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); + final int dw = rotated ? dc.mBaseDisplayHeight : dc.mBaseDisplayWidth; + final int dh = rotated ? dc.mBaseDisplayWidth : dc.mBaseDisplayHeight; + DisplayPolicy.DecorInsets.Info decorInsets = dc.getDisplayPolicy() + .getDecorInsetsInfo(rotation, dw, dh); + final Rect stableBounds = decorInsets.mOverrideConfigFrame; + mTmpOverrideConfigOrientation = stableBounds.width() > stableBounds.height() + ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; + insets = Insets.of(decorInsets.mOverrideNonDecorInsets); + } else { + insets = Insets.NONE; + } + mTmpParentAppBoundsOverride.inset(insets); + } + + void resetTmpOverrides() { + mTmpOverrideDisplayInfo = null; + mTmpCompatInsets = null; + mTmpParentAppBoundsOverride = null; + mTmpOverrideConfigOrientation = ORIENTATION_UNDEFINED; + } } void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @@ -2311,7 +2350,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) { final Rect containingAppBounds; if (insideParentBounds) { - containingAppBounds = parentConfig.windowConfiguration.getAppBounds(); + containingAppBounds = useOverrideInsetsForConfig + ? overrideHint.mTmpParentAppBoundsOverride + : parentConfig.windowConfiguration.getAppBounds(); } else { // Restrict appBounds to display non-decor rather than parent because the // override bounds are beyond the parent. Otherwise, it won't match the diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index f47a59d6cec9..4be21d872383 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -131,17 +131,28 @@ public: } std::function<void()> createCallback(jlong vibrationId) { - return [vibrationId, this]() { + auto callbackId = ++mCallbackId; + return [vibrationId, callbackId, this]() { + auto currentCallbackId = mCallbackId.load(); + if (currentCallbackId != callbackId) { + // This callback is from an older HAL call that is no longer relevant to the service + return; + } auto jniEnv = GetOrAttachJNIEnvironment(sJvm); jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, mVibratorId, vibrationId); }; } + void disableOldCallbacks() { + mCallbackId++; + } + private: const std::shared_ptr<vibrator::HalController> mHal; const int32_t mVibratorId; const jobject mCallbackListener; + std::atomic<int64_t> mCallbackId; }; static aidl::BrakingPwle brakingPwle(aidl::Braking braking, int32_t duration) { @@ -236,6 +247,7 @@ static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong ptr) { } auto offFn = [](vibrator::HalWrapper* hal) { return hal->off(); }; wrapper->halCall<void>(offFn, "off"); + wrapper->disableOldCallbacks(); } static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jfloat amplitude) { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 19ce217e581c..9dac23f075e6 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -1681,7 +1681,7 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .compose(); VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100); - VibrationEffect effect5 = VibrationEffect.createOneShot(20, 222); + VibrationEffect effect5 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); long vibrationId1 = startThreadAndDispatcher(effect1); waitForCompletion(); @@ -1745,13 +1745,12 @@ public class VibrationThreadTest { verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF); assertTrue("Tested duration=" + duration4, duration4 < 2000); - // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have - // started. + // Effect5: played normally after effect4, which may or may not have played. verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId5)); verifyCallbacksTriggered(vibrationId5, Vibration.Status.FINISHED); - assertEquals(Arrays.asList(expectedOneShot(20)), + assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), fakeVibrator.getEffectSegments(vibrationId5)); } diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index c67d1ec63827..a39a1a8637df 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -550,7 +550,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { }).when(appWindow.mSession).setOnBackInvokedCallbackInfo(eq(appWindow.mClient), any()); addToWindowMap(appWindow, true); - dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null, null); + dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null); OnBackInvokedCallback appCallback = createBackCallback(appLatch); 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 7adac5b283c5..1a366b3e3a4f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -105,7 +105,6 @@ import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsDisabled; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.view.InsetsFrameProvider; @@ -339,10 +338,9 @@ public class SizeCompatTests extends WindowTestsBase { } } - // TODO(b/333663877): Enable test after fix @Test - @RequiresFlagsDisabled({Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION}) @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING) + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testRepositionLandscapeImmersiveAppWithDisplayCutout() { final int dw = 2100; final int dh = 2000; @@ -356,11 +354,14 @@ public class SizeCompatTests extends WindowTestsBase { mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); - doReturn(true).when(mActivity).isImmersiveMode(any()); - prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, - SCREEN_ORIENTATION_LANDSCAPE); - addWindowToActivity(mActivity); - mActivity.mRootWindowContainer.performSurfacePlacement(); + final ActivityRecord activity = getActivityBuilderOnSameTask() + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE) + .build(); + + doReturn(true).when(activity).isImmersiveMode(any()); + addWindowToActivity(activity); + activity.mRootWindowContainer.performSurfacePlacement(); final Function<ActivityRecord, Rect> innerBoundsOf = (ActivityRecord a) -> { @@ -371,22 +372,22 @@ public class SizeCompatTests extends WindowTestsBase { final Consumer<Integer> doubleClick = (Integer y) -> { - mActivity.mLetterboxUiController.handleVerticalDoubleTap(y); - mActivity.mRootWindowContainer.performSurfacePlacement(); + activity.mLetterboxUiController.handleVerticalDoubleTap(y); + activity.mRootWindowContainer.performSurfacePlacement(); }; - final Rect bounds = mActivity.getBounds(); + final Rect bounds = activity.getBounds(); assertTrue(bounds.top > cutoutHeight && bounds.bottom < dh); assertEquals(dw, bounds.width()); // Double click bottom. doubleClick.accept(dh - 10); - assertEquals(dh, innerBoundsOf.apply(mActivity).bottom); + assertEquals(dh, innerBoundsOf.apply(activity).bottom); // Double click top. doubleClick.accept(10); doubleClick.accept(10); - assertEquals(cutoutHeight, innerBoundsOf.apply(mActivity).top); + assertEquals(cutoutHeight, innerBoundsOf.apply(activity).top); } @Test @@ -417,26 +418,25 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } final int notchHeight = 100; setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800).setNotch(notchHeight).build()); final Rect displayBounds = mActivity.mDisplayContent.getWindowConfiguration().getBounds(); final float aspectRatio = 1.2f; - mActivity.info.setMaxAspectRatio(aspectRatio); - mActivity.info.setMinAspectRatio(aspectRatio); - prepareUnresizable(mActivity, -1f, SCREEN_ORIENTATION_UNSPECIFIED); - final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds(); + final ActivityRecord activity = getActivityBuilderOnSameTask() + .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED) + .setMinAspectRatio(aspectRatio) + .setMaxAspectRatio(aspectRatio) + .setResizeMode(RESIZE_MODE_UNRESIZEABLE) + .build(); + final Rect appBounds = activity.getWindowConfiguration().getAppBounds(); // The parent configuration doesn't change since the first resolved configuration, so the // activity should fit in the parent naturally (size=583x700, appBounds=[9, 100 - 592, 800], // horizontal offset = round((600 - 583) / 2) = 9)). - assertFitted(); + assertFitted(activity); final int offsetX = (int) ((1f + displayBounds.width() - appBounds.width()) / 2); // The bounds must be horizontal centered. assertEquals(offsetX, appBounds.left); @@ -444,30 +444,30 @@ public class SizeCompatTests extends WindowTestsBase { // Ensure the app bounds keep the declared aspect ratio. assertEquals(appBounds.height(), appBounds.width() * aspectRatio, 0.5f /* delta */); // The decor height should be a part of the effective bounds. - assertEquals(mActivity.getBounds().height(), appBounds.height() + notchHeight); + assertEquals(activity.getBounds().height(), appBounds.height() + notchHeight); // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio. - assertActivityMaxBoundsSandboxed(); + assertActivityMaxBoundsSandboxed(activity); // Activity max bounds ignore notch, since an app can be shown past the notch (although app // is currently limited by the notch). - assertThat(mActivity.getWindowConfiguration().getMaxBounds().height()) + assertThat(activity.getWindowConfiguration().getMaxBounds().height()) .isEqualTo(displayBounds.height()); - mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); - assertFitted(); + activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + assertFitted(activity); // After the orientation of activity is changed, the display is rotated, the aspect // ratio should be the same (bounds=[0, 0 - 800, 583], appBounds=[100, 0 - 800, 583]). assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */); // Activity max bounds are sandboxed. - assertActivityMaxBoundsSandboxed(); + assertActivityMaxBoundsSandboxed(activity); - mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); - assertFitted(); + activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + assertFitted(activity); // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio. - assertActivityMaxBoundsSandboxed(); + assertActivityMaxBoundsSandboxed(activity); // Activity max bounds ignore notch, since an app can be shown past the notch (although app // is currently limited by the notch). - assertThat(mActivity.getWindowConfiguration().getMaxBounds().height()) + assertThat(activity.getWindowConfiguration().getMaxBounds().height()) .isEqualTo(displayBounds.height()); } @@ -674,12 +674,8 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testMoveToDifferentOrientationDisplay() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } setUpDisplaySizeWithApp(1000, 2500); + mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true; prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertFitted(); @@ -726,16 +722,12 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testFixedOrientationRotateCutoutDisplay() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } // Create a display with a notch/cutout final int notchHeight = 60; final int width = 1000; setUpApp(new TestDisplayContent.Builder(mAtm, width, 2500) .setNotch(notchHeight).build()); + mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true; // Bounds=[0, 0 - 1000, 1400], AppBounds=[0, 60 - 1000, 1460]. final float maxAspect = 1.4f; prepareUnresizable(mActivity, 1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); @@ -1328,12 +1320,8 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 1200); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); // The per-package override forces the activity into a 3:2 aspect ratio @@ -1345,24 +1333,18 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testOverrideMinAspectRatioLowerThanManifest() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } - final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1400, 1800) - .setNotch(200).setSystemDecorations(true).build(); + final int dh = 1800; + final int notchHeight = 200; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1400, dh) + .setNotch(notchHeight).setSystemDecorations(true).build(); mTask = new TaskBuilder(mSupervisor).setDisplay(display).build(); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) .setMinAspectRatio(2f) - .setUid(android.os.Process.myUid()) .build(); // The per-package override should have no effect, because the manifest aspect ratio is @@ -1371,7 +1353,7 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals("App bounds must have min aspect ratio", 2f, (float) appBounds.height() / appBounds.width(), 0.0001f /* delta */); assertEquals("Long side must fit task", - mTask.getWindowConfiguration().getAppBounds().height(), appBounds.height()); + dh - notchHeight, appBounds.height()); assertEquals("Bounds can include insets", mTask.getBounds().height(), activity.getBounds().height()); } @@ -1383,13 +1365,9 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1400, 1600); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) .setMinAspectRatio(1.1f) - .setUid(android.os.Process.myUid()) .build(); // The per-package override should have no effect, because the manifest aspect ratio is @@ -1406,12 +1384,8 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1500, 1600); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); // The per-package override forces the activity into a 16:9 aspect ratio @@ -1430,12 +1404,8 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1400, 1600); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); // The per-package override forces the activity into a 16:9 aspect ratio @@ -1455,12 +1425,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 1200); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) - .build(); + final ActivityRecord activity = getActivityBuilderOnSameTask().build(); // The per-package override should have no effect assertEquals(1200, activity.getBounds().height()); @@ -1487,12 +1452,8 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 1200); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); // The per-package override should have no effect @@ -1517,12 +1478,8 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 1200); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); // The per-package override forces the activity into a 3:2 aspect ratio @@ -1547,12 +1504,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 1200); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) - .build(); + final ActivityRecord activity = getActivityBuilderOnSameTask().build(); // The per-package override forces the activity into a 3:2 aspect ratio assertEquals(1200, activity.getBounds().height()); @@ -1571,12 +1523,8 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 1200); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); // The per-package override forces the activity into a 3:2 aspect ratio @@ -1594,12 +1542,8 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 1200); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); // The per-package override should have no effect @@ -1614,12 +1558,8 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(/* dw= */ 1000, /* dh= */ 2800); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); final TestSplitOrganizer organizer = @@ -1697,15 +1637,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testLaunchWithFixedRotationTransform() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } final int dw = 1000; final int dh = 2500; final int notchHeight = 200; setUpApp(new TestDisplayContent.Builder(mAtm, dw, dh).setNotch(notchHeight).build()); + mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true; // The test assumes the notch will be at left side when the orientation is landscape. if (mContext.getResources().getBoolean( com.android.internal.R.bool.config_reverseDefaultRotation)) { @@ -2315,12 +2251,7 @@ public class SizeCompatTests extends WindowTestsBase { private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation, float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio, boolean enabled) { - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) - .build(); + final ActivityRecord activity = getActivityBuilderOnSameTask().build(); activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); spyOn(activity.mWmService.mLetterboxConfiguration); doReturn(enabled).when(activity.mWmService.mLetterboxConfiguration) @@ -2351,12 +2282,8 @@ public class SizeCompatTests extends WindowTestsBase { final int displayWidth = 1400; final int displayHeight = 1600; setUpDisplaySizeWithApp(displayWidth, displayHeight); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setMinAspectRatio(1.1f) - .setUid(android.os.Process.myUid()) .build(); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -2376,12 +2303,8 @@ public class SizeCompatTests extends WindowTestsBase { final int displayWidth = 1600; final int displayHeight = 1400; setUpDisplaySizeWithApp(displayWidth, displayHeight); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setMinAspectRatio(1.1f) - .setUid(android.os.Process.myUid()) .build(); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -2402,12 +2325,8 @@ public class SizeCompatTests extends WindowTestsBase { final int displayWidth = 1400; final int displayHeight = 1600; setUpDisplaySizeWithApp(displayWidth, displayHeight); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setMinAspectRatio(1.1f) - .setUid(android.os.Process.myUid()) .build(); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -2428,12 +2347,8 @@ public class SizeCompatTests extends WindowTestsBase { final int displayWidth = 1600; final int displayHeight = 1400; setUpDisplaySizeWithApp(displayWidth, displayHeight); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setMinAspectRatio(1.1f) - .setUid(android.os.Process.myUid()) .build(); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -2454,12 +2369,7 @@ public class SizeCompatTests extends WindowTestsBase { final int screenWidth = 1800; final int screenHeight = 1000; setUpDisplaySizeWithApp(screenWidth, screenHeight); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) - .build(); + final ActivityRecord activity = getActivityBuilderOnSameTask().build(); activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Simulate real display with top insets. @@ -2495,12 +2405,7 @@ public class SizeCompatTests extends WindowTestsBase { final int screenWidth = 1000; final int screenHeight = 1800; setUpDisplaySizeWithApp(screenWidth, screenHeight); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) - .build(); + final ActivityRecord activity = getActivityBuilderOnSameTask().build(); activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Simulate real display with top insets. @@ -2538,12 +2443,7 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) - .build(); + final ActivityRecord activity = getActivityBuilderOnSameTask().build(); // Non-resizable portrait activity prepareUnresizable(activity, 0f, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); @@ -2573,12 +2473,7 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); // Create a size compat activity on the same task. - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) - .build(); + final ActivityRecord activity = getActivityBuilderOnSameTask().build(); final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, activity.getDisplayContent()); @@ -3636,17 +3531,13 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } // Align to center so that we don't overlap with the status bar mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) .setNotch(100) .build(); setUpApp(display); + mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true; TestWindowState statusBar = addStatusBar(mActivity.mDisplayContent); spyOn(statusBar); doReturn(new Rect(0, 0, statusBar.mRequestedWidth, statusBar.mRequestedHeight)) @@ -3658,12 +3549,12 @@ public class SizeCompatTests extends WindowTestsBase { // Refresh the letterbox mActivity.mRootWindowContainer.performSurfacePlacement(); - Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); - assertEquals(mBounds, new Rect(0, 900, 1000, 2000)); + Rect bounds = new Rect(mActivity.getWindowConfiguration().getBounds()); + assertEquals(new Rect(0, 900, 1000, 2000), bounds); DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy(); LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails( - mBounds, + bounds, mActivity.getDisplayContent().getBounds(), mActivity.findMainWindow().mAttrs.insetsFlags.appearance )}; @@ -3952,12 +3843,8 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo()); display.sendNewConfiguration(); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); // Activity should not be letterboxed and should have portrait app bounds even though @@ -3989,12 +3876,8 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo()); display.sendNewConfiguration(); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) + final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) .build(); final Rect bounds = activity.getBounds(); @@ -4021,14 +3904,11 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(dc.getDisplayPolicy().updateDecorInsetsInfo()); dc.sendNewConfiguration(); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setComponent(ComponentName.createRelative(mContext, - SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) + final ActivityRecord activity = getActivityBuilderOnSameTask() + .setResizeMode(RESIZE_MODE_UNRESIZEABLE) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE) .build(); - prepareMinAspectRatio(activity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, - SCREEN_ORIENTATION_LANDSCAPE); // To force config to update again but with the same landscape orientation. activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); @@ -4042,11 +3922,6 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testApplyAspectRatio_activityAlignWithParentAppVertical() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } // The display's app bounds will be (0, 100, 1000, 2350) final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500) .setCanRotate(false) @@ -4054,19 +3929,16 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); - prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true; + prepareUnresizable(mActivity, 2.1f, SCREEN_ORIENTATION_UNSPECIFIED); // The activity height is 2100 and the display's app bounds height is 2250, so the activity // can be aligned inside parentAppBounds - assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200)); + assertEquals(new Rect(0, 0, 1000, 2200), mActivity.getBounds()); } @Test + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } // The display's app bounds will be (0, 100, 1000, 2150) final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2300) .setCanRotate(false) @@ -4074,19 +3946,21 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); - prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + + final ActivityRecord activity = getActivityBuilderOnSameTask() + .setResizeMode(RESIZE_MODE_UNRESIZEABLE) + .setMaxAspectRatio(2.1f) + .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED) + .build(); + // The activity height is 2100 and the display's app bounds height is 2050, so the activity // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display - assertEquals(mActivity.getBounds(), display.getBounds()); + assertEquals(activity.getBounds(), display.getBounds()); } @Test + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testApplyAspectRatio_activityAlignWithParentAppHorizontal() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } // The display's app bounds will be (100, 0, 2350, 1000) final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2500, 1000) .setCanRotate(false) @@ -4094,18 +3968,19 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); - prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + + final ActivityRecord activity = getActivityBuilderOnSameTask() + .setResizeMode(RESIZE_MODE_UNRESIZEABLE) + .setMaxAspectRatio(2.1f) + .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED) + .build(); // The activity width is 2100 and the display's app bounds width is 2250, so the activity // can be aligned inside parentAppBounds - assertEquals(mActivity.getBounds(), new Rect(175, 0, 2275, 1000)); + assertEquals(activity.getBounds(), new Rect(175, 0, 2275, 1000)); } @Test + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } // The display's app bounds will be (100, 0, 2150, 1000) final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2300, 1000) .setCanRotate(false) @@ -4113,10 +3988,14 @@ public class SizeCompatTests extends WindowTestsBase { .build(); setUpApp(display); - prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + final ActivityRecord activity = getActivityBuilderOnSameTask() + .setResizeMode(RESIZE_MODE_UNRESIZEABLE) + .setMaxAspectRatio(2.1f) + .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED) + .build(); // The activity width is 2100 and the display's app bounds width is 2050, so the activity // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display - assertEquals(mActivity.getBounds(), display.getBounds()); + assertEquals(activity.getBounds(), display.getBounds()); } @Test @@ -4344,26 +4223,23 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testUpdateResolvedBoundsPosition_alignToTop() { - if (Flags.insetsDecoupledConfiguration()) { - // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config - // bounds no longer contains display cutout. - return; - } final int notchHeight = 100; final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) .setNotch(notchHeight) .build(); setUpApp(display); + mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true; // Prepare unresizable activity with max aspect ratio - prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); + prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); - Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); + Rect bounds = new Rect(mActivity.getWindowConfiguration().getBounds()); Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds()); // The insets should be cut for aspect ratio and then added back because the appBounds // are aligned to the top of the parentAppBounds - assertEquals(mBounds, new Rect(0, 0, 1000, 1200)); - assertEquals(appBounds, new Rect(0, notchHeight, 1000, 1200)); + assertEquals(new Rect(0, notchHeight, 1000, 1200), appBounds); + assertEquals(new Rect(0, 0, 1000, 1200), bounds); + } private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity( @@ -4859,15 +4735,19 @@ public class SizeCompatTests extends WindowTestsBase { */ private ActivityRecord buildActivityRecord(boolean supportsSizeChanges, int resizeMode, @ScreenOrientation int screenOrientation) { - return new ActivityBuilder(mAtm) - .setTask(mTask) + return getActivityBuilderOnSameTask() .setResizeMode(resizeMode) .setSupportsSizeChanges(supportsSizeChanges) .setScreenOrientation(screenOrientation) + .build(); + } + + private ActivityBuilder getActivityBuilderOnSameTask() { + return new ActivityBuilder(mAtm) + .setTask(mTask) .setComponent(ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) - .setUid(android.os.Process.myUid()) - .build(); + .setUid(android.os.Process.myUid()); } static void prepareMinAspectRatio(ActivityRecord activity, float minAspect, @@ -4931,21 +4811,29 @@ public class SizeCompatTests extends WindowTestsBase { } } - /** Asserts that the size of activity is larger than its parent so it is scaling. */ private void assertScaled() { - assertTrue(mActivity.inSizeCompatMode()); - assertNotEquals(1f, mActivity.getCompatScale(), 0.0001f /* delta */); + assertScaled(mActivity); + } + + /** Asserts that the size of activity is larger than its parent so it is scaling. */ + private void assertScaled(ActivityRecord activity) { + assertTrue(activity.inSizeCompatMode()); + assertNotEquals(1f, activity.getCompatScale(), 0.0001f /* delta */); } - /** Asserts that the activity is best fitted in the parent. */ private void assertFitted() { - final boolean inSizeCompatMode = mActivity.inSizeCompatMode(); + assertFitted(mActivity); + } + + /** Asserts that the activity is best fitted in the parent. */ + private void assertFitted(ActivityRecord activity) { + final boolean inSizeCompatMode = activity.inSizeCompatMode(); final String failedConfigInfo = inSizeCompatMode - ? ("ParentConfig=" + mActivity.getParent().getConfiguration() - + " ActivityConfig=" + mActivity.getConfiguration()) + ? ("ParentConfig=" + activity.getParent().getConfiguration() + + " ActivityConfig=" + activity.getConfiguration()) : ""; assertFalse(failedConfigInfo, inSizeCompatMode); - assertFalse(mActivity.hasSizeCompatBounds()); + assertFalse(activity.hasSizeCompatBounds()); } /** Asserts the activity max bounds inherit from the TaskDisplayArea. */ |