feat: Better At-a-Glance perceptive wallpaper colour luminance detection
This commit is contained in:
@@ -23,7 +23,7 @@ import android.os.Build;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.window.flags2.Flags;
|
||||
import com.android.window.flags.Flags;
|
||||
|
||||
/**
|
||||
* Constants for desktop mode feature
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.wm.shell.shared;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
|
||||
import com.android.wm.shell.shared.annotations.ExternalThread;
|
||||
|
||||
/**
|
||||
@@ -33,6 +31,6 @@ public interface FocusTransitionListener {
|
||||
/**
|
||||
* Called when the per-app or system-wide focus state has changed for a task.
|
||||
*/
|
||||
default void onFocusedTaskChanged(ActivityManager.RunningTaskInfo taskInfo,
|
||||
boolean isFocusedOnDisplay, boolean isFocusedGlobally) {}
|
||||
default void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay,
|
||||
boolean isFocusedGlobally) {}
|
||||
}
|
||||
|
||||
@@ -224,20 +224,19 @@ public class GroupedTaskInfo implements Parcelable {
|
||||
|
||||
/**
|
||||
* Get primary {@link TaskInfo}.
|
||||
* Nullable only if the group if TYPE_DESK, non-null for TYPE_FULLSCREEN and TYPE_SPLIT.
|
||||
*
|
||||
* @throws IllegalStateException if the group is TYPE_MIXED.
|
||||
*/
|
||||
@Nullable
|
||||
@NonNull
|
||||
public TaskInfo getTaskInfo1() {
|
||||
if (mType == TYPE_MIXED) {
|
||||
throw new IllegalStateException("No indexed tasks for a mixed task");
|
||||
}
|
||||
return CollectionsKt.firstOrNull(mTasks);
|
||||
return mTasks.getFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secondary {@link TaskInfo}, used primarily for TYPE_SPLIT, not null for TYPE_SPLIT.
|
||||
* Get secondary {@link TaskInfo}, used primarily for TYPE_SPLIT.
|
||||
*
|
||||
* @throws IllegalStateException if the group is TYPE_MIXED.
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.wm.shell.shared;
|
||||
|
||||
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||
import static android.view.RemoteAnimationTarget.MODE_CHANGING;
|
||||
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
|
||||
import static android.view.RemoteAnimationTarget.MODE_OPENING;
|
||||
@@ -125,12 +124,6 @@ public class TransitionUtil {
|
||||
return isNonApp(change) && change.hasFlags(FLAG_IS_DIVIDER_BAR);
|
||||
}
|
||||
|
||||
/** Returns `true` if `change` is a pinned Task. */
|
||||
private static boolean isPipTask(TransitionInfo.Change change) {
|
||||
return change.getTaskInfo() != null
|
||||
&& change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED;
|
||||
}
|
||||
|
||||
/** Returns `true` if `change` is an app's dim layer. */
|
||||
public static boolean isDimLayer(TransitionInfo.Change change) {
|
||||
return isNonApp(change) && change.hasFlags(FLAG_IS_DIM_LAYER);
|
||||
@@ -317,10 +310,9 @@ public class TransitionUtil {
|
||||
// actual dim value).
|
||||
t.setAlpha(change.getLeash(), 1.0f);
|
||||
}
|
||||
if (!isDividerBar(change) && !isPipTask(change)) {
|
||||
// For certain components such as Divider and PiP, don't modify its inner leash
|
||||
// position when creating the outer leash for the transition. In case the position
|
||||
// being wrong after the transition finished.
|
||||
if (!isDividerBar(change)) {
|
||||
// For divider, don't modify its inner leash position when creating the outer leash
|
||||
// for the transition. In case the position being wrong after the transition finished.
|
||||
t.setPosition(change.getLeash(), 0, 0);
|
||||
}
|
||||
t.setLayer(change.getLeash(), 0);
|
||||
|
||||
@@ -23,12 +23,9 @@ import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.view.Choreographer
|
||||
import android.view.SurfaceControl.Transaction
|
||||
import android.window.DesktopExperienceFlags
|
||||
import android.window.TransitionInfo.Change
|
||||
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
|
||||
import com.android.internal.jank.InteractionJankMonitor
|
||||
import com.android.wm.shell.shared.animation.WindowAnimator.BoundsAnimationParams.AnimationBounds
|
||||
import java.time.Duration
|
||||
|
||||
/** Creates minimization animation */
|
||||
object MinimizeAnimator {
|
||||
@@ -41,16 +38,6 @@ object MinimizeAnimator {
|
||||
endOffsetYDp = 12f,
|
||||
endScale = 0.97f,
|
||||
interpolator = Interpolators.STANDARD_ACCELERATE,
|
||||
animBounds = if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
|
||||
// In some cases, nav-back on the last desktop task may cause it to be reparented
|
||||
// into a fullscreen TDA before being minimized back into a desk by
|
||||
// [DesktopBackNavTransitionObserver]. The minimize animation would then occur when
|
||||
// the task is still fullscreen, which means it should use the start bounds for the
|
||||
// minimize animation.
|
||||
AnimationBounds.START
|
||||
} else {
|
||||
AnimationBounds.END
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -61,7 +48,6 @@ object MinimizeAnimator {
|
||||
* @param animationHandler the Handler that the animation is running on.
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun create(
|
||||
context: Context,
|
||||
change: Change,
|
||||
@@ -69,7 +55,6 @@ object MinimizeAnimator {
|
||||
onAnimFinish: (Animator) -> Unit,
|
||||
interactionJankMonitor: InteractionJankMonitor,
|
||||
animationHandler: Handler,
|
||||
startAnimDelay: Duration = Duration.ZERO,
|
||||
): Animator {
|
||||
val boundsAnimator = WindowAnimator.createBoundsAnimator(
|
||||
context.resources.displayMetrics,
|
||||
@@ -106,7 +91,6 @@ object MinimizeAnimator {
|
||||
}
|
||||
}
|
||||
return AnimatorSet().apply {
|
||||
startDelay = startAnimDelay.toMillis()
|
||||
playTogether(boundsAnimator, alphaAnimator)
|
||||
addListener(listener)
|
||||
}
|
||||
|
||||
@@ -117,9 +117,6 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
/** End actions to run when all animations have completed. */
|
||||
private val endActions = ArrayList<EndAction>()
|
||||
|
||||
/** End actions to run when all animations have completed or canceled. */
|
||||
private val endOrCancelActions = ArrayList<EndAction>()
|
||||
|
||||
/** SpringConfig to use by default for properties whose springs were not provided. */
|
||||
private var defaultSpring: SpringConfig = globalDefaultSpring
|
||||
|
||||
@@ -387,8 +384,7 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
* Adds a listener that will be called when a property stops animating. This is useful if
|
||||
* you care about a specific property ending, or want to use the end value/end velocity from a
|
||||
* particular property's animation. If you just want to run an action when all property
|
||||
* animations have ended, use [withEndActions]. If you want an action to run when all property
|
||||
* animations have ended or canceled, use [withEndOrCancelActions].
|
||||
* animations have ended, use [withEndActions].
|
||||
*/
|
||||
fun addEndListener(listener: EndListener<T>): PhysicsAnimator<T> {
|
||||
endListeners.add(listener)
|
||||
@@ -397,8 +393,8 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
|
||||
/**
|
||||
* Adds end actions that will be run sequentially when animations for every property involved in
|
||||
* this specific animation have ended (unless they were explicitly canceled, in which you should
|
||||
* use [withEndOrCancelActions]). For example, if you call:
|
||||
* this specific animation have ended (unless they were explicitly canceled). For example, if
|
||||
* you call:
|
||||
*
|
||||
* animator
|
||||
* .spring(TRANSLATION_X, ...)
|
||||
@@ -428,9 +424,6 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
* access to the animation's end value/velocity, or you want to run these actions even if the
|
||||
* animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
|
||||
* which indicates that all relevant animations have ended.
|
||||
*
|
||||
* These actions run after those added via [addEndListener], and before actions added via
|
||||
* [withEndOrCancelActions].
|
||||
*/
|
||||
fun withEndActions(vararg endActions: EndAction?): PhysicsAnimator<T> {
|
||||
this.endActions.addAll(endActions.filterNotNull())
|
||||
@@ -446,15 +439,6 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Like [withEndActions], but called if the animator is canceled as well. These actions are
|
||||
* always run after those added via [addEndListener] and [withEndActions].
|
||||
*/
|
||||
fun withEndOrCancelActions(vararg endOrCancelActions: Runnable?): PhysicsAnimator<T> {
|
||||
this.endOrCancelActions.addAll(endOrCancelActions.filterNotNull().map { it::run })
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDefaultSpringConfig(defaultSpring: SpringConfig) {
|
||||
this.defaultSpring = defaultSpring
|
||||
}
|
||||
@@ -597,8 +581,7 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
getAnimatedProperties(),
|
||||
ArrayList(updateListeners),
|
||||
ArrayList(endListeners),
|
||||
ArrayList(endActions),
|
||||
ArrayList(endOrCancelActions)))
|
||||
ArrayList(endActions)))
|
||||
|
||||
// Actually start the DynamicAnimations. This is delayed until after the InternalListener is
|
||||
// constructed and added so that we don't miss the end listener firing for any animations
|
||||
@@ -616,7 +599,6 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
updateListeners.clear()
|
||||
endListeners.clear()
|
||||
endActions.clear()
|
||||
endOrCancelActions.clear()
|
||||
}
|
||||
|
||||
/** Retrieves a spring animation for the given property, building one if needed. */
|
||||
@@ -677,8 +659,7 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
private var properties: Set<FloatPropertyCompat<in T>>,
|
||||
private var updateListeners: List<UpdateListener<T>>,
|
||||
private var endListeners: List<EndListener<T>>,
|
||||
private var endActions: List<EndAction>,
|
||||
private var endOrCancelActions: List<EndAction>
|
||||
private var endActions: List<EndAction>
|
||||
) {
|
||||
|
||||
/** The number of properties whose animations haven't ended. */
|
||||
@@ -754,12 +735,9 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
}
|
||||
|
||||
// If all of the animations that this listener cares about have ended, run the end
|
||||
// actions
|
||||
if (allEnded) {
|
||||
if (!canceled) {
|
||||
endActions.forEach { it() }
|
||||
}
|
||||
endOrCancelActions.forEach { it() }
|
||||
// actions unless the animation was canceled.
|
||||
if (allEnded && !canceled) {
|
||||
endActions.forEach { it() }
|
||||
}
|
||||
|
||||
return allEnded
|
||||
@@ -807,8 +785,7 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
* animator is under test.
|
||||
*/
|
||||
internal fun cancelInternal(properties: Set<FloatPropertyCompat<in T>>) {
|
||||
val propertiesCopy = properties.toSet()
|
||||
for (property in propertiesCopy) {
|
||||
for (property in properties) {
|
||||
flingAnimations[property]?.cancel()
|
||||
springAnimations[property]?.cancel()
|
||||
}
|
||||
@@ -934,10 +911,8 @@ class PhysicsAnimator<T> private constructor (target: T) {
|
||||
* to respond to specific property animations concluding (such as hiding a view when ALPHA
|
||||
* ends, even if the corresponding TRANSLATION animations have not ended).
|
||||
*
|
||||
* If you just want to run an action when all of the property animations have ended (but not
|
||||
* canceled), you can use [PhysicsAnimator.withEndActions]. If you need to run an action
|
||||
* regardless of whether the animations were canceled, use
|
||||
* [PhysicsAnimator.withEndOrCancelActions].
|
||||
* If you just want to run an action when all of the property animations have ended, you can
|
||||
* use [PhysicsAnimator.withEndActions].
|
||||
*
|
||||
* @param target The animated object itself.
|
||||
* @param property The property whose animation has just ended.
|
||||
|
||||
@@ -26,8 +26,6 @@ import android.view.Choreographer
|
||||
import android.view.SurfaceControl
|
||||
import android.view.animation.Interpolator
|
||||
import android.window.TransitionInfo
|
||||
import com.android.wm.shell.shared.animation.WindowAnimator.BoundsAnimationParams.AnimationBounds.END
|
||||
import com.android.wm.shell.shared.animation.WindowAnimator.BoundsAnimationParams.AnimationBounds.START
|
||||
|
||||
/** Creates animations that can be applied to windows/surfaces. */
|
||||
object WindowAnimator {
|
||||
@@ -40,10 +38,7 @@ object WindowAnimator {
|
||||
val startScale: Float = 1f,
|
||||
val endScale: Float = 1f,
|
||||
val interpolator: Interpolator,
|
||||
val animBounds: AnimationBounds = END,
|
||||
) {
|
||||
enum class AnimationBounds { START, END }
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates an animator to reposition and scale the bounds of the leash of the given change.
|
||||
@@ -59,14 +54,10 @@ object WindowAnimator {
|
||||
change: TransitionInfo.Change,
|
||||
transaction: SurfaceControl.Transaction,
|
||||
): ValueAnimator {
|
||||
val bounds = when (boundsAnimDef.animBounds) {
|
||||
START -> change.startAbsBounds
|
||||
END -> change.endAbsBounds
|
||||
}
|
||||
val startPos =
|
||||
getPosition(
|
||||
displayMetrics,
|
||||
bounds,
|
||||
change.endAbsBounds,
|
||||
boundsAnimDef.startScale,
|
||||
boundsAnimDef.startOffsetYDp,
|
||||
)
|
||||
@@ -74,7 +65,7 @@ object WindowAnimator {
|
||||
val endPos =
|
||||
getPosition(
|
||||
displayMetrics,
|
||||
bounds,
|
||||
change.endAbsBounds,
|
||||
boundsAnimDef.endScale,
|
||||
boundsAnimDef.endOffsetYDp,
|
||||
)
|
||||
|
||||
@@ -31,17 +31,6 @@ public class BubbleAnythingFlagHelper {
|
||||
return enableBubbleAnything() || Flags.enableCreateAnyBubble();
|
||||
}
|
||||
|
||||
/** Whether creating any bubble and force task excluded from recents are enabled. */
|
||||
public static boolean enableCreateAnyBubbleWithForceExcludedFromRecents() {
|
||||
return Flags.enableCreateAnyBubble()
|
||||
&& com.android.window.flags2.Flags.excludeTaskFromRecents();
|
||||
}
|
||||
|
||||
/** Whether creating any bubble and app compat fixes for bubbles are enabled. */
|
||||
public static boolean enableCreateAnyBubbleWithAppCompatFixes() {
|
||||
return Flags.enableCreateAnyBubble() && Flags.enableBubbleAppCompatFixes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether creating any bubble and transforming to fullscreen, or the overall bubble anything
|
||||
* feature is enabled.
|
||||
@@ -52,26 +41,6 @@ public class BubbleAnythingFlagHelper {
|
||||
&& Flags.enableCreateAnyBubble());
|
||||
}
|
||||
|
||||
/** Whether creating a root task to manage the bubble tasks in the Core. */
|
||||
public static boolean enableRootTaskForBubble() {
|
||||
// This is needed to prevent tasks being hidden and re-parented to TDA when move-to-back.
|
||||
if (!enableCreateAnyBubbleWithForceExcludedFromRecents()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is needed to allow the activity behind the root task remains in RESUMED state.
|
||||
if (!com.android.window.flags2.Flags.enableSeeThroughTaskFragments()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is needed to allow the leaf task can be started in expected bounds.
|
||||
if (!com.android.window.flags2.Flags.respectLeafTaskBounds()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return com.android.window.flags2.Flags.rootTaskForBubble();
|
||||
}
|
||||
|
||||
/** Whether the overall bubble anything feature is enabled. */
|
||||
public static boolean enableBubbleAnything() {
|
||||
return Flags.enableBubbleAnything();
|
||||
|
||||
@@ -60,18 +60,6 @@ enum class BubbleBarLocation : Parcelable {
|
||||
|
||||
override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether locations are on the different sides from each other. If any of the
|
||||
* locations is null returns false.
|
||||
*/
|
||||
fun isDifferentSides(
|
||||
first: BubbleBarLocation?,
|
||||
second: BubbleBarLocation?,
|
||||
isRtl: Boolean
|
||||
): Boolean {
|
||||
return first != null && second != null && first.isOnLeft(isRtl) != second.isOnLeft(isRtl)
|
||||
}
|
||||
}
|
||||
|
||||
/** Define set of constants that allow to determine why location changed. */
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.wm.shell.shared.bubbles
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.WindowManagerPolicyConstants
|
||||
import com.android.internal.R
|
||||
|
||||
/** Simplifies accessing context fields. */
|
||||
object ContextUtils {
|
||||
@@ -26,7 +27,7 @@ object ContextUtils {
|
||||
/** Gets navigation mode. */
|
||||
@JvmStatic
|
||||
val Context.navigationMode: Int
|
||||
get() = resources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)
|
||||
get() = resources.getInteger(R.integer.config_navBarInteractionMode)
|
||||
|
||||
/** Returns whether the navigation mode is gestures. */
|
||||
@JvmStatic
|
||||
|
||||
@@ -55,13 +55,7 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
@DimenRes val targetSizeResId: Int,
|
||||
/** dimen resource id of the icon size in the dismiss target */
|
||||
@DimenRes val iconSizeResId: Int,
|
||||
/**
|
||||
* dimen resource id of the bottom margin for the dismiss target
|
||||
*
|
||||
* By default the margin is applied on top of the bottom navigation bar inset. To ignore
|
||||
* the bottom navigation inset, and apply a margin relative to the bottom edge of the
|
||||
* screen set [applyMarginOverNavBarInset] to `false`.
|
||||
*/
|
||||
/** dimen resource id of the bottom margin for the dismiss target */
|
||||
@DimenRes var bottomMarginResId: Int,
|
||||
/** dimen resource id of the height for dismiss area gradient */
|
||||
@DimenRes val floatingGradientHeightResId: Int,
|
||||
@@ -70,13 +64,7 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
/** drawable resource id of the dismiss target background */
|
||||
@DrawableRes val backgroundResId: Int,
|
||||
/** drawable resource id of the icon for the dismiss target */
|
||||
@DrawableRes val iconResId: Int,
|
||||
/**
|
||||
* Whether the value provided in [bottomMarginResId] should be applied on top of the
|
||||
* bottom navigation bar inset. If this is `false` the margin is relative to the bottom
|
||||
* edge of the screen.
|
||||
*/
|
||||
val applyMarginOverNavBarInset: Boolean = true,
|
||||
@DrawableRes val iconResId: Int
|
||||
)
|
||||
|
||||
companion object {
|
||||
@@ -107,9 +95,9 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
}
|
||||
|
||||
init {
|
||||
clipToPadding = false
|
||||
clipChildren = false
|
||||
visibility = View.INVISIBLE
|
||||
setClipToPadding(false)
|
||||
setClipChildren(false)
|
||||
setVisibility(View.INVISIBLE)
|
||||
addView(circle)
|
||||
}
|
||||
|
||||
@@ -147,12 +135,10 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
|
||||
/**
|
||||
* Animates this view in.
|
||||
*
|
||||
* @return `true` if the view was shown, `false` otherwise
|
||||
*/
|
||||
fun show(): Boolean {
|
||||
if (isShowing) return false
|
||||
val gradientDrawable = checkExists(gradientDrawable) ?: return false
|
||||
fun show() {
|
||||
if (isShowing) return
|
||||
val gradientDrawable = checkExists(gradientDrawable) ?: return
|
||||
isShowing = true
|
||||
setVisibility(View.VISIBLE)
|
||||
val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
|
||||
@@ -164,7 +150,6 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
animator
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
|
||||
.start()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,16 +209,11 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
|
||||
private fun updatePadding() {
|
||||
val config = checkExists(config) ?: return
|
||||
val bottomMargin = resources.getDimensionPixelSize(config.bottomMarginResId)
|
||||
if (config.applyMarginOverNavBarInset) {
|
||||
val insets: WindowInsets = wm.currentWindowMetrics.windowInsets
|
||||
val navInset = insets.getInsetsIgnoringVisibility(
|
||||
WindowInsets.Type.navigationBars()
|
||||
)
|
||||
setPadding(0, 0, 0, navInset.bottom + bottomMargin)
|
||||
} else {
|
||||
setPadding(0, 0, 0, bottomMargin)
|
||||
}
|
||||
val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
|
||||
val navInset = insets.getInsetsIgnoringVisibility(
|
||||
WindowInsets.Type.navigationBars())
|
||||
setPadding(0, 0, 0, navInset.bottom +
|
||||
resources.getDimensionPixelSize(config.bottomMarginResId))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.wm.shell.shared.bubbles
|
||||
|
||||
import android.graphics.Rect
|
||||
import kotlin.math.hypot
|
||||
|
||||
/**
|
||||
* Represents an invisible area on the screen that determines what happens to a dragged object if it
|
||||
@@ -31,78 +30,42 @@ import kotlin.math.hypot
|
||||
sealed interface DragZone {
|
||||
|
||||
/** The bounds of this drag zone. */
|
||||
val bounds: Bounds
|
||||
val bounds: Rect
|
||||
/** The bounds of the drop target associated with this drag zone. */
|
||||
val dropTarget: DropTargetRect?
|
||||
|
||||
/** The bounds of the second drop target associated with this drag zone. */
|
||||
val secondDropTarget: DropTargetRect?
|
||||
val dropTarget: Rect?
|
||||
|
||||
fun contains(x: Int, y: Int) = bounds.contains(x, y)
|
||||
|
||||
sealed interface Bounds {
|
||||
fun contains(x: Int, y: Int) =
|
||||
when (this) {
|
||||
is RectZone -> rect.contains(x, y)
|
||||
is CircleZone -> hypot((x - this.x).toFloat(), (y - this.y).toFloat()) < radius
|
||||
}
|
||||
|
||||
data class RectZone(val rect: Rect) : Bounds
|
||||
|
||||
data class CircleZone(val x: Int, val y: Int, val radius: Int) : Bounds
|
||||
}
|
||||
|
||||
data class DropTargetRect(val rect: Rect, val cornerRadius: Float)
|
||||
|
||||
/** Represents the bubble drag area on the screen. */
|
||||
sealed class Bubble(
|
||||
override val bounds: Bounds.RectZone,
|
||||
override val dropTarget: DropTargetRect?,
|
||||
) : DragZone {
|
||||
data class Left(
|
||||
override val bounds: Bounds.RectZone,
|
||||
override val dropTarget: DropTargetRect?,
|
||||
override val secondDropTarget: DropTargetRect? = null,
|
||||
) : Bubble(bounds, dropTarget)
|
||||
sealed class Bubble(override val bounds: Rect, override val dropTarget: Rect) : DragZone {
|
||||
data class Left(override val bounds: Rect, override val dropTarget: Rect) :
|
||||
Bubble(bounds, dropTarget)
|
||||
|
||||
data class Right(
|
||||
override val bounds: Bounds.RectZone,
|
||||
override val dropTarget: DropTargetRect?,
|
||||
override val secondDropTarget: DropTargetRect? = null,
|
||||
) : Bubble(bounds, dropTarget)
|
||||
data class Right(override val bounds: Rect, override val dropTarget: Rect) :
|
||||
Bubble(bounds, dropTarget)
|
||||
}
|
||||
|
||||
/** Represents dragging to Desktop Window. */
|
||||
data class DesktopWindow(
|
||||
override val bounds: Bounds.RectZone,
|
||||
override val dropTarget: DropTargetRect,
|
||||
override val secondDropTarget: DropTargetRect? = null,
|
||||
) : DragZone
|
||||
data class DesktopWindow(override val bounds: Rect, override val dropTarget: Rect) : DragZone
|
||||
|
||||
/** Represents dragging to Full Screen. */
|
||||
data class FullScreen(
|
||||
override val bounds: Bounds.RectZone,
|
||||
override val dropTarget: DropTargetRect,
|
||||
override val secondDropTarget: DropTargetRect? = null,
|
||||
) : DragZone
|
||||
data class FullScreen(override val bounds: Rect, override val dropTarget: Rect) : DragZone
|
||||
|
||||
/** Represents dragging to dismiss. */
|
||||
data class Dismiss(override val bounds: Bounds.CircleZone) : DragZone {
|
||||
override val dropTarget: DropTargetRect? = null
|
||||
override val secondDropTarget: DropTargetRect? = null
|
||||
data class Dismiss(override val bounds: Rect) : DragZone {
|
||||
override val dropTarget: Rect? = null
|
||||
}
|
||||
|
||||
/** Represents dragging to enter Split or replace a Split app. */
|
||||
sealed class Split(override val bounds: Bounds.RectZone) : DragZone {
|
||||
override val dropTarget: DropTargetRect? = null
|
||||
override val secondDropTarget: DropTargetRect? = null
|
||||
sealed class Split(override val bounds: Rect) : DragZone {
|
||||
override val dropTarget: Rect? = null
|
||||
|
||||
data class Left(override val bounds: Bounds.RectZone) : Split(bounds)
|
||||
data class Left(override val bounds: Rect) : Split(bounds)
|
||||
|
||||
data class Right(override val bounds: Bounds.RectZone) : Split(bounds)
|
||||
data class Right(override val bounds: Rect) : Split(bounds)
|
||||
|
||||
data class Top(override val bounds: Bounds.RectZone) : Split(bounds)
|
||||
data class Top(override val bounds: Rect) : Split(bounds)
|
||||
|
||||
data class Bottom(override val bounds: Bounds.RectZone) : Split(bounds)
|
||||
data class Bottom(override val bounds: Rect) : Split(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@ import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.DimenRes
|
||||
import com.android.wm.shell.shared.bubbles.DragZone.Bounds.CircleZone
|
||||
import com.android.wm.shell.shared.bubbles.DragZone.Bounds.RectZone
|
||||
import com.android.wm.shell.shared.bubbles.DragZone.DropTargetRect
|
||||
import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode
|
||||
|
||||
/** A class for creating drag zones for dragging bubble objects or dragging into bubbles. */
|
||||
@@ -31,14 +28,12 @@ class DragZoneFactory(
|
||||
private val deviceConfig: DeviceConfig,
|
||||
private val splitScreenModeChecker: SplitScreenModeChecker,
|
||||
private val desktopWindowModeChecker: DesktopWindowModeChecker,
|
||||
private val bubbleBarPropertiesProvider: BubbleBarPropertiesProvider,
|
||||
) {
|
||||
|
||||
private val windowBounds: Rect
|
||||
get() = deviceConfig.windowBounds
|
||||
|
||||
private var dismissDragZoneRadius = 0
|
||||
private var dismissDragZoneBottomMargin = 0
|
||||
private var dismissDragZoneSize = 0
|
||||
private var bubbleDragZoneTabletSize = 0
|
||||
private var bubbleDragZoneFoldableSize = 0
|
||||
private var fullScreenDragZoneWidth = 0
|
||||
@@ -47,7 +42,6 @@ class DragZoneFactory(
|
||||
private var desktopWindowDragZoneHeight = 0
|
||||
private var desktopWindowFromExpandedViewDragZoneWidth = 0
|
||||
private var desktopWindowFromExpandedViewDragZoneHeight = 0
|
||||
private var desktopWindowFromExpandedViewDragZoneYOffset = 0
|
||||
private var splitFromBubbleDragZoneHeight = 0
|
||||
private var splitFromBubbleDragZoneWidth = 0
|
||||
private var hSplitFromExpandedViewDragZoneWidth = 0
|
||||
@@ -63,98 +57,53 @@ class DragZoneFactory(
|
||||
private var expandedViewDropTargetHeight = 0
|
||||
private var expandedViewDropTargetPaddingBottom = 0
|
||||
private var expandedViewDropTargetPaddingHorizontal = 0
|
||||
private var bubbleBarDropTargetPaddingHorizontal = 0
|
||||
|
||||
private var dropTargetCornerRadius = 0f
|
||||
|
||||
private val fullScreenDropTarget: DropTargetRect
|
||||
private val fullScreenDropTarget: Rect
|
||||
get() =
|
||||
DropTargetRect(
|
||||
Rect(windowBounds).apply {
|
||||
inset(fullScreenDropTargetPadding, fullScreenDropTargetPadding)
|
||||
},
|
||||
dropTargetCornerRadius
|
||||
Rect(windowBounds).apply {
|
||||
inset(fullScreenDropTargetPadding, fullScreenDropTargetPadding)
|
||||
}
|
||||
|
||||
private val desktopWindowDropTarget: Rect
|
||||
get() =
|
||||
Rect(windowBounds).apply {
|
||||
if (deviceConfig.isLandscape) {
|
||||
inset(
|
||||
/* dx= */ desktopWindowDropTargetPaddingLarge,
|
||||
/* dy= */ desktopWindowDropTargetPaddingSmall
|
||||
)
|
||||
} else {
|
||||
inset(
|
||||
/* dx= */ desktopWindowDropTargetPaddingSmall,
|
||||
/* dy= */ desktopWindowDropTargetPaddingLarge
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val expandedViewDropTargetLeft: Rect
|
||||
get() =
|
||||
Rect(
|
||||
expandedViewDropTargetPaddingHorizontal,
|
||||
windowBounds.bottom -
|
||||
expandedViewDropTargetPaddingBottom -
|
||||
expandedViewDropTargetHeight,
|
||||
expandedViewDropTargetWidth + expandedViewDropTargetPaddingHorizontal,
|
||||
windowBounds.bottom - expandedViewDropTargetPaddingBottom
|
||||
)
|
||||
|
||||
private val desktopWindowDropTarget: DropTargetRect
|
||||
private val expandedViewDropTargetRight: Rect
|
||||
get() =
|
||||
DropTargetRect(
|
||||
Rect(windowBounds).apply {
|
||||
if (deviceConfig.isLandscape) {
|
||||
inset(
|
||||
/* dx= */ desktopWindowDropTargetPaddingLarge,
|
||||
/* dy= */ desktopWindowDropTargetPaddingSmall
|
||||
)
|
||||
} else {
|
||||
inset(
|
||||
/* dx= */ desktopWindowDropTargetPaddingSmall,
|
||||
/* dy= */ desktopWindowDropTargetPaddingLarge
|
||||
)
|
||||
}
|
||||
},
|
||||
dropTargetCornerRadius
|
||||
Rect(
|
||||
windowBounds.right -
|
||||
expandedViewDropTargetPaddingHorizontal -
|
||||
expandedViewDropTargetWidth,
|
||||
windowBounds.bottom -
|
||||
expandedViewDropTargetPaddingBottom -
|
||||
expandedViewDropTargetHeight,
|
||||
windowBounds.right - expandedViewDropTargetPaddingHorizontal,
|
||||
windowBounds.bottom - expandedViewDropTargetPaddingBottom
|
||||
)
|
||||
|
||||
private val expandedViewDropTargetLeft: DropTargetRect
|
||||
get() =
|
||||
DropTargetRect(
|
||||
Rect(
|
||||
expandedViewDropTargetPaddingHorizontal,
|
||||
windowBounds.bottom -
|
||||
expandedViewDropTargetPaddingBottom -
|
||||
expandedViewDropTargetHeight,
|
||||
expandedViewDropTargetWidth + expandedViewDropTargetPaddingHorizontal,
|
||||
windowBounds.bottom - expandedViewDropTargetPaddingBottom
|
||||
),
|
||||
dropTargetCornerRadius
|
||||
)
|
||||
|
||||
private val expandedViewDropTargetRight: DropTargetRect
|
||||
get() =
|
||||
DropTargetRect(
|
||||
Rect(
|
||||
windowBounds.right -
|
||||
expandedViewDropTargetPaddingHorizontal -
|
||||
expandedViewDropTargetWidth,
|
||||
windowBounds.bottom -
|
||||
expandedViewDropTargetPaddingBottom -
|
||||
expandedViewDropTargetHeight,
|
||||
windowBounds.right - expandedViewDropTargetPaddingHorizontal,
|
||||
windowBounds.bottom - expandedViewDropTargetPaddingBottom
|
||||
),
|
||||
dropTargetCornerRadius
|
||||
)
|
||||
|
||||
private val bubbleBarDropTargetLeft: DropTargetRect
|
||||
get() {
|
||||
val rect =
|
||||
Rect(
|
||||
bubbleBarDropTargetPaddingHorizontal,
|
||||
windowBounds.bottom -
|
||||
bubbleBarPropertiesProvider.getBottomPadding() -
|
||||
bubbleBarPropertiesProvider.getHeight(),
|
||||
bubbleBarDropTargetPaddingHorizontal + bubbleBarPropertiesProvider.getWidth(),
|
||||
windowBounds.bottom - bubbleBarPropertiesProvider.getBottomPadding()
|
||||
)
|
||||
return DropTargetRect(rect, rect.height() / 2f)
|
||||
}
|
||||
|
||||
private val bubbleBarDropTargetRight: DropTargetRect
|
||||
get() {
|
||||
val rect =
|
||||
Rect(
|
||||
windowBounds.right -
|
||||
bubbleBarDropTargetPaddingHorizontal -
|
||||
bubbleBarPropertiesProvider.getWidth(),
|
||||
windowBounds.bottom -
|
||||
bubbleBarPropertiesProvider.getBottomPadding() -
|
||||
bubbleBarPropertiesProvider.getHeight(),
|
||||
windowBounds.right - bubbleBarDropTargetPaddingHorizontal,
|
||||
windowBounds.bottom - bubbleBarPropertiesProvider.getBottomPadding()
|
||||
)
|
||||
return DropTargetRect(rect, rect.height() / 2f)
|
||||
}
|
||||
|
||||
init {
|
||||
onConfigurationUpdated()
|
||||
}
|
||||
@@ -163,8 +112,8 @@ class DragZoneFactory(
|
||||
fun onConfigurationUpdated() {
|
||||
// TODO b/396539130: Use the shared xml resources once we can easily access them from
|
||||
// launcher
|
||||
dismissDragZoneRadius = 96.dpToPx()
|
||||
dismissDragZoneBottomMargin = 12.dpToPx()
|
||||
dismissDragZoneSize =
|
||||
if (deviceConfig.isSmallTablet) 140.dpToPx() else 200.dpToPx()
|
||||
bubbleDragZoneTabletSize = 200.dpToPx()
|
||||
bubbleDragZoneFoldableSize = 140.dpToPx()
|
||||
fullScreenDragZoneWidth = 512.dpToPx()
|
||||
@@ -173,7 +122,6 @@ class DragZoneFactory(
|
||||
desktopWindowDragZoneHeight = 300.dpToPx()
|
||||
desktopWindowFromExpandedViewDragZoneWidth = 200.dpToPx()
|
||||
desktopWindowFromExpandedViewDragZoneHeight = 350.dpToPx()
|
||||
desktopWindowFromExpandedViewDragZoneYOffset = 25.dpToPx()
|
||||
splitFromBubbleDragZoneHeight = 100.dpToPx()
|
||||
splitFromBubbleDragZoneWidth = 60.dpToPx()
|
||||
hSplitFromExpandedViewDragZoneWidth = 60.dpToPx()
|
||||
@@ -188,9 +136,6 @@ class DragZoneFactory(
|
||||
expandedViewDropTargetHeight = 578.dpToPx()
|
||||
expandedViewDropTargetPaddingBottom = 108.dpToPx()
|
||||
expandedViewDropTargetPaddingHorizontal = 24.dpToPx()
|
||||
bubbleBarDropTargetPaddingHorizontal = 24.dpToPx()
|
||||
|
||||
dropTargetCornerRadius = 28.dpToPx().toFloat()
|
||||
}
|
||||
|
||||
private fun Context.resolveDimension(@DimenRes dimension: Int) =
|
||||
@@ -215,7 +160,7 @@ class DragZoneFactory(
|
||||
when (draggedObject) {
|
||||
is DraggedObject.BubbleBar -> {
|
||||
dragZones.add(createDismissDragZone())
|
||||
dragZones.addAll(createBubbleHalfScreenDragZones(forBubbleBar = true))
|
||||
dragZones.addAll(createBubbleHalfScreenDragZones())
|
||||
}
|
||||
is DraggedObject.Bubble -> {
|
||||
dragZones.add(createDismissDragZone())
|
||||
@@ -237,80 +182,65 @@ class DragZoneFactory(
|
||||
} else {
|
||||
dragZones.addAll(createSplitScreenDragZonesForExpandedViewOnTablet())
|
||||
}
|
||||
dragZones.addAll(createBubbleHalfScreenDragZones(forBubbleBar = false))
|
||||
}
|
||||
is DraggedObject.LauncherIcon -> {
|
||||
val showDropTarget = draggedObject.showDropTarget
|
||||
val showSecondDropTarget = !draggedObject.bubbleBarHasBubbles
|
||||
dragZones.addAll(createBubbleCornerDragZones(showDropTarget, showSecondDropTarget))
|
||||
dragZones.addAll(createBubbleHalfScreenDragZones())
|
||||
}
|
||||
}
|
||||
return dragZones
|
||||
}
|
||||
|
||||
fun getBubbleBarDropRect(isLeftSide: Boolean): Rect {
|
||||
private fun createDismissDragZone(): DragZone {
|
||||
return DragZone.Dismiss(
|
||||
bounds =
|
||||
Rect(
|
||||
windowBounds.right / 2 - dismissDragZoneSize / 2,
|
||||
windowBounds.bottom - dismissDragZoneSize,
|
||||
windowBounds.right / 2 + dismissDragZoneSize / 2,
|
||||
windowBounds.bottom
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun createBubbleCornerDragZones(): List<DragZone> {
|
||||
val dragZoneSize =
|
||||
if (deviceConfig.isSmallTablet) {
|
||||
bubbleDragZoneFoldableSize
|
||||
} else {
|
||||
bubbleDragZoneTabletSize
|
||||
}
|
||||
return Rect(
|
||||
if (isLeftSide) 0 else windowBounds.right - dragZoneSize,
|
||||
windowBounds.bottom - dragZoneSize,
|
||||
if (isLeftSide) dragZoneSize else windowBounds.right,
|
||||
windowBounds.bottom
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDismissDragZone(): DragZone {
|
||||
return DragZone.Dismiss(
|
||||
bounds =
|
||||
CircleZone(
|
||||
x = windowBounds.right / 2,
|
||||
y = windowBounds.bottom - dismissDragZoneBottomMargin - dismissDragZoneRadius,
|
||||
radius = dismissDragZoneRadius
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun createBubbleCornerDragZones(
|
||||
showDropTarget: Boolean = true,
|
||||
showSecondDropTarget: Boolean = false
|
||||
): List<DragZone> {
|
||||
return listOf(
|
||||
DragZone.Bubble.Left(
|
||||
bounds = RectZone(getBubbleBarDropRect(isLeftSide = true)),
|
||||
dropTarget = if (showDropTarget) expandedViewDropTargetLeft else null,
|
||||
secondDropTarget = if (showSecondDropTarget) bubbleBarDropTargetLeft else null
|
||||
bounds =
|
||||
Rect(0, windowBounds.bottom - dragZoneSize, dragZoneSize, windowBounds.bottom),
|
||||
dropTarget = expandedViewDropTargetLeft,
|
||||
),
|
||||
DragZone.Bubble.Right(
|
||||
bounds = RectZone(getBubbleBarDropRect(isLeftSide = false)),
|
||||
dropTarget = if (showDropTarget) expandedViewDropTargetRight else null,
|
||||
secondDropTarget = if (showSecondDropTarget) bubbleBarDropTargetRight else null
|
||||
bounds =
|
||||
Rect(
|
||||
windowBounds.right - dragZoneSize,
|
||||
windowBounds.bottom - dragZoneSize,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom,
|
||||
),
|
||||
dropTarget = expandedViewDropTargetRight,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun createBubbleHalfScreenDragZones(forBubbleBar: Boolean): List<DragZone> {
|
||||
private fun createBubbleHalfScreenDragZones(): List<DragZone> {
|
||||
return listOf(
|
||||
DragZone.Bubble.Left(
|
||||
bounds = RectZone(Rect(0, 0, windowBounds.right / 2, windowBounds.bottom)),
|
||||
dropTarget =
|
||||
if (forBubbleBar) bubbleBarDropTargetLeft else expandedViewDropTargetLeft,
|
||||
bounds = Rect(0, 0, windowBounds.right / 2, windowBounds.bottom),
|
||||
dropTarget = expandedViewDropTargetLeft,
|
||||
),
|
||||
DragZone.Bubble.Right(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
windowBounds.right / 2,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom,
|
||||
),
|
||||
Rect(
|
||||
windowBounds.right / 2,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom,
|
||||
),
|
||||
dropTarget =
|
||||
if (forBubbleBar) bubbleBarDropTargetRight else expandedViewDropTargetRight,
|
||||
dropTarget = expandedViewDropTargetRight,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -318,13 +248,11 @@ class DragZoneFactory(
|
||||
private fun createFullScreenDragZone(): DragZone {
|
||||
return DragZone.FullScreen(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
windowBounds.right / 2 - fullScreenDragZoneWidth / 2,
|
||||
0,
|
||||
windowBounds.right / 2 + fullScreenDragZoneWidth / 2,
|
||||
fullScreenDragZoneHeight
|
||||
),
|
||||
Rect(
|
||||
windowBounds.right / 2 - fullScreenDragZoneWidth / 2,
|
||||
0,
|
||||
windowBounds.right / 2 + fullScreenDragZoneWidth / 2,
|
||||
fullScreenDragZoneHeight
|
||||
),
|
||||
dropTarget = fullScreenDropTarget
|
||||
)
|
||||
@@ -337,22 +265,18 @@ class DragZoneFactory(
|
||||
return DragZone.DesktopWindow(
|
||||
bounds =
|
||||
if (deviceConfig.isLandscape) {
|
||||
RectZone(
|
||||
Rect(
|
||||
windowBounds.right / 2 - desktopWindowDragZoneWidth / 2,
|
||||
windowBounds.bottom / 2 - desktopWindowDragZoneHeight / 2,
|
||||
windowBounds.right / 2 + desktopWindowDragZoneWidth / 2,
|
||||
windowBounds.bottom / 2 + desktopWindowDragZoneHeight / 2
|
||||
)
|
||||
Rect(
|
||||
windowBounds.right / 2 - desktopWindowDragZoneWidth / 2,
|
||||
windowBounds.bottom / 2 - desktopWindowDragZoneHeight / 2,
|
||||
windowBounds.right / 2 + desktopWindowDragZoneWidth / 2,
|
||||
windowBounds.bottom / 2 + desktopWindowDragZoneHeight / 2
|
||||
)
|
||||
} else {
|
||||
RectZone(
|
||||
Rect(
|
||||
0,
|
||||
windowBounds.bottom / 2 - desktopWindowDragZoneHeight / 2,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom / 2 + desktopWindowDragZoneHeight / 2
|
||||
)
|
||||
Rect(
|
||||
0,
|
||||
windowBounds.bottom / 2 - desktopWindowDragZoneHeight / 2,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom / 2 + desktopWindowDragZoneHeight / 2
|
||||
)
|
||||
},
|
||||
dropTarget = desktopWindowDropTarget
|
||||
@@ -362,15 +286,11 @@ class DragZoneFactory(
|
||||
private fun createDesktopWindowDragZoneForExpandedView(): DragZone {
|
||||
return DragZone.DesktopWindow(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
windowBounds.right / 2 - desktopWindowFromExpandedViewDragZoneWidth / 2,
|
||||
windowBounds.bottom / 2 - desktopWindowFromExpandedViewDragZoneHeight / 2 -
|
||||
desktopWindowFromExpandedViewDragZoneYOffset,
|
||||
windowBounds.right / 2 + desktopWindowFromExpandedViewDragZoneWidth / 2,
|
||||
windowBounds.bottom / 2 + desktopWindowFromExpandedViewDragZoneHeight / 2 -
|
||||
desktopWindowFromExpandedViewDragZoneYOffset
|
||||
),
|
||||
Rect(
|
||||
windowBounds.right / 2 - desktopWindowFromExpandedViewDragZoneWidth / 2,
|
||||
windowBounds.bottom / 2 - desktopWindowFromExpandedViewDragZoneHeight / 2,
|
||||
windowBounds.right / 2 + desktopWindowFromExpandedViewDragZoneWidth / 2,
|
||||
windowBounds.bottom / 2 + desktopWindowFromExpandedViewDragZoneHeight / 2
|
||||
),
|
||||
dropTarget = desktopWindowDropTarget
|
||||
)
|
||||
@@ -387,18 +307,15 @@ class DragZoneFactory(
|
||||
SplitScreenMode.NONE ->
|
||||
listOf(
|
||||
DragZone.Split.Top(
|
||||
bounds =
|
||||
RectZone(Rect(0, 0, windowBounds.right, windowBounds.bottom / 2)),
|
||||
bounds = Rect(0, 0, windowBounds.right, windowBounds.bottom / 2),
|
||||
),
|
||||
DragZone.Split.Bottom(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
0,
|
||||
windowBounds.bottom / 2,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
Rect(
|
||||
0,
|
||||
windowBounds.bottom / 2,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -406,24 +323,20 @@ class DragZoneFactory(
|
||||
listOf(
|
||||
DragZone.Split.Top(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
0,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom - splitFromBubbleDragZoneHeight
|
||||
),
|
||||
Rect(
|
||||
0,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom - splitFromBubbleDragZoneHeight
|
||||
),
|
||||
),
|
||||
DragZone.Split.Bottom(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
0,
|
||||
windowBounds.bottom - splitFromBubbleDragZoneHeight,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
Rect(
|
||||
0,
|
||||
windowBounds.bottom - splitFromBubbleDragZoneHeight,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -431,20 +344,15 @@ class DragZoneFactory(
|
||||
SplitScreenMode.SPLIT_10_90 -> {
|
||||
listOf(
|
||||
DragZone.Split.Top(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(0, 0, windowBounds.right, splitFromBubbleDragZoneHeight)
|
||||
),
|
||||
bounds = Rect(0, 0, windowBounds.right, splitFromBubbleDragZoneHeight),
|
||||
),
|
||||
DragZone.Split.Bottom(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
0,
|
||||
splitFromBubbleDragZoneHeight,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
Rect(
|
||||
0,
|
||||
splitFromBubbleDragZoneHeight,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -457,18 +365,15 @@ class DragZoneFactory(
|
||||
SplitScreenMode.NONE ->
|
||||
listOf(
|
||||
DragZone.Split.Left(
|
||||
bounds =
|
||||
RectZone(Rect(0, 0, windowBounds.right / 2, windowBounds.bottom)),
|
||||
bounds = Rect(0, 0, windowBounds.right / 2, windowBounds.bottom),
|
||||
),
|
||||
DragZone.Split.Right(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
windowBounds.right / 2,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
Rect(
|
||||
windowBounds.right / 2,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -476,44 +381,35 @@ class DragZoneFactory(
|
||||
listOf(
|
||||
DragZone.Split.Left(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
0,
|
||||
0,
|
||||
windowBounds.right - splitFromBubbleDragZoneWidth,
|
||||
windowBounds.bottom
|
||||
),
|
||||
Rect(
|
||||
0,
|
||||
0,
|
||||
windowBounds.right - splitFromBubbleDragZoneWidth,
|
||||
windowBounds.bottom
|
||||
),
|
||||
),
|
||||
DragZone.Split.Right(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
windowBounds.right - splitFromBubbleDragZoneWidth,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
Rect(
|
||||
windowBounds.right - splitFromBubbleDragZoneWidth,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
)
|
||||
)
|
||||
SplitScreenMode.SPLIT_10_90 ->
|
||||
listOf(
|
||||
DragZone.Split.Left(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(0, 0, splitFromBubbleDragZoneWidth, windowBounds.bottom)
|
||||
),
|
||||
bounds = Rect(0, 0, splitFromBubbleDragZoneWidth, windowBounds.bottom),
|
||||
),
|
||||
DragZone.Split.Right(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
splitFromBubbleDragZoneWidth,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
Rect(
|
||||
splitFromBubbleDragZoneWidth,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -526,35 +422,27 @@ class DragZoneFactory(
|
||||
createHorizontalSplitDragZonesForExpandedView()
|
||||
} else {
|
||||
// for tablets in portrait mode, split drag zones appear below the full screen drag zone
|
||||
// for the top split zone. the bottom edge of the bottom split zone starts at the
|
||||
// dismiss zone upper half circle to cover the area outside of the dismiss circle but
|
||||
// within the split zone width. Both are horizontally centered.
|
||||
// for the top split zone, and above the dismiss zone. Both are horizontally centered.
|
||||
val splitZoneLeft = windowBounds.right / 2 - vSplitFromExpandedViewDragZoneWidth / 2
|
||||
val splitZoneRight = splitZoneLeft + vSplitFromExpandedViewDragZoneWidth
|
||||
val bottomSplitZoneBottom =
|
||||
windowBounds.bottom - dismissDragZoneBottomMargin - dismissDragZoneRadius * 2
|
||||
val bottomSplitZoneBottom = windowBounds.bottom - dismissDragZoneSize
|
||||
listOf(
|
||||
DragZone.Split.Top(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
fullScreenDragZoneHeight,
|
||||
splitZoneRight,
|
||||
fullScreenDragZoneHeight +
|
||||
vSplitFromExpandedViewDragZoneHeightTablet
|
||||
),
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
fullScreenDragZoneHeight,
|
||||
splitZoneRight,
|
||||
fullScreenDragZoneHeight + vSplitFromExpandedViewDragZoneHeightTablet
|
||||
),
|
||||
),
|
||||
DragZone.Split.Bottom(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
bottomSplitZoneBottom - vSplitFromExpandedViewDragZoneHeightTablet,
|
||||
splitZoneRight,
|
||||
bottomSplitZoneBottom
|
||||
),
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
bottomSplitZoneBottom - vSplitFromExpandedViewDragZoneHeightTablet,
|
||||
splitZoneRight,
|
||||
bottomSplitZoneBottom
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -572,26 +460,22 @@ class DragZoneFactory(
|
||||
listOf(
|
||||
DragZone.Split.Top(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
fullScreenDragZoneHeight,
|
||||
splitZoneLeft + fullScreenDragZoneWidth,
|
||||
fullScreenDragZoneHeight +
|
||||
vSplitFromExpandedViewDragZoneHeightFoldTall
|
||||
),
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
fullScreenDragZoneHeight,
|
||||
splitZoneLeft + fullScreenDragZoneWidth,
|
||||
fullScreenDragZoneHeight +
|
||||
vSplitFromExpandedViewDragZoneHeightFoldTall
|
||||
),
|
||||
),
|
||||
DragZone.Split.Bottom(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
windowBounds.bottom / 2,
|
||||
splitZoneLeft + fullScreenDragZoneWidth,
|
||||
windowBounds.bottom / 2 +
|
||||
vSplitFromExpandedViewDragZoneHeightFoldTall
|
||||
),
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
windowBounds.bottom / 2,
|
||||
splitZoneLeft + fullScreenDragZoneWidth,
|
||||
windowBounds.bottom / 2 +
|
||||
vSplitFromExpandedViewDragZoneHeightFoldTall
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -599,25 +483,21 @@ class DragZoneFactory(
|
||||
listOf(
|
||||
DragZone.Split.Top(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
0,
|
||||
0,
|
||||
windowBounds.right,
|
||||
vSplitFromExpandedViewDragZoneHeightFoldShort
|
||||
),
|
||||
Rect(
|
||||
0,
|
||||
0,
|
||||
windowBounds.right,
|
||||
vSplitFromExpandedViewDragZoneHeightFoldShort
|
||||
),
|
||||
),
|
||||
DragZone.Split.Bottom(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
vSplitFromExpandedViewDragZoneHeightFoldShort,
|
||||
splitZoneLeft + fullScreenDragZoneWidth,
|
||||
vSplitFromExpandedViewDragZoneHeightFoldShort +
|
||||
vSplitFromExpandedViewDragZoneHeightFoldTall
|
||||
),
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
vSplitFromExpandedViewDragZoneHeightFoldShort,
|
||||
splitZoneLeft + fullScreenDragZoneWidth,
|
||||
vSplitFromExpandedViewDragZoneHeightFoldShort +
|
||||
vSplitFromExpandedViewDragZoneHeightFoldTall
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -625,26 +505,22 @@ class DragZoneFactory(
|
||||
listOf(
|
||||
DragZone.Split.Top(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
fullScreenDragZoneHeight,
|
||||
splitZoneLeft + fullScreenDragZoneWidth,
|
||||
fullScreenDragZoneHeight +
|
||||
vSplitFromExpandedViewDragZoneHeightFoldTall
|
||||
),
|
||||
Rect(
|
||||
splitZoneLeft,
|
||||
fullScreenDragZoneHeight,
|
||||
splitZoneLeft + fullScreenDragZoneWidth,
|
||||
fullScreenDragZoneHeight +
|
||||
vSplitFromExpandedViewDragZoneHeightFoldTall
|
||||
),
|
||||
),
|
||||
DragZone.Split.Bottom(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
0,
|
||||
windowBounds.bottom -
|
||||
vSplitFromExpandedViewDragZoneHeightFoldShort,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
Rect(
|
||||
0,
|
||||
windowBounds.bottom -
|
||||
vSplitFromExpandedViewDragZoneHeightFoldShort,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -658,20 +534,23 @@ class DragZoneFactory(
|
||||
private fun createHorizontalSplitDragZonesForExpandedView(): List<DragZone> {
|
||||
// horizontal split drag zones for expanded view appear on the edges of the screen from the
|
||||
// top down until the dismiss drag zone height
|
||||
val bottomY = windowBounds.bottom - dismissDragZoneBottomMargin - dismissDragZoneRadius * 2
|
||||
return listOf(
|
||||
DragZone.Split.Left(
|
||||
bounds = RectZone(Rect(0, 0, hSplitFromExpandedViewDragZoneWidth, bottomY))
|
||||
bounds =
|
||||
Rect(
|
||||
0,
|
||||
0,
|
||||
hSplitFromExpandedViewDragZoneWidth,
|
||||
windowBounds.bottom - dismissDragZoneSize
|
||||
),
|
||||
),
|
||||
DragZone.Split.Right(
|
||||
bounds =
|
||||
RectZone(
|
||||
Rect(
|
||||
windowBounds.right - hSplitFromExpandedViewDragZoneWidth,
|
||||
0,
|
||||
windowBounds.right,
|
||||
bottomY
|
||||
),
|
||||
Rect(
|
||||
windowBounds.right - hSplitFromExpandedViewDragZoneWidth,
|
||||
0,
|
||||
windowBounds.right,
|
||||
windowBounds.bottom - dismissDragZoneSize
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -694,13 +573,4 @@ class DragZoneFactory(
|
||||
fun interface DesktopWindowModeChecker {
|
||||
fun isSupported(): Boolean
|
||||
}
|
||||
|
||||
/** Bubble bar properties for generating a drop target. */
|
||||
interface BubbleBarPropertiesProvider {
|
||||
fun getHeight(): Int = 0
|
||||
|
||||
fun getWidth(): Int = 0
|
||||
|
||||
fun getBottomPadding(): Int = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,17 +18,10 @@ package com.android.wm.shell.shared.bubbles
|
||||
|
||||
/** A Bubble object being dragged. */
|
||||
sealed interface DraggedObject {
|
||||
/** The initial location of the object at the start of the drag gesture. */
|
||||
val initialLocation: BubbleBarLocation
|
||||
|
||||
data class Bubble(val initialLocation: BubbleBarLocation) : DraggedObject
|
||||
|
||||
data class BubbleBar(val initialLocation: BubbleBarLocation) : DraggedObject
|
||||
|
||||
data class ExpandedView(val initialLocation: BubbleBarLocation) : DraggedObject
|
||||
|
||||
// TODO(b/411505605) Remove onDropAction and move showDropTarget up
|
||||
data class LauncherIcon(
|
||||
val bubbleBarHasBubbles: Boolean,
|
||||
val showDropTarget: Boolean = true,
|
||||
val onDropAction: Runnable
|
||||
) : DraggedObject
|
||||
data class Bubble(override val initialLocation: BubbleBarLocation) : DraggedObject
|
||||
data class BubbleBar(override val initialLocation: BubbleBarLocation) : DraggedObject
|
||||
data class ExpandedView(override val initialLocation: BubbleBarLocation) : DraggedObject
|
||||
}
|
||||
|
||||
@@ -17,20 +17,14 @@
|
||||
package com.android.wm.shell.shared.bubbles
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.animation.Animator
|
||||
import androidx.core.animation.AnimatorListenerAdapter
|
||||
import androidx.core.animation.ValueAnimator
|
||||
import com.android.wm.shell.shared.bubbles.DragZone.DropTargetRect
|
||||
import com.android.wm.shell.shared.bubbles.DraggedObject.Bubble
|
||||
import com.android.wm.shell.shared.bubbles.DraggedObject.BubbleBar
|
||||
import com.android.wm.shell.shared.bubbles.DraggedObject.ExpandedView
|
||||
import com.android.wm.shell.shared.bubbles.DraggedObject.LauncherIcon
|
||||
import com.android.wm.shell.R
|
||||
|
||||
/**
|
||||
* Manages animating drop targets in response to dragging bubble icons or bubble expanded views
|
||||
@@ -43,19 +37,15 @@ class DropTargetManager(
|
||||
) {
|
||||
|
||||
private var state: DragState? = null
|
||||
|
||||
@VisibleForTesting val dropTargetView = DropTargetView(context)
|
||||
@VisibleForTesting var secondDropTargetView: DropTargetView? = null
|
||||
private val dropTargetView = DropTargetView(context)
|
||||
private var animator: ValueAnimator? = null
|
||||
private var morphRect: RectF = RectF(0f, 0f, 0f, 0f)
|
||||
private val isLayoutRtl = container.isLayoutRtl
|
||||
private val viewAnimatorsMap = mutableMapOf<View, ValueAnimator>()
|
||||
private var onDropTargetsRemovedAction: Runnable? = null
|
||||
|
||||
private companion object {
|
||||
const val MORPH_ANIM_DURATION = 250L
|
||||
const val DROP_TARGET_ALPHA_IN_DURATION = 150L
|
||||
const val DROP_TARGET_ALPHA_OUT_DURATION = 100L
|
||||
const val DROP_TARGET_ELEVATION_DP = 2f
|
||||
}
|
||||
|
||||
/** Must be called when a drag gesture is starting. */
|
||||
@@ -63,38 +53,22 @@ class DropTargetManager(
|
||||
val state = DragState(dragZones, draggedObject)
|
||||
dragZoneChangedListener.onInitialDragZoneSet(state.initialDragZone)
|
||||
this.state = state
|
||||
viewAnimatorsMap.values.forEach { it.cancel() }
|
||||
setupDropTarget(dropTargetView)
|
||||
if (dragZones.any { it.secondDropTarget != null }) {
|
||||
secondDropTargetView = secondDropTargetView ?: DropTargetView(context)
|
||||
setupDropTarget(secondDropTargetView)
|
||||
} else {
|
||||
secondDropTargetView?.let { container.removeView(it) }
|
||||
secondDropTargetView = null
|
||||
}
|
||||
animator?.cancel()
|
||||
setupDropTarget()
|
||||
}
|
||||
|
||||
private fun setupDropTarget(view: View?) {
|
||||
if (view == null) return
|
||||
if (view.parent != null) container.removeView(view)
|
||||
container.addView(view, 0)
|
||||
view.alpha = 0f
|
||||
|
||||
view.elevation = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
DROP_TARGET_ELEVATION_DP, context.resources.displayMetrics
|
||||
)
|
||||
private fun setupDropTarget() {
|
||||
if (dropTargetView.parent != null) container.removeView(dropTargetView)
|
||||
container.addView(dropTargetView, 0)
|
||||
dropTargetView.alpha = 0f
|
||||
dropTargetView.elevation = context.resources.getDimension(R.dimen.drop_target_elevation)
|
||||
// Match parent and the target is drawn within the view
|
||||
view.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
dropTargetView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user drags to a new location.
|
||||
*
|
||||
* @return DragZone that matches provided x and y coordinates.
|
||||
*/
|
||||
fun onDragUpdated(x: Int, y: Int): DragZone? {
|
||||
val state = state ?: return null
|
||||
/** Called when the user drags to a new location. */
|
||||
fun onDragUpdated(x: Int, y: Int) {
|
||||
val state = state ?: return
|
||||
val oldDragZone = state.currentDragZone
|
||||
val newDragZone = state.getMatchingDragZone(x = x, y = y)
|
||||
state.currentDragZone = newDragZone
|
||||
@@ -102,156 +76,95 @@ class DropTargetManager(
|
||||
dragZoneChangedListener.onDragZoneChanged(
|
||||
draggedObject = state.draggedObject,
|
||||
from = oldDragZone,
|
||||
to = newDragZone,
|
||||
to = newDragZone
|
||||
)
|
||||
updateDropTarget()
|
||||
}
|
||||
return newDragZone
|
||||
}
|
||||
|
||||
/** Called when the drag ended. */
|
||||
fun onDragEnded() {
|
||||
val dropState = state ?: return
|
||||
startFadeAnimation(dropTargetView, to = 0f) {
|
||||
startFadeAnimation(from = dropTargetView.alpha, to = 0f) {
|
||||
container.removeView(dropTargetView)
|
||||
onDropTargetRemoved()
|
||||
}
|
||||
startFadeAnimation(secondDropTargetView, to = 0f) {
|
||||
container.removeView(secondDropTargetView)
|
||||
secondDropTargetView = null
|
||||
onDropTargetRemoved()
|
||||
}
|
||||
dragZoneChangedListener.onDragEnded(dropState.currentDragZone)
|
||||
state = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the provided action once all drop target views are removed from the container.
|
||||
* If there are no drop target views currently present or being animated, the action will be
|
||||
* executed immediately.
|
||||
*/
|
||||
fun onDropTargetRemoved(action: Runnable) {
|
||||
onDropTargetsRemovedAction = action
|
||||
onDropTargetRemoved()
|
||||
}
|
||||
|
||||
private fun updateDropTarget() {
|
||||
val dropState = state ?: return
|
||||
val currentDragZone = dropState.currentDragZone
|
||||
if (currentDragZone == null) {
|
||||
startFadeAnimation(dropTargetView, to = 0f)
|
||||
startFadeAnimation(secondDropTargetView, to = 0f)
|
||||
return
|
||||
}
|
||||
val dropTargetRect = currentDragZone.dropTarget
|
||||
val currentDragZone = state?.currentDragZone ?: return
|
||||
val dropTargetBounds = currentDragZone.dropTarget
|
||||
when {
|
||||
dropTargetRect == null -> startFadeAnimation(dropTargetView, to = 0f)
|
||||
|
||||
dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f)
|
||||
dropTargetView.alpha == 0f -> {
|
||||
dropTargetView.update(RectF(dropTargetRect.rect), dropTargetRect.cornerRadius)
|
||||
startFadeAnimation(dropTargetView, to = 1f)
|
||||
}
|
||||
|
||||
else -> startMorphAnimation(dropTargetRect)
|
||||
}
|
||||
|
||||
val secondDropTargetRect = currentDragZone.secondDropTarget
|
||||
when {
|
||||
secondDropTargetRect == null -> startFadeAnimation(secondDropTargetView, to = 0f)
|
||||
else -> {
|
||||
val secondDropTargetView = secondDropTargetView ?: return
|
||||
secondDropTargetView.update(
|
||||
RectF(secondDropTargetRect.rect),
|
||||
secondDropTargetRect.cornerRadius,
|
||||
)
|
||||
startFadeAnimation(secondDropTargetView, to = 1f)
|
||||
dropTargetView.update(RectF(dropTargetBounds))
|
||||
startFadeAnimation(from = 0f, to = 1f)
|
||||
}
|
||||
else -> startMorphAnimation(dropTargetBounds)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startFadeAnimation(view: View?, to: Float, onEnd: (() -> Unit)? = null) {
|
||||
if (view == null) return
|
||||
val from = view.alpha
|
||||
viewAnimatorsMap[view]?.cancel()
|
||||
private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) {
|
||||
animator?.cancel()
|
||||
val duration =
|
||||
if (from < to) DROP_TARGET_ALPHA_IN_DURATION else DROP_TARGET_ALPHA_OUT_DURATION
|
||||
val animator = ValueAnimator.ofFloat(from, to).setDuration(duration)
|
||||
animator.addUpdateListener { _ -> view.alpha = animator.animatedValue as Float }
|
||||
animator.addUpdateListener { _ -> dropTargetView.alpha = animator.animatedValue as Float }
|
||||
if (onEnd != null) {
|
||||
animator.doOnEnd(onEnd)
|
||||
}
|
||||
viewAnimatorsMap[view] = animator
|
||||
this.animator = animator
|
||||
animator.start()
|
||||
}
|
||||
|
||||
private fun startMorphAnimation(dropTargetRect: DropTargetRect) {
|
||||
viewAnimatorsMap[dropTargetView]?.cancel()
|
||||
private fun startMorphAnimation(endBounds: Rect) {
|
||||
animator?.cancel()
|
||||
val startAlpha = dropTargetView.alpha
|
||||
val startRect = dropTargetView.getRect()
|
||||
val endRect = dropTargetRect.rect
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(MORPH_ANIM_DURATION)
|
||||
animator.addUpdateListener { _ ->
|
||||
val fraction = animator.animatedValue as Float
|
||||
dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction
|
||||
|
||||
morphRect.left = (startRect.left + (endRect.left - startRect.left) * fraction)
|
||||
morphRect.top = (startRect.top + (endRect.top - startRect.top) * fraction)
|
||||
morphRect.right = (startRect.right + (endRect.right - startRect.right) * fraction)
|
||||
morphRect.bottom = (startRect.bottom + (endRect.bottom - startRect.bottom) * fraction)
|
||||
dropTargetView.update(morphRect, dropTargetRect.cornerRadius)
|
||||
morphRect.left = (startRect.left + (endBounds.left - startRect.left) * fraction)
|
||||
morphRect.top = (startRect.top + (endBounds.top - startRect.top) * fraction)
|
||||
morphRect.right = (startRect.right + (endBounds.right - startRect.right) * fraction)
|
||||
morphRect.bottom = (startRect.bottom + (endBounds.bottom - startRect.bottom) * fraction)
|
||||
dropTargetView.update(morphRect)
|
||||
}
|
||||
viewAnimatorsMap[dropTargetView] = animator
|
||||
this.animator = animator
|
||||
animator.start()
|
||||
}
|
||||
|
||||
private fun onDropTargetRemoved() {
|
||||
val action = onDropTargetsRemovedAction ?: return
|
||||
if ((0 until container.childCount).any { container.getChildAt(it) is DropTargetView }) {
|
||||
return
|
||||
}
|
||||
onDropTargetsRemovedAction = null
|
||||
action.run()
|
||||
}
|
||||
|
||||
/** Stores the current drag state. */
|
||||
private inner class DragState(
|
||||
private val dragZones: List<DragZone>,
|
||||
val draggedObject: DraggedObject,
|
||||
val draggedObject: DraggedObject
|
||||
) {
|
||||
val initialDragZone =
|
||||
draggedObject.initialLocation?.let {
|
||||
if (it.isOnLeft(isLayoutRtl)) {
|
||||
dragZones.filterIsInstance<DragZone.Bubble.Left>().first()
|
||||
} else {
|
||||
dragZones.filterIsInstance<DragZone.Bubble.Right>().first()
|
||||
}
|
||||
if (draggedObject.initialLocation.isOnLeft(isLayoutRtl)) {
|
||||
dragZones.filterIsInstance<DragZone.Bubble.Left>().first()
|
||||
} else {
|
||||
dragZones.filterIsInstance<DragZone.Bubble.Right>().first()
|
||||
}
|
||||
var currentDragZone: DragZone? = initialDragZone
|
||||
var currentDragZone: DragZone = initialDragZone
|
||||
|
||||
fun getMatchingDragZone(x: Int, y: Int): DragZone? {
|
||||
return dragZones.firstOrNull { it.contains(x, y) }
|
||||
fun getMatchingDragZone(x: Int, y: Int): DragZone {
|
||||
return dragZones.firstOrNull { it.contains(x, y) } ?: currentDragZone
|
||||
}
|
||||
}
|
||||
|
||||
private val DraggedObject.initialLocation: BubbleBarLocation?
|
||||
get() =
|
||||
when (this) {
|
||||
is Bubble -> initialLocation
|
||||
is BubbleBar -> initialLocation
|
||||
is ExpandedView -> initialLocation
|
||||
is LauncherIcon -> null
|
||||
}
|
||||
|
||||
/** An interface to be notified when drag zones change. */
|
||||
interface DragZoneChangedListener {
|
||||
/** An initial drag zone was set. Called when a drag starts. */
|
||||
fun onInitialDragZoneSet(dragZone: DragZone?)
|
||||
fun onInitialDragZoneSet(dragZone: DragZone)
|
||||
|
||||
/** Called when the object was dragged to a different drag zone. */
|
||||
fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone?, to: DragZone?)
|
||||
fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone, to: DragZone)
|
||||
|
||||
/** Called when the drag has ended with the zone it ended in. */
|
||||
fun onDragEnded(zone: DragZone?)
|
||||
fun onDragEnded(zone: DragZone)
|
||||
}
|
||||
|
||||
private fun Animator.doOnEnd(onEnd: () -> Unit) {
|
||||
|
||||
@@ -23,34 +23,34 @@ import android.graphics.RectF
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
|
||||
/** Shows a drop target within this view. */
|
||||
/**
|
||||
* Shows a drop target within this view.
|
||||
*/
|
||||
class DropTargetView(context: Context) : View(context) {
|
||||
|
||||
private val rectPaint =
|
||||
Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
|
||||
style = Paint.Style.FILL
|
||||
alpha = (0.35f * 255).toInt()
|
||||
}
|
||||
private val rectPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
|
||||
style = Paint.Style.FILL
|
||||
alpha = (0.35f * 255).toInt()
|
||||
}
|
||||
|
||||
private val strokePaint =
|
||||
Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 2.dpToPx()
|
||||
}
|
||||
private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 2.dpToPx()
|
||||
}
|
||||
|
||||
private var cornerRadius = 0f
|
||||
private val cornerRadius = 28.dpToPx()
|
||||
|
||||
private val rect = RectF(0f, 0f, 0f, 0f)
|
||||
|
||||
// TODO b/396539130: Use shared xml resources once we can access them in launcher
|
||||
private fun Int.dpToPx() =
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
this.toFloat(),
|
||||
context.resources.displayMetrics
|
||||
)
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
this.toFloat(),
|
||||
context.resources.displayMetrics
|
||||
)
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
canvas.save()
|
||||
@@ -59,8 +59,7 @@ class DropTargetView(context: Context) : View(context) {
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
fun update(positionRect: RectF, cornerRadius: Float) {
|
||||
this.cornerRadius = cornerRadius
|
||||
fun update(positionRect: RectF) {
|
||||
rect.set(positionRect)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
madym@google.com
|
||||
atsjenk@google.com
|
||||
liranb@google.com
|
||||
sukeshram@google.com
|
||||
mpodolian@google.com
|
||||
|
||||
+22
-59
@@ -20,7 +20,6 @@ import android.Manifest.permission.SYSTEM_ALERT_WINDOW
|
||||
import android.app.TaskInfo
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.window.DesktopExperienceFlags
|
||||
import android.window.DesktopModeFlags
|
||||
import com.android.internal.R
|
||||
import com.android.internal.policy.DesktopModeCompatUtils
|
||||
@@ -44,56 +43,28 @@ class DesktopModeCompatPolicy(private val context: Context) {
|
||||
|
||||
/**
|
||||
* If the top activity should be exempt from desktop windowing and forced back to fullscreen.
|
||||
* Currently includes all system ui, default home and transparent stack activities with the
|
||||
* relevant permission or signature. However if the top activity is not being displayed,
|
||||
* regardless of its configuration, we will not exempt it as to remain in the desktop windowing
|
||||
* environment.
|
||||
* Currently includes all system ui, default home and transparent stack activities. However if
|
||||
* the top activity is not being displayed, regardless of its configuration, we will not exempt
|
||||
* it as to remain in the desktop windowing environment.
|
||||
*/
|
||||
fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo): Boolean {
|
||||
val packageName = task.baseActivity?.packageName ?: return false
|
||||
fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) =
|
||||
isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName,
|
||||
task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent,
|
||||
task.userId)
|
||||
|
||||
return when {
|
||||
!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue -> false
|
||||
// If activity is not being displayed, window mode change has no visual affect so leave
|
||||
// unchanged.
|
||||
task.isTopActivityNoDisplay -> false
|
||||
// If activity belongs to system ui package, safe to force out of desktop.
|
||||
isSystemUiTask(packageName) -> true
|
||||
// If activity belongs to default home package, safe to force out of desktop.
|
||||
isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) -> true
|
||||
// If all activities in task stack are transparent AND package has the relevant
|
||||
// fullscreen transparent permission OR is signed with platform key, safe to force out
|
||||
// of desktop.
|
||||
isTransparentTask(task.isActivityStackTransparent, task.numActivities) &&
|
||||
(hasFullscreenTransparentPermission(packageName, task.userId) ||
|
||||
hasPlatformSignature(task)) -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldDisableDesktopEntryPoints(task: TaskInfo) = shouldDisableDesktopEntryPoints(
|
||||
task.baseActivity?.packageName, task.numActivities, task.isTopActivityNoDisplay,
|
||||
task.isActivityStackTransparent)
|
||||
|
||||
fun shouldDisableDesktopEntryPoints(
|
||||
fun isTopActivityExemptFromDesktopWindowing(
|
||||
packageName: String?,
|
||||
numActivities: Int,
|
||||
isTopActivityNoDisplay: Boolean,
|
||||
isActivityStackTransparent: Boolean,
|
||||
) = when {
|
||||
// Activity will not be displayed, no need to show desktop entry point.
|
||||
isTopActivityNoDisplay -> true
|
||||
// If activity belongs to system ui package, hide desktop entry point.
|
||||
isSystemUiTask(packageName) -> true
|
||||
// If activity belongs to default home package, safe to force out of desktop.
|
||||
isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) -> true
|
||||
// If all activities in task stack are transparent AND package has the relevant fullscreen
|
||||
// transparent permission, safe to force out of desktop.
|
||||
isTransparentTask(isActivityStackTransparent, numActivities) -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
userId: Int
|
||||
) =
|
||||
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue &&
|
||||
((isSystemUiTask(packageName) ||
|
||||
isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) ||
|
||||
(isTransparentTask(isActivityStackTransparent, numActivities) &&
|
||||
hasFullscreenTransparentPermission(packageName, userId))) &&
|
||||
!isTopActivityNoDisplay)
|
||||
|
||||
/** @see DesktopModeCompatUtils.shouldExcludeCaptionFromAppBounds */
|
||||
fun shouldExcludeCaptionFromAppBounds(taskInfo: TaskInfo): Boolean =
|
||||
@@ -115,8 +86,11 @@ class DesktopModeCompatPolicy(private val context: Context) {
|
||||
private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
|
||||
|
||||
// Checks if the app for the given package has the SYSTEM_ALERT_WINDOW permission.
|
||||
private fun hasFullscreenTransparentPermission(packageName: String, userId: Int): Boolean {
|
||||
private fun hasFullscreenTransparentPermission(packageName: String?, userId: Int): Boolean {
|
||||
if (DesktopModeFlags.ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS.isTrue) {
|
||||
if (packageName == null) {
|
||||
return false
|
||||
}
|
||||
return packageInfoCache.getOrPut("$userId@$packageName") {
|
||||
try {
|
||||
val packageInfo = pkgManager.getPackageInfoAsUser(
|
||||
@@ -130,19 +104,8 @@ class DesktopModeCompatPolicy(private val context: Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS flag is disabled, make neutral condition
|
||||
// dependant on the ENABLE_MODALS_FULLSCREEN_WITH_PLATFORM_SIGNATURE flag.
|
||||
return !DesktopExperienceFlags.ENABLE_MODALS_FULLSCREEN_WITH_PLATFORM_SIGNATURE.isTrue
|
||||
}
|
||||
|
||||
// Checks if the app is signed with the platform signature.
|
||||
private fun hasPlatformSignature(task: TaskInfo): Boolean {
|
||||
if (DesktopExperienceFlags.ENABLE_MODALS_FULLSCREEN_WITH_PLATFORM_SIGNATURE.isTrue) {
|
||||
return task.topActivityInfo?.applicationInfo?.isSignedWithPlatformKey ?: false
|
||||
}
|
||||
// If the ENABLE_MODALS_FULLSCREEN_WITH_PLATFORM_SIGNATURE flag is disabled, make neutral
|
||||
// condition dependant on the ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS flag.
|
||||
return !DesktopModeFlags.ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS.isTrue
|
||||
// If the flag is disabled we make this condition neutral.
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+268
-59
@@ -17,11 +17,14 @@
|
||||
package com.android.wm.shell.shared.desktopmode;
|
||||
|
||||
import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
|
||||
import static android.window.DesktopExperienceFlags.ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE;
|
||||
|
||||
import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
|
||||
import static com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper.enableBubbleToFullscreen;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.app.TaskInfo;
|
||||
import android.content.Context;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.SystemProperties;
|
||||
@@ -32,21 +35,48 @@ import android.window.DesktopModeFlags;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.window.flags2.Flags;
|
||||
import com.android.window.flags.Flags;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Constants for desktop mode feature
|
||||
*
|
||||
* @deprecated Use {@link DesktopState} or {@link DesktopConfig} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
// TODO(b/237575897): Move this file to the `com.android.wm.shell.shared.desktopmode` package
|
||||
public class DesktopModeStatus {
|
||||
|
||||
private static final String TAG = "DesktopModeStatus";
|
||||
|
||||
@Nullable
|
||||
private static Boolean sIsLargeScreenDevice = null;
|
||||
|
||||
/**
|
||||
* Flag to indicate whether task resizing is veiled.
|
||||
*/
|
||||
private static final boolean IS_VEILED_RESIZE_ENABLED = SystemProperties.getBoolean(
|
||||
"persist.wm.debug.desktop_veiled_resizing", true);
|
||||
|
||||
/**
|
||||
* Flag to indicate is moving task to another display is enabled.
|
||||
*/
|
||||
public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
|
||||
"persist.wm.debug.desktop_change_display", false);
|
||||
|
||||
/**
|
||||
* Flag to indicate whether to apply shadows to windows in desktop mode.
|
||||
*/
|
||||
private static final boolean USE_WINDOW_SHADOWS = SystemProperties.getBoolean(
|
||||
"persist.wm.debug.desktop_use_window_shadows", true);
|
||||
|
||||
/**
|
||||
* Flag to indicate whether to apply shadows to the focused window in desktop mode.
|
||||
*
|
||||
* Note: this flag is only relevant if USE_WINDOW_SHADOWS is false.
|
||||
*/
|
||||
private static final boolean USE_WINDOW_SHADOWS_FOCUSED_WINDOW = SystemProperties.getBoolean(
|
||||
"persist.wm.debug.desktop_use_window_shadows_focused_window", false);
|
||||
|
||||
/**
|
||||
* Flag to indicate whether to use rounded corners for windows in desktop mode.
|
||||
*/
|
||||
@@ -59,6 +89,27 @@ public class DesktopModeStatus {
|
||||
private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
|
||||
"persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
|
||||
|
||||
private static final boolean USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS =
|
||||
SystemProperties.getBoolean(
|
||||
"persist.wm.debug.use_app_to_web_build_time_generic_links", true);
|
||||
|
||||
/** Whether the desktop density override is enabled. */
|
||||
public static final boolean DESKTOP_DENSITY_OVERRIDE_ENABLED =
|
||||
SystemProperties.getBoolean("persist.wm.debug.desktop_mode_density_enabled", false);
|
||||
|
||||
/** Override density for tasks when they're inside the desktop. */
|
||||
public static final int DESKTOP_DENSITY_OVERRIDE =
|
||||
SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284);
|
||||
|
||||
/** The minimum override density allowed for tasks inside the desktop. */
|
||||
private static final int DESKTOP_DENSITY_MIN = 100;
|
||||
|
||||
/** The maximum override density allowed for tasks inside the desktop. */
|
||||
private static final int DESKTOP_DENSITY_MAX = 1000;
|
||||
|
||||
/** The number of [WindowDecorViewHost] instances to warm up on system start. */
|
||||
private static final int WINDOW_DECOR_PRE_WARM_SIZE = 2;
|
||||
|
||||
/**
|
||||
* Sysprop declaring whether to enters desktop mode by default when the windowing mode of the
|
||||
* display's root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM.
|
||||
@@ -69,6 +120,51 @@ public class DesktopModeStatus {
|
||||
public static final String ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP =
|
||||
"persist.wm.debug.enter_desktop_by_default_on_freeform_display";
|
||||
|
||||
/**
|
||||
* Sysprop declaring whether to enable drag-to-maximize for desktop windows.
|
||||
*
|
||||
* <p>If it is not defined, then {@code R.integer.config_dragToMaximizeInDesktopMode}
|
||||
* is used.
|
||||
*/
|
||||
public static final String ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP =
|
||||
"persist.wm.debug.enable_drag_to_maximize";
|
||||
|
||||
/**
|
||||
* Sysprop declaring the maximum number of Tasks to show in Desktop Mode at any one time.
|
||||
*
|
||||
* <p>If it is not defined, then {@code R.integer.config_maxDesktopWindowingActiveTasks} is
|
||||
* used.
|
||||
*
|
||||
* <p>The limit does NOT affect Picture-in-Picture, Bubbles, or System Modals (like a screen
|
||||
* recording window, or Bluetooth pairing window).
|
||||
*/
|
||||
private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit";
|
||||
|
||||
/**
|
||||
* Sysprop declaring the number of [WindowDecorViewHost] instances to warm up on system start.
|
||||
*
|
||||
* <p>If it is not defined, then [WINDOW_DECOR_PRE_WARM_SIZE] is used.
|
||||
*/
|
||||
private static final String WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP =
|
||||
"persist.wm.debug.desktop_window_decor_pre_warm_size";
|
||||
|
||||
/**
|
||||
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
|
||||
*/
|
||||
public static boolean isVeiledResizeEnabled() {
|
||||
return IS_VEILED_RESIZE_ENABLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to use window shadows.
|
||||
*
|
||||
* @param isFocusedWindow whether the window to apply shadows to is focused
|
||||
*/
|
||||
public static boolean useWindowShadow(boolean isFocusedWindow) {
|
||||
return USE_WINDOW_SHADOWS
|
||||
|| (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to use rounded corners for windows.
|
||||
*/
|
||||
@@ -84,6 +180,35 @@ public class DesktopModeStatus {
|
||||
return ENFORCE_DEVICE_RESTRICTIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time.
|
||||
*/
|
||||
public static int getMaxTaskLimit(@NonNull Context context) {
|
||||
return SystemProperties.getInt(MAX_TASK_LIMIT_SYS_PROP,
|
||||
context.getResources().getInteger(R.integer.config_maxDesktopWindowingActiveTasks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum size of the window decoration surface control view host pool, or zero if
|
||||
* there should be no pooling.
|
||||
*/
|
||||
public static int getWindowDecorScvhPoolSize(@NonNull Context context) {
|
||||
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_SCVH_CACHE.isTrue()) return 0;
|
||||
final int maxTaskLimit = getMaxTaskLimit(context);
|
||||
if (maxTaskLimit > 0) {
|
||||
return maxTaskLimit;
|
||||
}
|
||||
// TODO: b/368032552 - task limit equal to 0 means unlimited. Figure out what the pool
|
||||
// size should be in that case.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** The number of [WindowDecorViewHost] instances to warm up on system start. */
|
||||
public static int getWindowDecorPreWarmSize() {
|
||||
return SystemProperties.getInt(WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP,
|
||||
WINDOW_DECOR_PRE_WARM_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the current device supports desktop mode.
|
||||
*/
|
||||
@@ -119,7 +244,7 @@ public class DesktopModeStatus {
|
||||
*/
|
||||
public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) {
|
||||
return Flags.showDesktopExperienceDevOption()
|
||||
&& isDeviceEligibleForDesktopExperienceDevOption(context);
|
||||
&& isDeviceEligibleForDesktopMode(context);
|
||||
}
|
||||
|
||||
/** Returns if desktop mode dev option should be enabled if there is no user override. */
|
||||
@@ -132,20 +257,28 @@ public class DesktopModeStatus {
|
||||
* Return {@code true} if desktop mode is enabled and can be entered on the current device.
|
||||
*/
|
||||
public static boolean canEnterDesktopMode(@NonNull Context context) {
|
||||
boolean isEligibleForDesktopMode = isDeviceEligibleForDesktopMode(context) && (
|
||||
DesktopExperienceFlags.ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE.isTrue()
|
||||
|| canInternalDisplayHostDesktops(context));
|
||||
boolean desktopModeEnabled =
|
||||
isEligibleForDesktopMode && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue();
|
||||
return desktopModeEnabled || isDesktopModeEnabledByDevOption(context);
|
||||
try {
|
||||
return (isDeviceEligibleForDesktopMode(context)
|
||||
&& DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue())
|
||||
|| isDesktopModeEnabledByDevOption(context);
|
||||
} catch (Throwable e) {
|
||||
// Lawnchair-TODO-Postmerge: All of the LC-Ignored MAY be only accessible to newer APIs.
|
||||
// LC-Ignored
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Desktop mode should be enabled because the dev option is shown and enabled.
|
||||
*/
|
||||
private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) {
|
||||
return DesktopModeFlags.isDesktopModeForcedEnabled()
|
||||
try {
|
||||
return DesktopModeFlags.isDesktopModeForcedEnabled()
|
||||
&& canShowDesktopModeDevOption(context);
|
||||
} catch (Throwable e) {
|
||||
// LC-Ignored
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,21 +297,14 @@ public class DesktopModeStatus {
|
||||
}
|
||||
|
||||
// TODO (b/395014779): Change this to use WM API
|
||||
if (!DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
|
||||
return false;
|
||||
if ((display.getType() == Display.TYPE_EXTERNAL
|
||||
|| display.getType() == Display.TYPE_OVERLAY)
|
||||
&& enableDisplayContentModeManagement()) {
|
||||
final WindowManager wm = context.getSystemService(WindowManager.class);
|
||||
return wm != null && wm.shouldShowSystemDecors(display.getDisplayId());
|
||||
}
|
||||
final WindowManager wm = context.getSystemService(WindowManager.class);
|
||||
return wm != null && wm.isEligibleForDesktopMode(display.getDisplayId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the multi-desks frontend should be enabled on the display.
|
||||
*/
|
||||
public static boolean isMultipleDesktopFrontendEnabledOnDisplay(@NonNull Context context,
|
||||
Display display) {
|
||||
return DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_FRONTEND.isTrue()
|
||||
&& DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()
|
||||
&& isDesktopModeSupportedOnDisplay(context, display);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,9 +312,14 @@ public class DesktopModeStatus {
|
||||
* frontend implementations).
|
||||
*/
|
||||
public static boolean enableMultipleDesktops(@NonNull Context context) {
|
||||
return DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()
|
||||
&& DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_FRONTEND.isTrue()
|
||||
try {
|
||||
return DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()
|
||||
&& Flags.enableMultipleDesktopsFrontend()
|
||||
&& canEnterDesktopMode(context);
|
||||
} catch (Throwable e) {
|
||||
// LC-Ignored
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,8 +327,27 @@ public class DesktopModeStatus {
|
||||
* necessarily enabling desktop mode
|
||||
*/
|
||||
public static boolean overridesShowAppHandle(@NonNull Context context) {
|
||||
return (Flags.showAppHandleLargeScreens() || enableBubbleToFullscreen())
|
||||
&& deviceHasLargeScreen(context);
|
||||
try {
|
||||
return (Flags.showAppHandleLargeScreens() || enableBubbleToFullscreen())
|
||||
&& deviceHasLargeScreen(context);
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If {@code true} we set opaque background for all freeform tasks to prevent freeform
|
||||
* tasks below from being visible if freeform task window above is translucent.
|
||||
* Otherwise if fluid resize is enabled, add a background to freeform tasks.
|
||||
*/
|
||||
public static boolean shouldSetBackground(@NonNull TaskInfo taskInfo) {
|
||||
try {
|
||||
return taskInfo.isFreeform() && (!DesktopModeStatus.isVeiledResizeEnabled()
|
||||
|| DesktopModeFlags.ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS.isTrue());
|
||||
} catch (Throwable e) {
|
||||
// LC-Ignored
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,30 +358,64 @@ public class DesktopModeStatus {
|
||||
return canEnterDesktopMode(context) || overridesShowAppHandle(context);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Return {@code true} if the override desktop density is enabled and valid.
|
||||
*/
|
||||
public static boolean useDesktopOverrideDensity() {
|
||||
return isDesktopDensityOverrideEnabled() && isValidDesktopDensityOverrideSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the app-to-web feature is using the build-time generic links list.
|
||||
*/
|
||||
public static boolean useAppToWebBuildTimeGenericLinks() {
|
||||
return USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the override desktop density is enabled.
|
||||
*/
|
||||
private static boolean isDesktopDensityOverrideEnabled() {
|
||||
return DESKTOP_DENSITY_OVERRIDE_ENABLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the override desktop density is set and within a valid range.
|
||||
*/
|
||||
private static boolean isValidDesktopDensityOverrideSet() {
|
||||
return DESKTOP_DENSITY_OVERRIDE >= DESKTOP_DENSITY_MIN
|
||||
&& DESKTOP_DENSITY_OVERRIDE <= DESKTOP_DENSITY_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if desktop mode is unrestricted and is supported on the device.
|
||||
*/
|
||||
public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
|
||||
if (!enforceDeviceRestrictions()) {
|
||||
return true;
|
||||
}
|
||||
final boolean enableDesktopModeThroughDevOption;
|
||||
if (false) {
|
||||
// LC-Ignored: Lawnchair-TODO: Intentional unless we can find a way to detect QPR1 build or skip to Android 17
|
||||
enableDesktopModeThroughDevOption = Flags.enableDesktopModeThroughDevOption();
|
||||
} else {
|
||||
enableDesktopModeThroughDevOption = false;
|
||||
}
|
||||
final boolean desktopModeSupportedByDevOptions =
|
||||
enableDesktopModeThroughDevOption
|
||||
try {
|
||||
// If projected display is enabled, #canInternalDisplayHostDesktops is no longer a
|
||||
// requirement.
|
||||
final boolean desktopModeSupported = ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE.isTrue()
|
||||
? isDesktopModeSupported(context) : (isDesktopModeSupported(context)
|
||||
&& canInternalDisplayHostDesktops(context));
|
||||
final boolean desktopModeSupportedByDevOptions =
|
||||
Flags.enableDesktopModeThroughDevOption()
|
||||
&& isDesktopModeDevOptionSupported(context);
|
||||
return isDesktopModeSupported(context) || desktopModeSupportedByDevOptions;
|
||||
return desktopModeSupported || desktopModeSupportedByDevOptions;
|
||||
} catch (Throwable e) {
|
||||
// LC-Ignored
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the developer option for desktop mode is supported on this device.
|
||||
* Return {@code true} if the developer option for desktop mode is unrestricted and is supported
|
||||
* in the device.
|
||||
*
|
||||
* <p> This method doesn't check if the developer option flag is enabled or not.
|
||||
* Note that, if {@link #isDeviceEligibleForDesktopMode(Context)} is true, then
|
||||
* {@link #isDeviceEligibleForDesktopModeDevOption(Context)} is also true.
|
||||
*/
|
||||
private static boolean isDeviceEligibleForDesktopModeDevOption(@NonNull Context context) {
|
||||
if (!enforceDeviceRestrictions()) {
|
||||
@@ -242,19 +426,6 @@ public class DesktopModeStatus {
|
||||
return desktopModeSupported || isDesktopModeDevOptionSupported(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the developer option for desktop experience is supported on this
|
||||
* device.
|
||||
*
|
||||
* <p> This method doesn't check if the developer option flag is enabled or not.
|
||||
*/
|
||||
private static boolean isDeviceEligibleForDesktopExperienceDevOption(@NonNull Context context) {
|
||||
if (!enforceDeviceRestrictions()) {
|
||||
return true;
|
||||
}
|
||||
return isDesktopModeSupported(context) || isDesktopModeDevOptionSupported(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if this device has an internal large screen
|
||||
*/
|
||||
@@ -275,14 +446,52 @@ public class DesktopModeStatus {
|
||||
* of the display's root [TaskDisplayArea] is set to WINDOWING_MODE_FREEFORM.
|
||||
*/
|
||||
public static boolean enterDesktopByDefaultOnFreeformDisplay(@NonNull Context context) {
|
||||
if (DesktopExperienceFlags.ENABLE_DESKTOP_FIRST_BASED_DEFAULT_TO_DESKTOP_BUGFIX.isTrue()) {
|
||||
return true;
|
||||
}
|
||||
if (!DesktopExperienceFlags.ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS.isTrue()) {
|
||||
try {
|
||||
if (!DesktopExperienceFlags.ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS.isTrue()) {
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// LC-Ignored
|
||||
return false;
|
||||
}
|
||||
|
||||
return SystemProperties.getBoolean(ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP,
|
||||
context.getResources().getBoolean(
|
||||
R.bool.config_enterDesktopByDefaultOnFreeformDisplay));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if a window should be maximized when it's dragged to the top edge of the
|
||||
* screen.
|
||||
*/
|
||||
public static boolean shouldMaximizeWhenDragToTopEdge(@NonNull Context context) {
|
||||
try {
|
||||
if (!DesktopExperienceFlags.ENABLE_DRAG_TO_MAXIMIZE.isTrue()) {
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// LC-Ignored
|
||||
return false;
|
||||
}
|
||||
return SystemProperties.getBoolean(ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP,
|
||||
context.getResources().getBoolean(R.bool.config_dragToMaximizeInDesktopMode));
|
||||
}
|
||||
|
||||
/** Dumps DesktopModeStatus flags and configs. */
|
||||
public static void dump(PrintWriter pw, String prefix, Context context) {
|
||||
String innerPrefix = prefix + " ";
|
||||
pw.print(prefix); pw.println(TAG);
|
||||
pw.print(innerPrefix); pw.print("maxTaskLimit="); pw.println(getMaxTaskLimit(context));
|
||||
|
||||
pw.print(innerPrefix); pw.print("maxTaskLimit config override=");
|
||||
pw.println(context.getResources().getInteger(
|
||||
R.integer.config_maxDesktopWindowingActiveTasks));
|
||||
|
||||
SystemProperties.Handle maxTaskLimitHandle = SystemProperties.find(MAX_TASK_LIMIT_SYS_PROP);
|
||||
pw.print(innerPrefix); pw.print("maxTaskLimit sysprop=");
|
||||
pw.println(maxTaskLimitHandle == null ? "null" : maxTaskLimitHandle.getInt(/* def= */ -1));
|
||||
|
||||
pw.print(innerPrefix); pw.print("showAppHandle config override=");
|
||||
pw.println(overridesShowAppHandle(context));
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -21,8 +21,6 @@ import android.os.Parcelable
|
||||
|
||||
/** Transition source types for Desktop Mode. */
|
||||
enum class DesktopModeTransitionSource : Parcelable {
|
||||
/** Transitions that originated from an adb command. */
|
||||
ADB_COMMAND,
|
||||
/** Transitions that originated as a consequence of task dragging. */
|
||||
TASK_DRAG,
|
||||
/** Transitions that originated from an app from Overview. */
|
||||
|
||||
-1
@@ -35,7 +35,6 @@ import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.widget.LinearLayout
|
||||
import android.window.TaskSnapshot
|
||||
import com.android.wm.shell.R
|
||||
//import com.android.wm.shell.shared.R
|
||||
|
||||
/**
|
||||
* View for the All Windows menu option, used by both Desktop Windowing and Taskbar.
|
||||
|
||||
@@ -15,18 +15,12 @@
|
||||
*/
|
||||
package com.android.wm.shell.shared.split;
|
||||
|
||||
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -53,42 +47,13 @@ public class SplitBounds implements Parcelable {
|
||||
* the bounds were originally in
|
||||
*/
|
||||
public final boolean appsStackedVertically;
|
||||
/**
|
||||
* If {@code true}, that means at the time of creation of this object, the phone was in
|
||||
* seascape orientation. This is important on devices with insets, because they do not split
|
||||
* evenly -- one of the insets must be slightly larger to account for the inset.
|
||||
* From landscape, it is the leftTop task that expands slightly.
|
||||
* From seascape, it is the rightBottom task that expands slightly.
|
||||
*/
|
||||
public final boolean initiatedFromSeascape;
|
||||
/** @deprecated Use {@link #leftTopTaskIds} instead. */
|
||||
@Deprecated
|
||||
public final int leftTopTaskId;
|
||||
/** @deprecated Use {@link #rightBottomTaskIds} instead. */
|
||||
@Deprecated
|
||||
public final int rightBottomTaskId;
|
||||
@NonNull
|
||||
public final List<Integer> leftTopTaskIds;
|
||||
@NonNull
|
||||
public final List<Integer> rightBottomTaskIds;
|
||||
|
||||
public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
|
||||
int leftTopTaskId, int rightBottomTaskId,
|
||||
@NonNull List<Integer> leftTopTaskIds, @NonNull List<Integer> rightBottomTaskIds,
|
||||
@PersistentSnapPosition int snapPosition) {
|
||||
if (leftTopTaskId == INVALID_TASK_ID || rightBottomTaskId == INVALID_TASK_ID
|
||||
|| leftTopTaskId == rightBottomTaskId
|
||||
|| leftTopTaskIds.isEmpty() || rightBottomTaskIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("The Split task ids are invalid:"
|
||||
+ " leftTopTaskId: " + leftTopTaskId
|
||||
+ " rightBottomTaskId: " + rightBottomTaskId
|
||||
+ " leftTopTaskId size: " + leftTopTaskIds.size()
|
||||
+ " rightBottomTaskId size: " + rightBottomTaskIds.size());
|
||||
}
|
||||
public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
|
||||
int rightBottomTaskId, @PersistentSnapPosition int snapPosition) {
|
||||
this.leftTopBounds = leftTopBounds;
|
||||
this.rightBottomBounds = rightBottomBounds;
|
||||
this.leftTopTaskIds = List.copyOf(leftTopTaskIds);
|
||||
this.rightBottomTaskIds = List.copyOf(rightBottomTaskIds);
|
||||
this.leftTopTaskId = leftTopTaskId;
|
||||
this.rightBottomTaskId = rightBottomTaskId;
|
||||
this.snapPosition = snapPosition;
|
||||
@@ -98,18 +63,11 @@ public class SplitBounds implements Parcelable {
|
||||
this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom,
|
||||
leftTopBounds.right, rightBottomBounds.top);
|
||||
appsStackedVertically = true;
|
||||
initiatedFromSeascape = false;
|
||||
} else {
|
||||
// horizontal apps, vertical divider
|
||||
this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top,
|
||||
rightBottomBounds.left, leftTopBounds.bottom);
|
||||
appsStackedVertically = false;
|
||||
// The following check is unreliable on devices without insets
|
||||
// (initiatedFromSeascape will always be set to false.) This happens to be OK for
|
||||
// all our current uses, but should be refactored.
|
||||
// TODO: Create a more reliable check, or refactor how splitting works on devices
|
||||
// with insets.
|
||||
initiatedFromSeascape = rightBottomBounds.width() > leftTopBounds.width();
|
||||
}
|
||||
|
||||
float totalWidth = rightBottomBounds.right - leftTopBounds.left;
|
||||
@@ -120,46 +78,6 @@ public class SplitBounds implements Parcelable {
|
||||
dividerHeightPercent = visualDividerBounds.height() / totalHeight;
|
||||
}
|
||||
|
||||
public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
|
||||
int rightBottomTaskId, @PersistentSnapPosition int snapPosition) {
|
||||
this(leftTopBounds, rightBottomBounds, leftTopTaskId, rightBottomTaskId,
|
||||
Collections.singletonList(leftTopTaskId),
|
||||
Collections.singletonList(rightBottomTaskId), snapPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage size of the left/top task (compared to the full width/height of
|
||||
* the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and the
|
||||
* right task is 4 units, this method will return 0.4f.
|
||||
*/
|
||||
public float getLeftTopTaskPercent() {
|
||||
// topTaskPercent and leftTaskPercent are defined at creation time, and are not updated
|
||||
// on device rotate, so we have to check appsStackedVertically to return the right
|
||||
// creation-time measurements.
|
||||
return appsStackedVertically ? topTaskPercent : leftTaskPercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage size of the divider's thickness (compared to the full width/height
|
||||
* of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and
|
||||
* the right task is 4 units, this method will return 0.2f.
|
||||
*/
|
||||
public float getDividerPercent() {
|
||||
// dividerHeightPercent and dividerWidthPercent are defined at creation time, and are
|
||||
// not updated on device rotate, so we have to check appsStackedVertically to return
|
||||
// the right creation-time measurements.
|
||||
return appsStackedVertically ? dividerHeightPercent : dividerWidthPercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage size of the right/bottom task (compared to the full width/height
|
||||
* of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and
|
||||
* the right task is 4 units, this method will return 0.4f.
|
||||
*/
|
||||
public float getRightBottomTaskPercent() {
|
||||
return 1 - (getLeftTopTaskPercent() + getDividerPercent());
|
||||
}
|
||||
|
||||
public SplitBounds(Parcel parcel) {
|
||||
leftTopBounds = parcel.readTypedObject(Rect.CREATOR);
|
||||
rightBottomBounds = parcel.readTypedObject(Rect.CREATOR);
|
||||
@@ -167,22 +85,11 @@ public class SplitBounds implements Parcelable {
|
||||
topTaskPercent = parcel.readFloat();
|
||||
leftTaskPercent = parcel.readFloat();
|
||||
appsStackedVertically = parcel.readBoolean();
|
||||
initiatedFromSeascape = parcel.readBoolean();
|
||||
leftTopTaskId = parcel.readInt();
|
||||
rightBottomTaskId = parcel.readInt();
|
||||
dividerWidthPercent = parcel.readFloat();
|
||||
dividerHeightPercent = parcel.readFloat();
|
||||
snapPosition = parcel.readInt();
|
||||
leftTopTaskId = parcel.readInt();
|
||||
rightBottomTaskId = parcel.readInt();
|
||||
int size = parcel.readInt();
|
||||
leftTopTaskIds = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
leftTopTaskIds.add(parcel.readInt());
|
||||
}
|
||||
size = parcel.readInt();
|
||||
rightBottomTaskIds = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
rightBottomTaskIds.add(parcel.readInt());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -193,20 +100,11 @@ public class SplitBounds implements Parcelable {
|
||||
parcel.writeFloat(topTaskPercent);
|
||||
parcel.writeFloat(leftTaskPercent);
|
||||
parcel.writeBoolean(appsStackedVertically);
|
||||
parcel.writeBoolean(initiatedFromSeascape);
|
||||
parcel.writeInt(leftTopTaskId);
|
||||
parcel.writeInt(rightBottomTaskId);
|
||||
parcel.writeFloat(dividerWidthPercent);
|
||||
parcel.writeFloat(dividerHeightPercent);
|
||||
parcel.writeInt(snapPosition);
|
||||
parcel.writeInt(leftTopTaskId);
|
||||
parcel.writeInt(rightBottomTaskId);
|
||||
parcel.writeInt(leftTopTaskIds.size());
|
||||
for (Integer id : leftTopTaskIds) {
|
||||
parcel.writeInt(id); // Write each Integer in the List
|
||||
}
|
||||
parcel.writeInt(rightBottomTaskIds.size());
|
||||
for (Integer id : rightBottomTaskIds) {
|
||||
parcel.writeInt(id); // Write each Integer in the List
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -223,23 +121,19 @@ public class SplitBounds implements Parcelable {
|
||||
final SplitBounds other = (SplitBounds) obj;
|
||||
return Objects.equals(leftTopBounds, other.leftTopBounds)
|
||||
&& Objects.equals(rightBottomBounds, other.rightBottomBounds)
|
||||
&& leftTopTaskIds.equals(other.leftTopTaskIds)
|
||||
&& rightBottomTaskIds.equals(other.rightBottomTaskIds)
|
||||
&& snapPosition == other.snapPosition;
|
||||
&& leftTopTaskId == other.leftTopTaskId
|
||||
&& rightBottomTaskId == other.rightBottomTaskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(leftTopBounds, rightBottomBounds,
|
||||
leftTopTaskId, rightBottomTaskId, leftTopTaskIds, rightBottomTaskIds);
|
||||
return Objects.hash(leftTopBounds, rightBottomBounds, leftTopTaskId, rightBottomTaskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LeftTop: " + leftTopBounds + " taskId: " + leftTopTaskId
|
||||
+ ", taskIds: " + leftTopTaskIds + "\n"
|
||||
+ "RightBottom: " + rightBottomBounds + " taskId: " + rightBottomTaskId
|
||||
+ ", taskIds: " + rightBottomTaskIds + "\n"
|
||||
return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
|
||||
+ "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n"
|
||||
+ "Divider: " + visualDividerBounds + "\n"
|
||||
+ "AppsVertical? " + appsStackedVertically + "\n"
|
||||
+ "snapPosition: " + snapPosition;
|
||||
|
||||
@@ -157,12 +157,6 @@ public class SplitScreenConstants {
|
||||
*/
|
||||
public static final int SNAP_TO_3_10_45_45 = 7;
|
||||
|
||||
/**
|
||||
* A transitional state where the user has tapped an offscreen app, and the offscreen app is
|
||||
* currently animating back onscreen.
|
||||
*/
|
||||
public static final int ANIMATING_OFFSCREEN_TAP = 100;
|
||||
|
||||
/**
|
||||
* These snap targets are used for split pairs in a stable, non-transient state. They may be
|
||||
* persisted in Launcher when the user saves an app pair. They are a subset of
|
||||
@@ -182,7 +176,7 @@ public class SplitScreenConstants {
|
||||
|
||||
/**
|
||||
* These are all the valid "states" that split screen can be in. It's the set of
|
||||
* {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT} + other mid-animation states.
|
||||
* {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}.
|
||||
*/
|
||||
@IntDef(value = {
|
||||
NOT_IN_SPLIT, // user is not in split screen
|
||||
@@ -195,11 +189,10 @@ public class SplitScreenConstants {
|
||||
SNAP_TO_3_33_33_33,
|
||||
SNAP_TO_3_45_45_10,
|
||||
SNAP_TO_3_10_45_45,
|
||||
ANIMATING_OFFSCREEN_TAP // user tapped offscreen app to retrieve it
|
||||
})
|
||||
public @interface SplitScreenState {}
|
||||
|
||||
/** Converts a {@link SplitScreenState} to a human-readable string, for debug use. */
|
||||
/** Converts a {@link SplitScreenState} to a human-readable string. */
|
||||
public static String stateToString(@SplitScreenState int state) {
|
||||
return switch (state) {
|
||||
case NOT_IN_SPLIT -> "NOT_IN_SPLIT";
|
||||
@@ -212,38 +205,10 @@ public class SplitScreenConstants {
|
||||
case SNAP_TO_3_33_33_33 -> "SNAP_TO_3_33_33_33";
|
||||
case SNAP_TO_3_45_45_10 -> "SNAP_TO_3_45_45_10";
|
||||
case SNAP_TO_3_10_45_45 -> "SNAP_TO_3_10_45_45";
|
||||
case ANIMATING_OFFSCREEN_TAP -> "ANIMATING_OFFSCREEN_TAP";
|
||||
default -> "UNKNOWN";
|
||||
};
|
||||
}
|
||||
|
||||
/** Converts a {@link SnapPosition} to a string, for UI use. */
|
||||
public static String snapPositionToUIString(@SnapPosition int snapPosition) {
|
||||
return switch (snapPosition) {
|
||||
case SNAP_TO_START_AND_DISMISS -> "\u2715";
|
||||
case SNAP_TO_END_AND_DISMISS -> "\u2715";
|
||||
case SNAP_TO_2_33_66 -> "30:70";
|
||||
case SNAP_TO_2_50_50 -> "50:50";
|
||||
case SNAP_TO_2_66_33 -> "70:30";
|
||||
case SNAP_TO_2_90_10 -> "90:10";
|
||||
case SNAP_TO_2_10_90 -> "10:90";
|
||||
default -> "Split";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to convert between the IntDef's to avoid some errors
|
||||
* @return {@code -1} if splitScreenState does not have a valid/corresponding
|
||||
* PersistentSnapPosition
|
||||
*/
|
||||
@PersistentSnapPosition
|
||||
public static int splitStateToSnapPosition(@SplitScreenState int splitScreenState) {
|
||||
return switch (splitScreenState) {
|
||||
case NOT_IN_SPLIT, SNAP_TO_NONE, ANIMATING_OFFSCREEN_TAP -> -1;
|
||||
default -> splitScreenState;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user