Merge "Share bubbles dismiss functionality with Launcher" into udc-qpr-dev

This commit is contained in:
Ivan Tkachenko
2023-06-15 10:29:36 +00:00
committed by Android (Google) Code Review
11 changed files with 175 additions and 53 deletions

View File

@@ -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",
}

View File

@@ -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);

View File

@@ -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
))
}

View File

@@ -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));
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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;
/**

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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,