Share bubbles dismiss functionality with Launcher
* `MagnetizedObject`, `RelativeTouchListener` and related animation classes made accessible in Launcher code * `DismissView` and `DismissCircleView` made accessible in Launcher: * Extracted module resources dependencies in configuration * Updated tests Test: manual, atest DismissAnimationControllerTest MenuListViewTouchHandlerTest Flag: WM_BUBBLE_BAR Bug: 271466616 Change-Id: I18ba51a8ed9b1eabd220646403b50217ff9cbe2f
This commit is contained in:
@@ -42,16 +42,19 @@ filegroup {
|
||||
filegroup {
|
||||
name: "wm_shell_util-sources",
|
||||
srcs: [
|
||||
"src/com/android/wm/shell/util/**/*.java",
|
||||
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
|
||||
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
|
||||
"src/com/android/wm/shell/common/TransactionPool.java",
|
||||
"src/com/android/wm/shell/common/bubbles/*.java",
|
||||
"src/com/android/wm/shell/common/TriangleShape.java",
|
||||
"src/com/android/wm/shell/animation/Interpolators.java",
|
||||
"src/com/android/wm/shell/animation/PhysicsAnimator.kt",
|
||||
"src/com/android/wm/shell/common/bubbles/*.kt",
|
||||
"src/com/android/wm/shell/common/bubbles/*.java",
|
||||
"src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt",
|
||||
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
|
||||
"src/com/android/wm/shell/common/TransactionPool.java",
|
||||
"src/com/android/wm/shell/common/TriangleShape.java",
|
||||
"src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
|
||||
"src/com/android/wm/shell/pip/PipContentOverlay.java",
|
||||
"src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
|
||||
"src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
|
||||
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
|
||||
"src/com/android/wm/shell/util/**/*.java",
|
||||
],
|
||||
path: "src",
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
|
||||
import com.android.wm.shell.bubbles.animation.StackAnimationController;
|
||||
import com.android.wm.shell.common.FloatingContentCoordinator;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.bubbles.RelativeTouchListener;
|
||||
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -1179,6 +1181,7 @@ public class BubbleStackView extends FrameLayout
|
||||
removeView(mDismissView);
|
||||
}
|
||||
mDismissView = new DismissView(getContext());
|
||||
DismissViewUtils.setup(mDismissView);
|
||||
int elevation = getResources().getDimensionPixelSize(R.dimen.bubble_elevation);
|
||||
|
||||
addView(mDismissView);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@file:JvmName("DismissViewUtils")
|
||||
|
||||
package com.android.wm.shell.bubbles
|
||||
|
||||
import com.android.wm.shell.R
|
||||
import com.android.wm.shell.common.bubbles.DismissView
|
||||
|
||||
fun DismissView.setup() {
|
||||
setup(DismissView.Config(
|
||||
targetSizeResId = R.dimen.dismiss_circle_size,
|
||||
iconSizeResId = R.dimen.dismiss_target_x_size,
|
||||
bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
|
||||
floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height,
|
||||
floatingGradientColorResId = android.R.color.system_neutral1_900,
|
||||
backgroundResId = R.drawable.dismiss_circle_background,
|
||||
iconResId = R.drawable.pip_ic_close_white
|
||||
))
|
||||
}
|
||||
@@ -14,16 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.common;
|
||||
package com.android.wm.shell.common.bubbles;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.view.Gravity;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
/**
|
||||
* Circular view with a semitransparent, circular background with an 'X' inside it.
|
||||
@@ -31,33 +32,44 @@ import com.android.wm.shell.R;
|
||||
* This is used by both Bubbles and PIP as the dismiss target.
|
||||
*/
|
||||
public class DismissCircleView extends FrameLayout {
|
||||
@DrawableRes int mBackgroundResId;
|
||||
@DimenRes int mIconSizeResId;
|
||||
|
||||
private final ImageView mIconView = new ImageView(getContext());
|
||||
|
||||
public DismissCircleView(Context context) {
|
||||
super(context);
|
||||
final Resources res = getResources();
|
||||
|
||||
setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
|
||||
|
||||
mIconView.setImageDrawable(res.getDrawable(R.drawable.pip_ic_close_white));
|
||||
addView(mIconView);
|
||||
|
||||
setViewSizes();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
final Resources res = getResources();
|
||||
setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
|
||||
setBackground(ContextCompat.getDrawable(getContext(), mBackgroundResId));
|
||||
setViewSizes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up view with the provided resource ids.
|
||||
* Decouples resource dependency in order to be used externally (e.g. Launcher)
|
||||
*
|
||||
* @param backgroundResId drawable resource id of the circle background
|
||||
* @param iconResId drawable resource id of the icon for the dismiss view
|
||||
* @param iconSizeResId dimen resource id of the icon size
|
||||
*/
|
||||
public void setup(@DrawableRes int backgroundResId, @DrawableRes int iconResId,
|
||||
@DimenRes int iconSizeResId) {
|
||||
mBackgroundResId = backgroundResId;
|
||||
mIconSizeResId = iconSizeResId;
|
||||
|
||||
setBackground(ContextCompat.getDrawable(getContext(), backgroundResId));
|
||||
mIconView.setImageDrawable(ContextCompat.getDrawable(getContext(), iconResId));
|
||||
setViewSizes();
|
||||
}
|
||||
|
||||
/** Retrieves the current dimensions for the icon and circle and applies them. */
|
||||
private void setViewSizes() {
|
||||
final Resources res = getResources();
|
||||
final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size);
|
||||
final int iconSize = getResources().getDimensionPixelSize(mIconSizeResId);
|
||||
mIconView.setLayoutParams(
|
||||
new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
|
||||
}
|
||||
@@ -14,41 +14,73 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.bubbles
|
||||
package com.android.wm.shell.common.bubbles
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.IntProperty
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DimenRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation
|
||||
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
|
||||
import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
|
||||
import com.android.wm.shell.R
|
||||
import com.android.wm.shell.animation.PhysicsAnimator
|
||||
import com.android.wm.shell.common.DismissCircleView
|
||||
|
||||
/*
|
||||
/**
|
||||
* View that handles interactions between DismissCircleView and BubbleStackView.
|
||||
*
|
||||
* @note [setup] method should be called after initialisation
|
||||
*/
|
||||
class DismissView(context: Context) : FrameLayout(context) {
|
||||
/**
|
||||
* The configuration is used to provide module specific resource ids
|
||||
*
|
||||
* @see [setup] method
|
||||
*/
|
||||
data class Config(
|
||||
/** dimen resource id of the dismiss target circle view size */
|
||||
@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 */
|
||||
@DimenRes var bottomMarginResId: Int,
|
||||
/** dimen resource id of the height for dismiss area gradient */
|
||||
@DimenRes val floatingGradientHeightResId: Int,
|
||||
/** color resource id of the dismiss area gradient color */
|
||||
@ColorRes val floatingGradientColorResId: Int,
|
||||
/** 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
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val SHOULD_SETUP =
|
||||
"The view isn't ready. Should be called after `setup`"
|
||||
private val TAG = DismissView::class.simpleName
|
||||
}
|
||||
|
||||
var circle = DismissCircleView(context)
|
||||
var isShowing = false
|
||||
var targetSizeResId: Int
|
||||
var config: Config? = null
|
||||
|
||||
private val animator = PhysicsAnimator.getInstance(circle)
|
||||
private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
|
||||
private val DISMISS_SCRIM_FADE_MS = 200L
|
||||
private var wm: WindowManager =
|
||||
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
private var gradientDrawable = createGradient()
|
||||
private var gradientDrawable: GradientDrawable? = null
|
||||
|
||||
private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
|
||||
object : IntProperty<GradientDrawable>("alpha") {
|
||||
@@ -61,23 +93,41 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
}
|
||||
|
||||
init {
|
||||
setLayoutParams(LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
|
||||
Gravity.BOTTOM))
|
||||
updatePadding()
|
||||
setClipToPadding(false)
|
||||
setClipChildren(false)
|
||||
setVisibility(View.INVISIBLE)
|
||||
addView(circle)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up view with the provided resource ids.
|
||||
*
|
||||
* Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called
|
||||
* with default params in module specific extension:
|
||||
* @see [DismissView.setup] in DismissViewExt.kt
|
||||
*/
|
||||
fun setup(config: Config) {
|
||||
this.config = config
|
||||
|
||||
// Setup layout
|
||||
layoutParams = LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
resources.getDimensionPixelSize(config.floatingGradientHeightResId),
|
||||
Gravity.BOTTOM)
|
||||
updatePadding()
|
||||
|
||||
// Setup gradient
|
||||
gradientDrawable = createGradient(color = config.floatingGradientColorResId)
|
||||
setBackgroundDrawable(gradientDrawable)
|
||||
|
||||
targetSizeResId = R.dimen.dismiss_circle_size
|
||||
val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId)
|
||||
addView(circle, LayoutParams(targetSize, targetSize,
|
||||
Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL))
|
||||
// start with circle offscreen so it's animated up
|
||||
circle.setTranslationY(resources.getDimensionPixelSize(
|
||||
R.dimen.floating_dismiss_gradient_height).toFloat())
|
||||
// Setup DismissCircleView
|
||||
circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId)
|
||||
val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId)
|
||||
circle.layoutParams = LayoutParams(targetSize, targetSize,
|
||||
Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
|
||||
// Initial position with circle offscreen so it's animated up
|
||||
circle.translationY = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
|
||||
.toFloat()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,6 +135,7 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
*/
|
||||
fun show() {
|
||||
if (isShowing) return
|
||||
val gradientDrawable = checkExists(gradientDrawable) ?: return
|
||||
isShowing = true
|
||||
setVisibility(View.VISIBLE)
|
||||
val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
|
||||
@@ -104,6 +155,7 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
*/
|
||||
fun hide() {
|
||||
if (!isShowing) return
|
||||
val gradientDrawable = checkExists(gradientDrawable) ?: return
|
||||
isShowing = false
|
||||
val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
|
||||
gradientDrawable.alpha, 0)
|
||||
@@ -124,18 +176,17 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
}
|
||||
|
||||
fun updateResources() {
|
||||
val config = checkExists(config) ?: return
|
||||
updatePadding()
|
||||
layoutParams.height = resources.getDimensionPixelSize(
|
||||
R.dimen.floating_dismiss_gradient_height)
|
||||
|
||||
val targetSize = resources.getDimensionPixelSize(targetSizeResId)
|
||||
layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
|
||||
val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
|
||||
circle.layoutParams.width = targetSize
|
||||
circle.layoutParams.height = targetSize
|
||||
circle.requestLayout()
|
||||
}
|
||||
|
||||
private fun createGradient(): GradientDrawable {
|
||||
val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900)
|
||||
private fun createGradient(@ColorRes color: Int): GradientDrawable {
|
||||
val gradientColor = ContextCompat.getColor(context, color)
|
||||
val alpha = 0.7f * 255
|
||||
val gradientColorWithAlpha = Color.argb(alpha.toInt(),
|
||||
Color.red(gradientColor),
|
||||
@@ -150,10 +201,22 @@ class DismissView(context: Context) : FrameLayout(context) {
|
||||
}
|
||||
|
||||
private fun updatePadding() {
|
||||
val config = checkExists(config) ?: return
|
||||
val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
|
||||
val navInset = insets.getInsetsIgnoringVisibility(
|
||||
WindowInsets.Type.navigationBars())
|
||||
setPadding(0, 0, 0, navInset.bottom +
|
||||
resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
|
||||
resources.getDimensionPixelSize(config.bottomMarginResId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is set up and exists, if not logs an exception.
|
||||
* Used for convenient logging in case `setup` wasn't called before
|
||||
*
|
||||
* @return value provided as argument
|
||||
*/
|
||||
private fun <T>checkExists(value: T?): T? {
|
||||
if (value == null) Log.e(TAG, SHOULD_SETUP)
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.bubbles
|
||||
package com.android.wm.shell.common.bubbles
|
||||
|
||||
import android.graphics.PointF
|
||||
import android.view.MotionEvent
|
||||
@@ -33,9 +33,10 @@ import android.view.WindowManager;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.DismissCircleView;
|
||||
import com.android.wm.shell.bubbles.DismissViewUtils;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.bubbles.DismissCircleView;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
|
||||
import com.android.wm.shell.pip.PipUiEventLogger;
|
||||
|
||||
@@ -106,6 +107,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
|
||||
}
|
||||
|
||||
mTargetViewContainer = new DismissView(mContext);
|
||||
DismissViewUtils.setup(mTargetViewContainer);
|
||||
mTargetView = mTargetViewContainer.getCircle();
|
||||
mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
|
||||
if (!windowInsets.equals(mWindowInsets)) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.wm.shell.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,7 +61,8 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
import com.android.wm.shell.bubbles.DismissView;
|
||||
import com.android.wm.shell.bubbles.DismissViewUtils;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -184,6 +185,7 @@ class MenuViewLayer extends FrameLayout implements
|
||||
mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
|
||||
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
|
||||
mDismissView = new DismissView(context);
|
||||
DismissViewUtils.setup(mDismissView);
|
||||
mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
|
||||
mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
|
||||
@Override
|
||||
|
||||
@@ -29,7 +29,8 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
import com.android.wm.shell.bubbles.DismissView;
|
||||
import com.android.wm.shell.bubbles.DismissViewUtils;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -63,6 +64,7 @@ public class DismissAnimationControllerTest extends SysuiTestCase {
|
||||
final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
|
||||
stubMenuViewAppearance);
|
||||
mDismissView = spy(new DismissView(mContext));
|
||||
DismissViewUtils.setup(mDismissView);
|
||||
mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ import com.android.internal.accessibility.dialog.AccessibilityTarget;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.accessibility.MotionEventHelper;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
import com.android.wm.shell.bubbles.DismissView;
|
||||
import com.android.wm.shell.bubbles.DismissViewUtils;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -88,6 +89,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
|
||||
mStubMenuView.setTranslationY(0);
|
||||
mMenuAnimationController = spy(new MenuAnimationController(mStubMenuView));
|
||||
mDismissView = spy(new DismissView(mContext));
|
||||
DismissViewUtils.setup(mDismissView);
|
||||
mDismissAnimationController =
|
||||
spy(new DismissAnimationController(mDismissView, mStubMenuView));
|
||||
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
|
||||
|
||||
Reference in New Issue
Block a user