diff options
Diffstat (limited to 'libs')
26 files changed, 968 insertions, 42 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index 7cb56605cc12..0799fe3b6eb2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -94,6 +94,8 @@ class CrossActivityBackAnimation @Inject constructor( private var scrimLayer: SurfaceControl? = null private var maxScrimAlpha: Float = 0f + private var isLetterboxed = false + override fun onConfigurationChanged(newConfiguration: Configuration) { cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) } @@ -112,9 +114,15 @@ class CrossActivityBackAnimation @Inject constructor( initialTouchPos.set(backMotionEvent.touchX, backMotionEvent.touchY) transaction.setAnimationTransaction() - + isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed + if (isLetterboxed) { + // Include letterbox in back animation + backAnimRect.set(closingTarget!!.windowConfiguration.bounds) + } else { + // otherwise play animation on localBounds only + backAnimRect.set(closingTarget!!.localBounds) + } // Offset start rectangle to align task bounds. - backAnimRect.set(closingTarget!!.localBounds) backAnimRect.offsetTo(0, 0) startClosingRect.set(backAnimRect) @@ -241,6 +249,7 @@ class CrossActivityBackAnimation @Inject constructor( } finishCallback = null removeScrimLayer() + isLetterboxed = false } private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) { @@ -274,10 +283,11 @@ class CrossActivityBackAnimation @Inject constructor( scrimLayer = scrimBuilder.build() val colorComponents = floatArrayOf(0f, 0f, 0f) maxScrimAlpha = if (isDarkTheme) MAX_SCRIM_ALPHA_DARK else MAX_SCRIM_ALPHA_LIGHT + val scrimCrop = if (isLetterboxed) backAnimRect else closingTarget!!.localBounds transaction .setColor(scrimLayer, colorComponents) .setAlpha(scrimLayer!!, maxScrimAlpha) - .setCrop(scrimLayer!!, closingTarget!!.localBounds) + .setCrop(scrimLayer!!, scrimCrop) .setRelativeLayer(scrimLayer!!, closingTarget!!.leash, -1) .show(scrimLayer) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index 7c280994042b..8fb4bdbea933 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -237,7 +237,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract final int letterboxWidth = taskInfo.topActivityLetterboxWidth; // App is not visibly letterboxed if it covers status bar/bottom insets or matches the // stable bounds, so don't show the button - if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth) { + if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth + && !taskInfo.isUserFullscreenOverrideEnabled) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index a454d48ac863..e829d4ef650e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -17,8 +17,10 @@ package com.android.wm.shell.pip2.phone; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; +import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; @@ -182,6 +184,10 @@ public class PipTransition extends PipTransitionController { mResizeTransition = null; return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback); } + + if (isRemovePipTransition(info)) { + return removePipImmediately(info, startTransaction, finishTransaction, finishCallback); + } return false; } @@ -291,6 +297,10 @@ public class PipTransition extends PipTransitionController { startOverlayFadeoutAnimation(); } + // + // Subroutines setting up and starting transitions' animations. + // + private void startOverlayFadeoutAnimation() { ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f); animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS); @@ -326,6 +336,7 @@ public class PipTransition extends PipTransitionController { mPipScheduler.setPipTaskToken(mPipTaskToken); startTransaction.apply(); + // TODO: b/275910498 Use a new implementation of the PiP animator here. finishCallback.onTransitionFinished(null); return true; } @@ -353,11 +364,26 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { startTransaction.apply(); + // TODO: b/275910498 Use a new implementation of the PiP animator here. finishCallback.onTransitionFinished(null); onExitPip(); return true; } + private boolean removePipImmediately(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + startTransaction.apply(); + finishCallback.onTransitionFinished(null); + onExitPip(); + return true; + } + + // + // Utility methods for checking PiP-related transition info and requests. + // + @Nullable private TransitionInfo.Change getPipChange(TransitionInfo info) { for (TransitionInfo.Change change : info.getChanges()) { @@ -415,6 +441,25 @@ public class PipTransition extends PipTransitionController { && info.getChanges().size() == 1; } + private boolean isRemovePipTransition(@NonNull TransitionInfo info) { + if (mPipTaskToken == null) { + // PiP removal makes sense if enter-PiP has cached a valid pinned task token. + return false; + } + TransitionInfo.Change pipChange = info.getChange(mPipTaskToken); + if (pipChange == null) { + // Search for the PiP change by token since the windowing mode might be FULLSCREEN now. + return false; + } + + boolean isPipMovedToBack = info.getType() == TRANSIT_TO_BACK + && pipChange.getMode() == TRANSIT_TO_BACK; + boolean isPipClosed = info.getType() == TRANSIT_CLOSE + && pipChange.getMode() == TRANSIT_CLOSE; + // PiP is being removed if the pinned task is either moved to back or closed. + return isPipMovedToBack || isPipClosed; + } + /** * TODO: b/275910498 Use a new implementation of the PiP animator here. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java index eebd13370321..77b8663861ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -16,6 +16,9 @@ package com.android.wm.shell.recents; +import android.annotation.Nullable; +import android.graphics.Color; + import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.util.GroupedRecentTaskInfo; @@ -40,4 +43,12 @@ public interface RecentTasks { */ default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) { } + + /** + * Sets a background color on the transition root layered behind the outgoing task. {@code null} + * may be used to clear any previously set colors to avoid showing a background at all. The + * color is always shown at full opacity. + */ + default void setTransitionBackgroundColor(@Nullable Color color) { + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 0c99aed6852e..e7d9812e5393 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -30,6 +30,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.os.Bundle; import android.os.RemoteException; import android.util.Slog; @@ -476,6 +477,16 @@ public class RecentTasksController implements TaskStackListenerCallback, }); }); } + + @Override + public void setTransitionBackgroundColor(@Nullable Color color) { + mMainExecutor.execute(() -> { + if (mTransitionHandler == null) { + return; + } + mTransitionHandler.setTransitionBackgroundColor(color); + }); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 24cf3706e25a..c625b69deac0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -36,6 +36,7 @@ import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.PendingIntent; import android.content.Intent; +import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -56,6 +57,8 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.protolog.common.ProtoLog; @@ -92,6 +95,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>(); private final HomeTransitionObserver mHomeTransitionObserver; + private @Nullable Color mBackgroundColor; public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, @Nullable RecentTasksController recentTasksController, @@ -123,6 +127,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mStateListeners.add(listener); } + /** + * Sets a background color on the transition root layered behind the outgoing task. {@code null} + * may be used to clear any previously set colors to avoid showing a background at all. The + * color is always shown at full opacity. + */ + public void setTransitionBackgroundColor(@Nullable Color color) { + mBackgroundColor = color; + } + @VisibleForTesting public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener) { @@ -469,6 +482,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { final int belowLayers = info.getChanges().size(); final int middleLayers = info.getChanges().size() * 2; final int aboveLayers = info.getChanges().size() * 3; + + // Add a background color to each transition root in this transition. + if (mBackgroundColor != null) { + info.getChanges().stream() + .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info)) + .distinct() + .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash()) + .forEach((root) -> createBackgroundSurface(t, root, middleLayers)); + } + for (int i = 0; i < info.getChanges().size(); ++i) { final TransitionInfo.Change change = info.getChanges().get(i); final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); @@ -1107,6 +1130,29 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { return true; } + private void createBackgroundSurface(SurfaceControl.Transaction transaction, + SurfaceControl parent, int layer) { + if (mBackgroundColor == null) { + return; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " adding background color to layer=%d", layer); + final SurfaceControl background = new SurfaceControl.Builder() + .setName("recents_background") + .setColorLayer() + .setOpaque(true) + .setParent(parent) + .build(); + transaction.setColor(background, colorToFloatArray(mBackgroundColor)); + transaction.setLayer(background, layer); + transaction.setAlpha(background, 1F); + transaction.show(background); + } + + private static float[] colorToFloatArray(@NonNull Color color) { + return new float[]{color.red(), color.green(), color.blue()}; + } + private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) { if (!sendUserLeaveHint && task.isLeaf()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 2bbe530fbaf6..da1699cd6e33 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -467,8 +467,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * until a resize event calls showResizeVeil below. */ void createResizeVeil() { - mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo, mTaskSurface, - mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier); + mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconDrawable, mTaskInfo, + mTaskSurface, mSurfaceControlTransactionSupplier); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java index d072f8cec194..2c4092ac6d2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.ColorRes; +import android.annotation.NonNull; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.res.Configuration; @@ -40,7 +41,7 @@ import android.widget.ImageView; import android.window.TaskConstants; import com.android.wm.shell.R; -import com.android.wm.shell.common.SurfaceUtils; +import com.android.wm.shell.common.DisplayController; import java.util.function.Supplier; @@ -48,6 +49,7 @@ import java.util.function.Supplier; * Creates and updates a veil that covers task contents on resize. */ public class ResizeVeil { + private static final String TAG = "ResizeVeil"; private static final int RESIZE_ALPHA_DURATION = 100; private static final int VEIL_CONTAINER_LAYER = TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL; @@ -57,8 +59,10 @@ public class ResizeVeil { private static final int VEIL_ICON_LAYER = 1; private final Context mContext; - private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier; + private final DisplayController mDisplayController; private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; + private final SurfaceControlBuilderFactory mSurfaceControlBuilderFactory; + private final WindowDecoration.SurfaceControlViewHostFactory mSurfaceControlViewHostFactory; private final SurfaceSession mSurfaceSession = new SurfaceSession(); private final Drawable mAppIcon; private ImageView mIconView; @@ -74,41 +78,82 @@ public class ResizeVeil { private final RunningTaskInfo mTaskInfo; private SurfaceControlViewHost mViewHost; - private final Display mDisplay; + private Display mDisplay; private ValueAnimator mVeilAnimator; - public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo, + private boolean mIsShowing = false; + + private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = + new DisplayController.OnDisplaysChangedListener() { + @Override + public void onDisplayAdded(int displayId) { + if (mTaskInfo.displayId != displayId) { + return; + } + mDisplayController.removeDisplayWindowListener(this); + setupResizeVeil(); + } + }; + + public ResizeVeil(Context context, + @NonNull DisplayController displayController, + Drawable appIcon, RunningTaskInfo taskInfo, SurfaceControl taskSurface, - Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) { + this(context, + displayController, + appIcon, + taskInfo, + taskSurface, + surfaceControlTransactionSupplier, + new SurfaceControlBuilderFactory() {}, + new WindowDecoration.SurfaceControlViewHostFactory() {}); + } + + public ResizeVeil(Context context, + @NonNull DisplayController displayController, + Drawable appIcon, RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, + SurfaceControlBuilderFactory surfaceControlBuilderFactory, + WindowDecoration.SurfaceControlViewHostFactory surfaceControlViewHostFactory) { mContext = context; + mDisplayController = displayController; mAppIcon = appIcon; - mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier; mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; mTaskInfo = taskInfo; mParentSurface = taskSurface; - mDisplay = display; + mSurfaceControlBuilderFactory = surfaceControlBuilderFactory; + mSurfaceControlViewHostFactory = surfaceControlViewHostFactory; setupResizeVeil(); } - /** * Create the veil in its default invisible state. */ private void setupResizeVeil() { - mVeilSurface = mSurfaceControlBuilderSupplier.get() + if (!obtainDisplayOrRegisterListener()) { + // Display may not be available yet, skip this until then. + return; + } + mVeilSurface = mSurfaceControlBuilderFactory + .create("Resize veil of Task=" + mTaskInfo.taskId) .setContainerLayer() - .setName("Resize veil of Task=" + mTaskInfo.taskId) .setHidden(true) .setParent(mParentSurface) .setCallsite("ResizeVeil#setupResizeVeil") .build(); - mBackgroundSurface = SurfaceUtils.makeColorLayer(mVeilSurface, - "Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession); - mIconSurface = mSurfaceControlBuilderSupplier.get() - .setName("Resize veil icon of Task= " + mTaskInfo.taskId) - .setContainerLayer() + mBackgroundSurface = mSurfaceControlBuilderFactory + .create("Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession) + .setColorLayer() + .setHidden(true) .setParent(mVeilSurface) + .setCallsite("ResizeVeil#setupResizeVeil") + .build(); + mIconSurface = mSurfaceControlBuilderFactory + .create("Resize veil icon of Task=" + mTaskInfo.taskId) + .setContainerLayer() .setHidden(true) + .setParent(mVeilSurface) .setCallsite("ResizeVeil#setupResizeVeil") .build(); @@ -131,10 +176,20 @@ public class ResizeVeil { final WindowlessWindowManager wwm = new WindowlessWindowManager(mTaskInfo.configuration, mIconSurface, null /* hostInputToken */); - mViewHost = new SurfaceControlViewHost(mContext, mDisplay, wwm, "ResizeVeil"); + + mViewHost = mSurfaceControlViewHostFactory.create(mContext, mDisplay, wwm, "ResizeVeil"); mViewHost.setView(root, lp); } + private boolean obtainDisplayOrRegisterListener() { + mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); + if (mDisplay == null) { + mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener); + return false; + } + return true; + } + /** * Shows the veil surface/view. * @@ -146,6 +201,12 @@ public class ResizeVeil { */ public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface, Rect taskBounds, boolean fadeIn) { + if (!isReady() || isVisible()) { + t.apply(); + return; + } + mIsShowing = true; + // Parent surface can change, ensure it is up to date. if (!parentSurface.equals(mParentSurface)) { t.reparent(mVeilSurface, parentSurface); @@ -226,6 +287,9 @@ public class ResizeVeil { * Animate veil's alpha to 1, fading it in. */ public void showVeil(SurfaceControl parentSurface, Rect taskBounds) { + if (!isReady() || isVisible()) { + return; + } SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); showVeil(t, parentSurface, taskBounds, true /* fadeIn */); } @@ -247,6 +311,9 @@ public class ResizeVeil { * @param newBounds bounds to update veil to. */ public void updateResizeVeil(Rect newBounds) { + if (!isVisible()) { + return; + } SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); updateResizeVeil(t, newBounds); } @@ -260,6 +327,10 @@ public class ResizeVeil { * @param newBounds bounds to update veil to. */ public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) { + if (!isVisible()) { + t.apply(); + return; + } if (mVeilAnimator != null && mVeilAnimator.isStarted()) { mVeilAnimator.removeAllUpdateListeners(); mVeilAnimator.end(); @@ -272,6 +343,9 @@ public class ResizeVeil { * Animate veil's alpha to 0, fading it out. */ public void hideVeil() { + if (!isVisible()) { + return; + } cancelAnimation(); mVeilAnimator = new ValueAnimator(); mVeilAnimator.setFloatValues(1, 0); @@ -292,6 +366,7 @@ public class ResizeVeil { } }); mVeilAnimator.start(); + mIsShowing = false; } @ColorRes @@ -318,10 +393,26 @@ public class ResizeVeil { } /** + * Whether the resize veil is currently visible. + * + * Note: when animating a {@link ResizeVeil#hideVeil()}, the veil is considered visible as soon + * as the animation starts. + */ + private boolean isVisible() { + return mIsShowing; + } + + /** Whether the resize veil is ready to be shown. */ + private boolean isReady() { + return mViewHost != null; + } + + /** * Dispose of veil when it is no longer needed, likely on close of its container decor. */ void dispose() { cancelAnimation(); + mIsShowing = false; mVeilAnimator = null; if (mViewHost != null) { @@ -342,5 +433,16 @@ public class ResizeVeil { mVeilSurface = null; } t.apply(); + mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener); + } + + interface SurfaceControlBuilderFactory { + default SurfaceControl.Builder create(@NonNull String name) { + return new SurfaceControl.Builder().setName(name); + } + default SurfaceControl.Builder create(@NonNull String name, + @NonNull SurfaceSession surfaceSession) { + return new SurfaceControl.Builder(surfaceSession).setName(name); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 51b0a246f3e9..36da1ace8408 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -668,6 +668,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) { return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration"); } + default SurfaceControlViewHost create(Context c, Display d, + WindowlessWindowManager wmm, String callsite) { + return new SurfaceControlViewHost(c, d, wmm, callsite); + } } /** diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS new file mode 100644 index 000000000000..73a5a23909c5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/OWNERS @@ -0,0 +1,5 @@ +# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Freeform +# Bug component: 929241 + +uysalorhan@google.com +pragyabajoria@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt new file mode 100644 index 000000000000..4c781d36acf6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragLandscape.kt @@ -0,0 +1,74 @@ +/* + * 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.wm.shell.flicker.service.desktopmode.flicker + +import android.tools.Rotation +import android.tools.flicker.AssertionInvocationGroup +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways +import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd +import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd +import android.tools.flicker.config.AssertionTemplates +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerConfigEntry +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.config.ScenarioId +import android.tools.flicker.config.desktopmode.Components +import android.tools.flicker.extractors.ITransitionMatcher +import android.tools.flicker.extractors.ShellTransitionScenarioExtractor +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import android.tools.traces.wm.Transition +import android.tools.traces.wm.TransitionType +import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterDesktopWithDragLandscape : EnterDesktopWithDrag(Rotation.ROTATION_90) { + @ExpectedScenarios(["END_DRAG_TO_DESKTOP"]) @Test override fun enterDesktopWithDrag() = + super.enterDesktopWithDrag() + + companion object { + private val END_DRAG_TO_DESKTOP = FlickerConfigEntry( + scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"), + extractor = ShellTransitionScenarioExtractor( + transitionMatcher = object : ITransitionMatcher { + override fun findAll( + transitions: Collection<Transition> + ): Collection<Transition> { + return transitions.filter { + it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP} + } + }), + assertions = AssertionTemplates.COMMON_ASSERTIONS + + listOf( + AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP), + AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP), + AppWindowHasDesktopModeInitialBoundsAtTheEnd(Components.DESKTOP_MODE_APP) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + ) + + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig() + .use(FlickerServiceConfig.DEFAULT) + .use(END_DRAG_TO_DESKTOP) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt new file mode 100644 index 000000000000..d99d875fb126 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/EnterDesktopWithDragPortrait.kt @@ -0,0 +1,74 @@ +/* + * 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.wm.shell.flicker.service.desktopmode.flicker + +import android.tools.Rotation +import android.tools.flicker.AssertionInvocationGroup +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways +import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd +import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd +import android.tools.flicker.config.AssertionTemplates +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerConfigEntry +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.config.ScenarioId +import android.tools.flicker.config.desktopmode.Components +import android.tools.flicker.extractors.ITransitionMatcher +import android.tools.flicker.extractors.ShellTransitionScenarioExtractor +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import android.tools.traces.wm.Transition +import android.tools.traces.wm.TransitionType +import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterDesktopWithDragPortrait : EnterDesktopWithDrag(Rotation.ROTATION_0) { + @ExpectedScenarios(["END_DRAG_TO_DESKTOP"]) @Test override fun enterDesktopWithDrag() = + super.enterDesktopWithDrag() + + companion object { + private val END_DRAG_TO_DESKTOP = FlickerConfigEntry( + scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"), + extractor = ShellTransitionScenarioExtractor( + transitionMatcher = object : ITransitionMatcher { + override fun findAll( + transitions: Collection<Transition> + ): Collection<Transition> { + return transitions.filter { + it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP} + } + }), + assertions = AssertionTemplates.COMMON_ASSERTIONS + + listOf( + AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP), + AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP), + AppWindowHasDesktopModeInitialBoundsAtTheEnd(Components.DESKTOP_MODE_APP) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + ) + + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig() + .use(FlickerServiceConfig.DEFAULT) + .use(END_DRAG_TO_DESKTOP) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt new file mode 100644 index 000000000000..0403b4f64faf --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt @@ -0,0 +1,63 @@ +/* + * 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.wm.shell.flicker.service.desktopmode.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.wm.shell.flicker.service.common.Utils +import com.android.wm.shell.flicker.utils.DesktopModeUtils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterDesktopWithDrag +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = SimpleAppHelper(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + } + + @Test + open fun enterDesktopWithDrag() { + DesktopModeUtils.enterDesktopWithDrag(wmHelper, device, testApp) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt new file mode 100644 index 000000000000..345bc5ebb20e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/DesktopModeUtils.kt @@ -0,0 +1,112 @@ +/* + * 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.wm.shell.flicker.utils + +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.helpers.SYSTEMUI_PACKAGE +import android.tools.traces.component.IComponentMatcher +import android.tools.traces.parsers.WindowManagerStateHelper +import android.tools.traces.wm.WindowingMode +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until + +/** + * Provides a collection of utility functions for desktop mode testing. + */ +object DesktopModeUtils { + private const val TIMEOUT_MS = 3_000L + private const val CAPTION = "desktop_mode_caption" + private const val CAPTION_HANDLE = "caption_handle" + private const val MAXIMIZE_BUTTON = "maximize_button_view" + + private val captionFullscreen: BySelector + get() = By.res(SYSTEMUI_PACKAGE, CAPTION) + private val captionHandle: BySelector + get() = By.res(SYSTEMUI_PACKAGE, CAPTION_HANDLE) + private val maximizeButton: BySelector + get() = By.res(SYSTEMUI_PACKAGE, MAXIMIZE_BUTTON) + + /** + * Wait for an app moved to desktop to finish its transition. + */ + private fun waitForAppToMoveToDesktop( + wmHelper: WindowManagerStateHelper, + currentApp: IComponentMatcher, + ) { + wmHelper + .StateSyncBuilder() + .withWindowSurfaceAppeared(currentApp) + .withFreeformApp(currentApp) + .withAppTransitionIdle() + .waitForAndVerify() + } + + /** + * Click maximise button on the app header for the given app. + */ + fun maximiseDesktopApp( + wmHelper: WindowManagerStateHelper, + device: UiDevice, + currentApp: StandardAppHelper + ) { + if (wmHelper.getWindow(currentApp)?.windowingMode + != WindowingMode.WINDOWING_MODE_FREEFORM.value) + error("expected a freeform window to maximise but window is not in freefrom mode") + + val maximizeButton = + device.wait(Until.findObject(maximizeButton), TIMEOUT_MS) + ?: error("Unable to find view $maximizeButton\n") + maximizeButton.click() + } + + /** + * Move an app to Desktop by dragging the app handle at the top. + */ + fun enterDesktopWithDrag( + wmHelper: WindowManagerStateHelper, + device: UiDevice, + currentApp: StandardAppHelper, + ) { + currentApp.launchViaIntent(wmHelper) + dragToDesktop(wmHelper, currentApp, device) + waitForAppToMoveToDesktop(wmHelper, currentApp) + } + + private fun dragToDesktop( + wmHelper: WindowManagerStateHelper, + currentApp: StandardAppHelper, + device: UiDevice + ) { + val windowRect = wmHelper.getWindowRegion(currentApp).bounds + val startX = windowRect.centerX() + + // Start dragging a little under the top to prevent dragging the notification shade. + val startY = 10 + + val displayRect = + wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect + ?: throw IllegalStateException("Default display is null") + + // The position we want to drag to + val endY = displayRect.centerY() / 2 + + // drag the window to move to desktop + device.drag(startX, startY, startX, endY, 100) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java index 81ba4b37d13b..94e168ed70ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -292,6 +292,24 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + public void testUserFullscreenOverrideEnabled_buttonAlwaysShown() { + TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); + + final Rect stableBounds = mWindowManager.getTaskStableBounds(); + + // Letterboxed activity that has user fullscreen override should always show button, + // layout should be inflated + taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableBounds.height(); + taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = stableBounds.width(); + taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled = true; + + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + + verify(mWindowManager).inflateLayout(); + } + + @Test public void testUpdateDisplayLayout() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 1000; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt new file mode 100644 index 000000000000..847c2dd77d0a --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt @@ -0,0 +1,216 @@ +/* + * 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.wm.shell.windowdecor + +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.Display +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.WindowlessWindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener +import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Spy +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.whenever + + +/** + * Tests for [ResizeVeil]. + * + * Build/Install/Run: + * atest WMShellUnitTests:ResizeVeilTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class ResizeVeilTest : ShellTestCase() { + + @Mock + private lateinit var mockDisplayController: DisplayController + @Mock + private lateinit var mockAppIcon: Drawable + @Mock + private lateinit var mockDisplay: Display + @Mock + private lateinit var mockSurfaceControlViewHost: SurfaceControlViewHost + @Mock + private lateinit var mockSurfaceControlBuilderFactory: ResizeVeil.SurfaceControlBuilderFactory + @Mock + private lateinit var mockSurfaceControlViewHostFactory: SurfaceControlViewHostFactory + @Spy + private val spyResizeVeilSurfaceBuilder = SurfaceControl.Builder() + @Mock + private lateinit var mockResizeVeilSurface: SurfaceControl + @Spy + private val spyBackgroundSurfaceBuilder = SurfaceControl.Builder() + @Mock + private lateinit var mockBackgroundSurface: SurfaceControl + @Spy + private val spyIconSurfaceBuilder = SurfaceControl.Builder() + @Mock + private lateinit var mockIconSurface: SurfaceControl + @Mock + private lateinit var mockTransaction: SurfaceControl.Transaction + + private val taskInfo = TestRunningTaskInfoBuilder().build() + + @Before + fun setUp() { + whenever(mockSurfaceControlViewHostFactory.create(any(), any(), any(), any())) + .thenReturn(mockSurfaceControlViewHost) + whenever(mockSurfaceControlBuilderFactory + .create("Resize veil of Task=" + taskInfo.taskId)) + .thenReturn(spyResizeVeilSurfaceBuilder) + doReturn(mockResizeVeilSurface).whenever(spyResizeVeilSurfaceBuilder).build() + whenever(mockSurfaceControlBuilderFactory + .create(eq("Resize veil background of Task=" + taskInfo.taskId), any())) + .thenReturn(spyBackgroundSurfaceBuilder) + doReturn(mockBackgroundSurface).whenever(spyBackgroundSurfaceBuilder).build() + whenever(mockSurfaceControlBuilderFactory + .create("Resize veil icon of Task=" + taskInfo.taskId)) + .thenReturn(spyIconSurfaceBuilder) + doReturn(mockIconSurface).whenever(spyIconSurfaceBuilder).build() + } + + @Test + fun init_displayAvailable_viewHostCreated() { + createResizeVeil(withDisplayAvailable = true) + + verify(mockSurfaceControlViewHostFactory) + .create(any(), eq(mockDisplay), any(), eq("ResizeVeil")) + } + + @Test + fun init_displayUnavailable_viewHostNotCreatedUntilDisplayAppears() { + createResizeVeil(withDisplayAvailable = false) + + verify(mockSurfaceControlViewHostFactory, never()) + .create(any(), eq(mockDisplay), any<WindowlessWindowManager>(), eq("ResizeVeil")) + val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java) + verify(mockDisplayController).addDisplayWindowListener(captor.capture()) + + whenever(mockDisplayController.getDisplay(taskInfo.displayId)).thenReturn(mockDisplay) + captor.value.onDisplayAdded(taskInfo.displayId) + + verify(mockSurfaceControlViewHostFactory) + .create(any(), eq(mockDisplay), any(), eq("ResizeVeil")) + verify(mockDisplayController).removeDisplayWindowListener(any()) + } + + @Test + fun dispose_removesDisplayWindowListener() { + createResizeVeil().dispose() + + verify(mockDisplayController).removeDisplayWindowListener(any()) + } + + @Test + fun showVeil() { + val veil = createResizeVeil() + val tx = mock<SurfaceControl.Transaction>() + + veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) + + verify(tx).show(mockResizeVeilSurface) + verify(tx).show(mockBackgroundSurface) + verify(tx).show(mockIconSurface) + verify(tx).apply() + } + + @Test + fun showVeil_displayUnavailable_doesNotShow() { + val veil = createResizeVeil(withDisplayAvailable = false) + val tx = mock<SurfaceControl.Transaction>() + + veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) + + verify(tx, never()).show(mockResizeVeilSurface) + verify(tx, never()).show(mockBackgroundSurface) + verify(tx, never()).show(mockIconSurface) + verify(tx).apply() + } + + @Test + fun showVeil_alreadyVisible_doesNotShowAgain() { + val veil = createResizeVeil() + val tx = mock<SurfaceControl.Transaction>() + + veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) + veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) + + verify(tx, times(1)).show(mockResizeVeilSurface) + verify(tx, times(1)).show(mockBackgroundSurface) + verify(tx, times(1)).show(mockIconSurface) + verify(tx, times(2)).apply() + } + + @Test + fun showVeil_reparentsVeilToNewParent() { + val veil = createResizeVeil(parent = mock()) + val tx = mock<SurfaceControl.Transaction>() + + val newParent = mock<SurfaceControl>() + veil.showVeil(tx, newParent, Rect(0, 0, 100, 100), false /* fadeIn */) + + verify(tx).reparent(mockResizeVeilSurface, newParent) + } + + @Test + fun hideVeil_alreadyHidden_doesNothing() { + val veil = createResizeVeil() + + veil.hideVeil() + + verifyZeroInteractions(mockTransaction) + } + + private fun createResizeVeil( + withDisplayAvailable: Boolean = true, + parent: SurfaceControl = mock() + ): ResizeVeil { + whenever(mockDisplayController.getDisplay(taskInfo.displayId)) + .thenReturn(if (withDisplayAvailable) mockDisplay else null) + return ResizeVeil( + context, + mockDisplayController, + mockAppIcon, + taskInfo, + parent, + { mockTransaction }, + mockSurfaceControlBuilderFactory, + mockSurfaceControlViewHostFactory + ) + } +} diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h index 2573931c8543..36d8fba0d61a 100644 --- a/libs/hostgraphics/gui/Surface.h +++ b/libs/hostgraphics/gui/Surface.h @@ -52,6 +52,8 @@ public: virtual void destroy() {} + int getBuffersDataSpace() { return 0; } + protected: virtual ~Surface() {} diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 7439fbc1149c..753a69960b4c 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -1,4 +1,5 @@ package { + default_team: "trendy_team_android_core_graphics_stack", default_applicable_licenses: ["frameworks_base_libs_hwui_license"], } @@ -93,6 +94,7 @@ cc_defaults { host: { include_dirs: [ "external/vulkan-headers/include", + "frameworks/av/media/ndk/include", ], cflags: [ "-Wno-unused-variable", @@ -142,7 +144,6 @@ cc_defaults { "libsync", "libui", "aconfig_text_flags_c_lib", - "server_configurable_flags", ], static_libs: [ "libEGL_blobCache", @@ -267,6 +268,7 @@ cc_defaults { cppflags: ["-Wno-conversion-null"], srcs: [ + "apex/android_canvas.cpp", "apex/android_matrix.cpp", "apex/android_paint.cpp", "apex/android_region.cpp", @@ -279,7 +281,6 @@ cc_defaults { android: { srcs: [ // sources that depend on android only libraries "apex/android_bitmap.cpp", - "apex/android_canvas.cpp", "apex/jni_runtime.cpp", ], }, @@ -338,6 +339,8 @@ cc_defaults { "jni/android_graphics_ColorSpace.cpp", "jni/android_graphics_drawable_AnimatedVectorDrawable.cpp", "jni/android_graphics_drawable_VectorDrawable.cpp", + "jni/android_graphics_HardwareRenderer.cpp", + "jni/android_graphics_HardwareBufferRenderer.cpp", "jni/android_graphics_HardwareRendererObserver.cpp", "jni/android_graphics_Matrix.cpp", "jni/android_graphics_Picture.cpp", @@ -422,8 +425,6 @@ cc_defaults { android: { srcs: [ // sources that depend on android only libraries "jni/android_graphics_TextureLayer.cpp", - "jni/android_graphics_HardwareRenderer.cpp", - "jni/android_graphics_HardwareBufferRenderer.cpp", "jni/GIFMovie.cpp", "jni/GraphicsStatsService.cpp", "jni/Movie.cpp", @@ -448,6 +449,12 @@ cc_defaults { "libstatssocket_lazy", ], }, + linux: { + srcs: ["platform/linux/utils/SharedLib.cpp"], + }, + darwin: { + srcs: ["platform/darwin/utils/SharedLib.cpp"], + }, host: { cflags: [ "-Wno-unused-const-variable", @@ -543,6 +550,7 @@ cc_defaults { "renderthread/CanvasContext.cpp", "renderthread/DrawFrameTask.cpp", "renderthread/Frame.cpp", + "renderthread/RenderEffectCapabilityQuery.cpp", "renderthread/RenderProxy.cpp", "renderthread/RenderTask.cpp", "renderthread/TimeLord.cpp", @@ -576,6 +584,7 @@ cc_defaults { "HWUIProperties.sysprop", "Interpolator.cpp", "JankTracker.cpp", + "Layer.cpp", "LayerUpdateQueue.cpp", "LightingInfo.cpp", "Matrix.cpp", @@ -624,7 +633,6 @@ cc_defaults { "renderthread/CacheManager.cpp", "renderthread/EglManager.cpp", "renderthread/ReliableSurface.cpp", - "renderthread/RenderEffectCapabilityQuery.cpp", "renderthread/VulkanManager.cpp", "renderthread/VulkanSurface.cpp", "renderthread/RenderThread.cpp", @@ -635,7 +643,6 @@ cc_defaults { "AutoBackendTextureRelease.cpp", "DeferredLayerUpdater.cpp", "HardwareBitmapUploader.cpp", - "Layer.cpp", "ProfileDataContainer.cpp", "Readback.cpp", "WebViewFunctorManager.cpp", diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp index c67b135855f7..5a45ad9085e7 100644 --- a/libs/hwui/SkiaInterpolator.cpp +++ b/libs/hwui/SkiaInterpolator.cpp @@ -20,6 +20,7 @@ #include "include/core/SkTypes.h" #include <cstdlib> +#include <cstring> #include <log/log.h> typedef int Dot14; diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp index 3ebf7d19202d..0a30c6c14c4c 100644 --- a/libs/hwui/effects/GainmapRenderer.cpp +++ b/libs/hwui/effects/GainmapRenderer.cpp @@ -32,6 +32,8 @@ #include "src/core/SkColorFilterPriv.h" #include "src/core/SkImageInfoPriv.h" #include "src/core/SkRuntimeEffectPriv.h" + +#include <cmath> #endif namespace android::uirenderer { @@ -206,12 +208,12 @@ private: void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) { - const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR), - sk_float_log(gainmapInfo.fGainmapRatioMin.fG), - sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f}); - const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR), - sk_float_log(gainmapInfo.fGainmapRatioMax.fG), - sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f}); + const SkColor4f logRatioMin({std::log(gainmapInfo.fGainmapRatioMin.fR), + std::log(gainmapInfo.fGainmapRatioMin.fG), + std::log(gainmapInfo.fGainmapRatioMin.fB), 1.f}); + const SkColor4f logRatioMax({std::log(gainmapInfo.fGainmapRatioMax.fR), + std::log(gainmapInfo.fGainmapRatioMax.fG), + std::log(gainmapInfo.fGainmapRatioMax.fB), 1.f}); const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f && gainmapInfo.fGainmapGamma.fG == 1.f && gainmapInfo.fGainmapGamma.fB == 1.f; @@ -248,10 +250,10 @@ private: float W = 0.f; if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) { if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) { - W = (sk_float_log(targetHdrSdrRatio) - - sk_float_log(mGainmapInfo.fDisplayRatioSdr)) / - (sk_float_log(mGainmapInfo.fDisplayRatioHdr) - - sk_float_log(mGainmapInfo.fDisplayRatioSdr)); + W = (std::log(targetHdrSdrRatio) - + std::log(mGainmapInfo.fDisplayRatioSdr)) / + (std::log(mGainmapInfo.fDisplayRatioHdr) - + std::log(mGainmapInfo.fDisplayRatioSdr)); } else { W = 1.f; } diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp index 7e3f771b6b3d..d3b48d36b677 100644 --- a/libs/hwui/jni/HardwareBufferHelpers.cpp +++ b/libs/hwui/jni/HardwareBufferHelpers.cpp @@ -16,7 +16,9 @@ #include "HardwareBufferHelpers.h" +#ifdef __ANDROID__ #include <dlfcn.h> +#endif #include <log/log.h> #ifdef __ANDROID__ diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index d9e2c8c25327..df9f83036709 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -25,13 +25,16 @@ #include <SkColorSpace.h> #include <SkData.h> #include <SkImage.h> +#ifdef __ANDROID__ #include <SkImageAndroid.h> +#else +#include <SkImagePriv.h> +#endif #include <SkPicture.h> #include <SkPixmap.h> #include <SkSerialProcs.h> #include <SkStream.h> #include <SkTypeface.h> -#include <dlfcn.h> #include <gui/TraceUtils.h> #include <include/encode/SkPngEncoder.h> #include <inttypes.h> @@ -39,8 +42,10 @@ #include <media/NdkImage.h> #include <media/NdkImageReader.h> #include <nativehelper/JNIPlatformHelp.h> +#ifdef __ANDROID__ #include <pipeline/skia/ShaderCache.h> #include <private/EGL/cache.h> +#endif #include <renderthread/CanvasContext.h> #include <renderthread/RenderProxy.h> #include <renderthread/RenderTask.h> @@ -59,6 +64,7 @@ #include "JvmErrorReporter.h" #include "android_graphics_HardwareRendererObserver.h" #include "utils/ForceDark.h" +#include "utils/SharedLib.h" namespace android { @@ -498,7 +504,11 @@ public: return sk_ref_sp(img); } bm.setImmutable(); +#ifdef __ANDROID__ return SkImages::PinnableRasterFromBitmap(bm); +#else + return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode); +#endif } return sk_ref_sp(img); } @@ -713,6 +723,7 @@ public: static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env, jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) { +#ifdef __ANDROID__ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); if (jwidth <= 0 || jheight <= 0) { ALOGW("Invalid width %d or height %d", jwidth, jheight); @@ -796,6 +807,9 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode( sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs); return bitmap::createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Premultiplied); +#else + return nullptr; +#endif } static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) { @@ -909,6 +923,7 @@ static void android_view_ThreadedRenderer_removeObserver(JNIEnv* env, jclass cla static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz, jstring diskCachePath, jstring skiaDiskCachePath) { +#ifdef __ANDROID__ const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); android::egl_set_cache_filename(cacheArray); env->ReleaseStringUTFChars(diskCachePath, cacheArray); @@ -916,6 +931,7 @@ static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, job const char* skiaCacheArray = env->GetStringUTFChars(skiaDiskCachePath, NULL); uirenderer::skiapipeline::ShaderCache::get().setFilename(skiaCacheArray); env->ReleaseStringUTFChars(skiaDiskCachePath, skiaCacheArray); +#endif } static jboolean android_view_ThreadedRenderer_isWebViewOverlaysEnabled(JNIEnv* env, jobject clazz) { @@ -1092,8 +1108,12 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) { gCopyRequest.getDestinationBitmap = GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J"); - void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); - fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface"); +#ifdef __ANDROID__ + void* handle_ = SharedLib::openSharedLib("libandroid"); +#else + void* handle_ = SharedLib::openSharedLib("libandroid_runtime"); +#endif + fromSurface = (ANW_fromSurface)SharedLib::getSymbol(handle_, "ANativeWindow_fromSurface"); LOG_ALWAYS_FATAL_IF(fromSurface == nullptr, "Failed to find required symbol ANativeWindow_fromSurface!"); diff --git a/libs/hwui/platform/darwin/utils/SharedLib.cpp b/libs/hwui/platform/darwin/utils/SharedLib.cpp new file mode 100644 index 000000000000..6e9f0b486513 --- /dev/null +++ b/libs/hwui/platform/darwin/utils/SharedLib.cpp @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#include "utils/SharedLib.h" + +#include <dlfcn.h> + +namespace android { +namespace uirenderer { + +void* SharedLib::openSharedLib(std::string filename) { + return dlopen((filename + ".dylib").c_str(), RTLD_NOW | RTLD_NODELETE); +} + +void* SharedLib::getSymbol(void* library, const char* symbol) { + return dlsym(library, symbol); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/platform/linux/utils/SharedLib.cpp b/libs/hwui/platform/linux/utils/SharedLib.cpp new file mode 100644 index 000000000000..a9acf37dfef4 --- /dev/null +++ b/libs/hwui/platform/linux/utils/SharedLib.cpp @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#include "utils/SharedLib.h" + +#include <dlfcn.h> + +namespace android { +namespace uirenderer { + +void* SharedLib::openSharedLib(std::string filename) { + return dlopen((filename + ".so").c_str(), RTLD_NOW | RTLD_NODELETE); +} + +void* SharedLib::getSymbol(void* library, const char* symbol) { + return dlsym(library, symbol); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index f6c57927cc85..6a560b365247 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -403,7 +403,7 @@ skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) { } static skcms_TransferFunction trfn_apply_gain(const skcms_TransferFunction trfn, float gain) { - float pow_gain_ginv = sk_float_pow(gain, 1 / trfn.g); + float pow_gain_ginv = std::pow(gain, 1 / trfn.g); skcms_TransferFunction result; result.g = trfn.g; result.a = trfn.a * pow_gain_ginv; diff --git a/libs/hwui/utils/SharedLib.h b/libs/hwui/utils/SharedLib.h new file mode 100644 index 000000000000..f4dcf0f664a2 --- /dev/null +++ b/libs/hwui/utils/SharedLib.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#ifndef SHAREDLIB_H +#define SHAREDLIB_H + +#include <string> + +namespace android { +namespace uirenderer { + +class SharedLib { +public: + static void* openSharedLib(std::string filename); + static void* getSymbol(void* library, const char* symbol); +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif // SHAREDLIB_H |