diff options
Diffstat (limited to 'libs')
108 files changed, 3677 insertions, 557 deletions
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index e73d8802f0b2..8487e3792993 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles import android.content.Context import android.content.Intent import android.content.pm.ShortcutInfo +import android.content.res.Resources import android.graphics.Insets import android.graphics.PointF import android.graphics.Rect @@ -43,6 +44,9 @@ class BubblePositionerTest { private lateinit var positioner: BubblePositioner private val context = ApplicationProvider.getApplicationContext<Context>() + private val resources: Resources + get() = context.resources + private val defaultDeviceConfig = DeviceConfig( windowBounds = Rect(0, 0, 1000, 2000), @@ -205,6 +209,58 @@ class BubblePositionerTest { } @Test + fun testBubbleBarExpandedViewHeightAndWidth() { + val deviceConfig = + defaultDeviceConfig.copy( + // portrait orientation + isLandscape = false, + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + val bubbleBarBounds = Rect(1700, 2500, 1780, 2600) + + positioner.setShowingInBubbleBar(true) + positioner.update(deviceConfig) + positioner.bubbleBarBounds = bubbleBarBounds + + val spaceBetweenTopInsetAndBubbleBarInLandscape = 1680 + val expandedViewVerticalSpacing = + resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) + val expectedHeight = + spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewVerticalSpacing + val expectedWidth = resources.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width) + + assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth) + assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight) + } + + @Test + fun testBubbleBarExpandedViewHeightAndWidth_screenWidthTooSmall() { + val screenWidth = 300 + val deviceConfig = + defaultDeviceConfig.copy( + // portrait orientation + isLandscape = false, + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, screenWidth, 2600) + ) + val bubbleBarBounds = Rect(100, 2500, 280, 2550) + positioner.setShowingInBubbleBar(true) + positioner.update(deviceConfig) + positioner.bubbleBarBounds = bubbleBarBounds + + val spaceBetweenTopInsetAndBubbleBarInLandscape = 180 + val expandedViewSpacing = + resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) + val expectedHeight = spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewSpacing + val expectedWidth = screenWidth - 15 /* horizontal insets */ - 2 * expandedViewSpacing + assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth) + assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight) + } + + @Test fun testGetExpandedViewHeight_max() { val deviceConfig = defaultDeviceConfig.copy( diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml index ef7478c04dda..c0ff1922edc8 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml @@ -22,14 +22,15 @@ android:layout_height="wrap_content" android:gravity="center_horizontal"> - <ImageButton + <com.android.wm.shell.windowdecor.HandleImageButton android:id="@+id/caption_handle" android:layout_width="@dimen/desktop_mode_fullscreen_decor_caption_width" android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height" android:paddingVertical="16dp" + android:paddingHorizontal="10dp" android:contentDescription="@string/handle_text" android:src="@drawable/decor_handle_dark" tools:tint="@color/desktop_mode_caption_handle_bar_dark" android:scaleType="fitXY" - android:background="?android:selectableItemBackground"/> + android:background="@android:color/transparent"/> </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 39dd4d3af98d..4ee2c1a1da60 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -254,6 +254,8 @@ <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen> <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. --> <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen> + <!-- Width of the expanded bubble bar view shown when the bubble is expanded. --> + <dimen name="bubble_bar_expanded_view_width">412dp</dimen> <!-- Minimum width of the bubble bar manage menu. --> <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen> <!-- Size of the dismiss icon in the bubble bar manage menu. --> @@ -423,8 +425,9 @@ <!-- Height of desktop mode caption for fullscreen tasks. --> <dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen> - <!-- Width of desktop mode caption for fullscreen tasks. --> - <dimen name="desktop_mode_fullscreen_decor_caption_width">128dp</dimen> + <!-- Width of desktop mode caption for fullscreen tasks. + 80 dp for handle + 20 dp for room to grow on the sides when hovered. --> + <dimen name="desktop_mode_fullscreen_decor_caption_width">100dp</dimen> <!-- Required empty space to be visible for partially offscreen tasks. --> <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 97bf8f7bf459..73b2656d596a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -32,6 +32,7 @@ import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.content.ContentResolver; import android.content.Context; +import android.content.res.Configuration; import android.database.ContentObserver; import android.hardware.input.InputManager; import android.net.Uri; @@ -71,6 +72,7 @@ import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -81,7 +83,8 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Controls the window animation run when a user initiates a back gesture. */ -public class BackAnimationController implements RemoteCallable<BackAnimationController> { +public class BackAnimationController implements RemoteCallable<BackAnimationController>, + ConfigurationChangeListener { private static final String TAG = "ShellBackPreview"; private static final int SETTING_VALUE_OFF = 0; private static final int SETTING_VALUE_ON = 1; @@ -248,6 +251,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION, this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); + mShellController.addConfigurationChangeListener(this); } private void setupAnimationDeveloperSettingsObserver( @@ -297,6 +301,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private final BackAnimationImpl mBackAnimation = new BackAnimationImpl(); @Override + public void onConfigurationChanged(Configuration newConfig) { + mShellBackAnimationRegistry.onConfigurationChanged(newConfig); + } + + @Override public Context getContext() { return mContext; } 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 c6d46207b119..112ed0941122 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 @@ -68,7 +68,7 @@ class CrossActivityBackAnimation @Inject constructor( private val backAnimRect = Rect() - private val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) + private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) private val backAnimationRunner = BackAnimationRunner( Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY @@ -94,6 +94,10 @@ class CrossActivityBackAnimation @Inject constructor( private var scrimLayer: SurfaceControl? = null private var maxScrimAlpha: Float = 0f + override fun onConfigurationChanged(newConfiguration: Configuration) { + cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) + } + override fun getRunner() = backAnimationRunner private fun startBackAnimation(backMotionEvent: BackMotionEvent) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 987001d66219..c34f30df33a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -29,6 +29,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.Rect; @@ -80,7 +81,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { private static final int POST_ANIMATION_DURATION_MS = 500; private final Rect mStartTaskRect = new Rect(); - private final float mCornerRadius; + private float mCornerRadius; // The closing window properties. private final Rect mClosingStartRect = new Rect(); @@ -120,6 +121,11 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { mContext = context; } + @Override + public void onConfigurationChanged(Configuration newConfig) { + mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext); + } + private static float mapRange(float value, float min, float max) { return min + (value * (max - min)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java index e33aa7568d09..838dab43d6e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Rect; import android.os.RemoteException; @@ -63,7 +64,7 @@ import javax.inject.Inject; public class CustomizeActivityAnimation extends ShellBackAnimation { private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); private final BackAnimationRunner mBackAnimationRunner; - private final float mCornerRadius; + private float mCornerRadius; private final SurfaceControl.Transaction mTransaction; private final BackAnimationBackground mBackground; private RemoteAnimationTarget mEnteringTarget; @@ -88,6 +89,7 @@ public class CustomizeActivityAnimation extends ShellBackAnimation { final Transformation mTransformation = new Transformation(); private final Choreographer mChoreographer; + private final Context mContext; @Inject public CustomizeActivityAnimation(Context context, BackAnimationBackground background) { @@ -108,6 +110,12 @@ public class CustomizeActivityAnimation extends ShellBackAnimation { .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction; mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance(); + mContext = context; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext); } private float getLatestProgress() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java index dc659197848e..8a0daaa72e24 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java @@ -16,6 +16,7 @@ package com.android.wm.shell.back; +import android.content.res.Configuration; import android.window.BackNavigationInfo; import javax.inject.Qualifier; @@ -48,4 +49,8 @@ public abstract class ShellBackAnimation { public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) { return false; } + + void onConfigurationChanged(Configuration newConfig) { + + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java index 26d20972c751..7a6032c60cce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java @@ -18,6 +18,7 @@ package com.android.wm.shell.back; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.res.Configuration; import android.util.Log; import android.util.SparseArray; import android.window.BackNavigationInfo; @@ -27,8 +28,9 @@ public class ShellBackAnimationRegistry { private static final String TAG = "ShellBackPreview"; private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>(); - private final ShellBackAnimation mDefaultCrossActivityAnimation; + private ShellBackAnimation mDefaultCrossActivityAnimation; private final ShellBackAnimation mCustomizeActivityAnimation; + private final ShellBackAnimation mCrossTaskAnimation; public ShellBackAnimationRegistry( @ShellBackAnimation.CrossActivity @Nullable ShellBackAnimation crossActivityAnimation, @@ -57,6 +59,7 @@ public class ShellBackAnimationRegistry { mDefaultCrossActivityAnimation = crossActivityAnimation; mCustomizeActivityAnimation = customizeActivityAnimation; + mCrossTaskAnimation = crossTaskAnimation; // TODO(b/236760237): register dialog close animation when it's completed. } @@ -64,10 +67,18 @@ public class ShellBackAnimationRegistry { void registerAnimation( @BackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner) { mAnimationDefinition.set(type, runner); + // Only happen in test + if (BackNavigationInfo.TYPE_CROSS_ACTIVITY == type) { + mDefaultCrossActivityAnimation = null; + } } void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) { mAnimationDefinition.remove(type); + // Only happen in test + if (BackNavigationInfo.TYPE_CROSS_ACTIVITY == type) { + mDefaultCrossActivityAnimation = null; + } } /** @@ -125,6 +136,18 @@ public class ShellBackAnimationRegistry { BackNavigationInfo.TYPE_CROSS_ACTIVITY, mDefaultCrossActivityAnimation.getRunner()); } + void onConfigurationChanged(Configuration newConfig) { + if (mCustomizeActivityAnimation != null) { + mCustomizeActivityAnimation.onConfigurationChanged(newConfig); + } + if (mDefaultCrossActivityAnimation != null) { + mDefaultCrossActivityAnimation.onConfigurationChanged(newConfig); + } + if (mCrossTaskAnimation != null) { + mCrossTaskAnimation.onConfigurationChanged(newConfig); + } + } + BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) { int type = backNavigationInfo.getType(); // Initiate customized cross-activity animation, or fall back to cross activity animation diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 313d0d24b459..d2958779c0d4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -455,8 +455,7 @@ public class BubbleController implements ConfigurationChangeListener, ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s", task.taskId, b.getKey()); - mBubbleData.setSelectedBubble(b); - mBubbleData.setExpanded(true); + mBubbleData.setSelectedBubbleAndExpandStack(b); return; } } @@ -593,13 +592,6 @@ public class BubbleController implements ConfigurationChangeListener, } } - private void openBubbleOverflow() { - ensureBubbleViewsAndWindowCreated(); - mBubbleData.setShowingOverflow(true); - mBubbleData.setSelectedBubble(mBubbleData.getOverflow()); - mBubbleData.setExpanded(true); - } - /** * Called when the status bar has become visible or invisible (either permanently or * temporarily). @@ -1247,8 +1239,7 @@ public class BubbleController implements ConfigurationChangeListener, } if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) { // already in the stack - mBubbleData.setSelectedBubble(b); - mBubbleData.setExpanded(true); + mBubbleData.setSelectedBubbleAndExpandStack(b); } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) { // promote it out of the overflow promoteBubbleFromOverflow(b); @@ -1273,8 +1264,7 @@ public class BubbleController implements ConfigurationChangeListener, String key = entry.getKey(); Bubble bubble = mBubbleData.getBubbleInStackWithKey(key); if (bubble != null) { - mBubbleData.setSelectedBubble(bubble); - mBubbleData.setExpanded(true); + mBubbleData.setSelectedBubbleAndExpandStack(bubble); } else { bubble = mBubbleData.getOverflowBubbleWithKey(key); if (bubble != null) { @@ -1367,8 +1357,7 @@ public class BubbleController implements ConfigurationChangeListener, } else { // App bubble is not selected, select it & expand Log.i(TAG, " showOrHideAppBubble, expand and select existing app bubble"); - mBubbleData.setSelectedBubble(existingAppBubble); - mBubbleData.setExpanded(true); + mBubbleData.setSelectedBubbleAndExpandStack(existingAppBubble); } } else { // Check if it exists in the overflow diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 61f0ed22b537..ae3d0c559014 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -365,6 +365,19 @@ public class BubbleData { mSelectedBubble = bubble; } + /** + * Sets the selected bubble and expands it. + * + * <p>This dispatches a single state update for both changes and should be used instead of + * calling {@link #setSelectedBubble(BubbleViewProvider)} followed by + * {@link #setExpanded(boolean)} immediately after, which will generate 2 separate updates. + */ + public void setSelectedBubbleAndExpandStack(BubbleViewProvider bubble) { + setSelectedBubbleInternal(bubble); + setExpandedInternal(true); + dispatchPendingChanges(); + } + public void setSelectedBubble(BubbleViewProvider bubble) { setSelectedBubbleInternal(bubble); dispatchPendingChanges(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 4d5e516f76e5..14c3a0701c83 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -149,9 +149,10 @@ public class BubblePositioner { mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); if (mShowingInBubbleBar) { - mExpandedViewLargeScreenWidth = isLandscape() - ? (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT) - : (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_PORTRAIT_WIDTH_PERCENT); + mExpandedViewLargeScreenWidth = Math.min( + res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width), + mPositionRect.width() - 2 * mExpandedViewPadding + ); } else if (mDeviceConfig.isSmallTablet()) { mExpandedViewLargeScreenWidth = (int) (bounds.width() * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT); @@ -839,11 +840,42 @@ public class BubblePositioner { * How tall the expanded view should be when showing from the bubble bar. */ public int getExpandedViewHeightForBubbleBar(boolean isOverflow) { - return isOverflow - ? mOverflowHeight - : getExpandedViewBottomForBubbleBar() - mInsets.top - mExpandedViewPadding; + if (isOverflow) { + return mOverflowHeight; + } else { + return getBubbleBarExpandedViewHeightForLandscape(); + } } + /** + * Calculate the height of expanded view in landscape mode regardless current orientation. + * Here is an explanation: + * ------------------------ mScreenRect.top + * | top inset ↕ | + * |----------------------- + * | 16dp spacing ↕ | + * | --------- | --- expanded view top + * | | | | ↑ + * | | | | ↓ expanded view height + * | --------- | --- expanded view bottom + * | 16dp spacing ↕ | ↑ + * | @bubble bar@ | | height of the bubble bar container + * ------------------------ | already includes bottom inset and spacing + * | bottom inset ↕ | ↓ + * |----------------------| --- mScreenRect.bottom + */ + private int getBubbleBarExpandedViewHeightForLandscape() { + int heightOfBubbleBarContainer = + mScreenRect.height() - getExpandedViewBottomForBubbleBar(); + // getting landscape height from screen rect + int expandedViewHeight = Math.min(mScreenRect.width(), mScreenRect.height()); + expandedViewHeight -= heightOfBubbleBarContainer; /* removing bubble container height */ + expandedViewHeight -= mInsets.top; /* removing top inset */ + expandedViewHeight -= mExpandedViewPadding; /* removing spacing */ + return expandedViewHeight; + } + + /** The bottom position of the expanded view when showing above the bubble bar. */ public int getExpandedViewBottomForBubbleBar() { return mBubbleBarBounds.top - mExpandedViewPadding; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index b86e39fca742..4eff3f03670e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -23,19 +23,25 @@ import android.os.Handler; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipPerfHintController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip2.phone.PhonePipMenuController; import com.android.wm.shell.pip2.phone.PipController; +import com.android.wm.shell.pip2.phone.PipMotionHelper; import com.android.wm.shell.pip2.phone.PipScheduler; +import com.android.wm.shell.pip2.phone.PipTouchHandler; import com.android.wm.shell.pip2.phone.PipTransition; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellController; @@ -62,6 +68,7 @@ public abstract class Pip2Module { PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm, Optional<PipController> pipController, + PipTouchHandler pipTouchHandler, @NonNull PipScheduler pipScheduler) { return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, pipBoundsAlgorithm, pipScheduler); @@ -109,4 +116,34 @@ public abstract class Pip2Module { return new PhonePipMenuController(context, pipBoundsState, pipMediaController, systemWindows, pipUiEventLogger, mainExecutor, mainHandler); } + + + @WMSingleton + @Provides + static PipTouchHandler providePipTouchHandler(Context context, + ShellInit shellInit, + PhonePipMenuController menuPhoneController, + PipBoundsAlgorithm pipBoundsAlgorithm, + @NonNull PipBoundsState pipBoundsState, + @NonNull SizeSpecSource sizeSpecSource, + PipMotionHelper pipMotionHelper, + FloatingContentCoordinator floatingContentCoordinator, + PipUiEventLogger pipUiEventLogger, + @ShellMainThread ShellExecutor mainExecutor, + Optional<PipPerfHintController> pipPerfHintControllerOptional) { + return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, + pipBoundsState, sizeSpecSource, pipMotionHelper, floatingContentCoordinator, + pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional); + } + + @WMSingleton + @Provides + static PipMotionHelper providePipMotionHelper(Context context, + PipBoundsState pipBoundsState, PhonePipMenuController menuController, + PipSnapAlgorithm pipSnapAlgorithm, + FloatingContentCoordinator floatingContentCoordinator, + Optional<PipPerfHintController> pipPerfHintControllerOptional) { + return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm, + floatingContentCoordinator, pipPerfHintControllerOptional); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index e210ea731f7a..58942ec92a71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -1354,6 +1354,13 @@ class DesktopTasksController( "setTaskListener" ) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() } } + + override fun moveToDesktop(taskId: Int) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "moveToDesktop" + ) { c -> c.moveToDesktop(taskId) } + } } companion object { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 6bdaf1eadb8a..fa4352241193 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -45,4 +45,7 @@ interface IDesktopMode { /** Set listener that will receive callbacks about updates to desktop tasks */ oneway void setTaskListener(IDesktopTaskListener listener); + + /** Move a task with given `taskId` to desktop */ + void moveToDesktop(int taskId); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java new file mode 100644 index 000000000000..e7e797096c0e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip2.phone; + +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.WindowInsets; +import android.view.WindowManager; + +import androidx.annotation.NonNull; + +import com.android.wm.shell.R; +import com.android.wm.shell.bubbles.DismissViewUtils; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.bubbles.DismissCircleView; +import com.android.wm.shell.common.bubbles.DismissView; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; +import com.android.wm.shell.common.pip.PipUiEventLogger; + +import kotlin.Unit; + +/** + * Handler of all Magnetized Object related code for PiP. + */ +public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListener { + + /* The multiplier to apply scale the target size by when applying the magnetic field radius */ + private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f; + + /** + * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move + * PIP. + */ + private MagnetizedObject<Rect> mMagnetizedPip; + + /** + * Container for the dismiss circle, so that it can be animated within the container via + * translation rather than within the WindowManager via slow layout animations. + */ + private DismissView mTargetViewContainer; + + /** Circle view used to render the dismiss target. */ + private DismissCircleView mTargetView; + + /** + * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius. + */ + private MagnetizedObject.MagneticTarget mMagneticTarget; + + // Allow dragging the PIP to a location to close it + private boolean mEnableDismissDragToEdge; + + private int mTargetSize; + private int mDismissAreaHeight; + private float mMagneticFieldRadiusPercent = 1f; + private WindowInsets mWindowInsets; + + private SurfaceControl mTaskLeash; + private boolean mHasDismissTargetSurface; + + private final Context mContext; + private final PipMotionHelper mMotionHelper; + private final PipUiEventLogger mPipUiEventLogger; + private final WindowManager mWindowManager; + private final ShellExecutor mMainExecutor; + + public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger, + PipMotionHelper motionHelper, ShellExecutor mainExecutor) { + mContext = context; + mPipUiEventLogger = pipUiEventLogger; + mMotionHelper = motionHelper; + mMainExecutor = mainExecutor; + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + } + + void init() { + Resources res = mContext.getResources(); + mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge); + mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); + + if (mTargetViewContainer != null) { + // init can be called multiple times, remove the old one from view hierarchy first. + cleanUpDismissTarget(); + } + + mTargetViewContainer = new DismissView(mContext); + DismissViewUtils.setup(mTargetViewContainer); + mTargetView = mTargetViewContainer.getCircle(); + mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> { + if (!windowInsets.equals(mWindowInsets)) { + mWindowInsets = windowInsets; + updateMagneticTargetSize(); + } + return windowInsets; + }); + + mMagnetizedPip = mMotionHelper.getMagnetizedPip(); + mMagnetizedPip.clearAllTargets(); + mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); + updateMagneticTargetSize(); + + mMagnetizedPip.setAnimateStuckToTarget( + (target, velX, velY, flung, after) -> { + if (mEnableDismissDragToEdge) { + mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after); + } + return Unit.INSTANCE; + }); + mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target, + @NonNull MagnetizedObject<?> draggedObject) { + // Show the dismiss target, in case the initial touch event occurred within + // the magnetic field radius. + if (mEnableDismissDragToEdge) { + showDismissTargetMaybe(); + } + } + + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + @NonNull MagnetizedObject<?> draggedObject, + float velX, float velY, boolean wasFlungOut) { + if (wasFlungOut) { + mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */); + hideDismissTargetMaybe(); + } else { + mMotionHelper.setSpringingToTouch(true); + } + } + + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target, + @NonNull MagnetizedObject<?> draggedObject) { + if (mEnableDismissDragToEdge) { + mMainExecutor.executeDelayed(() -> { + mMotionHelper.notifyDismissalPending(); + mMotionHelper.animateDismiss(); + hideDismissTargetMaybe(); + + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE); + }, 0); + } + } + }); + + } + + @Override + public boolean onPreDraw() { + mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this); + mHasDismissTargetSurface = true; + updateDismissTargetLayer(); + return true; + } + + /** + * Potentially start consuming future motion events if PiP is currently near the magnetized + * object. + */ + public boolean maybeConsumeMotionEvent(MotionEvent ev) { + return mMagnetizedPip.maybeConsumeMotionEvent(ev); + } + + /** + * Update the magnet size. + */ + public void updateMagneticTargetSize() { + if (mTargetView == null) { + return; + } + if (mTargetViewContainer != null) { + mTargetViewContainer.updateResources(); + } + + final Resources res = mContext.getResources(); + mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); + mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); + + // Set the magnetic field radius equal to the target size from the center of the target + setMagneticFieldRadiusPercent(mMagneticFieldRadiusPercent); + } + + /** + * Increase or decrease the field radius of the magnet object, e.g. with larger percent, + * PiP will magnetize to the field sooner. + */ + public void setMagneticFieldRadiusPercent(float percent) { + mMagneticFieldRadiusPercent = percent; + mMagneticTarget.setMagneticFieldRadiusPx((int) (mMagneticFieldRadiusPercent * mTargetSize + * MAGNETIC_FIELD_RADIUS_MULTIPLIER)); + } + + public void setTaskLeash(SurfaceControl taskLeash) { + mTaskLeash = taskLeash; + } + + private void updateDismissTargetLayer() { + if (!mHasDismissTargetSurface || mTaskLeash == null) { + // No dismiss target surface, can just return + return; + } + + final SurfaceControl targetViewLeash = + mTargetViewContainer.getViewRootImpl().getSurfaceControl(); + if (!targetViewLeash.isValid()) { + // The surface of mTargetViewContainer is somehow not ready, bail early + return; + } + + // Put the dismiss target behind the task + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setRelativeLayer(targetViewLeash, mTaskLeash, -1); + t.apply(); + } + + /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */ + public void createOrUpdateDismissTarget() { + if (mTargetViewContainer.getParent() == null) { + mTargetViewContainer.cancelAnimators(); + + mTargetViewContainer.setVisibility(View.INVISIBLE); + mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this); + mHasDismissTargetSurface = false; + + mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams()); + } else { + mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams()); + } + } + + /** Returns layout params for the dismiss target, using the latest display metrics. */ + private WindowManager.LayoutParams getDismissTargetLayoutParams() { + final Point windowSize = new Point(); + mWindowManager.getDefaultDisplay().getRealSize(windowSize); + int height = Math.min(windowSize.y, mDismissAreaHeight); + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + height, + 0, windowSize.y - height, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + + lp.setTitle("pip-dismiss-overlay"); + lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + lp.setFitInsetsTypes(0 /* types */); + + return lp; + } + + /** Makes the dismiss target visible and animates it in, if it isn't already visible. */ + public void showDismissTargetMaybe() { + if (!mEnableDismissDragToEdge) { + return; + } + + createOrUpdateDismissTarget(); + + if (mTargetViewContainer.getVisibility() != View.VISIBLE) { + mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this); + } + // always invoke show, since the target might still be VISIBLE while playing hide animation, + // so we want to ensure it will show back again + mTargetViewContainer.show(); + } + + /** Animates the magnetic dismiss target out and then sets it to GONE. */ + public void hideDismissTargetMaybe() { + if (!mEnableDismissDragToEdge) { + return; + } + mTargetViewContainer.hide(); + } + + /** + * Removes the dismiss target and cancels any pending callbacks to show it. + */ + public void cleanUpDismissTarget() { + if (mTargetViewContainer.getParent() != null) { + mWindowManager.removeViewImmediate(mTargetViewContainer); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java new file mode 100644 index 000000000000..619bed4e19ca --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -0,0 +1,719 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip2.phone; + +import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY; +import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW; +import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM; + +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT; +import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS; +import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.PointF; +import android.graphics.Rect; +import android.os.Debug; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; +import com.android.wm.shell.animation.FloatProperties; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; +import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipPerfHintController; +import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.animation.PhysicsAnimator; + +import kotlin.Unit; +import kotlin.jvm.functions.Function0; + +import java.util.Optional; +import java.util.function.Consumer; + +/** + * A helper to animate and manipulate the PiP. + */ +public class PipMotionHelper implements PipAppOpsListener.Callback, + FloatingContentCoordinator.FloatingContent { + private static final String TAG = "PipMotionHelper"; + private static final boolean DEBUG = false; + + private static final int SHRINK_STACK_FROM_MENU_DURATION = 250; + private static final int EXPAND_STACK_TO_MENU_DURATION = 250; + private static final int UNSTASH_DURATION = 250; + private static final int LEAVE_PIP_DURATION = 300; + private static final int SHIFT_DURATION = 300; + + /** Friction to use for PIP when it moves via physics fling animations. */ + private static final float DEFAULT_FRICTION = 1.9f; + /** How much of the dismiss circle size to use when scaling down PIP. **/ + private static final float DISMISS_CIRCLE_PERCENT = 0.85f; + + private final Context mContext; + private @NonNull PipBoundsState mPipBoundsState; + + private PhonePipMenuController mMenuController; + private PipSnapAlgorithm mSnapAlgorithm; + + /** The region that all of PIP must stay within. */ + private final Rect mFloatingAllowedArea = new Rect(); + + /** Coordinator instance for resolving conflicts with other floating content. */ + private FloatingContentCoordinator mFloatingContentCoordinator; + + @Nullable private final PipPerfHintController mPipPerfHintController; + @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession; + + /** + * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()} + * using physics animations. + */ + private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator; + + private MagnetizedObject<Rect> mMagnetizedPip; + + /** + * Update listener that resizes the PIP to {@link PipBoundsState#getMotionBoundsState()}. + */ + private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener; + + /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ + private PhysicsAnimator.FlingConfig mFlingConfigX; + private PhysicsAnimator.FlingConfig mFlingConfigY; + /** FlingConfig instances provided to PhysicsAnimator for stashing. */ + private PhysicsAnimator.FlingConfig mStashConfigX; + + /** SpringConfig to use for fling-then-spring animations. */ + private final PhysicsAnimator.SpringConfig mSpringConfig = + new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY); + + /** SpringConfig used for animating into the dismiss region, matches the one in + * {@link MagnetizedObject}. */ + private final PhysicsAnimator.SpringConfig mAnimateToDismissSpringConfig = + new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_NO_BOUNCY); + + /** SpringConfig used for animating the pip to catch up to the finger once it leaves the dismiss + * drag region. */ + private final PhysicsAnimator.SpringConfig mCatchUpSpringConfig = + new PhysicsAnimator.SpringConfig(5000f, DAMPING_RATIO_NO_BOUNCY); + + /** SpringConfig to use for springing PIP away from conflicting floating content. */ + private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig = + new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY); + + private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> { + if (mPipBoundsState.getBounds().equals(newBounds)) { + return; + } + + mMenuController.updateMenuLayout(newBounds); + mPipBoundsState.setBounds(newBounds); + }; + + /** + * Whether we're springing to the touch event location (vs. moving it to that position + * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was + * 'stuck' in the target and needs to catch up to the touch location. + */ + private boolean mSpringingToTouch = false; + + /** + * Whether PIP was released in the dismiss target, and will be animated out and dismissed + * shortly. + */ + private boolean mDismissalPending = false; + + /** + * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is + * used to show menu activity when the expand animation is completed. + */ + private Runnable mPostPipTransitionCallback; + + public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, + PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, + FloatingContentCoordinator floatingContentCoordinator, + Optional<PipPerfHintController> pipPerfHintControllerOptional) { + mContext = context; + mPipBoundsState = pipBoundsState; + mMenuController = menuController; + mSnapAlgorithm = snapAlgorithm; + mFloatingContentCoordinator = floatingContentCoordinator; + mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); + mResizePipUpdateListener = (target, values) -> { + if (mPipBoundsState.getMotionBoundsState().isInMotion()) { + /* + mPipTaskOrganizer.scheduleUserResizePip(getBounds(), + mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null); + */ + } + }; + } + + void init() { + mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( + mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); + } + + @NonNull + @Override + public Rect getFloatingBoundsOnScreen() { + return !mPipBoundsState.getMotionBoundsState().getAnimatingToBounds().isEmpty() + ? mPipBoundsState.getMotionBoundsState().getAnimatingToBounds() : getBounds(); + } + + @NonNull + @Override + public Rect getAllowedFloatingBoundsRegion() { + return mFloatingAllowedArea; + } + + @Override + public void moveToBounds(@NonNull Rect bounds) { + animateToBounds(bounds, mConflictResolutionSpringConfig); + } + + /** + * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations. + */ + void synchronizePinnedStackBounds() { + cancelPhysicsAnimation(); + mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded(); + + /* + if (mPipTaskOrganizer.isInPip()) { + mFloatingContentCoordinator.onContentMoved(this); + } + */ + } + + /** + * Tries to move the pinned stack to the given {@param bounds}. + */ + void movePip(Rect toBounds) { + movePip(toBounds, false /* isDragging */); + } + + /** + * Tries to move the pinned stack to the given {@param bounds}. + * + * @param isDragging Whether this movement is the result of a drag touch gesture. If so, we + * won't notify the floating content coordinator of this move, since that will + * happen when the gesture ends. + */ + void movePip(Rect toBounds, boolean isDragging) { + if (!isDragging) { + mFloatingContentCoordinator.onContentMoved(this); + } + + if (!mSpringingToTouch) { + // If we are moving PIP directly to the touch event locations, cancel any animations and + // move PIP to the given bounds. + cancelPhysicsAnimation(); + + if (!isDragging) { + resizePipUnchecked(toBounds); + mPipBoundsState.setBounds(toBounds); + } else { + mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds); + /* + mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds, + (Rect newBounds) -> { + mMenuController.updateMenuLayout(newBounds); + }); + */ + } + } else { + // If PIP is 'catching up' after being stuck in the dismiss target, update the animation + // to spring towards the new touch location. + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mCatchUpSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mCatchUpSpringConfig) + .spring(FloatProperties.RECT_X, toBounds.left, mCatchUpSpringConfig) + .spring(FloatProperties.RECT_Y, toBounds.top, mCatchUpSpringConfig); + + startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */); + } + } + + /** Animates the PIP into the dismiss target, scaling it down. */ + void animateIntoDismissTarget( + MagnetizedObject.MagneticTarget target, + float velX, float velY, + boolean flung, Function0<Unit> after) { + final PointF targetCenter = target.getCenterOnScreen(); + + // PIP should fit in the circle + final float dismissCircleSize = mContext.getResources().getDimensionPixelSize( + R.dimen.dismiss_circle_size); + + final float width = getBounds().width(); + final float height = getBounds().height(); + final float ratio = width / height; + + // Width should be a little smaller than the circle size. + final float desiredWidth = dismissCircleSize * DISMISS_CIRCLE_PERCENT; + final float desiredHeight = desiredWidth / ratio; + final float destinationX = targetCenter.x - (desiredWidth / 2f); + final float destinationY = targetCenter.y - (desiredHeight / 2f); + + // If we're already in the dismiss target area, then there won't be a move to set the + // temporary bounds, so just initialize it to the current bounds. + if (!mPipBoundsState.getMotionBoundsState().isInMotion()) { + mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds()); + } + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, destinationX, velX, mAnimateToDismissSpringConfig) + .spring(FloatProperties.RECT_Y, destinationY, velY, mAnimateToDismissSpringConfig) + .spring(FloatProperties.RECT_WIDTH, desiredWidth, mAnimateToDismissSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mAnimateToDismissSpringConfig) + .withEndActions(after); + + startBoundsAnimator(destinationX, destinationY); + } + + /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ + void setSpringingToTouch(boolean springingToTouch) { + mSpringingToTouch = springingToTouch; + } + + /** + * Resizes the pinned stack back to unknown windowing mode, which could be freeform or + * * fullscreen depending on the display area's windowing mode. + */ + void expandLeavePip(boolean skipAnimation) { + expandLeavePip(skipAnimation, false /* enterSplit */); + } + + /** + * Resizes the pinned task to split-screen mode. + */ + void expandIntoSplit() { + expandLeavePip(false, true /* enterSplit */); + } + + /** + * Resizes the pinned stack back to unknown windowing mode, which could be freeform or + * fullscreen depending on the display area's windowing mode. + */ + private void expandLeavePip(boolean skipAnimation, boolean enterSplit) { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exitPip: skipAnimation=%s" + + " callers=\n%s", TAG, skipAnimation, Debug.getCallers(5, " ")); + } + cancelPhysicsAnimation(); + mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */); + // mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit); + } + + /** + * Dismisses the pinned stack. + */ + @Override + public void dismissPip() { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, " ")); + } + cancelPhysicsAnimation(); + mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */); + // mPipTaskOrganizer.removePip(); + } + + /** Sets the movement bounds to use to constrain PIP position animations. */ + void onMovementBoundsChanged() { + rebuildFlingConfigs(); + + // The movement bounds represent the area within which we can move PIP's top-left position. + // The allowed area for all of PIP is those bounds plus PIP's width and height. + mFloatingAllowedArea.set(mPipBoundsState.getMovementBounds()); + mFloatingAllowedArea.right += getBounds().width(); + mFloatingAllowedArea.bottom += getBounds().height(); + } + + /** + * @return the PiP bounds. + */ + private Rect getBounds() { + return mPipBoundsState.getBounds(); + } + + /** + * Flings the PiP to the closest snap target. + */ + void flingToSnapTarget( + float velocityX, float velocityY, @Nullable Runnable postBoundsUpdateCallback) { + movetoTarget(velocityX, velocityY, postBoundsUpdateCallback, false /* isStash */); + } + + /** + * Stash PiP to the closest edge. We set velocityY to 0 to limit pure horizontal motion. + */ + void stashToEdge(float velX, float velY, @Nullable Runnable postBoundsUpdateCallback) { + velY = mPipBoundsState.getStashedState() == STASH_TYPE_NONE ? 0 : velY; + movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */); + } + + private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {} + + private void cleanUpHighPerfSessionMaybe() { + if (mPipHighPerfSession != null) { + // Close the high perf session once pointer interactions are over; + mPipHighPerfSession.close(); + mPipHighPerfSession = null; + } + } + + private void movetoTarget( + float velocityX, + float velocityY, + @Nullable Runnable postBoundsUpdateCallback, + boolean isStash) { + // If we're flinging to a snap target now, we're not springing to catch up to the touch + // location now. + mSpringingToTouch = false; + + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig) + .flingThenSpring( + FloatProperties.RECT_X, velocityX, + isStash ? mStashConfigX : mFlingConfigX, + mSpringConfig, true /* flingMustReachMinOrMax */) + .flingThenSpring( + FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig); + + final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets(); + final float leftEdge = isStash + ? mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width() + + insetBounds.left + : mPipBoundsState.getMovementBounds().left; + final float rightEdge = isStash + ? mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset() + - insetBounds.right + : mPipBoundsState.getMovementBounds().right; + + final float xEndValue = velocityX < 0 ? leftEdge : rightEdge; + + final int startValueY = mPipBoundsState.getMotionBoundsState().getBoundsInMotion().top; + final float estimatedFlingYEndValue = + PhysicsAnimator.estimateFlingEndValue(startValueY, velocityY, mFlingConfigY); + + startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */, + postBoundsUpdateCallback); + } + + /** + * Animates PIP to the provided bounds, using physics animations and the given spring + * configuration + */ + void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) { + if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { + // Animate from the current bounds if we're not already animating. + mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds()); + } + + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, bounds.left, springConfig) + .spring(FloatProperties.RECT_Y, bounds.top, springConfig); + startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */); + } + + /** + * Animates the dismissal of the PiP off the edge of the screen. + */ + void animateDismiss() { + // Animate off the bottom of the screen, then dismiss PIP. + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_Y, + mPipBoundsState.getMovementBounds().bottom + getBounds().height() * 2, + 0, + mSpringConfig) + .withEndActions(this::dismissPip); + + startBoundsAnimator( + getBounds().left /* toX */, getBounds().bottom + getBounds().height() /* toY */); + + mDismissalPending = false; + } + + /** + * Animates the PiP to the expanded state to show the menu. + */ + float animateToExpandedState(Rect expandedBounds, Rect movementBounds, + Rect expandedMovementBounds, Runnable callback) { + float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()), + movementBounds); + mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction); + mPostPipTransitionCallback = callback; + resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION); + return savedSnapFraction; + } + + /** + * Animates the PiP from the expanded state to the normal state after the menu is hidden. + */ + void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction, + Rect normalMovementBounds, Rect currentMovementBounds, boolean immediate) { + if (savedSnapFraction < 0f) { + // If there are no saved snap fractions, then just use the current bounds + savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()), + currentMovementBounds, mPipBoundsState.getStashedState()); + } + + mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction, + mPipBoundsState.getStashedState(), mPipBoundsState.getStashOffset(), + mPipBoundsState.getDisplayBounds(), + mPipBoundsState.getDisplayLayout().stableInsets()); + + if (immediate) { + movePip(normalBounds); + } else { + resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION); + } + } + + /** + * Animates the PiP to the stashed state, choosing the closest edge. + */ + void animateToStashedClosestEdge() { + Rect tmpBounds = new Rect(); + final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets(); + final int stashType = + mPipBoundsState.getBounds().left == mPipBoundsState.getMovementBounds().left + ? STASH_TYPE_LEFT : STASH_TYPE_RIGHT; + final float leftEdge = stashType == STASH_TYPE_LEFT + ? mPipBoundsState.getStashOffset() + - mPipBoundsState.getBounds().width() + insetBounds.left + : mPipBoundsState.getDisplayBounds().right + - mPipBoundsState.getStashOffset() - insetBounds.right; + tmpBounds.set((int) leftEdge, + mPipBoundsState.getBounds().top, + (int) (leftEdge + mPipBoundsState.getBounds().width()), + mPipBoundsState.getBounds().bottom); + resizeAndAnimatePipUnchecked(tmpBounds, UNSTASH_DURATION); + mPipBoundsState.setStashed(stashType); + } + + /** + * Animates the PiP from stashed state into un-stashed, popping it out from the edge. + */ + void animateToUnStashedBounds(Rect unstashedBounds) { + resizeAndAnimatePipUnchecked(unstashedBounds, UNSTASH_DURATION); + } + + /** + * Animates the PiP to offset it from the IME or shelf. + */ + void animateToOffset(Rect originalBounds, int offset) { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: animateToOffset: originalBounds=%s offset=%s" + + " callers=\n%s", TAG, originalBounds, offset, + Debug.getCallers(5, " ")); + } + cancelPhysicsAnimation(); + /* + mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION, + mUpdateBoundsCallback); + */ + } + + /** + * Cancels all existing animations. + */ + private void cancelPhysicsAnimation() { + mTemporaryBoundsPhysicsAnimator.cancel(); + mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded(); + mSpringingToTouch = false; + } + + /** Set new fling configs whose min/max values respect the given movement bounds. */ + private void rebuildFlingConfigs() { + mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION, + mPipBoundsState.getMovementBounds().left, + mPipBoundsState.getMovementBounds().right); + mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION, + mPipBoundsState.getMovementBounds().top, + mPipBoundsState.getMovementBounds().bottom); + final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets(); + mStashConfigX = new PhysicsAnimator.FlingConfig( + DEFAULT_FRICTION, + mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width() + + insetBounds.left, + mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset() + - insetBounds.right); + } + + private void startBoundsAnimator(float toX, float toY) { + startBoundsAnimator(toX, toY, null /* postBoundsUpdateCallback */); + } + + /** + * Starts the physics animator which will update the animated PIP bounds using physics + * animations, as well as the TimeAnimator which will apply those bounds to PIP. + * + * This will also add end actions to the bounds animator that cancel the TimeAnimator and update + * the 'real' bounds to equal the final animated bounds. + * + * If one wishes to supply a callback after all the 'real' bounds update has happened, + * pass @param postBoundsUpdateCallback. + */ + private void startBoundsAnimator(float toX, float toY, Runnable postBoundsUpdateCallback) { + if (!mSpringingToTouch) { + cancelPhysicsAnimation(); + } + + setAnimatingToBounds(new Rect( + (int) toX, + (int) toY, + (int) toX + getBounds().width(), + (int) toY + getBounds().height())); + + if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { + if (mPipPerfHintController != null) { + // Start a high perf session with a timeout callback. + mPipHighPerfSession = mPipPerfHintController.startSession( + this::onHighPerfSessionTimeout, "startBoundsAnimator"); + } + if (postBoundsUpdateCallback != null) { + mTemporaryBoundsPhysicsAnimator + .addUpdateListener(mResizePipUpdateListener) + .withEndActions(this::onBoundsPhysicsAnimationEnd, + postBoundsUpdateCallback); + } else { + mTemporaryBoundsPhysicsAnimator + .addUpdateListener(mResizePipUpdateListener) + .withEndActions(this::onBoundsPhysicsAnimationEnd); + } + } + + mTemporaryBoundsPhysicsAnimator.start(); + } + + /** + * Notify that PIP was released in the dismiss target and will be animated out and dismissed + * shortly. + */ + void notifyDismissalPending() { + mDismissalPending = true; + } + + private void onBoundsPhysicsAnimationEnd() { + // The physics animation ended, though we may not necessarily be done animating, such as + // when we're still dragging after moving out of the magnetic target. + if (!mDismissalPending + && !mSpringingToTouch + && !mMagnetizedPip.getObjectStuckToTarget()) { + // All motion operations have actually finished. + mPipBoundsState.setBounds( + mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); + mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded(); + if (!mDismissalPending) { + // do not schedule resize if PiP is dismissing, which may cause app re-open to + // mBounds instead of its normal bounds. + // mPipTaskOrganizer.scheduleFinishResizePip(getBounds()); + } + } + mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded(); + mSpringingToTouch = false; + mDismissalPending = false; + cleanUpHighPerfSessionMaybe(); + } + + /** + * Notifies the floating coordinator that we're moving, and sets the animating to bounds so + * we return these bounds from + * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}. + */ + private void setAnimatingToBounds(Rect bounds) { + mPipBoundsState.getMotionBoundsState().setAnimatingToBounds(bounds); + mFloatingContentCoordinator.onContentMoved(this); + } + + /** + * Directly resizes the PiP to the given {@param bounds}. + */ + private void resizePipUnchecked(Rect toBounds) { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: resizePipUnchecked: toBounds=%s" + + " callers=\n%s", TAG, toBounds, Debug.getCallers(5, " ")); + } + if (!toBounds.equals(getBounds())) { + // mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback); + } + } + + /** + * Directly resizes the PiP to the given {@param bounds}. + */ + private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: resizeAndAnimatePipUnchecked: toBounds=%s" + + " duration=%s callers=\n%s", TAG, toBounds, duration, + Debug.getCallers(5, " ")); + } + + // Intentionally resize here even if the current bounds match the destination bounds. + // This is so all the proper callbacks are performed. + + // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration, + // TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */); + // setAnimatingToBounds(toBounds); + } + + /** + * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the + * magnetic dismiss target so it can calculate PIP's size and position. + */ + MagnetizedObject<Rect> getMagnetizedPip() { + if (mMagnetizedPip == null) { + mMagnetizedPip = new MagnetizedObject<Rect>( + mContext, mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), + FloatProperties.RECT_X, FloatProperties.RECT_Y) { + @Override + public float getWidth(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.width(); + } + + @Override + public float getHeight(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.height(); + } + + @Override + public void getLocationOnScreen( + @NonNull Rect animatedPipBounds, @NonNull int[] loc) { + loc[0] = animatedPipBounds.left; + loc[1] = animatedPipBounds.top; + } + }; + mMagnetizedPip.setFlingToTargetEnabled(false); + } + + return mMagnetizedPip; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java new file mode 100644 index 000000000000..cc8e3e0934e6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -0,0 +1,1081 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip2.phone; + +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE; +import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; +import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_FULL; +import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_NONE; +import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.content.ComponentName; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.provider.DeviceConfig; +import android.util.Size; +import android.view.DisplayCutout; +import android.view.InputEvent; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; +import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDoubleTapHelper; +import com.android.wm.shell.common.pip.PipPerfHintController; +import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.common.pip.SizeSpecSource; +import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.sysui.ShellInit; + +import java.io.PrintWriter; +import java.util.Optional; + +/** + * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding + * the PIP. + */ +public class PipTouchHandler { + + private static final String TAG = "PipTouchHandler"; + private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; + + // Allow PIP to resize to a slightly bigger state upon touch + private boolean mEnableResize; + private final Context mContext; + private final PipBoundsAlgorithm mPipBoundsAlgorithm; + @NonNull private final PipBoundsState mPipBoundsState; + @NonNull private final SizeSpecSource mSizeSpecSource; + private final PipUiEventLogger mPipUiEventLogger; + private final PipDismissTargetHandler mPipDismissTargetHandler; + private final ShellExecutor mMainExecutor; + @Nullable private final PipPerfHintController mPipPerfHintController; + + private PipResizeGestureHandler mPipResizeGestureHandler; + + private final PhonePipMenuController mMenuController; + private final AccessibilityManager mAccessibilityManager; + + /** + * Whether PIP stash is enabled or not. When enabled, if the user flings toward the edge of the + * screen, it will be shown in "stashed" mode, where PIP will only show partially. + */ + private boolean mEnableStash = true; + + private float mStashVelocityThreshold; + + // The reference inset bounds, used to determine the dismiss fraction + private final Rect mInsetBounds = new Rect(); + + // Used to workaround an issue where the WM rotation happens before we are notified, allowing + // us to send stale bounds + private int mDeferResizeToNormalBoundsUntilRotation = -1; + private int mDisplayRotation; + + // Behaviour states + private int mMenuState = MENU_STATE_NONE; + private boolean mIsImeShowing; + private int mImeHeight; + private int mImeOffset; + private boolean mIsShelfShowing; + private int mShelfHeight; + private int mMovementBoundsExtraOffsets; + private int mBottomOffsetBufferPx; + private float mSavedSnapFraction = -1f; + private boolean mSendingHoverAccessibilityEvents; + private boolean mMovementWithinDismiss; + + // Touch state + private final PipTouchState mTouchState; + private final FloatingContentCoordinator mFloatingContentCoordinator; + private PipMotionHelper mMotionHelper; + private PipTouchGesture mGesture; + + // Temp vars + private final Rect mTmpBounds = new Rect(); + + /** + * A listener for the PIP menu activity. + */ + private class PipMenuListener implements PhonePipMenuController.Listener { + @Override + public void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) { + PipTouchHandler.this.onPipMenuStateChangeStart(menuState, resize, callback); + } + + @Override + public void onPipMenuStateChangeFinish(int menuState) { + setMenuState(menuState); + } + + @Override + public void onPipExpand() { + mMotionHelper.expandLeavePip(false /* skipAnimation */); + } + + @Override + public void onPipDismiss() { + mTouchState.removeDoubleTapTimeoutCallback(); + mMotionHelper.dismissPip(); + } + + @Override + public void onPipShowMenu() { + mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(), + true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()); + } + } + + @SuppressLint("InflateParams") + public PipTouchHandler(Context context, + ShellInit shellInit, + PhonePipMenuController menuController, + PipBoundsAlgorithm pipBoundsAlgorithm, + @NonNull PipBoundsState pipBoundsState, + @NonNull SizeSpecSource sizeSpecSource, + PipMotionHelper pipMotionHelper, + FloatingContentCoordinator floatingContentCoordinator, + PipUiEventLogger pipUiEventLogger, + ShellExecutor mainExecutor, + Optional<PipPerfHintController> pipPerfHintControllerOptional) { + mContext = context; + mMainExecutor = mainExecutor; + mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); + mAccessibilityManager = context.getSystemService(AccessibilityManager.class); + mPipBoundsAlgorithm = pipBoundsAlgorithm; + mPipBoundsState = pipBoundsState; + mSizeSpecSource = sizeSpecSource; + mMenuController = menuController; + mPipUiEventLogger = pipUiEventLogger; + mFloatingContentCoordinator = floatingContentCoordinator; + mMenuController.addListener(new PipMenuListener()); + mGesture = new DefaultPipTouchGesture(); + mMotionHelper = pipMotionHelper; + mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger, + mMotionHelper, mainExecutor); + mTouchState = new PipTouchState(ViewConfiguration.get(context), + () -> { + if (mPipBoundsState.isStashed()) { + animateToUnStashedState(); + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); + mPipBoundsState.setStashed(STASH_TYPE_NONE); + } else { + mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL, + mPipBoundsState.getBounds(), true /* allowMenuTimeout */, + willResizeMenu(), + shouldShowResizeHandle()); + } + }, + menuController::hideMenu, + mainExecutor); + mPipResizeGestureHandler = + new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, + mTouchState, this::updateMovementBounds, pipUiEventLogger, + menuController, mainExecutor, mPipPerfHintController); + + if (PipUtils.isPip2ExperimentEnabled()) { + shellInit.addInitCallback(this::onInit, this); + } + } + + /** + * Called when the touch handler is initialized. + */ + public void onInit() { + Resources res = mContext.getResources(); + mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu); + reloadResources(); + + mMotionHelper.init(); + mPipResizeGestureHandler.init(); + mPipDismissTargetHandler.init(); + + mEnableStash = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + PIP_STASHING, + /* defaultValue = */ true); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, + mMainExecutor, + properties -> { + if (properties.getKeyset().contains(PIP_STASHING)) { + mEnableStash = properties.getBoolean( + PIP_STASHING, /* defaultValue = */ true); + } + }); + mStashVelocityThreshold = DeviceConfig.getFloat( + DeviceConfig.NAMESPACE_SYSTEMUI, + PIP_STASH_MINIMUM_VELOCITY_THRESHOLD, + DEFAULT_STASH_VELOCITY_THRESHOLD); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, + mMainExecutor, + properties -> { + if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) { + mStashVelocityThreshold = properties.getFloat( + PIP_STASH_MINIMUM_VELOCITY_THRESHOLD, + DEFAULT_STASH_VELOCITY_THRESHOLD); + } + }); + } + + public PipTransitionController getTransitionHandler() { + // return mPipTaskOrganizer.getTransitionController(); + return null; + } + + private void reloadResources() { + final Resources res = mContext.getResources(); + mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer); + mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset); + mPipDismissTargetHandler.updateMagneticTargetSize(); + } + + void onOverlayChanged() { + // onOverlayChanged is triggered upon theme change, update the dismiss target accordingly. + mPipDismissTargetHandler.init(); + } + + private boolean shouldShowResizeHandle() { + return false; + } + + void setTouchGesture(PipTouchGesture gesture) { + mGesture = gesture; + } + + void setTouchEnabled(boolean enabled) { + mTouchState.setAllowTouches(enabled); + } + + void showPictureInPictureMenu() { + // Only show the menu if the user isn't currently interacting with the PiP + if (!mTouchState.isUserInteracting()) { + mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(), + false /* allowMenuTimeout */, willResizeMenu(), + shouldShowResizeHandle()); + } + } + + void onActivityPinned() { + mPipDismissTargetHandler.createOrUpdateDismissTarget(); + + mPipResizeGestureHandler.onActivityPinned(); + mFloatingContentCoordinator.onContentAdded(mMotionHelper); + } + + void onActivityUnpinned(ComponentName topPipActivity) { + if (topPipActivity == null) { + // Clean up state after the last PiP activity is removed + mPipDismissTargetHandler.cleanUpDismissTarget(); + + mFloatingContentCoordinator.onContentRemoved(mMotionHelper); + } + mPipResizeGestureHandler.onActivityUnpinned(); + } + + void onPinnedStackAnimationEnded( + @PipAnimationController.TransitionDirection int direction) { + // Always synchronize the motion helper bounds once PiP animations finish + mMotionHelper.synchronizePinnedStackBounds(); + updateMovementBounds(); + if (direction == TRANSITION_DIRECTION_TO_PIP) { + // Set the initial bounds as the user resize bounds. + mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); + } + } + + void onConfigurationChanged() { + mPipResizeGestureHandler.onConfigurationChanged(); + mMotionHelper.synchronizePinnedStackBounds(); + reloadResources(); + + /* + if (mPipTaskOrganizer.isInPip()) { + // Recreate the dismiss target for the new orientation. + mPipDismissTargetHandler.createOrUpdateDismissTarget(); + } + */ + } + + void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { + mIsImeShowing = imeVisible; + mImeHeight = imeHeight; + } + + void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) { + mIsShelfShowing = shelfVisible; + mShelfHeight = shelfHeight; + } + + /** + * Called when SysUI state changed. + * + * @param isSysUiStateValid Is SysUI valid or not. + */ + public void onSystemUiStateChanged(boolean isSysUiStateValid) { + mPipResizeGestureHandler.onSystemUiStateChanged(isSysUiStateValid); + } + + void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) { + final Rect toMovementBounds = new Rect(); + mPipBoundsAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0); + final int prevBottom = mPipBoundsState.getMovementBounds().bottom + - mMovementBoundsExtraOffsets; + if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) { + outBounds.offsetTo(outBounds.left, toMovementBounds.bottom); + } + } + + /** + * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window. + */ + public void onAspectRatioChanged() { + mPipResizeGestureHandler.invalidateUserResizeBounds(); + } + + void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds, + boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) { + // Set the user resized bounds equal to the new normal bounds in case they were + // invalidated (e.g. by an aspect ratio change). + if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) { + mPipResizeGestureHandler.setUserResizeBounds(normalBounds); + } + + final int bottomOffset = mIsImeShowing ? mImeHeight : 0; + final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation); + if (fromDisplayRotationChanged) { + mTouchState.reset(); + } + + // Re-calculate the expanded bounds + Rect normalMovementBounds = new Rect(); + mPipBoundsAlgorithm.getMovementBounds(normalBounds, insetBounds, + normalMovementBounds, bottomOffset); + + if (mPipBoundsState.getMovementBounds().isEmpty()) { + // mMovementBounds is not initialized yet and a clean movement bounds without + // bottom offset shall be used later in this function. + mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds, + mPipBoundsState.getMovementBounds(), 0 /* bottomOffset */); + } + + // Calculate the expanded size + float aspectRatio = (float) normalBounds.width() / normalBounds.height(); + Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio); + mPipBoundsState.setExpandedBounds( + new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight())); + Rect expandedMovementBounds = new Rect(); + mPipBoundsAlgorithm.getMovementBounds( + mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds, + bottomOffset); + + updatePipSizeConstraints(normalBounds, aspectRatio); + + // The extra offset does not really affect the movement bounds, but are applied based on the + // current state (ime showing, or shelf offset) when we need to actually shift + int extraOffset = Math.max( + mIsImeShowing ? mImeOffset : 0, + !mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0); + + // Update the movement bounds after doing the calculations based on the old movement bounds + // above + mPipBoundsState.setNormalMovementBounds(normalMovementBounds); + mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds); + mDisplayRotation = displayRotation; + mInsetBounds.set(insetBounds); + updateMovementBounds(); + mMovementBoundsExtraOffsets = extraOffset; + + // If we have a deferred resize, apply it now + if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) { + mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction, + mPipBoundsState.getNormalMovementBounds(), mPipBoundsState.getMovementBounds(), + true /* immediate */); + mSavedSnapFraction = -1f; + mDeferResizeToNormalBoundsUntilRotation = -1; + } + } + + /** + * Update the values for min/max allowed size of picture in picture window based on the aspect + * ratio. + * @param aspectRatio aspect ratio to use for the calculation of min/max size + */ + public void updateMinMaxSize(float aspectRatio) { + updatePipSizeConstraints(mPipBoundsState.getNormalBounds(), + aspectRatio); + } + + private void updatePipSizeConstraints(Rect normalBounds, + float aspectRatio) { + if (mPipResizeGestureHandler.isUsingPinchToZoom()) { + updatePinchResizeSizeConstraints(aspectRatio); + } else { + mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height()); + mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(), + mPipBoundsState.getExpandedBounds().height()); + } + } + + private void updatePinchResizeSizeConstraints(float aspectRatio) { + mPipBoundsState.updateMinMaxSize(aspectRatio); + mPipResizeGestureHandler.updateMinSize(mPipBoundsState.getMinSize().x, + mPipBoundsState.getMinSize().y); + mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getMaxSize().x, + mPipBoundsState.getMaxSize().y); + } + + /** + * TODO Add appropriate description + */ + public void onRegistrationChanged(boolean isRegistered) { + if (isRegistered) { + // Register the accessibility connection. + } else { + mAccessibilityManager.setPictureInPictureActionReplacingConnection(null); + } + if (!isRegistered && mTouchState.isUserInteracting()) { + // If the input consumer is unregistered while the user is interacting, then we may not + // get the final TOUCH_UP event, so clean up the dismiss target as well + mPipDismissTargetHandler.cleanUpDismissTarget(); + } + } + + private void onAccessibilityShowMenu() { + mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(), + true /* allowMenuTimeout */, willResizeMenu(), + shouldShowResizeHandle()); + } + + /** + * TODO Add appropriate description + */ + public boolean handleTouchEvent(InputEvent inputEvent) { + // Skip any non motion events + if (!(inputEvent instanceof MotionEvent)) { + return true; + } + + // do not process input event if not allowed + if (!mTouchState.getAllowInputEvents()) { + return true; + } + + MotionEvent ev = (MotionEvent) inputEvent; + if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) { + // Initialize the touch state for the gesture, but immediately reset to invalidate the + // gesture + mTouchState.onTouchEvent(ev); + mTouchState.reset(); + return true; + } + + if (mPipResizeGestureHandler.hasOngoingGesture()) { + mGesture.cleanUpHighPerfSessionMaybe(); + mPipDismissTargetHandler.hideDismissTargetMaybe(); + return true; + } + + if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting()) + && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) { + // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event + // to the touch state. Touch state needs a DOWN event in order to later process MOVE + // events it'll receive if the object is dragged out of the magnetic field. + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mTouchState.onTouchEvent(ev); + } + + // Continue tracking velocity when the object is in the magnetic field, since we want to + // respect touch input velocity if the object is dragged out and then flung. + mTouchState.addMovementToVelocityTracker(ev); + + return true; + } + + if (!mTouchState.isUserInteracting()) { + ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE, + "%s: Waiting to start the entry animation, skip the motion event.", TAG); + return true; + } + + // Update the touch state + mTouchState.onTouchEvent(ev); + + boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE; + + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: { + mGesture.onDown(mTouchState); + break; + } + case MotionEvent.ACTION_MOVE: { + if (mGesture.onMove(mTouchState)) { + break; + } + + shouldDeliverToMenu = !mTouchState.isDragging(); + break; + } + case MotionEvent.ACTION_UP: { + // Update the movement bounds again if the state has changed since the user started + // dragging (ie. when the IME shows) + updateMovementBounds(); + + if (mGesture.onUp(mTouchState)) { + break; + } + } + // Fall through to clean up + case MotionEvent.ACTION_CANCEL: { + shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging(); + mTouchState.reset(); + break; + } + case MotionEvent.ACTION_HOVER_ENTER: { + // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably + // on and changing MotionEvents into HoverEvents. + // Let's not enable menu show/hide for a11y services. + if (!mAccessibilityManager.isTouchExplorationEnabled()) { + mTouchState.removeHoverExitTimeoutCallback(); + mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(), + false /* allowMenuTimeout */, false /* willResizeMenu */, + shouldShowResizeHandle()); + } + } + // Fall through + case MotionEvent.ACTION_HOVER_MOVE: { + if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) { + sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + mSendingHoverAccessibilityEvents = true; + } + break; + } + case MotionEvent.ACTION_HOVER_EXIT: { + // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably + // on and changing MotionEvents into HoverEvents. + // Let's not enable menu show/hide for a11y services. + if (!mAccessibilityManager.isTouchExplorationEnabled()) { + mTouchState.scheduleHoverExitTimeoutCallback(); + } + if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) { + sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); + mSendingHoverAccessibilityEvents = false; + } + break; + } + } + + shouldDeliverToMenu &= !mPipBoundsState.isStashed(); + + // Deliver the event to PipMenuActivity to handle button click if the menu has shown. + if (shouldDeliverToMenu) { + final MotionEvent cloneEvent = MotionEvent.obtain(ev); + // Send the cancel event and cancel menu timeout if it starts to drag. + if (mTouchState.startedDragging()) { + cloneEvent.setAction(MotionEvent.ACTION_CANCEL); + mMenuController.pokeMenu(); + } + + mMenuController.handlePointerEvent(cloneEvent); + cloneEvent.recycle(); + } + + return true; + } + + private void sendAccessibilityHoverEvent(int type) { + if (!mAccessibilityManager.isEnabled()) { + return; + } + + AccessibilityEvent event = AccessibilityEvent.obtain(type); + event.setImportantForAccessibility(true); + event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID); + event.setWindowId( + AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); + mAccessibilityManager.sendAccessibilityEvent(event); + } + + /** + * Called when the PiP menu state is in the process of animating/changing from one to another. + */ + private void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) { + if (mMenuState == menuState && !resize) { + return; + } + + if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) { + // Save the current snap fraction and if we do not drag or move the PiP, then + // we store back to this snap fraction. Otherwise, we'll reset the snap + // fraction and snap to the closest edge. + if (resize) { + // PIP is too small to show the menu actions and thus needs to be resized to a + // size that can fit them all. Resize to the default size. + animateToNormalSize(callback); + } + } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) { + // Try and restore the PiP to the closest edge, using the saved snap fraction + // if possible + if (resize && !mPipResizeGestureHandler.isResizing()) { + if (mDeferResizeToNormalBoundsUntilRotation == -1) { + // This is a very special case: when the menu is expanded and visible, + // navigating to another activity can trigger auto-enter PiP, and if the + // revealed activity has a forced rotation set, then the controller will get + // updated with the new rotation of the display. However, at the same time, + // SystemUI will try to hide the menu by creating an animation to the normal + // bounds which are now stale. In such a case we defer the animation to the + // normal bounds until after the next onMovementBoundsChanged() call to get the + // bounds in the new orientation + int displayRotation = mContext.getDisplay().getRotation(); + if (mDisplayRotation != displayRotation) { + mDeferResizeToNormalBoundsUntilRotation = displayRotation; + } + } + + if (mDeferResizeToNormalBoundsUntilRotation == -1) { + animateToUnexpandedState(getUserResizeBounds()); + } + } else { + mSavedSnapFraction = -1f; + } + } + } + + private void setMenuState(int menuState) { + mMenuState = menuState; + updateMovementBounds(); + // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip + // as well, or it can't handle a11y focus and pip menu can't perform any action. + onRegistrationChanged(menuState == MENU_STATE_NONE); + if (menuState == MENU_STATE_NONE) { + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_HIDE_MENU); + } else if (menuState == MENU_STATE_FULL) { + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_MENU); + } + } + + private void animateToMaximizedState(Runnable callback) { + Rect maxMovementBounds = new Rect(); + Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x, + mPipBoundsState.getMaxSize().y); + mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds, + mIsImeShowing ? mImeHeight : 0); + mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds, + mPipBoundsState.getMovementBounds(), maxMovementBounds, + callback); + } + + private void animateToNormalSize(Runnable callback) { + // Save the current bounds as the user-resize bounds. + mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); + + final Size minMenuSize = mMenuController.getEstimatedMinMenuSize(); + final Rect normalBounds = mPipBoundsState.getNormalBounds(); + final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, + minMenuSize); + Rect restoredMovementBounds = new Rect(); + mPipBoundsAlgorithm.getMovementBounds(destBounds, + mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0); + mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds, + mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback); + } + + private void animateToUnexpandedState(Rect restoreBounds) { + Rect restoredMovementBounds = new Rect(); + mPipBoundsAlgorithm.getMovementBounds(restoreBounds, + mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0); + mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction, + restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */); + mSavedSnapFraction = -1f; + } + + private void animateToUnStashedState() { + final Rect pipBounds = mPipBoundsState.getBounds(); + final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left; + final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom); + unStashedBounds.left = onLeftEdge ? mInsetBounds.left + : mInsetBounds.right - pipBounds.width(); + unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width() + : mInsetBounds.right; + mMotionHelper.animateToUnStashedBounds(unStashedBounds); + } + + /** + * @return the motion helper. + */ + public PipMotionHelper getMotionHelper() { + return mMotionHelper; + } + + @VisibleForTesting + public PipResizeGestureHandler getPipResizeGestureHandler() { + return mPipResizeGestureHandler; + } + + @VisibleForTesting + public void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) { + mPipResizeGestureHandler = pipResizeGestureHandler; + } + + @VisibleForTesting + public void setPipMotionHelper(PipMotionHelper pipMotionHelper) { + mMotionHelper = pipMotionHelper; + } + + Rect getUserResizeBounds() { + return mPipResizeGestureHandler.getUserResizeBounds(); + } + + /** + * Sets the user resize bounds tracked by {@link PipResizeGestureHandler} + */ + void setUserResizeBounds(Rect bounds) { + mPipResizeGestureHandler.setUserResizeBounds(bounds); + } + + /** + * Gesture controlling normal movement of the PIP. + */ + private class DefaultPipTouchGesture extends PipTouchGesture { + private final Point mStartPosition = new Point(); + private final PointF mDelta = new PointF(); + private boolean mShouldHideMenuAfterFling; + + @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession; + + private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {} + + @Override + public void cleanUpHighPerfSessionMaybe() { + if (mPipHighPerfSession != null) { + // Close the high perf session once pointer interactions are over; + mPipHighPerfSession.close(); + mPipHighPerfSession = null; + } + } + + @Override + public void onDown(PipTouchState touchState) { + if (!touchState.isUserInteracting()) { + return; + } + + if (mPipPerfHintController != null) { + // Cache the PiP high perf session to close it upon touch up. + mPipHighPerfSession = mPipPerfHintController.startSession( + this::onHighPerfSessionTimeout, "DefaultPipTouchGesture#onDown"); + } + + Rect bounds = getPossiblyMotionBounds(); + mDelta.set(0f, 0f); + mStartPosition.set(bounds.left, bounds.top); + mMovementWithinDismiss = touchState.getDownTouchPosition().y + >= mPipBoundsState.getMovementBounds().bottom; + mMotionHelper.setSpringingToTouch(false); + // mPipDismissTargetHandler.setTaskLeash(mPipTaskOrganizer.getSurfaceControl()); + + // If the menu is still visible then just poke the menu + // so that it will timeout after the user stops touching it + if (mMenuState != MENU_STATE_NONE && !mPipBoundsState.isStashed()) { + mMenuController.pokeMenu(); + } + } + + @Override + public boolean onMove(PipTouchState touchState) { + if (!touchState.isUserInteracting()) { + return false; + } + + if (touchState.startedDragging()) { + mSavedSnapFraction = -1f; + mPipDismissTargetHandler.showDismissTargetMaybe(); + } + + if (touchState.isDragging()) { + mPipBoundsState.setHasUserMovedPip(true); + + // Move the pinned stack freely + final PointF lastDelta = touchState.getLastTouchDelta(); + float lastX = mStartPosition.x + mDelta.x; + float lastY = mStartPosition.y + mDelta.y; + float left = lastX + lastDelta.x; + float top = lastY + lastDelta.y; + + // Add to the cumulative delta after bounding the position + mDelta.x += left - lastX; + mDelta.y += top - lastY; + + mTmpBounds.set(getPossiblyMotionBounds()); + mTmpBounds.offsetTo((int) left, (int) top); + mMotionHelper.movePip(mTmpBounds, true /* isDragging */); + + final PointF curPos = touchState.getLastTouchPosition(); + if (mMovementWithinDismiss) { + // Track if movement remains near the bottom edge to identify swipe to dismiss + mMovementWithinDismiss = curPos.y >= mPipBoundsState.getMovementBounds().bottom; + } + return true; + } + return false; + } + + @Override + public boolean onUp(PipTouchState touchState) { + mPipDismissTargetHandler.hideDismissTargetMaybe(); + mPipDismissTargetHandler.setTaskLeash(null); + + if (!touchState.isUserInteracting()) { + return false; + } + + final PointF vel = touchState.getVelocity(); + + if (touchState.isDragging()) { + if (mMenuState != MENU_STATE_NONE) { + // If the menu is still visible, then just poke the menu so that + // it will timeout after the user stops touching it + mMenuController.showMenu(mMenuState, mPipBoundsState.getBounds(), + true /* allowMenuTimeout */, willResizeMenu(), + shouldShowResizeHandle()); + } + mShouldHideMenuAfterFling = mMenuState == MENU_STATE_NONE; + + // Reset the touch state on up before the fling settles + mTouchState.reset(); + if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) { + mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */); + } else { + if (mPipBoundsState.isStashed()) { + // Reset stashed state if previously stashed + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); + mPipBoundsState.setStashed(STASH_TYPE_NONE); + } + mMotionHelper.flingToSnapTarget(vel.x, vel.y, + this::flingEndAction /* endAction */); + } + } else if (mTouchState.isDoubleTap() && !mPipBoundsState.isStashed() + && mMenuState != MENU_STATE_FULL) { + // If using pinch to zoom, double-tap functions as resizing between max/min size + if (mPipResizeGestureHandler.isUsingPinchToZoom()) { + final boolean toExpand = mPipBoundsState.getBounds().width() + < mPipBoundsState.getMaxSize().x + && mPipBoundsState.getBounds().height() + < mPipBoundsState.getMaxSize().y; + if (mMenuController.isMenuVisible()) { + mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */); + } + + // the size to toggle to after a double tap + int nextSize = PipDoubleTapHelper + .nextSizeSpec(mPipBoundsState, getUserResizeBounds()); + + // actually toggle to the size chosen + if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) { + mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); + animateToMaximizedState(null); + } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) { + mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); + animateToNormalSize(null); + } else { + animateToUnexpandedState(getUserResizeBounds()); + } + } else { + // Expand to fullscreen if this is a double tap + // the PiP should be frozen until the transition ends + setTouchEnabled(false); + mMotionHelper.expandLeavePip(false /* skipAnimation */); + } + } else if (mMenuState != MENU_STATE_FULL) { + if (mPipBoundsState.isStashed()) { + // Unstash immediately if stashed, and don't wait for the double tap timeout + animateToUnStashedState(); + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); + mPipBoundsState.setStashed(STASH_TYPE_NONE); + mTouchState.removeDoubleTapTimeoutCallback(); + } else if (!mTouchState.isWaitingForDoubleTap()) { + // User has stalled long enough for this not to be a drag or a double tap, + // just expand the menu + mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(), + true /* allowMenuTimeout */, willResizeMenu(), + shouldShowResizeHandle()); + } else { + // Next touch event _may_ be the second tap for the double-tap, schedule a + // fallback runnable to trigger the menu if no touch event occurs before the + // next tap + mTouchState.scheduleDoubleTapTimeoutCallback(); + } + } + cleanUpHighPerfSessionMaybe(); + return true; + } + + private void stashEndAction() { + if (mPipBoundsState.getBounds().left < 0 + && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) { + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT); + mPipBoundsState.setStashed(STASH_TYPE_LEFT); + } else if (mPipBoundsState.getBounds().left >= 0 + && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) { + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT); + mPipBoundsState.setStashed(STASH_TYPE_RIGHT); + } + mMenuController.hideMenu(); + } + + private void flingEndAction() { + if (mShouldHideMenuAfterFling) { + // If the menu is not visible, then we can still be showing the activity for the + // dismiss overlay, so just finish it after the animation completes + mMenuController.hideMenu(); + } + } + + private boolean shouldStash(PointF vel, Rect motionBounds) { + final boolean flingToLeft = vel.x < -mStashVelocityThreshold; + final boolean flingToRight = vel.x > mStashVelocityThreshold; + final int offset = motionBounds.width() / 2; + final boolean droppingOnLeft = + motionBounds.left < mPipBoundsState.getDisplayBounds().left - offset; + final boolean droppingOnRight = + motionBounds.right > mPipBoundsState.getDisplayBounds().right + offset; + + // Do not allow stash if the destination edge contains display cutout. We only + // compare the left and right edges since we do not allow stash on top / bottom. + final DisplayCutout displayCutout = + mPipBoundsState.getDisplayLayout().getDisplayCutout(); + if (displayCutout != null) { + if ((flingToLeft || droppingOnLeft) + && !displayCutout.getBoundingRectLeft().isEmpty()) { + return false; + } else if ((flingToRight || droppingOnRight) + && !displayCutout.getBoundingRectRight().isEmpty()) { + return false; + } + } + + // If user flings the PIP window above the minimum velocity, stash PIP. + // Only allow stashing to the edge if PIP wasn't previously stashed on the opposite + // edge. + final boolean stashFromFlingToEdge = + (flingToLeft && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) + || (flingToRight && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT); + + // If User releases the PIP window while it's out of the display bounds, put + // PIP into stashed mode. + final boolean stashFromDroppingOnEdge = droppingOnLeft || droppingOnRight; + + return stashFromFlingToEdge || stashFromDroppingOnEdge; + } + } + + /** + * Updates the current movement bounds based on whether the menu is currently visible and + * resized. + */ + private void updateMovementBounds() { + mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(), + mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0); + mMotionHelper.onMovementBoundsChanged(); + } + + private Rect getMovementBounds(Rect curBounds) { + Rect movementBounds = new Rect(); + mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds, + movementBounds, mIsImeShowing ? mImeHeight : 0); + return movementBounds; + } + + /** + * @return {@code true} if the menu should be resized on tap because app explicitly specifies + * PiP window size that is too small to hold all the actions. + */ + private boolean willResizeMenu() { + if (!mEnableResize) { + return false; + } + final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize(); + if (estimatedMinMenuSize == null) { + ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE, + "%s: Failed to get estimated menu size", TAG); + return false; + } + final Rect currentBounds = mPipBoundsState.getBounds(); + return currentBounds.width() < estimatedMinMenuSize.getWidth() + || currentBounds.height() < estimatedMinMenuSize.getHeight(); + } + + /** + * Returns the PIP bounds if we're not in the middle of a motion operation, or the current, + * temporary motion bounds otherwise. + */ + Rect getPossiblyMotionBounds() { + return mPipBoundsState.getMotionBoundsState().isInMotion() + ? mPipBoundsState.getMotionBoundsState().getBoundsInMotion() + : mPipBoundsState.getBounds(); + } + + void setOhmOffset(int offset) { + mPipResizeGestureHandler.setOhmOffset(offset); + } + + /** + * Dumps the {@link PipTouchHandler} state. + */ + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "mMenuState=" + mMenuState); + pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing); + pw.println(innerPrefix + "mImeHeight=" + mImeHeight); + pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing); + pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight); + pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction); + pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets); + mPipBoundsAlgorithm.dump(pw, innerPrefix); + mTouchState.dump(pw, innerPrefix); + if (mPipResizeGestureHandler != null) { + mPipResizeGestureHandler.dump(pw, innerPrefix); + } + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 576219769e61..6aad4e2c9da4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -18,9 +18,14 @@ package com.android.wm.shell.splitscreen; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.graphics.Rect; +import android.os.Bundle; +import android.window.RemoteTransition; +import com.android.internal.logging.InstanceId; +import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.shared.annotations.ExternalThread; @@ -72,6 +77,12 @@ public interface SplitScreen { } } + /** Launches a pair of tasks into splitscreen */ + void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, + @Nullable Bundle options2, @SplitPosition int splitPosition, + @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + InstanceId instanceId); + /** Registers listener that gets split screen callback. */ void registerSplitScreenListener(@NonNull SplitScreenListener listener, @NonNull Executor executor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 088bb4814491..547457b018a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -506,6 +506,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator.getActivateSplitPosition(taskInfo); } + /** Start two tasks in parallel as a splitscreen pair. */ + public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, + @Nullable Bundle options2, @SplitPosition int splitPosition, + @PersistentSnapPosition int snapPosition, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + mStageCoordinator.startTasks(taskId1, options1, taskId2, options2, splitPosition, + snapPosition, remoteTransition, instanceId); + } + /** * Move a task to split select * @param taskInfo the task being moved to split select @@ -1120,6 +1129,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, }; @Override + public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, + @Nullable Bundle options2, int splitPosition, int snapPosition, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + mMainExecutor.execute(() -> SplitScreenController.this.startTasks( + taskId1, options1, taskId2, options2, splitPosition, snapPosition, + remoteTransition, instanceId)); + } + + @Override public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) { if (mExecutors.containsKey(listener)) return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 74e85f8dd468..9adb67c8a65e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -507,6 +507,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final Point animRelOffset = new Point( change.getEndAbsBounds().left - animRoot.getOffset().x, change.getEndAbsBounds().top - animRoot.getOffset().y); + + if (change.getActivityComponent() != null) { + // For appcompat letterbox: we intentionally report the task-bounds so that we + // can animate as-if letterboxes are "part of" the activity. This means we can't + // always rely solely on endAbsBounds and need to also max with endRelOffset. + animRelOffset.x = Math.max(animRelOffset.x, change.getEndRelOffset().x); + animRelOffset.y = Math.max(animRelOffset.y, change.getEndRelOffset().y); + } + if (change.getActivityComponent() != null && !isActivityLevel) { // At this point, this is an independent activity change in a non-activity // transition. This means that an activity transition got erroneously combined diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index c26604a84a61..7c2ba455c0c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -293,7 +293,13 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public void onFoldStateChanged(boolean isFolded) { if (isFolded) { + // Reset unfold animation finished flag on folding, so it could be used next time + // when we unfold the device as an indication that animation hasn't finished yet mAnimationFinished = false; + + // If we are currently animating unfold animation we should finish it because + // the animation might not start and finish as the device was folded + finishTransitionIfNeeded(); } } 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 d0879434657d..963b1303c379 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 @@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.windowingModeToString; +import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; @@ -28,6 +29,7 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; @@ -39,6 +41,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.util.Log; import android.view.Choreographer; import android.view.MotionEvent; import android.view.SurfaceControl; @@ -433,15 +436,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } private void loadAppInfo() { + final ActivityInfo activityInfo = mTaskInfo.topActivityInfo; + if (activityInfo == null) { + Log.e(TAG, "Top activity info not found in task"); + return; + } PackageManager pm = mContext.getApplicationContext().getPackageManager(); final IconProvider provider = new IconProvider(mContext); - mAppIconDrawable = provider.getIcon(mTaskInfo.topActivityInfo); + mAppIconDrawable = provider.getIcon(activityInfo); final Resources resources = mContext.getResources(); final BaseIconFactory factory = new BaseIconFactory(mContext, resources.getDisplayMetrics().densityDpi, resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius)); mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT); - final ApplicationInfo applicationInfo = mTaskInfo.topActivityInfo.applicationInfo; + final ApplicationInfo applicationInfo = activityInfo.applicationInfo; mAppName = pm.getApplicationLabel(applicationInfo); } @@ -752,7 +760,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final int action = ev.getActionMasked(); // The comparison against ACTION_UP is needed for the cancel drag to desktop case. handle.setHovered(inHandle && action != ACTION_UP); - handle.setPressed(inHandle && action == ACTION_DOWN); + // We want handle to remain pressed if the pointer moves outside of it during a drag. + handle.setPressed((inHandle && action == ACTION_DOWN) + || (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL)); if (isHandleMenuActive()) { mHandleMenu.checkMotionEvent(ev); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt new file mode 100644 index 000000000000..b21c3f522eab --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt @@ -0,0 +1,79 @@ +/* + * 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.animation.ValueAnimator +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageButton + +/** + * [ImageButton] for the handle at the top of fullscreen apps. Has custom hover + * and press handling to grow the handle on hover enter and shrink the handle on + * hover exit and press. + */ +class HandleImageButton (context: Context?, attrs: AttributeSet?) : + ImageButton(context, attrs) { + private val handleAnimator = ValueAnimator() + + override fun onHoverChanged(hovered: Boolean) { + super.onHoverChanged(hovered) + if (hovered) { + animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_HOVER_ENTER_SCALE) + } else { + if (!isPressed) { + animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_DEFAULT_SCALE) + } + } + } + + override fun setPressed(pressed: Boolean) { + if (isPressed != pressed) { + super.setPressed(pressed) + if (pressed) { + animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_PRESS_DOWN_SCALE) + } else { + animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_DEFAULT_SCALE) + } + } + } + + private fun animateHandle(duration: Long, endScale: Float) { + if (handleAnimator.isRunning) { + handleAnimator.cancel() + } + handleAnimator.duration = duration + handleAnimator.setFloatValues(scaleX, endScale) + handleAnimator.addUpdateListener { animator -> + scaleX = animator.animatedValue as Float + } + handleAnimator.start() + } + + companion object { + /** The duration of animations related to hover state. **/ + private const val HANDLE_HOVER_ANIM_DURATION = 300L + /** The duration of animations related to pressed state. **/ + private const val HANDLE_PRESS_ANIM_DURATION = 200L + /** Ending scale for hover enter. **/ + private const val HANDLE_HOVER_ENTER_SCALE = 1.2f + /** Ending scale for press down. **/ + private const val HANDLE_PRESS_DOWN_SCALE = 0.85f + /** Default scale for handle. **/ + private const val HANDLE_DEFAULT_SCALE = 1f + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt index 2fda3ea8daee..7898567b70e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt @@ -21,7 +21,7 @@ import android.view.MotionEvent import android.widget.ImageButton /** - * A custom [ImageButton] that intentionally does not handle hover events. + * A custom [ImageButton] for buttons inside handle menu that intentionally doesn't handle hovers. * This is due to the hover events being handled by [DesktopModeWindowDecorViewModel] * in order to take the status bar layer into account. Handling it in both classes results in a * flicker when the hover moves from outside to inside status bar layer. diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt index 3380adac0b3f..e9eabb4162e3 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.appcompat import android.content.Context -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.FlickerTestData import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.traces.component.ComponentNameMatcher import com.android.server.wm.flicker.helpers.LetterboxAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseTest diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt index f08eba5a73a3..16c2d47f9db3 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt @@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.appcompat import android.platform.test.annotations.Postsubmit import android.tools.flicker.assertions.FlickerTest -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.RequiresDevice import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt index 826fc541687e..d85b7718aa56 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt @@ -19,11 +19,11 @@ package com.android.wm.shell.flicker.appcompat import android.platform.test.annotations.Postsubmit import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.RequiresDevice import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt index 26e78bf625ba..164534c14d28 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -16,17 +16,17 @@ package com.android.wm.shell.flicker.appcompat +import android.graphics.Rect import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.RequiresDevice import android.tools.NavBar import android.tools.Rotation -import android.tools.datatypes.Rect import android.tools.flicker.assertions.FlickerTest -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -260,7 +260,7 @@ class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAp companion object { /** {@inheritDoc} */ - private var startDisplayBounds = Rect.EMPTY + private var startDisplayBounds = Rect() @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt index 2aa84b4e55b8..034d54b185ed 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt @@ -53,7 +53,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) class RepositionFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) { - val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation).bounds + val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt index 7ffa23345589..22543aa9f773 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt @@ -16,19 +16,19 @@ package com.android.wm.shell.flicker.appcompat +import android.graphics.Rect import android.os.Build import android.platform.test.annotations.Postsubmit import android.system.helpers.CommandsHelper import android.tools.NavBar import android.tools.Rotation -import android.tools.datatypes.Rect import android.tools.flicker.assertions.FlickerTest -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.FIND_TIMEOUT +import android.tools.traces.component.ComponentNameMatcher import android.tools.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice @@ -167,7 +167,7 @@ class RotateImmersiveAppInFullscreenTest(flicker: LegacyFlickerTest) : BaseAppCo } companion object { - private var startDisplayBounds = Rect.EMPTY + private var startDisplayBounds = Rect() const val LAUNCHER_PACKAGE = "com.google.android.apps.nexuslauncher" /** diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt index 521c0d0aaeb7..2a9b1078afe3 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt @@ -19,11 +19,11 @@ package com.android.wm.shell.flicker.bubble import android.content.Context import android.graphics.Point import android.platform.test.annotations.Presubmit -import android.tools.flicker.subject.layers.LayersTraceSubject -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.flicker.subject.layers.LayersTraceSubject +import android.tools.traces.component.ComponentNameMatcher import android.util.DisplayMetrics import android.view.WindowManager import androidx.test.uiautomator.By diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt index e059ac78dc6b..9ef49c1c9e7e 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.Postsubmit -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.traces.component.ComponentNameMatcher import android.view.WindowInsets import android.view.WindowManager import androidx.test.filters.FlakyTest diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt index a0edcfb17971..371fee225b34 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.traces.component.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -66,9 +66,7 @@ class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) : @Test fun pipOverlayNotShown() { val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY - flicker.assertLayers { - this.notContains(overlay) - } + flicker.assertLayers { this.notContains(overlay) } } @Presubmit @Test @@ -83,4 +81,4 @@ class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) : // auto enter and sourceRectHint that causes the app to move outside of the display // bounds during the transition. } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt index 031acf4919eb..1c0820a2b0db 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.traces.component.ComponentNameMatcher import com.android.wm.shell.flicker.pip.common.ClosePipTransition import org.junit.FixMethodOrder import org.junit.Test @@ -69,7 +69,8 @@ class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition wmHelper.currentState.layerState .getLayerWithBuffer(barComponent) ?.visibleRegion - ?.height + ?.bounds + ?.height() ?: error("Couldn't find Nav or Task bar layer") // The dismiss button doesn't appear at the complete bottom of the screen, // it appears above the hot seat but `hotseatBarSize` is not available outside diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index 9a1bd267ea1f..270ebf5dd29b 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -21,12 +21,12 @@ import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils +import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 25614ef63ccc..eeff167b1fc4 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.Rotation -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder import org.junit.Test diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt index b94989d98e97..12e395db8396 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt @@ -24,6 +24,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils import android.tools.traces.parsers.toFlickerComponent +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions @@ -143,6 +144,10 @@ class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : } } + @FlakyTest(bugId = 293133362) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt index 5f25d70acf7c..f81e8490b0eb 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt @@ -191,8 +191,9 @@ class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams() = LegacyFlickerTestFactory.nonRotationTests( - supportedRotations = listOf(Rotation.ROTATION_0) - ) + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt index e184cf04e4ae..ad3c69eae06a 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt @@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils +import android.tools.traces.component.ComponentNameMatcher import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.pip.common.PipTransition diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index 68417066ac0a..16d08e5e9055 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.Rotation -import android.tools.flicker.subject.exceptions.IncorrectRegionException import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.flicker.subject.exceptions.IncorrectRegionException import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.pip.common.PipTransition import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt index c9f4a6ca75b1..65b60ce1022b 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt @@ -18,12 +18,12 @@ package com.android.wm.shell.flicker.pip.apps import android.platform.test.annotations.Postsubmit import android.tools.Rotation -import android.tools.traces.component.ComponentNameMatcher import android.tools.device.apphelpers.StandardAppHelper import android.tools.flicker.junit.FlickerBuilderProvider import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.Test import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt index 88650107e63a..1fc9d9910a15 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt @@ -65,8 +65,8 @@ import org.junit.runners.Parameterized open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) { override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation) - override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS, - Manifest.permission.ACCESS_FINE_LOCATION) + override val permissions: Array<String> = + arrayOf(Manifest.permission.POST_NOTIFICATIONS, Manifest.permission.ACCESS_FINE_LOCATION) val locationManager: LocationManager = instrumentation.context.getSystemService(Context.LOCATION_SERVICE) as LocationManager diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt index e85da30440cf..3a0eeb67995b 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt @@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.pip.apps import android.Manifest import android.platform.test.annotations.Postsubmit import android.tools.Rotation -import android.tools.traces.component.ComponentNameMatcher import android.tools.device.apphelpers.NetflixAppHelper import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils +import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.statusBarLayerPositionAtEnd import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt index 3ae5937df4d0..35ed8de3a464 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt @@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip.apps import android.Manifest import android.platform.test.annotations.Postsubmit -import android.tools.traces.component.ComponentNameMatcher import android.tools.device.apphelpers.YouTubeAppHelper import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.RequiresDevice import org.junit.Assume import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt index de8e7c3b35b1..879034f32514 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt @@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.pip.apps import android.Manifest import android.platform.test.annotations.Postsubmit import android.tools.Rotation -import android.tools.traces.component.ComponentNameMatcher import android.tools.device.apphelpers.YouTubeAppHelper import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils +import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.statusBarLayerPositionAtEnd import org.junit.Assume diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt index dc122590388f..8cb81b46cf4d 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt @@ -18,10 +18,10 @@ package com.android.wm.shell.flicker.pip.common import android.platform.test.annotations.Presubmit import android.tools.Rotation -import android.tools.traces.component.ComponentNameMatcher.Companion.LAUNCHER import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher.Companion.LAUNCHER import com.android.server.wm.flicker.helpers.setRotation import org.junit.Test import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt index 3d9eae62b499..6dd3a175da65 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt @@ -18,10 +18,10 @@ package com.android.wm.shell.flicker.pip.common import android.platform.test.annotations.Presubmit import android.tools.Rotation -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher import org.junit.Test import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt index 7b6839dc123f..0742cf9c5887 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt @@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.pip.common import android.platform.test.annotations.Presubmit import android.tools.Rotation -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher import com.android.server.wm.flicker.helpers.SimpleAppHelper import org.junit.Test import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt index f4baf5f75928..c4881e7e17a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt @@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.pip.common import android.platform.test.annotations.Presubmit import android.tools.Rotation -import android.tools.flicker.subject.region.RegionSubject import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.flicker.subject.region.RegionSubject import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper import com.android.wm.shell.flicker.utils.Direction import org.junit.Test diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt index fd467e32e0dc..99c1ad2aaa4e 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt @@ -20,11 +20,11 @@ import android.app.Instrumentation import android.content.Intent import android.platform.test.annotations.Presubmit import android.tools.Rotation -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import android.tools.helpers.WindowUtils +import android.tools.traces.component.ComponentNameMatcher import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.testapp.ActivityOptions diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt index 9e6a686861c8..bcd0f126daef 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt @@ -24,6 +24,7 @@ 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.MultiWindowUtils import com.android.wm.shell.flicker.service.common.Utils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After @@ -51,6 +52,10 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { fun setup() { Assume.assumeTrue(tapl.isTablet) + MultiWindowUtils.executeShellCommand( + instrumentation, + "settings put system notification_cooldown_enabled 0" + ) // Send a notification sendNotificationApp.launchViaIntent(wmHelper) sendNotificationApp.postNotification(wmHelper) @@ -74,5 +79,10 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { primaryApp.exit(wmHelper) secondaryApp.exit(wmHelper) sendNotificationApp.exit(wmHelper) + + MultiWindowUtils.executeShellCommand( + instrumentation, + "settings reset system notification_cooldown_enabled" + ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt index 9312c0aebf98..db962e717a3b 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt @@ -141,7 +141,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private fun isLandscape(rotation: Rotation): Boolean { val displayBounds = WindowUtils.getDisplayBounds(rotation) - return displayBounds.width > displayBounds.height + return displayBounds.width() > displayBounds.height() } private fun isTablet(): Boolean { diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index d74c59ef0879..7f48499b0558 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -17,12 +17,12 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.Presubmit -import android.tools.traces.component.ComponentNameMatcher -import android.tools.traces.component.EdgeExtensionComponentMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher +import android.tools.traces.component.EdgeExtensionComponentMatcher import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt index 8724346427f4..a72b3d15eb9e 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt @@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.Presubmit import android.tools.NavBar -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.flicker.splitscreen.benchmark.SplitScreenBase diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt index 16d73318bd3a..90453640c91a 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt @@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.NavBar -import android.tools.flicker.subject.layers.LayersTraceSubject -import android.tools.flicker.subject.region.RegionSubject -import android.tools.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.flicker.subject.layers.LayersTraceSubject +import android.tools.flicker.subject.region.RegionSubject +import android.tools.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index 9c5a3fe35bfe..7e8e50843b90 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -16,11 +16,11 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory +import android.tools.traces.component.ComponentNameMatcher import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index 38206c396efb..6a6aa1abc9f3 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -128,7 +128,7 @@ abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: Legacy private fun isLandscape(rotation: Rotation): Boolean { val displayBounds = WindowUtils.getDisplayBounds(rotation) - return displayBounds.width > displayBounds.height + return displayBounds.width() > displayBounds.height() } companion object { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt index a19d232c9a2f..90d2635f6a51 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt @@ -17,8 +17,8 @@ package com.android.wm.shell.flicker import android.app.Instrumentation -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.traces.component.ComponentNameMatcher import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation import com.android.wm.shell.flicker.utils.ICommonAssertions diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt index 3df0954da2e9..509f4f202b6b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt @@ -18,13 +18,13 @@ package com.android.wm.shell.flicker.utils +import android.graphics.Region import android.tools.Rotation -import android.tools.datatypes.Region +import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.subject.layers.LayerTraceEntrySubject import android.tools.flicker.subject.layers.LayersTraceSubject -import android.tools.traces.component.IComponentMatcher -import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.helpers.WindowUtils +import android.tools.traces.component.IComponentMatcher fun LegacyFlickerTest.appPairsDividerIsVisibleAtEnd() { assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } @@ -263,41 +263,41 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( val displayBounds = WindowUtils.getDisplayBounds(rotation) return invoke { val dividerRegion = - layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region + layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region?.bounds ?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found") visibleRegion(component).isNotEmpty() visibleRegion(component) .coversAtMost( - if (displayBounds.width > displayBounds.height) { + if (displayBounds.width() > displayBounds.height()) { if (landscapePosLeft) { - Region.from( + Region( 0, 0, - (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2, - displayBounds.bounds.bottom + (dividerRegion.left + dividerRegion.right) / 2, + displayBounds.bottom ) } else { - Region.from( - (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2, + Region( + (dividerRegion.left + dividerRegion.right) / 2, 0, - displayBounds.bounds.right, - displayBounds.bounds.bottom + displayBounds.right, + displayBounds.bottom ) } } else { if (portraitPosTop) { - Region.from( + Region( 0, 0, - displayBounds.bounds.right, - (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2 + displayBounds.right, + (dividerRegion.top + dividerRegion.bottom) / 2 ) } else { - Region.from( + Region( 0, - (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2, - displayBounds.bounds.right, - displayBounds.bounds.bottom + (dividerRegion.top + dividerRegion.bottom) / 2, + displayBounds.right, + displayBounds.bottom ) } } @@ -420,17 +420,17 @@ fun LegacyFlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd( fun getPrimaryRegion(dividerRegion: Region, rotation: Rotation): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) return if (rotation.isRotated()) { - Region.from( + Region( 0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset, - displayBounds.bounds.bottom + displayBounds.bottom ) } else { - Region.from( + Region( 0, 0, - displayBounds.bounds.right, + displayBounds.right, dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset ) } @@ -439,18 +439,18 @@ fun getPrimaryRegion(dividerRegion: Region, rotation: Rotation): Region { fun getSecondaryRegion(dividerRegion: Region, rotation: Rotation): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) return if (rotation.isRotated()) { - Region.from( + Region( dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0, - displayBounds.bounds.right, - displayBounds.bounds.bottom + displayBounds.right, + displayBounds.bottom ) } else { - Region.from( + Region( 0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset, - displayBounds.bounds.right, - displayBounds.bounds.bottom + displayBounds.right, + displayBounds.bottom ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt index 50c04354528f..4465a16a8e0f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt @@ -17,8 +17,8 @@ package com.android.wm.shell.flicker.utils import android.platform.test.annotations.Presubmit -import android.tools.traces.component.ComponentNameMatcher import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.traces.component.ComponentNameMatcher import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt index 9cc3a989894e..c4954f90179c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt @@ -20,11 +20,11 @@ import android.app.Instrumentation import android.graphics.Point import android.os.SystemClock import android.tools.Rotation +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.traces.component.ComponentNameMatcher import android.tools.traces.component.IComponentMatcher import android.tools.traces.component.IComponentNameMatcher -import android.tools.device.apphelpers.StandardAppHelper -import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.traces.parsers.WindowManagerStateHelper import android.tools.traces.parsers.toFlickerComponent import android.view.InputDevice @@ -182,13 +182,7 @@ object SplitScreenUtils { val swipeXCoordinate = displayBounds.centerX() / 2 // Pull down the notifications - device.swipe( - swipeXCoordinate, - 5, - swipeXCoordinate, - displayBounds.bottom, - 50 /* steps */ - ) + device.swipe(swipeXCoordinate, 5, swipeXCoordinate, displayBounds.bottom, 50 /* steps */) SystemClock.sleep(TIMEOUT_MS) // Find the target notification @@ -211,7 +205,7 @@ object SplitScreenUtils { // Drag to split val dragStart = notificationContent.visibleCenter val dragMiddle = Point(dragStart.x + 50, dragStart.y) - val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) + val dragEnd = Point(displayBounds.width() / 4, displayBounds.width() / 4) val downTime = SystemClock.uptimeMillis() touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart) @@ -318,7 +312,7 @@ object SplitScreenUtils { wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace ?: error("Display not found") val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) - dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200) + dividerBar.drag(Point(displayBounds.width() * 1 / 3, displayBounds.height() * 2 / 3), 200) wmHelper .StateSyncBuilder() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 2ff1ddd8c0c8..9c623bd5b76f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -120,6 +120,8 @@ public class BackAnimationControllerTest extends ShellTestCase { private TestableContentResolver mContentResolver; private TestableLooper mTestableLooper; + private CrossActivityBackAnimation mCrossActivityBackAnimation; + private CrossTaskBackAnimation mCrossTaskBackAnimation; private ShellBackAnimationRegistry mShellBackAnimationRegistry; @Before @@ -133,11 +135,11 @@ public class BackAnimationControllerTest extends ShellTestCase { ANIMATION_ENABLED); mTestableLooper = TestableLooper.get(this); mShellInit = spy(new ShellInit(mShellExecutor)); + mCrossActivityBackAnimation = new CrossActivityBackAnimation(mContext, mAnimationBackground, + mRootTaskDisplayAreaOrganizer); + mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground); mShellBackAnimationRegistry = - new ShellBackAnimationRegistry( - new CrossActivityBackAnimation( - mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer), - new CrossTaskBackAnimation(mContext, mAnimationBackground), + new ShellBackAnimationRegistry(mCrossActivityBackAnimation, mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null, new CustomizeActivityAnimation(mContext, mAnimationBackground), /* defaultBackToHomeAnimation= */ null); @@ -576,16 +578,14 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void testBackToActivity() throws RemoteException { - final CrossActivityBackAnimation animation = new CrossActivityBackAnimation( - mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer); - verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner()); + verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, + mCrossActivityBackAnimation.getRunner()); } @Test public void testBackToTask() throws RemoteException { - final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext, - mAnimationBackground); - verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, animation.getRunner()); + verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, + mCrossTaskBackAnimation.getRunner()); } private void verifySystemBackBehavior(int type, BackAnimationRunner animation) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 48e396a4817f..6be411dd81d0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -1222,6 +1222,19 @@ public class BubbleDataTest extends ShellTestCase { assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT); } + @Test + public void setSelectedBubbleAndExpandStack() { + sendUpdatedEntryAtTime(mEntryA1, 1000); + sendUpdatedEntryAtTime(mEntryA2, 2000); + mBubbleData.setListener(mListener); + + mBubbleData.setSelectedBubbleAndExpandStack(mBubbleA1); + + verifyUpdateReceived(); + assertSelectionChangedTo(mBubbleA1); + assertExpandedChangedTo(true); + } + private void verifyUpdateReceived() { verify(mListener).applyUpdate(mUpdateCaptor.capture()); reset(mListener); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index e9da25813510..2366917a0158 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -83,6 +83,7 @@ import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; import android.window.IWindowContainerToken; import android.window.RemoteTransition; +import android.window.RemoteTransitionStub; import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -280,7 +281,7 @@ public class ShellTransitionTests extends ShellTestCase { final boolean[] remoteCalled = new boolean[]{false}; final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction(); - IRemoteTransition testRemote = new IRemoteTransition.Stub() { + IRemoteTransition testRemote = new RemoteTransitionStub() { @Override public void startAnimation(IBinder token, TransitionInfo info, SurfaceControl.Transaction t, @@ -288,16 +289,6 @@ public class ShellTransitionTests extends ShellTestCase { remoteCalled[0] = true; finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */); } - - @Override - public void mergeAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { - } - - @Override - public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { - } }; IBinder transitToken = new Binder(); transitions.requestStartTransition(transitToken, @@ -450,7 +441,7 @@ public class ShellTransitionTests extends ShellTestCase { transitions.replaceDefaultHandlerForTest(mDefaultHandler); final boolean[] remoteCalled = new boolean[]{false}; - IRemoteTransition testRemote = new IRemoteTransition.Stub() { + IRemoteTransition testRemote = new RemoteTransitionStub() { @Override public void startAnimation(IBinder token, TransitionInfo info, SurfaceControl.Transaction t, @@ -458,16 +449,6 @@ public class ShellTransitionTests extends ShellTestCase { remoteCalled[0] = true; finishCallback.onTransitionFinished(null /* wct */, null /* sct */); } - - @Override - public void mergeAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { - } - - @Override - public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { - } }; TransitionFilter filter = new TransitionFilter(); @@ -500,7 +481,7 @@ public class ShellTransitionTests extends ShellTestCase { final boolean[] remoteCalled = new boolean[]{false}; final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction(); - IRemoteTransition testRemote = new IRemoteTransition.Stub() { + IRemoteTransition testRemote = new RemoteTransitionStub() { @Override public void startAnimation(IBinder token, TransitionInfo info, SurfaceControl.Transaction t, @@ -508,16 +489,6 @@ public class ShellTransitionTests extends ShellTestCase { remoteCalled[0] = true; finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */); } - - @Override - public void mergeAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { - } - - @Override - public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { - } }; final int transitType = TRANSIT_FIRST_CUSTOM + 1; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java index 87330d2dc877..184e8955d08c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java @@ -20,6 +20,7 @@ import android.os.RemoteException; import android.view.SurfaceControl; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; +import android.window.RemoteTransitionStub; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; @@ -29,7 +30,7 @@ import android.window.WindowContainerTransaction; * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction, * IRemoteTransitionFinishedCallback)} being called. */ -public class TestRemoteTransition extends IRemoteTransition.Stub { +public class TestRemoteTransition extends RemoteTransitionStub { private boolean mCalled = false; private boolean mConsumed = false; final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction(); @@ -44,12 +45,6 @@ public class TestRemoteTransition extends IRemoteTransition.Stub { } @Override - public void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { - } - - @Override public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { mConsumed = true; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java index c5e229feaba7..acc0bce5cce9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java @@ -298,6 +298,32 @@ public class UnfoldTransitionHandlerTest { } @Test + public void fold_animationInProgress_finishesTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + // Unfold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + + // Start animation but don't finish it + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeProgress(0.5f); + + // Fold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true); + + verify(finishCallback).onTransitionFinished(any()); + } + + @Test public void mergeAnimation_eatsDisplayOnlyTransitions() { TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); diff --git a/libs/hostgraphics/ANativeWindow.cpp b/libs/hostgraphics/ANativeWindow.cpp new file mode 100644 index 000000000000..fcfaf0235293 --- /dev/null +++ b/libs/hostgraphics/ANativeWindow.cpp @@ -0,0 +1,106 @@ +/* + * 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 <system/window.h> + +static int32_t query(ANativeWindow* window, int what) { + int value; + int res = window->query(window, what, &value); + return res < 0 ? res : value; +} + +static int64_t query64(ANativeWindow* window, int what) { + int64_t value; + int res = window->perform(window, what, &value); + return res < 0 ? res : value; +} + +int ANativeWindow_setCancelBufferInterceptor(ANativeWindow* window, + ANativeWindow_cancelBufferInterceptor interceptor, + void* data) { + return window->perform(window, NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR, interceptor, data); +} + +int ANativeWindow_setDequeueBufferInterceptor(ANativeWindow* window, + ANativeWindow_dequeueBufferInterceptor interceptor, + void* data) { + return window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR, interceptor, data); +} + +int ANativeWindow_setQueueBufferInterceptor(ANativeWindow* window, + ANativeWindow_queueBufferInterceptor interceptor, + void* data) { + return window->perform(window, NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR, interceptor, data); +} + +int ANativeWindow_setPerformInterceptor(ANativeWindow* window, + ANativeWindow_performInterceptor interceptor, void* data) { + return window->perform(window, NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR, interceptor, data); +} + +int ANativeWindow_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, int* fenceFd) { + return window->dequeueBuffer(window, buffer, fenceFd); +} + +int ANativeWindow_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd) { + return window->cancelBuffer(window, buffer, fenceFd); +} + +int ANativeWindow_setDequeueTimeout(ANativeWindow* window, int64_t timeout) { + return window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT, timeout); +} + +// extern "C", so that it can be used outside libhostgraphics (in host hwui/.../CanvasContext.cpp) +extern "C" void ANativeWindow_tryAllocateBuffers(ANativeWindow* window) { + if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) { + return; + } + window->perform(window, NATIVE_WINDOW_ALLOCATE_BUFFERS); +} + +int64_t ANativeWindow_getLastDequeueStartTime(ANativeWindow* window) { + return query64(window, NATIVE_WINDOW_GET_LAST_DEQUEUE_START); +} + +int64_t ANativeWindow_getLastDequeueDuration(ANativeWindow* window) { + return query64(window, NATIVE_WINDOW_GET_LAST_DEQUEUE_DURATION); +} + +int64_t ANativeWindow_getLastQueueDuration(ANativeWindow* window) { + return query64(window, NATIVE_WINDOW_GET_LAST_QUEUE_DURATION); +} + +int32_t ANativeWindow_getWidth(ANativeWindow* window) { + return query(window, NATIVE_WINDOW_WIDTH); +} + +int32_t ANativeWindow_getHeight(ANativeWindow* window) { + return query(window, NATIVE_WINDOW_HEIGHT); +} + +int32_t ANativeWindow_getFormat(ANativeWindow* window) { + return query(window, NATIVE_WINDOW_FORMAT); +} + +void ANativeWindow_acquire(ANativeWindow* window) { + // incStrong/decStrong token must be the same, doesn't matter what it is + window->incStrong((void*)ANativeWindow_acquire); +} + +void ANativeWindow_release(ANativeWindow* window) { + // incStrong/decStrong token must be the same, doesn't matter what it is + window->decStrong((void*)ANativeWindow_acquire); +} diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp index 4407af68de99..09232b64616d 100644 --- a/libs/hostgraphics/Android.bp +++ b/libs/hostgraphics/Android.bp @@ -17,26 +17,18 @@ cc_library_host_static { static_libs: [ "libbase", "libmath", + "libui-types", "libutils", ], srcs: [ - ":libui_host_common", "ADisplay.cpp", + "ANativeWindow.cpp", "Fence.cpp", "HostBufferQueue.cpp", "PublicFormat.cpp", ], - include_dirs: [ - // Here we override all the headers automatically included with frameworks/native/include. - // When frameworks/native/include will be removed from the list of automatic includes. - // We will have to copy necessary headers with a pre-build step (generated headers). - ".", - "frameworks/native/libs/arect/include", - "frameworks/native/libs/ui/include_private", - ], - header_libs: [ "libnativebase_headers", "libnativedisplay_headers", diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 02fa5f2608ab..7439fbc1149c 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -79,13 +79,13 @@ cc_defaults { include_dirs: [ "external/skia/include/private", "external/skia/src/core", + "external/skia/src/utils", ], target: { android: { include_dirs: [ "external/skia/src/image", - "external/skia/src/utils", "external/skia/src/gpu", "external/skia/src/shaders", ], @@ -96,6 +96,7 @@ cc_defaults { ], cflags: [ "-Wno-unused-variable", + "-D__INTRODUCED_IN(n)=", ], }, }, @@ -530,13 +531,19 @@ cc_defaults { "effects/GainmapRenderer.cpp", "pipeline/skia/BackdropFilterDrawable.cpp", "pipeline/skia/HolePunch.cpp", + "pipeline/skia/SkiaCpuPipeline.cpp", "pipeline/skia/SkiaDisplayList.cpp", + "pipeline/skia/SkiaPipeline.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", "pipeline/skia/StretchMask.cpp", "pipeline/skia/RenderNodeDrawable.cpp", "pipeline/skia/ReorderBarrierDrawables.cpp", "pipeline/skia/TransformCanvas.cpp", + "renderstate/RenderState.cpp", + "renderthread/CanvasContext.cpp", + "renderthread/DrawFrameTask.cpp", "renderthread/Frame.cpp", + "renderthread/RenderProxy.cpp", "renderthread/RenderTask.cpp", "renderthread/TimeLord.cpp", "hwui/AnimatedImageDrawable.cpp", @@ -569,6 +576,7 @@ cc_defaults { "HWUIProperties.sysprop", "Interpolator.cpp", "JankTracker.cpp", + "LayerUpdateQueue.cpp", "LightingInfo.cpp", "Matrix.cpp", "Mesh.cpp", @@ -585,6 +593,7 @@ cc_defaults { "SkiaCanvas.cpp", "SkiaInterpolator.cpp", "Tonemapper.cpp", + "TreeInfo.cpp", "VectorDrawable.cpp", ], @@ -605,23 +614,19 @@ cc_defaults { "pipeline/skia/GLFunctorDrawable.cpp", "pipeline/skia/LayerDrawable.cpp", "pipeline/skia/ShaderCache.cpp", + "pipeline/skia/SkiaGpuPipeline.cpp", "pipeline/skia/SkiaMemoryTracer.cpp", "pipeline/skia/SkiaOpenGLPipeline.cpp", - "pipeline/skia/SkiaPipeline.cpp", "pipeline/skia/SkiaProfileRenderer.cpp", "pipeline/skia/SkiaVulkanPipeline.cpp", "pipeline/skia/VkFunctorDrawable.cpp", "pipeline/skia/VkInteropFunctorDrawable.cpp", - "renderstate/RenderState.cpp", "renderthread/CacheManager.cpp", - "renderthread/CanvasContext.cpp", - "renderthread/DrawFrameTask.cpp", "renderthread/EglManager.cpp", "renderthread/ReliableSurface.cpp", "renderthread/RenderEffectCapabilityQuery.cpp", "renderthread/VulkanManager.cpp", "renderthread/VulkanSurface.cpp", - "renderthread/RenderProxy.cpp", "renderthread/RenderThread.cpp", "renderthread/HintSessionWrapper.cpp", "service/GraphicsStatsService.cpp", @@ -631,10 +636,8 @@ cc_defaults { "DeferredLayerUpdater.cpp", "HardwareBitmapUploader.cpp", "Layer.cpp", - "LayerUpdateQueue.cpp", "ProfileDataContainer.cpp", "Readback.cpp", - "TreeInfo.cpp", "WebViewFunctorManager.cpp", "protos/graphicsstats.proto", ], @@ -652,6 +655,8 @@ cc_defaults { srcs: [ "platform/host/renderthread/CacheManager.cpp", + "platform/host/renderthread/HintSessionWrapper.cpp", + "platform/host/renderthread/ReliableSurface.cpp", "platform/host/renderthread/RenderThread.cpp", "platform/host/ProfileDataContainer.cpp", "platform/host/Readback.cpp", diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index ec53070f6cb8..c1510d96461f 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -242,7 +242,7 @@ enum class ProfileType { None, Console, Bars }; enum class OverdrawColorSet { Default = 0, Deuteranomaly }; -enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 }; +enum class RenderPipelineType { SkiaGL, SkiaVulkan, SkiaCpu, NotInitialized = 128 }; enum class StretchEffectBehavior { ShaderHWUI, // Stretch shader in HWUI only, matrix scale in SF diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index f526a280b113..589abb4d87f4 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -16,18 +16,6 @@ #include "RenderNode.h" -#include "DamageAccumulator.h" -#include "Debug.h" -#include "Properties.h" -#include "TreeInfo.h" -#include "VectorDrawable.h" -#include "private/hwui/WebViewFunctor.h" -#ifdef __ANDROID__ -#include "renderthread/CanvasContext.h" -#else -#include "DamageAccumulator.h" -#include "pipeline/skia/SkiaDisplayList.h" -#endif #include <SkPathOps.h> #include <gui/TraceUtils.h> #include <ui/FatVector.h> @@ -37,6 +25,14 @@ #include <sstream> #include <string> +#include "DamageAccumulator.h" +#include "Debug.h" +#include "Properties.h" +#include "TreeInfo.h" +#include "VectorDrawable.h" +#include "private/hwui/WebViewFunctor.h" +#include "renderthread/CanvasContext.h" + #ifdef __ANDROID__ #include "include/gpu/ganesh/SkImageGanesh.h" #endif @@ -186,7 +182,6 @@ void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) { } void RenderNode::pushLayerUpdate(TreeInfo& info) { -#ifdef __ANDROID__ // Layoutlib does not support CanvasContext and Layers LayerType layerType = properties().effectiveLayerType(); // If we are not a layer OR we cannot be rendered (eg, view was detached) // we need to destroy any Layers we may have had previously @@ -218,7 +213,6 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { // That might be us, so tell CanvasContext that this layer is in the // tree and should not be destroyed. info.canvasContext.markLayerInUse(this); -#endif } /** diff --git a/libs/hwui/RootRenderNode.cpp b/libs/hwui/RootRenderNode.cpp index ddbbf58b3071..5174e27ae587 100644 --- a/libs/hwui/RootRenderNode.cpp +++ b/libs/hwui/RootRenderNode.cpp @@ -18,11 +18,12 @@ #ifdef __ANDROID__ // Layoutlib does not support Looper (windows) #include <utils/Looper.h> +#else +#include "utils/MessageHandler.h" #endif namespace android::uirenderer { -#ifdef __ANDROID__ // Layoutlib does not support Looper class FinishAndInvokeListener : public MessageHandler { public: explicit FinishAndInvokeListener(PropertyValuesAnimatorSet* anim) : mAnimator(anim) { @@ -237,9 +238,13 @@ void RootRenderNode::detachVectorDrawableAnimator(PropertyValuesAnimatorSet* ani // user events, in which case the already posted listener's id will become stale, and // the onFinished callback will then be ignored. sp<FinishAndInvokeListener> message = new FinishAndInvokeListener(anim); +#ifdef __ANDROID__ // Layoutlib does not support Looper auto looper = Looper::getForThread(); LOG_ALWAYS_FATAL_IF(looper == nullptr, "Not on a looper thread?"); looper->sendMessageDelayed(ms2ns(remainingTimeInMs), message, 0); +#else + message->handleMessage(0); +#endif anim->clearOneShotListener(); } } @@ -285,22 +290,5 @@ private: AnimationContext* ContextFactoryImpl::createAnimationContext(renderthread::TimeLord& clock) { return new AnimationContextBridge(clock, mRootNode); } -#else - -void RootRenderNode::prepareTree(TreeInfo& info) { - info.errorHandler = mErrorHandler.get(); - info.updateWindowPositions = true; - RenderNode::prepareTree(info); - info.updateWindowPositions = false; - info.errorHandler = nullptr; -} - -void RootRenderNode::attachAnimatingNode(RenderNode* animatingNode) { } - -void RootRenderNode::destroy() { } - -void RootRenderNode::addVectorDrawableAnimator(PropertyValuesAnimatorSet* anim) { } - -#endif } // namespace android::uirenderer diff --git a/libs/hwui/RootRenderNode.h b/libs/hwui/RootRenderNode.h index 1d3f5a8a51e0..7a5cda7041ed 100644 --- a/libs/hwui/RootRenderNode.h +++ b/libs/hwui/RootRenderNode.h @@ -74,7 +74,6 @@ private: void detachVectorDrawableAnimator(PropertyValuesAnimatorSet* anim); }; -#ifdef __ANDROID__ // Layoutlib does not support Animations class ContextFactoryImpl : public IContextFactory { public: explicit ContextFactoryImpl(RootRenderNode* rootNode) : mRootNode(rootNode) {} @@ -84,6 +83,5 @@ public: private: RootRenderNode* mRootNode; }; -#endif } // namespace android::uirenderer diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp index efa9b1174a3a..9d16ee86739e 100644 --- a/libs/hwui/WebViewFunctorManager.cpp +++ b/libs/hwui/WebViewFunctorManager.cpp @@ -87,7 +87,7 @@ void WebViewFunctor_release(int functor) { WebViewFunctorManager::instance().releaseFunctor(functor); } -void WebViewFunctor_reportRenderingThreads(int functor, const int32_t* thread_ids, size_t size) { +void WebViewFunctor_reportRenderingThreads(int functor, const pid_t* thread_ids, size_t size) { WebViewFunctorManager::instance().reportRenderingThreads(functor, thread_ids, size); } @@ -265,8 +265,8 @@ void WebViewFunctor::reparentSurfaceControl(ASurfaceControl* parent) { funcs.transactionDeleteFunc(transaction); } -void WebViewFunctor::reportRenderingThreads(const int32_t* thread_ids, size_t size) { - mRenderingThreads = std::vector<int32_t>(thread_ids, thread_ids + size); +void WebViewFunctor::reportRenderingThreads(const pid_t* thread_ids, size_t size) { + mRenderingThreads = std::vector<pid_t>(thread_ids, thread_ids + size); } WebViewFunctorManager& WebViewFunctorManager::instance() { @@ -355,7 +355,7 @@ void WebViewFunctorManager::destroyFunctor(int functor) { } } -void WebViewFunctorManager::reportRenderingThreads(int functor, const int32_t* thread_ids, +void WebViewFunctorManager::reportRenderingThreads(int functor, const pid_t* thread_ids, size_t size) { std::lock_guard _lock{mLock}; for (auto& iter : mFunctors) { @@ -366,8 +366,8 @@ void WebViewFunctorManager::reportRenderingThreads(int functor, const int32_t* t } } -std::vector<int32_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() { - std::vector<int32_t> renderingThreads; +std::vector<pid_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() { + std::vector<pid_t> renderingThreads; std::lock_guard _lock{mLock}; for (const auto& iter : mActiveFunctors) { const auto& functorThreads = iter->getRenderingThreads(); diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h index 2d77dd8d09bc..ec17640f9b5e 100644 --- a/libs/hwui/WebViewFunctorManager.h +++ b/libs/hwui/WebViewFunctorManager.h @@ -17,13 +17,11 @@ #pragma once #include <private/hwui/WebViewFunctor.h> -#ifdef __ANDROID__ // Layoutlib does not support render thread #include <renderthread/RenderProxy.h> -#endif - #include <utils/LightRefBase.h> #include <utils/Log.h> #include <utils/StrongPointer.h> + #include <mutex> #include <vector> @@ -38,11 +36,7 @@ public: class Handle : public LightRefBase<Handle> { public: - ~Handle() { -#ifdef __ANDROID__ // Layoutlib does not support render thread - renderthread::RenderProxy::destroyFunctor(id()); -#endif - } + ~Handle() { renderthread::RenderProxy::destroyFunctor(id()); } int id() const { return mReference.id(); } @@ -60,7 +54,7 @@ public: void onRemovedFromTree() { mReference.onRemovedFromTree(); } - const std::vector<int32_t>& getRenderingThreads() const { + const std::vector<pid_t>& getRenderingThreads() const { return mReference.getRenderingThreads(); } @@ -85,8 +79,8 @@ public: ASurfaceControl* getSurfaceControl(); void mergeTransaction(ASurfaceTransaction* transaction); - void reportRenderingThreads(const int32_t* thread_ids, size_t size); - const std::vector<int32_t>& getRenderingThreads() const { return mRenderingThreads; } + void reportRenderingThreads(const pid_t* thread_ids, size_t size); + const std::vector<pid_t>& getRenderingThreads() const { return mRenderingThreads; } sp<Handle> createHandle() { LOG_ALWAYS_FATAL_IF(mCreatedHandle); @@ -107,7 +101,7 @@ private: bool mCreatedHandle = false; int32_t mParentSurfaceControlGenerationId = 0; ASurfaceControl* mSurfaceControl = nullptr; - std::vector<int32_t> mRenderingThreads; + std::vector<pid_t> mRenderingThreads; }; class WebViewFunctorManager { @@ -118,8 +112,8 @@ public: void releaseFunctor(int functor); void onContextDestroyed(); void destroyFunctor(int functor); - void reportRenderingThreads(int functor, const int32_t* thread_ids, size_t size); - std::vector<int32_t> getRenderingThreadsForActiveFunctors(); + void reportRenderingThreads(int functor, const pid_t* thread_ids, size_t size); + std::vector<pid_t> getRenderingThreadsForActiveFunctors(); sp<WebViewFunctor::Handle> handleFor(int functor); diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp index 770822a049b7..fd9915a54bb5 100644 --- a/libs/hwui/apex/LayoutlibLoader.cpp +++ b/libs/hwui/apex/LayoutlibLoader.cpp @@ -164,8 +164,10 @@ static vector<string> parseCsv(JNIEnv* env, jstring csvJString) { } // namespace android using namespace android; +using namespace android::uirenderer; void init_android_graphics() { + Properties::overrideRenderPipelineType(RenderPipelineType::SkiaCpu); SkGraphics::Init(); } diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp index 0f80c55d0ed0..b01e38d014a9 100644 --- a/libs/hwui/jni/AnimatedImageDrawable.cpp +++ b/libs/hwui/jni/AnimatedImageDrawable.cpp @@ -27,6 +27,8 @@ #include <hwui/ImageDecoder.h> #ifdef __ANDROID__ #include <utils/Looper.h> +#else +#include "utils/MessageHandler.h" #endif #include "ColorFilter.h" @@ -182,23 +184,6 @@ static void AnimatedImageDrawable_nSetRepeatCount(JNIEnv* env, jobject /*clazz*/ drawable->setRepetitionCount(loopCount); } -#ifndef __ANDROID__ -struct Message { - Message(int w) {} -}; - -class MessageHandler : public virtual RefBase { -protected: - virtual ~MessageHandler() override {} - -public: - /** - * Handles a message. - */ - virtual void handleMessage(const Message& message) = 0; -}; -#endif - class InvokeListener : public MessageHandler { public: InvokeListener(JNIEnv* env, jobject javaObject) { diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 9e21f860ce21..d4157008ca46 100644 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -1,8 +1,14 @@ // #define LOG_NDEBUG 0 #include "Bitmap.h" +#include <android-base/unique_fd.h> #include <hwui/Bitmap.h> #include <hwui/Paint.h> +#include <inttypes.h> +#include <renderthread/RenderProxy.h> +#include <string.h> + +#include <memory> #include "CreateJavaOutputStreamAdaptor.h" #include "Gainmap.h" @@ -24,16 +30,6 @@ #include "SkTypes.h" #include "android_nio_utils.h" -#ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread -#include <android-base/unique_fd.h> -#include <renderthread/RenderProxy.h> -#endif - -#include <inttypes.h> -#include <string.h> - -#include <memory> - #define DEBUG_PARCEL 0 static jclass gBitmap_class; @@ -1105,11 +1101,9 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, jlong bm1Ha } static void Bitmap_prepareToDraw(JNIEnv* env, jobject, jlong bitmapPtr) { -#ifdef __ANDROID__ // Layoutlib does not support render thread LocalScopedBitmap bitmapHandle(bitmapPtr); if (!bitmapHandle.valid()) return; android::uirenderer::renderthread::RenderProxy::prepareToDraw(bitmapHandle->bitmap()); -#endif } static jint Bitmap_getAllocationByteCount(JNIEnv* env, jobject, jlong bitmapPtr) { diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp index 426644ee6a4e..948362c30a31 100644 --- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp +++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp @@ -16,22 +16,19 @@ #include "GraphicsJNI.h" -#ifdef __ANDROID__ // Layoutlib does not support Looper and device properties +#ifdef __ANDROID__ // Layoutlib does not support Looper #include <utils/Looper.h> #endif -#include <SkRegion.h> -#include <SkRuntimeEffect.h> - +#include <CanvasProperty.h> #include <Rect.h> #include <RenderNode.h> -#include <CanvasProperty.h> +#include <SkRegion.h> +#include <SkRuntimeEffect.h> #include <hwui/Canvas.h> #include <hwui/Paint.h> #include <minikin/Layout.h> -#ifdef __ANDROID__ // Layoutlib does not support RenderThread #include <renderthread/RenderProxy.h> -#endif namespace android { @@ -85,11 +82,7 @@ static void android_view_DisplayListCanvas_resetDisplayListCanvas(CRITICAL_JNI_P } static jint android_view_DisplayListCanvas_getMaxTextureSize(JNIEnv*, jobject) { -#ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread) return android::uirenderer::renderthread::RenderProxy::maxTextureSize(); -#else - return 4096; -#endif } static void android_view_DisplayListCanvas_enableZ(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index a7d64231da80..6e03bbd0fa16 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -15,19 +15,17 @@ */ #define ATRACE_TAG ATRACE_TAG_VIEW -#include "GraphicsJNI.h" - #include <Animator.h> #include <DamageAccumulator.h> #include <Matrix.h> #include <RenderNode.h> -#ifdef __ANDROID__ // Layoutlib does not support CanvasContext -#include <renderthread/CanvasContext.h> -#endif #include <TreeInfo.h> #include <effects/StretchEffect.h> #include <gui/TraceUtils.h> #include <hwui/Paint.h> +#include <renderthread/CanvasContext.h> + +#include "GraphicsJNI.h" namespace android { @@ -640,7 +638,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, ATRACE_NAME("Update SurfaceView position"); -#ifdef __ANDROID__ // Layoutlib does not support CanvasContext JNIEnv* env = jnienv(); // Update the new position synchronously. We cannot defer this to // a worker pool to process asynchronously because the UI thread @@ -669,7 +666,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, env->DeleteGlobalRef(mListener); mListener = nullptr; } -#endif } virtual void onPositionLost(RenderNode& node, const TreeInfo* info) override { @@ -682,7 +678,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, ATRACE_NAME("SurfaceView position lost"); JNIEnv* env = jnienv(); -#ifdef __ANDROID__ // Layoutlib does not support CanvasContext // Update the lost position synchronously. We cannot defer this to // a worker pool to process asynchronously because the UI thread // may be unblocked by the time a worker thread can process this, @@ -698,7 +693,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, env->DeleteGlobalRef(mListener); mListener = nullptr; } -#endif } private: @@ -750,7 +744,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, StretchEffectBehavior::Shader) { JNIEnv* env = jnienv(); -#ifdef __ANDROID__ // Layoutlib does not support CanvasContext SkVector stretchDirection = effect->getStretchDirection(); jboolean keepListening = env->CallStaticBooleanMethod( gPositionListener.clazz, gPositionListener.callApplyStretch, mListener, @@ -762,7 +755,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, env->DeleteGlobalRef(mListener); mListener = nullptr; } -#endif } } diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp new file mode 100644 index 000000000000..5bbbc1009541 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp @@ -0,0 +1,132 @@ +/* + * 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 "pipeline/skia/SkiaCpuPipeline.h" + +#include <system/window.h> + +#include "DeviceInfo.h" +#include "LightingInfo.h" +#include "renderthread/Frame.h" +#include "utils/Color.h" + +using namespace android::uirenderer::renderthread; + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +void SkiaCpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { + // Render all layers that need to be updated, in order. + for (size_t i = 0; i < layers.entries().size(); i++) { + RenderNode* layerNode = layers.entries()[i].renderNode.get(); + // only schedule repaint if node still on layer - possible it may have been + // removed during a dropped frame, but layers may still remain scheduled so + // as not to lose info on what portion is damaged + if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) { + continue; + } + bool rendered = renderLayerImpl(layerNode, layers.entries()[i].damage); + if (!rendered) { + return; + } + } +} + +// If the given node didn't have a layer surface, or had one of the wrong size, this method +// creates a new one and returns true. Otherwise does nothing and returns false. +bool SkiaCpuPipeline::createOrUpdateLayer(RenderNode* node, + const DamageAccumulator& damageAccumulator, + ErrorHandler* errorHandler) { + // compute the size of the surface (i.e. texture) to be allocated for this layer + const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE; + const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE; + + SkSurface* layer = node->getLayerSurface(); + if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) { + SkImageInfo info; + info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(), + kPremul_SkAlphaType, getSurfaceColorSpace()); + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + node->setLayerSurface(SkSurfaces::Raster(info, &props)); + if (node->getLayerSurface()) { + // update the transform in window of the layer to reset its origin wrt light source + // position + Matrix4 windowTransform; + damageAccumulator.computeCurrentTransform(&windowTransform); + node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform); + } else { + String8 cachesOutput; + mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput, + &mRenderThread.renderState()); + ALOGE("%s", cachesOutput.c_str()); + if (errorHandler) { + std::ostringstream err; + err << "Unable to create layer for " << node->getName(); + const int maxTextureSize = DeviceInfo::get()->maxTextureSize(); + err << ", size " << info.width() << "x" << info.height() << " max size " + << maxTextureSize << " color type " << (int)info.colorType() << " has context " + << (int)(mRenderThread.getGrContext() != nullptr); + errorHandler->onError(err.str()); + } + } + return true; + } + return false; +} + +MakeCurrentResult SkiaCpuPipeline::makeCurrent() { + return MakeCurrentResult::AlreadyCurrent; +} + +Frame SkiaCpuPipeline::getFrame() { + return Frame(mSurface->width(), mSurface->height(), 0); +} + +IRenderPipeline::DrawResult SkiaCpuPipeline::draw( + const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, + const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) { + LightingInfo::updateLighting(lightGeometry, lightInfo); + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mSurface, + SkMatrix::I()); + return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}}; +} + +bool SkiaCpuPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) { + if (surface) { + ANativeWindowBuffer* buffer; + surface->dequeueBuffer(surface, &buffer, nullptr); + int width, height; + surface->query(surface, NATIVE_WINDOW_WIDTH, &width); + surface->query(surface, NATIVE_WINDOW_HEIGHT, &height); + SkImageInfo imageInfo = + SkImageInfo::Make(width, height, mSurfaceColorType, + SkAlphaType::kPremul_SkAlphaType, mSurfaceColorSpace); + size_t widthBytes = width * imageInfo.bytesPerPixel(); + void* pixels = buffer->reserved[0]; + mSurface = SkSurfaces::WrapPixels(imageInfo, pixels, widthBytes); + } else { + mSurface = sk_sp<SkSurface>(); + } + return true; +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.h b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h new file mode 100644 index 000000000000..5a1014c2c2de --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#pragma once + +#include "pipeline/skia/SkiaPipeline.h" + +namespace android { + +namespace uirenderer { +namespace skiapipeline { + +class SkiaCpuPipeline : public SkiaPipeline { +public: + SkiaCpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {} + ~SkiaCpuPipeline() {} + + bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; } + bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; } + void unpinImages() override {} + + // If the given node didn't have a layer surface, or had one of the wrong size, this method + // creates a new one and returns true. Otherwise does nothing and returns false. + bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, + ErrorHandler* errorHandler) override; + void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override; + void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {} + bool hasHardwareBuffer() override { return false; } + + renderthread::MakeCurrentResult makeCurrent() override; + renderthread::Frame getFrame() override; + renderthread::IRenderPipeline::DrawResult draw( + const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, + const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, + const renderthread::HardwareBufferRenderParams& bufferParams, + std::mutex& profilerLock) override; + bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult, + const SkRect& screenDirty, FrameInfo* currentFrameInfo, + bool* requireSwap) override { + return false; + } + DeferredLayerUpdater* createTextureLayer() override { return nullptr; } + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; + [[nodiscard]] android::base::unique_fd flush() override { + return android::base::unique_fd(-1); + }; + void onStop() override {} + bool isSurfaceReady() override { return mSurface.get() != nullptr; } + bool isContextReady() override { return true; } + + const SkM44& getPixelSnapMatrix() const override { + static const SkM44 sSnapMatrix = SkM44(); + return sSnapMatrix; + } + +private: + sk_sp<SkSurface> mSurface; +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index e0216b680064..36dc933aa7b0 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -15,22 +15,18 @@ */ #include "SkiaDisplayList.h" -#include "FunctorDrawable.h" +#include <SkImagePriv.h> +#include <SkPathOps.h> + +// clang-format off +#include "FunctorDrawable.h" // Must be included before DumpOpsCanvas.h #include "DumpOpsCanvas.h" -#ifdef __ANDROID__ // Layoutlib does not support SkiaPipeline +// clang-format on #include "SkiaPipeline.h" -#else -#include "DamageAccumulator.h" -#endif #include "TreeInfo.h" #include "VectorDrawable.h" -#ifdef __ANDROID__ #include "renderthread/CanvasContext.h" -#endif - -#include <SkImagePriv.h> -#include <SkPathOps.h> namespace android { namespace uirenderer { @@ -101,7 +97,6 @@ bool SkiaDisplayList::prepareListAndChildren( // If the prepare tree is triggered by the UI thread and no previous call to // pinImages has failed then we must pin all mutable images in the GPU cache // until the next UI thread draw. -#ifdef __ANDROID__ // Layoutlib does not support CanvasContext if (info.prepareTextures && !info.canvasContext.pinImages(mMutableImages)) { // In the event that pinning failed we prevent future pinImage calls for the // remainder of this tree traversal and also unpin any currently pinned images @@ -110,11 +105,11 @@ bool SkiaDisplayList::prepareListAndChildren( info.canvasContext.unpinImages(); } +#ifdef __ANDROID__ auto grContext = info.canvasContext.getGrContext(); for (const auto& bufferData : mMeshBufferData) { bufferData->updateBuffers(grContext); } - #endif bool hasBackwardProjectedNodesHere = false; diff --git a/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp new file mode 100644 index 000000000000..7bfbfdc4b96b --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2016 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 "pipeline/skia/SkiaGpuPipeline.h" + +#include <SkImageAndroid.h> +#include <gui/TraceUtils.h> +#include <include/android/SkSurfaceAndroid.h> +#include <include/gpu/ganesh/SkSurfaceGanesh.h> + +using namespace android::uirenderer::renderthread; + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +SkiaGpuPipeline::SkiaGpuPipeline(RenderThread& thread) : SkiaPipeline(thread) {} + +SkiaGpuPipeline::~SkiaGpuPipeline() { + unpinImages(); +} + +void SkiaGpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { + sk_sp<GrDirectContext> cachedContext; + + // Render all layers that need to be updated, in order. + for (size_t i = 0; i < layers.entries().size(); i++) { + RenderNode* layerNode = layers.entries()[i].renderNode.get(); + // only schedule repaint if node still on layer - possible it may have been + // removed during a dropped frame, but layers may still remain scheduled so + // as not to lose info on what portion is damaged + if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) { + continue; + } + bool rendered = renderLayerImpl(layerNode, layers.entries()[i].damage); + if (!rendered) { + return; + } + // cache the current context so that we can defer flushing it until + // either all the layers have been rendered or the context changes + GrDirectContext* currentContext = + GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext()); + if (cachedContext.get() != currentContext) { + if (cachedContext.get()) { + ATRACE_NAME("flush layers (context changed)"); + cachedContext->flushAndSubmit(); + } + cachedContext.reset(SkSafeRef(currentContext)); + } + } + if (cachedContext.get()) { + ATRACE_NAME("flush layers"); + cachedContext->flushAndSubmit(); + } +} + +// If the given node didn't have a layer surface, or had one of the wrong size, this method +// creates a new one and returns true. Otherwise does nothing and returns false. +bool SkiaGpuPipeline::createOrUpdateLayer(RenderNode* node, + const DamageAccumulator& damageAccumulator, + ErrorHandler* errorHandler) { + // compute the size of the surface (i.e. texture) to be allocated for this layer + const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE; + const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE; + + SkSurface* layer = node->getLayerSurface(); + if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) { + SkImageInfo info; + info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(), + kPremul_SkAlphaType, getSurfaceColorSpace()); + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + SkASSERT(mRenderThread.getGrContext() != nullptr); + node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(), + skgpu::Budgeted::kYes, info, 0, + this->getSurfaceOrigin(), &props)); + if (node->getLayerSurface()) { + // update the transform in window of the layer to reset its origin wrt light source + // position + Matrix4 windowTransform; + damageAccumulator.computeCurrentTransform(&windowTransform); + node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform); + } else { + String8 cachesOutput; + mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput, + &mRenderThread.renderState()); + ALOGE("%s", cachesOutput.c_str()); + if (errorHandler) { + std::ostringstream err; + err << "Unable to create layer for " << node->getName(); + const int maxTextureSize = DeviceInfo::get()->maxTextureSize(); + err << ", size " << info.width() << "x" << info.height() << " max size " + << maxTextureSize << " color type " << (int)info.colorType() << " has context " + << (int)(mRenderThread.getGrContext() != nullptr); + errorHandler->onError(err.str()); + } + } + return true; + } + return false; +} + +bool SkiaGpuPipeline::pinImages(std::vector<SkImage*>& mutableImages) { + if (!mRenderThread.getGrContext()) { + ALOGD("Trying to pin an image with an invalid GrContext"); + return false; + } + for (SkImage* image : mutableImages) { + if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) { + mPinnedImages.emplace_back(sk_ref_sp(image)); + } else { + return false; + } + } + return true; +} + +void SkiaGpuPipeline::unpinImages() { + for (auto& image : mPinnedImages) { + skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get()); + } + mPinnedImages.clear(); +} + +void SkiaGpuPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { + GrDirectContext* context = thread.getGrContext(); + if (context && !bitmap->isHardware()) { + ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height()); + auto image = bitmap->makeImage(); + if (image.get()) { + skgpu::ganesh::PinAsTexture(context, image.get()); + skgpu::ganesh::UnpinTexture(context, image.get()); + // A submit is necessary as there may not be a frame coming soon, so without a call + // to submit these texture uploads can just sit in the queue building up until + // we run out of RAM + context->flushAndSubmit(); + } + } +} + +sk_sp<SkSurface> SkiaGpuPipeline::getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams) { + auto bufferColorSpace = bufferParams.getColorSpace(); + if (mBufferSurface == nullptr || mBufferColorSpace == nullptr || + !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) { + mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer( + mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin, + bufferColorSpace, nullptr, true); + mBufferColorSpace = bufferColorSpace; + } + return mBufferSurface; +} + +void SkiaGpuPipeline::dumpResourceCacheUsage() const { + int resources; + size_t bytes; + mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes); + size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit(); + + SkString log("Resource Cache Usage:\n"); + log.appendf("%8d items\n", resources); + log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes, + bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f))); + + ALOGD("%s", log.c_str()); +} + +void SkiaGpuPipeline::setHardwareBuffer(AHardwareBuffer* buffer) { + if (mHardwareBuffer) { + AHardwareBuffer_release(mHardwareBuffer); + mHardwareBuffer = nullptr; + } + + if (buffer) { + AHardwareBuffer_acquire(buffer); + mHardwareBuffer = buffer; + } +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index c8d598702a7c..e4b1f916b4d6 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -14,25 +14,25 @@ * limitations under the License. */ -#include "SkiaOpenGLPipeline.h" +#include "pipeline/skia/SkiaOpenGLPipeline.h" -#include <include/gpu/ganesh/SkSurfaceGanesh.h> -#include <include/gpu/ganesh/gl/GrGLBackendSurface.h> -#include <include/gpu/gl/GrGLTypes.h> #include <GrBackendSurface.h> #include <SkBlendMode.h> #include <SkImageInfo.h> #include <cutils/properties.h> #include <gui/TraceUtils.h> +#include <include/gpu/ganesh/SkSurfaceGanesh.h> +#include <include/gpu/ganesh/gl/GrGLBackendSurface.h> +#include <include/gpu/gl/GrGLTypes.h> #include <strings.h> #include "DeferredLayerUpdater.h" #include "FrameInfo.h" -#include "LayerDrawable.h" #include "LightingInfo.h" -#include "SkiaPipeline.h" -#include "SkiaProfileRenderer.h" #include "hwui/Bitmap.h" +#include "pipeline/skia/LayerDrawable.h" +#include "pipeline/skia/SkiaGpuPipeline.h" +#include "pipeline/skia/SkiaProfileRenderer.h" #include "private/hwui/DrawGlInfo.h" #include "renderstate/RenderState.h" #include "renderthread/EglManager.h" @@ -47,7 +47,7 @@ namespace uirenderer { namespace skiapipeline { SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread) - : SkiaPipeline(thread), mEglManager(thread.eglManager()) { + : SkiaGpuPipeline(thread), mEglManager(thread.eglManager()) { thread.renderState().registerContextCallback(this); } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 99469d1e3628..34932b1b1e25 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -14,11 +14,8 @@ * limitations under the License. */ -#include "SkiaPipeline.h" +#include "pipeline/skia/SkiaPipeline.h" -#include <include/android/SkSurfaceAndroid.h> -#include <include/gpu/ganesh/SkSurfaceGanesh.h> -#include <include/encode/SkPngEncoder.h> #include <SkCanvas.h> #include <SkColor.h> #include <SkColorSpace.h> @@ -40,6 +37,9 @@ #include <SkTypeface.h> #include <android-base/properties.h> #include <gui/TraceUtils.h> +#include <include/android/SkSurfaceAndroid.h> +#include <include/encode/SkPngEncoder.h> +#include <include/gpu/ganesh/SkSurfaceGanesh.h> #include <unistd.h> #include <sstream> @@ -62,37 +62,13 @@ SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { setSurfaceColorProperties(mColorMode); } -SkiaPipeline::~SkiaPipeline() { - unpinImages(); -} +SkiaPipeline::~SkiaPipeline() {} void SkiaPipeline::onDestroyHardwareResources() { unpinImages(); mRenderThread.cacheManager().trimStaleResources(); } -bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) { - if (!mRenderThread.getGrContext()) { - ALOGD("Trying to pin an image with an invalid GrContext"); - return false; - } - for (SkImage* image : mutableImages) { - if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) { - mPinnedImages.emplace_back(sk_ref_sp(image)); - } else { - return false; - } - } - return true; -} - -void SkiaPipeline::unpinImages() { - for (auto& image : mPinnedImages) { - skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get()); - } - mPinnedImages.clear(); -} - void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, bool opaque, const LightInfo& lightInfo) { @@ -102,136 +78,48 @@ void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry, layerUpdateQueue->clear(); } -void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { - sk_sp<GrDirectContext> cachedContext; - - // Render all layers that need to be updated, in order. - for (size_t i = 0; i < layers.entries().size(); i++) { - RenderNode* layerNode = layers.entries()[i].renderNode.get(); - // only schedule repaint if node still on layer - possible it may have been - // removed during a dropped frame, but layers may still remain scheduled so - // as not to lose info on what portion is damaged - if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) { - continue; - } - SkASSERT(layerNode->getLayerSurface()); - SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl(); - if (!displayList || displayList->isEmpty()) { - ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName()); - return; - } - - const Rect& layerDamage = layers.entries()[i].damage; - - SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); - - int saveCount = layerCanvas->save(); - SkASSERT(saveCount == 1); - - layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); - - // TODO: put localized light center calculation and storage to a drawable related code. - // It does not seem right to store something localized in a global state - // fix here and in recordLayers - const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw()); - Vector3 transformedLightCenter(savedLightCenter); - // map current light center into RenderNode's coordinate space - layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter); - LightingInfo::setLightCenterRaw(transformedLightCenter); - - const RenderProperties& properties = layerNode->properties(); - const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); - if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) { - return; - } - - ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(), - bounds.height()); +bool SkiaPipeline::renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage) { + SkASSERT(layerNode->getLayerSurface()); + SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl(); + if (!displayList || displayList->isEmpty()) { + ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName()); + return false; + } - layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false; - layerCanvas->clear(SK_ColorTRANSPARENT); + SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); - RenderNodeDrawable root(layerNode, layerCanvas, false); - root.forceDraw(layerCanvas); - layerCanvas->restoreToCount(saveCount); + int saveCount = layerCanvas->save(); + SkASSERT(saveCount == 1); - LightingInfo::setLightCenterRaw(savedLightCenter); + layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); - // cache the current context so that we can defer flushing it until - // either all the layers have been rendered or the context changes - GrDirectContext* currentContext = - GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext()); - if (cachedContext.get() != currentContext) { - if (cachedContext.get()) { - ATRACE_NAME("flush layers (context changed)"); - cachedContext->flushAndSubmit(); - } - cachedContext.reset(SkSafeRef(currentContext)); - } + // TODO: put localized light center calculation and storage to a drawable related code. + // It does not seem right to store something localized in a global state + // fix here and in recordLayers + const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw()); + Vector3 transformedLightCenter(savedLightCenter); + // map current light center into RenderNode's coordinate space + layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter); + LightingInfo::setLightCenterRaw(transformedLightCenter); + + const RenderProperties& properties = layerNode->properties(); + const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); + if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) { + return false; } - if (cachedContext.get()) { - ATRACE_NAME("flush layers"); - cachedContext->flushAndSubmit(); - } -} + ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(), + bounds.height()); -bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, - ErrorHandler* errorHandler) { - // compute the size of the surface (i.e. texture) to be allocated for this layer - const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE; - const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE; - - SkSurface* layer = node->getLayerSurface(); - if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) { - SkImageInfo info; - info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(), - kPremul_SkAlphaType, getSurfaceColorSpace()); - SkSurfaceProps props(0, kUnknown_SkPixelGeometry); - SkASSERT(mRenderThread.getGrContext() != nullptr); - node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(), - skgpu::Budgeted::kYes, info, 0, - this->getSurfaceOrigin(), &props)); - if (node->getLayerSurface()) { - // update the transform in window of the layer to reset its origin wrt light source - // position - Matrix4 windowTransform; - damageAccumulator.computeCurrentTransform(&windowTransform); - node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform); - } else { - String8 cachesOutput; - mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput, - &mRenderThread.renderState()); - ALOGE("%s", cachesOutput.c_str()); - if (errorHandler) { - std::ostringstream err; - err << "Unable to create layer for " << node->getName(); - const int maxTextureSize = DeviceInfo::get()->maxTextureSize(); - err << ", size " << info.width() << "x" << info.height() << " max size " - << maxTextureSize << " color type " << (int)info.colorType() << " has context " - << (int)(mRenderThread.getGrContext() != nullptr); - errorHandler->onError(err.str()); - } - } - return true; - } - return false; -} + layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false; + layerCanvas->clear(SK_ColorTRANSPARENT); -void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { - GrDirectContext* context = thread.getGrContext(); - if (context && !bitmap->isHardware()) { - ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height()); - auto image = bitmap->makeImage(); - if (image.get()) { - skgpu::ganesh::PinAsTexture(context, image.get()); - skgpu::ganesh::UnpinTexture(context, image.get()); - // A submit is necessary as there may not be a frame coming soon, so without a call - // to submit these texture uploads can just sit in the queue building up until - // we run out of RAM - context->flushAndSubmit(); - } - } + RenderNodeDrawable root(layerNode, layerCanvas, false); + root.forceDraw(layerCanvas); + layerCanvas->restoreToCount(saveCount); + + LightingInfo::setLightCenterRaw(savedLightCenter); + return true; } static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) { @@ -599,45 +487,6 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip, } } -void SkiaPipeline::dumpResourceCacheUsage() const { - int resources; - size_t bytes; - mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes); - size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit(); - - SkString log("Resource Cache Usage:\n"); - log.appendf("%8d items\n", resources); - log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes, - bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f))); - - ALOGD("%s", log.c_str()); -} - -void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) { - if (mHardwareBuffer) { - AHardwareBuffer_release(mHardwareBuffer); - mHardwareBuffer = nullptr; - } - - if (buffer) { - AHardwareBuffer_acquire(buffer); - mHardwareBuffer = buffer; - } -} - -sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface( - const renderthread::HardwareBufferRenderParams& bufferParams) { - auto bufferColorSpace = bufferParams.getColorSpace(); - if (mBufferSurface == nullptr || mBufferColorSpace == nullptr || - !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) { - mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer( - mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin, - bufferColorSpace, nullptr, true); - mBufferColorSpace = bufferColorSpace; - } - return mBufferSurface; -} - void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mColorMode = colorMode; switch (colorMode) { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index befee8989383..cf14b1f9ebe3 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -42,18 +42,9 @@ public: void onDestroyHardwareResources() override; - bool pinImages(std::vector<SkImage*>& mutableImages) override; - bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; } - void unpinImages() override; - void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, bool opaque, const LightInfo& lightInfo) override; - // If the given node didn't have a layer surface, or had one of the wrong size, this method - // creates a new one and returns true. Otherwise does nothing and returns false. - bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, - ErrorHandler* errorHandler) override; - void setSurfaceColorProperties(ColorMode colorMode) override; SkColorType getSurfaceColorType() const override { return mSurfaceColorType; } sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; } @@ -63,9 +54,8 @@ public: const Rect& contentDrawBounds, sk_sp<SkSurface> surface, const SkMatrix& preTransform); - static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); - - void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque); + bool renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage); + virtual void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) = 0; // Sets the recording callback to the provided function and the recording mode // to CallbackAPI @@ -75,19 +65,11 @@ public: mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None; } - virtual void setHardwareBuffer(AHardwareBuffer* buffer) override; - bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; } - void setTargetSdrHdrRatio(float ratio) override; protected: - sk_sp<SkSurface> getBufferSkSurface( - const renderthread::HardwareBufferRenderParams& bufferParams); - void dumpResourceCacheUsage() const; - renderthread::RenderThread& mRenderThread; - AHardwareBuffer* mHardwareBuffer = nullptr; sk_sp<SkSurface> mBufferSurface = nullptr; sk_sp<SkColorSpace> mBufferColorSpace = nullptr; @@ -125,8 +107,6 @@ private: // Set up a multi frame capture. bool setupMultiFrameCapture(); - std::vector<sk_sp<SkImage>> mPinnedImages; - // Block of properties used only for debugging to record a SkPicture and save it in a file. // There are three possible ways of recording drawing commands. enum class CaptureMode { diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index fd0a8e06f39c..d06dba05ee88 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "SkiaVulkanPipeline.h" +#include "pipeline/skia/SkiaVulkanPipeline.h" #include <GrDirectContext.h> #include <GrTypes.h> @@ -28,10 +28,10 @@ #include "DeferredLayerUpdater.h" #include "LightingInfo.h" #include "Readback.h" -#include "ShaderCache.h" -#include "SkiaPipeline.h" -#include "SkiaProfileRenderer.h" -#include "VkInteropFunctorDrawable.h" +#include "pipeline/skia/ShaderCache.h" +#include "pipeline/skia/SkiaGpuPipeline.h" +#include "pipeline/skia/SkiaProfileRenderer.h" +#include "pipeline/skia/VkInteropFunctorDrawable.h" #include "renderstate/RenderState.h" #include "renderthread/Frame.h" #include "renderthread/IRenderPipeline.h" @@ -42,7 +42,8 @@ namespace android { namespace uirenderer { namespace skiapipeline { -SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) { +SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) + : SkiaGpuPipeline(thread) { thread.renderState().registerContextCallback(this); } diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h new file mode 100644 index 000000000000..9159eae46065 --- /dev/null +++ b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "pipeline/skia/SkiaPipeline.h" + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class SkiaGpuPipeline : public SkiaPipeline { +public: + SkiaGpuPipeline(renderthread::RenderThread& thread); + virtual ~SkiaGpuPipeline(); + + virtual GrSurfaceOrigin getSurfaceOrigin() = 0; + + // If the given node didn't have a layer surface, or had one of the wrong size, this method + // creates a new one and returns true. Otherwise does nothing and returns false. + bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, + ErrorHandler* errorHandler) override; + + bool pinImages(std::vector<SkImage*>& mutableImages) override; + bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; } + void unpinImages() override; + void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override; + void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override; + bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; } + + static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); + +protected: + sk_sp<SkSurface> getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams); + void dumpResourceCacheUsage() const; + + AHardwareBuffer* mHardwareBuffer = nullptr; + +private: + std::vector<sk_sp<SkImage>> mPinnedImages; +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h index ebe8b6e15d44..6e7478288777 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h @@ -19,7 +19,7 @@ #include <EGL/egl.h> #include <system/window.h> -#include "SkiaPipeline.h" +#include "pipeline/skia/SkiaGpuPipeline.h" #include "renderstate/RenderState.h" #include "renderthread/HardwareBufferRenderParams.h" @@ -30,7 +30,7 @@ class Bitmap; namespace uirenderer { namespace skiapipeline { -class SkiaOpenGLPipeline : public SkiaPipeline, public IGpuContextCallback { +class SkiaOpenGLPipeline : public SkiaGpuPipeline, public IGpuContextCallback { public: SkiaOpenGLPipeline(renderthread::RenderThread& thread); virtual ~SkiaOpenGLPipeline(); diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h index 624eaa51a584..0d30df48baee 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h @@ -17,7 +17,7 @@ #pragma once #include "SkRefCnt.h" -#include "SkiaPipeline.h" +#include "pipeline/skia/SkiaGpuPipeline.h" #include "renderstate/RenderState.h" #include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/VulkanManager.h" @@ -30,7 +30,7 @@ namespace android { namespace uirenderer { namespace skiapipeline { -class SkiaVulkanPipeline : public SkiaPipeline, public IGpuContextCallback { +class SkiaVulkanPipeline : public SkiaGpuPipeline, public IGpuContextCallback { public: explicit SkiaVulkanPipeline(renderthread::RenderThread& thread); virtual ~SkiaVulkanPipeline(); diff --git a/libs/hwui/platform/host/WebViewFunctorManager.cpp b/libs/hwui/platform/host/WebViewFunctorManager.cpp index 1d16655bf73c..4ba206b41b39 100644 --- a/libs/hwui/platform/host/WebViewFunctorManager.cpp +++ b/libs/hwui/platform/host/WebViewFunctorManager.cpp @@ -50,6 +50,8 @@ ASurfaceControl* WebViewFunctor::getSurfaceControl() { void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {} +void WebViewFunctor::reportRenderingThreads(const pid_t* thread_ids, size_t size) {} + void WebViewFunctor::reparentSurfaceControl(ASurfaceControl* parent) {} WebViewFunctorManager& WebViewFunctorManager::instance() { @@ -68,6 +70,13 @@ void WebViewFunctorManager::onContextDestroyed() {} void WebViewFunctorManager::destroyFunctor(int functor) {} +void WebViewFunctorManager::reportRenderingThreads(int functor, const pid_t* thread_ids, + size_t size) {} + +std::vector<pid_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() { + return {}; +} + sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) { return nullptr; } diff --git a/libs/hwui/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h new file mode 120000 index 000000000000..4fb4784f9f60 --- /dev/null +++ b/libs/hwui/platform/host/android/api-level.h @@ -0,0 +1 @@ +../../../../../../../bionic/libc/include/android/api-level.h
\ No newline at end of file diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h new file mode 100644 index 000000000000..a71726585081 --- /dev/null +++ b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "pipeline/skia/SkiaPipeline.h" +#include "renderthread/Frame.h" + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class SkiaGpuPipeline : public SkiaPipeline { +public: + SkiaGpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {} + ~SkiaGpuPipeline() {} + + bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; } + bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; } + void unpinImages() override {} + + // If the given node didn't have a layer surface, or had one of the wrong size, this method + // creates a new one and returns true. Otherwise does nothing and returns false. + bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, + ErrorHandler* errorHandler) override { + return false; + } + void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override {} + void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {} + bool hasHardwareBuffer() override { return false; } + + renderthread::MakeCurrentResult makeCurrent() override { + return renderthread::MakeCurrentResult::Failed; + } + renderthread::Frame getFrame() override { return renderthread::Frame(0, 0, 0); } + renderthread::IRenderPipeline::DrawResult draw( + const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, + const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, + const renderthread::HardwareBufferRenderParams& bufferParams, + std::mutex& profilerLock) override { + return {false, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd(-1)}; + } + bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult, + const SkRect& screenDirty, FrameInfo* currentFrameInfo, + bool* requireSwap) override { + return false; + } + DeferredLayerUpdater* createTextureLayer() override { return nullptr; } + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override { + return false; + } + [[nodiscard]] android::base::unique_fd flush() override { + return android::base::unique_fd(-1); + }; + void onStop() override {} + bool isSurfaceReady() override { return false; } + bool isContextReady() override { return false; } + + const SkM44& getPixelSnapMatrix() const override { + static const SkM44 sSnapMatrix = SkM44(); + return sSnapMatrix; + } + static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap) {} +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h new file mode 100644 index 000000000000..4fafbcc4748d --- /dev/null +++ b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "pipeline/skia/SkiaGpuPipeline.h" + +namespace android { + +namespace uirenderer { +namespace skiapipeline { + +class SkiaOpenGLPipeline : public SkiaGpuPipeline { +public: + SkiaOpenGLPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {} + + static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {} +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h new file mode 100644 index 000000000000..d54caef45bb5 --- /dev/null +++ b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "pipeline/skia/SkiaGpuPipeline.h" + +namespace android { + +namespace uirenderer { +namespace skiapipeline { + +class SkiaVulkanPipeline : public SkiaGpuPipeline { +public: + SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {} + + static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {} +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp b/libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp new file mode 100644 index 000000000000..b1b1d5830834 --- /dev/null +++ b/libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "renderthread/HintSessionWrapper.h" + +namespace android { +namespace uirenderer { +namespace renderthread { + +void HintSessionWrapper::HintSessionBinding::init() {} + +HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId) + : mUiThreadId(uiThreadId) + , mRenderThreadId(renderThreadId) + , mBinding(std::make_shared<HintSessionBinding>()) {} + +HintSessionWrapper::~HintSessionWrapper() {} + +void HintSessionWrapper::destroy() {} + +bool HintSessionWrapper::init() { + return false; +} + +void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) {} + +void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {} + +void HintSessionWrapper::sendLoadResetHint() {} + +void HintSessionWrapper::sendLoadIncreaseHint() {} + +bool HintSessionWrapper::alive() { + return false; +} + +nsecs_t HintSessionWrapper::getLastUpdate() { + return -1; +} + +void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay, + std::shared_ptr<HintSessionWrapper> wrapperPtr) {} + +void HintSessionWrapper::setActiveFunctorThreads(std::vector<pid_t> threadIds) {} + +} /* namespace renderthread */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/platform/host/renderthread/ReliableSurface.cpp b/libs/hwui/platform/host/renderthread/ReliableSurface.cpp new file mode 100644 index 000000000000..2deaaf3b909c --- /dev/null +++ b/libs/hwui/platform/host/renderthread/ReliableSurface.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 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 "renderthread/ReliableSurface.h" + +#include <log/log_main.h> +#include <system/window.h> + +namespace android::uirenderer::renderthread { + +ReliableSurface::ReliableSurface(ANativeWindow* window) : mWindow(window) { + LOG_ALWAYS_FATAL_IF(!mWindow, "Error, unable to wrap a nullptr"); + ANativeWindow_acquire(mWindow); +} + +ReliableSurface::~ReliableSurface() { + ANativeWindow_release(mWindow); +} + +void ReliableSurface::init() {} + +int ReliableSurface::reserveNext() { + return OK; +} + +}; // namespace android::uirenderer::renderthread diff --git a/libs/hwui/platform/host/renderthread/RenderThread.cpp b/libs/hwui/platform/host/renderthread/RenderThread.cpp index 6f08b5979772..f9d0f4704e08 100644 --- a/libs/hwui/platform/host/renderthread/RenderThread.cpp +++ b/libs/hwui/platform/host/renderthread/RenderThread.cpp @@ -17,6 +17,7 @@ #include "renderthread/RenderThread.h" #include "Readback.h" +#include "renderstate/RenderState.h" #include "renderthread/VulkanManager.h" namespace android { @@ -66,6 +67,7 @@ RenderThread::RenderThread() RenderThread::~RenderThread() {} void RenderThread::initThreadLocals() { + mRenderState = new RenderState(*this); mCacheManager = new CacheManager(*this); } diff --git a/libs/hwui/platform/host/utils/MessageHandler.h b/libs/hwui/platform/host/utils/MessageHandler.h new file mode 100644 index 000000000000..51ee48e0c6d2 --- /dev/null +++ b/libs/hwui/platform/host/utils/MessageHandler.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. + */ + +#pragma once + +#include <utils/RefBase.h> + +struct Message { + Message(int w) {} +}; + +class MessageHandler : public virtual android::RefBase { +protected: + virtual ~MessageHandler() override {} + +public: + /** + * Handles a message. + */ + virtual void handleMessage(const Message& message) = 0; +}; diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index e08d32a7735c..60657cf91123 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -16,11 +16,13 @@ #ifndef RENDERSTATE_H #define RENDERSTATE_H -#include "utils/Macros.h" - +#include <pthread.h> #include <utils/RefBase.h> + #include <set> +#include "utils/Macros.h" + namespace android { namespace uirenderer { diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 1fbd580874f6..66e089627a7b 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -35,8 +35,9 @@ #include "Properties.h" #include "RenderThread.h" #include "hwui/Canvas.h" +#include "pipeline/skia/SkiaCpuPipeline.h" +#include "pipeline/skia/SkiaGpuPipeline.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" -#include "pipeline/skia/SkiaPipeline.h" #include "pipeline/skia/SkiaVulkanPipeline.h" #include "thread/CommonPool.h" #include "utils/GLUtils.h" @@ -72,7 +73,7 @@ CanvasContext* ScopedActiveContext::sActiveContext = nullptr; CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory, - int32_t uiThreadId, int32_t renderThreadId) { + pid_t uiThreadId, pid_t renderThreadId) { auto renderType = Properties::getRenderPipelineType(); switch (renderType) { @@ -84,6 +85,12 @@ CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent, return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread), uiThreadId, renderThreadId); +#ifndef __ANDROID__ + case RenderPipelineType::SkiaCpu: + return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, + std::make_unique<skiapipeline::SkiaCpuPipeline>(thread), + uiThreadId, renderThreadId); +#endif default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType); break; @@ -108,7 +115,7 @@ void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor) } void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { - skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap); + skiapipeline::SkiaGpuPipeline::prepareToDraw(thread, bitmap); } CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, @@ -182,6 +189,7 @@ static void setBufferCount(ANativeWindow* window) { } void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) { +#ifdef __ANDROID__ if (mHardwareBuffer) { AHardwareBuffer_release(mHardwareBuffer); mHardwareBuffer = nullptr; @@ -192,6 +200,7 @@ void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) { mHardwareBuffer = buffer; } mRenderPipeline->setHardwareBuffer(mHardwareBuffer); +#endif } void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { @@ -561,6 +570,7 @@ Frame CanvasContext::getFrame() { } void CanvasContext::draw(bool solelyTextureViewUpdates) { +#ifdef __ANDROID__ if (auto grContext = getGrContext()) { if (grContext->abandoned()) { if (grContext->isDeviceLost()) { @@ -571,6 +581,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { return; } } +#endif SkRect dirty; mDamageAccumulator.finish(&dirty); @@ -594,11 +605,13 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { if (skippedFrameReason) { mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason); +#ifdef __ANDROID__ if (auto grContext = getGrContext()) { // Submit to ensure that any texture uploads complete and Skia can // free its staging buffers. grContext->flushAndSubmit(); } +#endif // Notify the callbacks, even if there's nothing to draw so they aren't waiting // indefinitely diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 1b333bfccbf1..826d00e1f32f 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -140,12 +140,14 @@ void DrawFrameTask::run() { if (CC_LIKELY(canDrawThisFrame)) { context->draw(solelyTextureViewUpdates); } else { +#ifdef __ANDROID__ // Do a flush in case syncFrameState performed any texture uploads. Since we skipped // the draw() call, those uploads (or deletes) will end up sitting in the queue. // Do them now if (GrDirectContext* grContext = mRenderThread->getGrContext()) { grContext->flushAndSubmit(); } +#endif // wait on fences so tasks don't overlap next frame context->waitOnFences(); } @@ -176,11 +178,13 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { bool canDraw = mContext->makeCurrent(); mContext->unpinImages(); +#ifdef __ANDROID__ for (size_t i = 0; i < mLayers.size(); i++) { if (mLayers[i]) { mLayers[i]->apply(); } } +#endif mLayers.clear(); mContext->setContentDrawBounds(mContentDrawBounds); diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 2904dfe76f40..708b0113e13e 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -442,14 +442,17 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, } // TODO: maybe we want to get rid of the WCG check if overlay properties just works? - const bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() || - DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType; - - if (canUseFp16) { - if (mEglConfigF16 == EGL_NO_CONFIG_KHR) { - colorMode = ColorMode::Default; - } else { - config = mEglConfigF16; + bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() || + DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType; + + if (colorMode == ColorMode::Hdr) { + if (canUseFp16 && !DeviceInfo::get()->isSupportRgba10101010ForHdr()) { + if (mEglConfigF16 == EGL_NO_CONFIG_KHR) { + // If the driver doesn't support fp16 then fallback to 8-bit + canUseFp16 = false; + } else { + config = mEglConfigF16; + } } } diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index b8c3a4de2bd4..ee1d1f8789d9 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -30,8 +30,6 @@ #include "SwapBehavior.h" #include "hwui/Bitmap.h" -class GrDirectContext; - struct ANativeWindow; namespace android { @@ -94,7 +92,6 @@ public: virtual void setSurfaceColorProperties(ColorMode colorMode) = 0; virtual SkColorType getSurfaceColorType() const = 0; virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0; - virtual GrSurfaceOrigin getSurfaceOrigin() = 0; virtual void setPictureCapturedCallback( const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0; diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h index 595964741049..d6a4d50d3327 100644 --- a/libs/hwui/renderthread/ReliableSurface.h +++ b/libs/hwui/renderthread/ReliableSurface.h @@ -21,7 +21,9 @@ #include <apex/window.h> #include <utils/Errors.h> #include <utils/Macros.h> +#ifdef __ANDROID__ #include <utils/NdkUtils.h> +#endif #include <utils/StrongPointer.h> #include <memory> @@ -62,9 +64,11 @@ private: mutable std::mutex mMutex; uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER; +#ifdef __ANDROID__ AHardwareBuffer_Format mFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; UniqueAHardwareBuffer mScratchBuffer; ANativeWindowBuffer* mReservedBuffer = nullptr; +#endif base::unique_fd mReservedFenceFd; bool mHasDequeuedBuffer = false; int mBufferQueueState = OK; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index eab36050896f..715153b5083d 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -42,7 +42,11 @@ namespace renderthread { RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) : mRenderThread(RenderThread::getInstance()), mContext(nullptr) { +#ifdef __ANDROID__ pid_t uiThreadId = pthread_gettid_np(pthread_self()); +#else + pid_t uiThreadId = 0; +#endif pid_t renderThreadId = getRenderThreadTid(); mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* { CanvasContext* context = CanvasContext::create(mRenderThread, translucent, rootRenderNode, @@ -90,6 +94,7 @@ void RenderProxy::setName(const char* name) { } void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) { +#ifdef __ANDROID__ if (buffer) { AHardwareBuffer_acquire(buffer); } @@ -99,6 +104,7 @@ void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) { AHardwareBuffer_release(hardwareBuffer); } }); +#endif } void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) { @@ -216,7 +222,9 @@ void RenderProxy::cancelLayerUpdate(DeferredLayerUpdater* layer) { } void RenderProxy::detachSurfaceTexture(DeferredLayerUpdater* layer) { +#ifdef __ANDROID__ return mRenderThread.queue().runSync([&]() { layer->detachSurfaceTexture(); }); +#endif } void RenderProxy::destroyHardwareResources() { @@ -324,11 +332,13 @@ void RenderProxy::dumpGraphicsMemory(int fd, bool includeProfileData, bool reset } }); } +#ifdef __ANDROID__ if (!Properties::isolatedProcess) { std::string grallocInfo; GraphicBufferAllocator::getInstance().dump(grallocInfo); dprintf(fd, "%s\n", grallocInfo.c_str()); } +#endif } void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) { @@ -352,7 +362,11 @@ void RenderProxy::rotateProcessStatsBuffer() { } int RenderProxy::getRenderThreadTid() { +#ifdef __ANDROID__ return mRenderThread.getTid(); +#else + return 0; +#endif } void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) { @@ -461,7 +475,7 @@ void RenderProxy::prepareToDraw(Bitmap& bitmap) { int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { ATRACE_NAME("HardwareBitmap readback"); RenderThread& thread = RenderThread::getInstance(); - if (gettid() == thread.getTid()) { + if (RenderThread::isCurrent()) { // TODO: fix everything that hits this. We should never be triggering a readback ourselves. return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); } else { @@ -472,7 +486,7 @@ int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { int RenderProxy::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) { RenderThread& thread = RenderThread::getInstance(); - if (gettid() == thread.getTid()) { + if (RenderThread::isCurrent()) { // TODO: fix everything that hits this. We should never be triggering a readback ourselves. return (int)thread.readback().copyImageInto(image, bitmap); } else { |