Merge changes from topic "qs-animation" into sc-dev
* changes: Animate QuickSettings long press (1/2). Add generic support for corner radius in launch animations. Animate the media activity launch.
This commit is contained in:
committed by
Android (Google) Code Review
commit
a4d39c2203
@@ -63,6 +63,8 @@ public interface ActivityStarter {
|
||||
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade);
|
||||
void startActivity(Intent intent, boolean dismissShade, Callback callback);
|
||||
void postStartActivityDismissingKeyguard(Intent intent, int delay);
|
||||
void postStartActivityDismissingKeyguard(Intent intent, int delay,
|
||||
@Nullable ActivityLaunchAnimator.Controller animationController);
|
||||
void postStartActivityDismissingKeyguard(PendingIntent intent);
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,12 +35,12 @@ class ActivityLaunchAnimator {
|
||||
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
|
||||
private const val ANIMATION_DELAY_NAV_FADE_IN =
|
||||
ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
|
||||
private const val LAUNCH_TIMEOUT = 500L
|
||||
private const val LAUNCH_TIMEOUT = 1000L
|
||||
|
||||
// TODO(b/184121838): Use android.R.interpolator.fast_out_extra_slow_in instead.
|
||||
// TODO(b/184121838): Move com.android.systemui.Interpolators in an animation library we can
|
||||
// reuse here.
|
||||
private val ANIMATION_INTERPOLATOR = PathInterpolator(0f, 0f, 0.2f, 1f)
|
||||
private val ANIMATION_INTERPOLATOR = PathInterpolator(0.4f, 0f, 0.2f, 1f)
|
||||
private val LINEAR_INTERPOLATOR = LinearInterpolator()
|
||||
private val ALPHA_IN_INTERPOLATOR = PathInterpolator(0.4f, 0f, 1f, 1f)
|
||||
private val ALPHA_OUT_INTERPOLATOR = PathInterpolator(0f, 0f, 0.8f, 1f)
|
||||
|
||||
@@ -7,6 +7,8 @@ import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffXfermode
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.view.GhostView
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -66,15 +68,28 @@ open class GhostedViewLaunchAnimatorController(
|
||||
topCornerRadius: Float,
|
||||
bottomCornerRadius: Float
|
||||
) {
|
||||
// TODO(b/184121838): Add default support for GradientDrawable and LayerDrawable to make
|
||||
// this work out of the box for common rounded backgrounds.
|
||||
// By default, we rely on WrappedDrawable to set/restore the background radii before/after
|
||||
// each draw.
|
||||
backgroundDrawable?.setBackgroundRadius(topCornerRadius, bottomCornerRadius)
|
||||
}
|
||||
|
||||
/** Return the current top corner radius of the background. */
|
||||
protected open fun getCurrentTopCornerRadius(): Float = 0f
|
||||
protected open fun getCurrentTopCornerRadius(): Float {
|
||||
val drawable = getBackground() ?: return 0f
|
||||
val gradient = findGradientDrawable(drawable) ?: return 0f
|
||||
|
||||
// TODO(b/184121838): Support more than symmetric top & bottom radius.
|
||||
return gradient.cornerRadii?.get(CORNER_RADIUS_TOP_INDEX) ?: gradient.cornerRadius
|
||||
}
|
||||
|
||||
/** Return the current bottom corner radius of the background. */
|
||||
protected open fun getCurrentBottomCornerRadius(): Float = 0f
|
||||
protected open fun getCurrentBottomCornerRadius(): Float {
|
||||
val drawable = getBackground() ?: return 0f
|
||||
val gradient = findGradientDrawable(drawable) ?: return 0f
|
||||
|
||||
// TODO(b/184121838): Support more than symmetric top & bottom radius.
|
||||
return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
|
||||
}
|
||||
|
||||
override fun getRootView(): View {
|
||||
return rootView
|
||||
@@ -94,7 +109,7 @@ open class GhostedViewLaunchAnimatorController(
|
||||
|
||||
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
|
||||
backgroundView = FrameLayout(rootView.context).apply {
|
||||
forceHasOverlappingRendering(true)
|
||||
forceHasOverlappingRendering(false)
|
||||
}
|
||||
rootViewOverlay.add(backgroundView)
|
||||
|
||||
@@ -143,6 +158,33 @@ open class GhostedViewLaunchAnimatorController(
|
||||
ghostedView.invalidate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CORNER_RADIUS_TOP_INDEX = 0
|
||||
private const val CORNER_RADIUS_BOTTOM_INDEX = 4
|
||||
|
||||
/**
|
||||
* Return the first [GradientDrawable] found in [drawable], or null if none is found. If
|
||||
* [drawable] is a [LayerDrawable], this will return the first layer that is a
|
||||
* [GradientDrawable].
|
||||
*/
|
||||
private fun findGradientDrawable(drawable: Drawable): GradientDrawable? {
|
||||
if (drawable is GradientDrawable) {
|
||||
return drawable
|
||||
}
|
||||
|
||||
if (drawable is LayerDrawable) {
|
||||
for (i in 0 until drawable.numberOfLayers) {
|
||||
val maybeGradient = drawable.getDrawable(i)
|
||||
if (maybeGradient is GradientDrawable) {
|
||||
return maybeGradient
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private class WrappedDrawable(val wrapped: Drawable?) : Drawable() {
|
||||
companion object {
|
||||
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
|
||||
@@ -151,6 +193,9 @@ open class GhostedViewLaunchAnimatorController(
|
||||
private var currentAlpha = 0xFF
|
||||
private var previousBounds = Rect()
|
||||
|
||||
private var cornerRadii = FloatArray(8) { -1f }
|
||||
private var previousCornerRadii = FloatArray(8)
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
val wrapped = this.wrapped ?: return
|
||||
|
||||
@@ -158,7 +203,8 @@ open class GhostedViewLaunchAnimatorController(
|
||||
|
||||
wrapped.alpha = currentAlpha
|
||||
wrapped.bounds = bounds
|
||||
wrapped.setXfermode(SRC_MODE)
|
||||
setXfermode(wrapped, SRC_MODE)
|
||||
applyBackgroundRadii()
|
||||
|
||||
wrapped.draw(canvas)
|
||||
|
||||
@@ -167,7 +213,8 @@ open class GhostedViewLaunchAnimatorController(
|
||||
// background.
|
||||
wrapped.alpha = 0
|
||||
wrapped.bounds = previousBounds
|
||||
wrapped.setXfermode(null)
|
||||
setXfermode(wrapped, null)
|
||||
restoreBackgroundRadii()
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
@@ -192,5 +239,91 @@ open class GhostedViewLaunchAnimatorController(
|
||||
override fun setColorFilter(filter: ColorFilter?) {
|
||||
wrapped?.colorFilter = filter
|
||||
}
|
||||
|
||||
private fun setXfermode(background: Drawable, mode: PorterDuffXfermode?) {
|
||||
if (background !is LayerDrawable) {
|
||||
background.setXfermode(mode)
|
||||
return
|
||||
}
|
||||
|
||||
// We set the xfermode on the first layer that is not a mask. Most of the time it will
|
||||
// be the "background layer".
|
||||
for (i in 0 until background.numberOfLayers) {
|
||||
if (background.getId(i) != android.R.id.mask) {
|
||||
background.getDrawable(i).setXfermode(mode)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setBackgroundRadius(topCornerRadius: Float, bottomCornerRadius: Float) {
|
||||
updateRadii(cornerRadii, topCornerRadius, bottomCornerRadius)
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
private fun updateRadii(
|
||||
radii: FloatArray,
|
||||
topCornerRadius: Float,
|
||||
bottomCornerRadius: Float
|
||||
) {
|
||||
radii[0] = topCornerRadius
|
||||
radii[1] = topCornerRadius
|
||||
radii[2] = topCornerRadius
|
||||
radii[3] = topCornerRadius
|
||||
|
||||
radii[4] = bottomCornerRadius
|
||||
radii[5] = bottomCornerRadius
|
||||
radii[6] = bottomCornerRadius
|
||||
radii[7] = bottomCornerRadius
|
||||
}
|
||||
|
||||
private fun applyBackgroundRadii() {
|
||||
if (cornerRadii[0] < 0 || wrapped == null) {
|
||||
return
|
||||
}
|
||||
|
||||
savePreviousBackgroundRadii(wrapped)
|
||||
applyBackgroundRadii(wrapped, cornerRadii)
|
||||
}
|
||||
|
||||
private fun savePreviousBackgroundRadii(background: Drawable) {
|
||||
// TODO(b/184121838): This method assumes that all GradientDrawable in background will
|
||||
// have the same radius. Should we save/restore the radii for each layer instead?
|
||||
val gradient = findGradientDrawable(background) ?: return
|
||||
|
||||
// TODO(b/184121838): GradientDrawable#getCornerRadii clones its radii array. Should we
|
||||
// try to avoid that?
|
||||
val radii = gradient.cornerRadii
|
||||
if (radii != null) {
|
||||
radii.copyInto(previousCornerRadii)
|
||||
} else {
|
||||
// Copy the cornerRadius into previousCornerRadii.
|
||||
val radius = gradient.cornerRadius
|
||||
updateRadii(previousCornerRadii, radius, radius)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyBackgroundRadii(drawable: Drawable, radii: FloatArray) {
|
||||
if (drawable is GradientDrawable) {
|
||||
drawable.cornerRadii = radii
|
||||
return
|
||||
}
|
||||
|
||||
if (drawable !is LayerDrawable) {
|
||||
return
|
||||
}
|
||||
|
||||
for (i in 0 until drawable.numberOfLayers) {
|
||||
(drawable.getDrawable(i) as? GradientDrawable)?.cornerRadii = radii
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreBackgroundRadii() {
|
||||
if (cornerRadii[0] < 0 || wrapped == null) {
|
||||
return
|
||||
}
|
||||
|
||||
applyBackgroundRadii(wrapped, previousCornerRadii)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
package com.android.systemui.plugins.qs;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.metrics.LogMaker;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.internal.logging.InstanceId;
|
||||
import com.android.systemui.plugins.annotations.DependsOn;
|
||||
@@ -53,10 +55,16 @@ public interface QSTile {
|
||||
void removeCallbacks();
|
||||
|
||||
QSIconView createTileView(Context context);
|
||||
|
||||
|
||||
void click();
|
||||
void secondaryClick();
|
||||
void longClick();
|
||||
|
||||
/**
|
||||
* The tile was long clicked.
|
||||
*
|
||||
* @param view The view that was clicked.
|
||||
*/
|
||||
void longClick(@Nullable View view);
|
||||
|
||||
void userSwitch(int currentUser);
|
||||
int getMetricsCategory();
|
||||
|
||||
@@ -18,6 +18,8 @@ import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
|
||||
@@ -106,6 +108,14 @@ public class ActivityStarterDelegate implements ActivityStarter {
|
||||
starter -> starter.get().postStartActivityDismissingKeyguard(intent, delay));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStartActivityDismissingKeyguard(Intent intent, int delay,
|
||||
@Nullable ActivityLaunchAnimator.Controller animationController) {
|
||||
mActualStarter.ifPresent(
|
||||
starter -> starter.get().postStartActivityDismissingKeyguard(intent, delay,
|
||||
animationController));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStartActivityDismissingKeyguard(PendingIntent intent) {
|
||||
mActualStarter.ifPresent(
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.graphics.ColorFilter
|
||||
import android.graphics.Outline
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Xfermode
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.util.MathUtils
|
||||
@@ -48,7 +49,15 @@ private const val BACKGROUND_ANIM_DURATION = 370L
|
||||
class IlluminationDrawable : Drawable() {
|
||||
|
||||
private var themeAttrs: IntArray? = null
|
||||
private var cornerRadius = 0f
|
||||
private var cornerRadiusOverride = -1f
|
||||
var cornerRadius = 0f
|
||||
get() {
|
||||
return if (cornerRadiusOverride >= 0) {
|
||||
cornerRadiusOverride
|
||||
} else {
|
||||
field
|
||||
}
|
||||
}
|
||||
private var highlightColor = Color.TRANSPARENT
|
||||
private var tmpHsl = floatArrayOf(0f, 0f, 0f)
|
||||
private var paint = Paint()
|
||||
@@ -122,8 +131,28 @@ class IlluminationDrawable : Drawable() {
|
||||
throw UnsupportedOperationException("Color filters are not supported")
|
||||
}
|
||||
|
||||
override fun setAlpha(value: Int) {
|
||||
throw UnsupportedOperationException("Alpha is not supported")
|
||||
override fun setAlpha(alpha: Int) {
|
||||
if (alpha == paint.alpha) {
|
||||
return
|
||||
}
|
||||
|
||||
paint.alpha = alpha
|
||||
invalidateSelf()
|
||||
|
||||
lightSources.forEach { it.alpha = alpha }
|
||||
}
|
||||
|
||||
override fun getAlpha(): Int {
|
||||
return paint.alpha
|
||||
}
|
||||
|
||||
override fun setXfermode(mode: Xfermode?) {
|
||||
if (mode == paint.xfermode) {
|
||||
return
|
||||
}
|
||||
|
||||
paint.xfermode = mode
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,9 +200,19 @@ class IlluminationDrawable : Drawable() {
|
||||
|
||||
fun registerLightSource(lightSource: View) {
|
||||
if (lightSource.background is LightSourceDrawable) {
|
||||
lightSources.add(lightSource.background as LightSourceDrawable)
|
||||
registerLightSource(lightSource.background as LightSourceDrawable)
|
||||
} else if (lightSource.foreground is LightSourceDrawable) {
|
||||
lightSources.add(lightSource.foreground as LightSourceDrawable)
|
||||
registerLightSource(lightSource.foreground as LightSourceDrawable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerLightSource(lightSource: LightSourceDrawable) {
|
||||
lightSource.alpha = paint.alpha
|
||||
lightSources.add(lightSource)
|
||||
}
|
||||
|
||||
/** Set or remove the corner radius override. This is typically set during animations. */
|
||||
fun setCornerRadiusOverride(cornerRadius: Float?) {
|
||||
cornerRadiusOverride = cornerRadius ?: -1f
|
||||
}
|
||||
}
|
||||
@@ -184,8 +184,13 @@ class LightSourceDrawable : Drawable() {
|
||||
throw UnsupportedOperationException("Color filters are not supported")
|
||||
}
|
||||
|
||||
override fun setAlpha(value: Int) {
|
||||
throw UnsupportedOperationException("Alpha is not supported")
|
||||
override fun setAlpha(alpha: Int) {
|
||||
if (alpha == paint.alpha) {
|
||||
return
|
||||
}
|
||||
|
||||
paint.alpha = alpha
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,8 @@ import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
|
||||
import com.android.systemui.plugins.animation.GhostedViewLaunchAnimatorController;
|
||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||
import com.android.systemui.util.animation.TransitionLayout;
|
||||
|
||||
@@ -101,11 +103,12 @@ public class MediaControlPanel {
|
||||
// This will provide the corners for the album art.
|
||||
private final ViewOutlineProvider mViewOutlineProvider;
|
||||
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
|
||||
|
||||
/**
|
||||
* Initialize a new control panel
|
||||
* @param context
|
||||
*
|
||||
* @param backgroundExecutor background executor, used for processing artwork
|
||||
* @param activityStarter activity starter
|
||||
* @param activityStarter activity starter
|
||||
*/
|
||||
@Inject
|
||||
public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
|
||||
@@ -147,6 +150,7 @@ public class MediaControlPanel {
|
||||
|
||||
/**
|
||||
* Get the view holder used to display media controls
|
||||
*
|
||||
* @return the view holder
|
||||
*/
|
||||
@Nullable
|
||||
@@ -156,6 +160,7 @@ public class MediaControlPanel {
|
||||
|
||||
/**
|
||||
* Get the view controller used to display media controls
|
||||
*
|
||||
* @return the media view controller
|
||||
*/
|
||||
@NonNull
|
||||
@@ -165,7 +170,7 @@ public class MediaControlPanel {
|
||||
|
||||
/**
|
||||
* Sets the listening state of the player.
|
||||
*
|
||||
* <p>
|
||||
* Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
|
||||
* unnecessary work when the QS panel is closed.
|
||||
*
|
||||
@@ -177,6 +182,7 @@ public class MediaControlPanel {
|
||||
|
||||
/**
|
||||
* Get the context
|
||||
*
|
||||
* @return context
|
||||
*/
|
||||
public Context getContext() {
|
||||
@@ -244,7 +250,8 @@ public class MediaControlPanel {
|
||||
if (clickIntent != null) {
|
||||
mViewHolder.getPlayer().setOnClickListener(v -> {
|
||||
if (mMediaViewController.isGutsVisible()) return;
|
||||
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent);
|
||||
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
|
||||
buildLaunchAnimatorController(mViewHolder.getPlayer()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -396,8 +403,42 @@ public class MediaControlPanel {
|
||||
mMediaViewController.refreshState();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ActivityLaunchAnimator.Controller buildLaunchAnimatorController(
|
||||
TransitionLayout player) {
|
||||
// TODO(b/174236650): Make sure that the carousel indicator also fades out.
|
||||
// TODO(b/174236650): Instrument the animation to measure jank.
|
||||
return new GhostedViewLaunchAnimatorController(player) {
|
||||
@Override
|
||||
protected float getCurrentTopCornerRadius() {
|
||||
return ((IlluminationDrawable) player.getBackground()).getCornerRadius();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getCurrentBottomCornerRadius() {
|
||||
// TODO(b/184121838): Make IlluminationDrawable support top and bottom radius.
|
||||
return getCurrentTopCornerRadius();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setBackgroundCornerRadius(Drawable background, float topCornerRadius,
|
||||
float bottomCornerRadius) {
|
||||
// TODO(b/184121838): Make IlluminationDrawable support top and bottom radius.
|
||||
float radius = Math.min(topCornerRadius, bottomCornerRadius);
|
||||
((IlluminationDrawable) background).setCornerRadiusOverride(radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
|
||||
super.onLaunchAnimationEnd(isExpandingFullyAbove);
|
||||
((IlluminationDrawable) player.getBackground()).setCornerRadiusOverride(null);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the guts for this player.
|
||||
*
|
||||
* @param immediate {@code true} if it should be closed without animation
|
||||
*/
|
||||
public void closeGuts(boolean immediate) {
|
||||
@@ -427,7 +468,7 @@ public class MediaControlPanel {
|
||||
if (bounds.width() > mAlbumArtSize || bounds.height() > mAlbumArtSize) {
|
||||
float offsetX = (bounds.width() - mAlbumArtSize) / 2.0f;
|
||||
float offsetY = (bounds.height() - mAlbumArtSize) / 2.0f;
|
||||
bounds.offset((int) -offsetX,(int) -offsetY);
|
||||
bounds.offset((int) -offsetX, (int) -offsetY);
|
||||
}
|
||||
drawable.setBounds(bounds);
|
||||
return drawable;
|
||||
@@ -435,6 +476,7 @@ public class MediaControlPanel {
|
||||
|
||||
/**
|
||||
* Get the current media controller
|
||||
*
|
||||
* @return the controller
|
||||
*/
|
||||
public MediaController getController() {
|
||||
@@ -443,6 +485,7 @@ public class MediaControlPanel {
|
||||
|
||||
/**
|
||||
* Check whether the media controlled by this player is currently playing
|
||||
*
|
||||
* @return whether it is playing, or false if no controller information
|
||||
*/
|
||||
public boolean isPlaying() {
|
||||
@@ -451,6 +494,7 @@ public class MediaControlPanel {
|
||||
|
||||
/**
|
||||
* Check whether the given controller is currently playing
|
||||
*
|
||||
* @param controller media controller to check
|
||||
* @return whether it is playing, or false if no controller information
|
||||
*/
|
||||
@@ -468,7 +512,7 @@ public class MediaControlPanel {
|
||||
}
|
||||
|
||||
private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) {
|
||||
set.setVisibility(actionId, visible? ConstraintSet.VISIBLE : ConstraintSet.GONE);
|
||||
set.setVisibility(actionId, visible ? ConstraintSet.VISIBLE : ConstraintSet.GONE);
|
||||
set.setAlpha(actionId, visible ? 1.0f : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
|
||||
@Override
|
||||
public void init(QSTile tile) {
|
||||
init(v -> tile.click(), v -> tile.secondaryClick(), view -> {
|
||||
tile.longClick();
|
||||
tile.longClick(this);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
|
||||
import android.annotation.CallSuper;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -43,6 +44,7 @@ import android.text.format.DateUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
@@ -58,6 +60,7 @@ import com.android.settingslib.Utils;
|
||||
import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
|
||||
import com.android.systemui.plugins.qs.DetailAdapter;
|
||||
import com.android.systemui.plugins.qs.QSIconView;
|
||||
import com.android.systemui.plugins.qs.QSTile;
|
||||
@@ -292,14 +295,15 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
|
||||
mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
|
||||
}
|
||||
|
||||
public void longClick() {
|
||||
@Override
|
||||
public void longClick(@Nullable View view) {
|
||||
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)
|
||||
.addTaggedData(FIELD_STATUS_BAR_STATE,
|
||||
mStatusBarStateController.getState())));
|
||||
mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS, 0, getMetricsSpec(),
|
||||
getInstanceId());
|
||||
mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
|
||||
mHandler.sendEmptyMessage(H.LONG_CLICK);
|
||||
mHandler.obtainMessage(H.LONG_CLICK, view).sendToTarget();
|
||||
}
|
||||
|
||||
public LogMaker populate(LogMaker logMaker) {
|
||||
@@ -374,10 +378,15 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
|
||||
|
||||
/**
|
||||
* Handles long click on the tile by launching the {@link Intent} defined in
|
||||
* {@link QSTileImpl#getLongClickIntent}
|
||||
* {@link QSTileImpl#getLongClickIntent}.
|
||||
*
|
||||
* @param view The view from which the opening window will be animated.
|
||||
*/
|
||||
protected void handleLongClick() {
|
||||
mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0);
|
||||
protected void handleLongClick(@Nullable View view) {
|
||||
ActivityLaunchAnimator.Controller animationController =
|
||||
view != null ? ActivityLaunchAnimator.Controller.fromView(view) : null;
|
||||
mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
|
||||
animationController);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -614,7 +623,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
|
||||
handleSecondaryClick();
|
||||
} else if (msg.what == LONG_CLICK) {
|
||||
name = "handleLongClick";
|
||||
handleLongClick();
|
||||
handleLongClick((View) msg.obj);
|
||||
} else if (msg.what == REFRESH_STATE) {
|
||||
name = "handleRefreshState";
|
||||
handleRefreshState(msg.obj);
|
||||
|
||||
@@ -142,7 +142,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleLongClick() {
|
||||
protected void handleLongClick(View view) {
|
||||
handleClick();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.MediaStore;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.view.View;
|
||||
import android.widget.Switch;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
@@ -107,7 +108,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleLongClick() {
|
||||
protected void handleLongClick(View view) {
|
||||
handleClick();
|
||||
}
|
||||
|
||||
|
||||
@@ -1802,7 +1802,8 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
@Override
|
||||
public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
|
||||
startActivityDismissingKeyguard(intent, false, dismissShade,
|
||||
false /* disallowEnterPictureInPictureWhileLaunching */, callback, 0);
|
||||
false /* disallowEnterPictureInPictureWhileLaunching */, callback, 0,
|
||||
null /* animationController */);
|
||||
}
|
||||
|
||||
public void setQsExpanded(boolean expanded) {
|
||||
@@ -2025,7 +2026,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
/** Whether we should animate an activity launch. */
|
||||
public boolean areLaunchAnimationsEnabled() {
|
||||
// TODO(b/184121838): Support lock screen launch animations.
|
||||
return mState == StatusBarState.SHADE;
|
||||
return mState == StatusBarState.SHADE && !isOccluded();
|
||||
}
|
||||
|
||||
public boolean isDeviceInVrMode() {
|
||||
@@ -2729,7 +2730,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
boolean dismissShade, int flags) {
|
||||
startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade,
|
||||
false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */,
|
||||
flags);
|
||||
flags, null /* animationController */);
|
||||
}
|
||||
|
||||
public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
|
||||
@@ -2737,55 +2738,75 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, 0);
|
||||
}
|
||||
|
||||
public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
|
||||
private void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
|
||||
final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching,
|
||||
final Callback callback, int flags) {
|
||||
final Callback callback, int flags,
|
||||
@Nullable ActivityLaunchAnimator.Controller animationController) {
|
||||
if (onlyProvisioned && !mDeviceProvisionedController.isDeviceProvisioned()) return;
|
||||
|
||||
final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
|
||||
intent, mLockscreenUserManager.getCurrentUserId());
|
||||
|
||||
ActivityLaunchAnimator.Controller animController = null;
|
||||
if (animationController != null && areLaunchAnimationsEnabled()) {
|
||||
animController = dismissShade ? new StatusBarLaunchAnimatorController(
|
||||
animationController, this, true /* isLaunchForActivity */)
|
||||
: animationController;
|
||||
}
|
||||
final ActivityLaunchAnimator.Controller animCallbackForLambda = animController;
|
||||
|
||||
// If we animate, we will dismiss the shade only once the animation is done. This is taken
|
||||
// care of by the StatusBarLaunchAnimationController.
|
||||
boolean dismissShadeDirectly = dismissShade && animController == null;
|
||||
|
||||
Runnable runnable = () -> {
|
||||
mAssistManagerLazy.get().hideAssist();
|
||||
intent.setFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.addFlags(flags);
|
||||
int result = ActivityManager.START_CANCELED;
|
||||
ActivityOptions options = new ActivityOptions(getActivityOptions(mDisplayId,
|
||||
null /* remoteAnimation */));
|
||||
options.setDisallowEnterPictureInPictureWhileLaunching(
|
||||
disallowEnterPictureInPictureWhileLaunching);
|
||||
if (CameraIntents.isInsecureCameraIntent(intent)) {
|
||||
// Normally an activity will set it's requested rotation
|
||||
// animation on its window. However when launching an activity
|
||||
// causes the orientation to change this is too late. In these cases
|
||||
// the default animation is used. This doesn't look good for
|
||||
// the camera (as it rotates the camera contents out of sync
|
||||
// with physical reality). So, we ask the WindowManager to
|
||||
// force the crossfade animation if an orientation change
|
||||
// happens to occur during the launch.
|
||||
options.setRotationAnimationHint(
|
||||
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
|
||||
}
|
||||
if (intent.getAction() == Settings.Panel.ACTION_VOLUME) {
|
||||
// Settings Panel is implemented as activity(not a dialog), so
|
||||
// underlying app is paused and may enter picture-in-picture mode
|
||||
// as a result.
|
||||
// So we need to disable picture-in-picture mode here
|
||||
// if it is volume panel.
|
||||
options.setDisallowEnterPictureInPictureWhileLaunching(true);
|
||||
}
|
||||
try {
|
||||
result = ActivityTaskManager.getService().startActivityAsUser(
|
||||
null, mContext.getBasePackageName(), mContext.getAttributionTag(),
|
||||
intent,
|
||||
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
|
||||
null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
|
||||
options.toBundle(), UserHandle.CURRENT.getIdentifier());
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Unable to start activity", e);
|
||||
}
|
||||
int[] result = new int[] { ActivityManager.START_CANCELED };
|
||||
|
||||
mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda, (adapter) -> {
|
||||
ActivityOptions options = new ActivityOptions(
|
||||
getActivityOptions(mDisplayId, adapter));
|
||||
options.setDisallowEnterPictureInPictureWhileLaunching(
|
||||
disallowEnterPictureInPictureWhileLaunching);
|
||||
if (CameraIntents.isInsecureCameraIntent(intent)) {
|
||||
// Normally an activity will set it's requested rotation
|
||||
// animation on its window. However when launching an activity
|
||||
// causes the orientation to change this is too late. In these cases
|
||||
// the default animation is used. This doesn't look good for
|
||||
// the camera (as it rotates the camera contents out of sync
|
||||
// with physical reality). So, we ask the WindowManager to
|
||||
// force the crossfade animation if an orientation change
|
||||
// happens to occur during the launch.
|
||||
options.setRotationAnimationHint(
|
||||
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
|
||||
}
|
||||
if (intent.getAction() == Settings.Panel.ACTION_VOLUME) {
|
||||
// Settings Panel is implemented as activity(not a dialog), so
|
||||
// underlying app is paused and may enter picture-in-picture mode
|
||||
// as a result.
|
||||
// So we need to disable picture-in-picture mode here
|
||||
// if it is volume panel.
|
||||
options.setDisallowEnterPictureInPictureWhileLaunching(true);
|
||||
}
|
||||
|
||||
try {
|
||||
result[0] = ActivityTaskManager.getService().startActivityAsUser(
|
||||
null, mContext.getBasePackageName(), mContext.getAttributionTag(),
|
||||
intent,
|
||||
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
|
||||
null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
|
||||
options.toBundle(), UserHandle.CURRENT.getIdentifier());
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Unable to start activity", e);
|
||||
}
|
||||
return result[0];
|
||||
});
|
||||
|
||||
if (callback != null) {
|
||||
callback.onActivityStarted(result);
|
||||
callback.onActivityStarted(result[0]);
|
||||
}
|
||||
};
|
||||
Runnable cancelRunnable = () -> {
|
||||
@@ -2793,7 +2814,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
callback.onActivityStarted(ActivityManager.START_CANCELED);
|
||||
}
|
||||
};
|
||||
executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade,
|
||||
executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly,
|
||||
afterKeyguardGone, true /* deferred */);
|
||||
}
|
||||
|
||||
@@ -3151,12 +3172,21 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
|
||||
@Override
|
||||
public void postStartActivityDismissingKeyguard(final Intent intent, int delay) {
|
||||
mHandler.postDelayed(() ->
|
||||
handleStartActivityDismissingKeyguard(intent, true /*onlyProvisioned*/), delay);
|
||||
postStartActivityDismissingKeyguard(intent, delay, null /* animationController */);
|
||||
}
|
||||
|
||||
private void handleStartActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned) {
|
||||
startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */);
|
||||
@Override
|
||||
public void postStartActivityDismissingKeyguard(Intent intent, int delay,
|
||||
@Nullable ActivityLaunchAnimator.Controller animationController) {
|
||||
mHandler.postDelayed(
|
||||
() ->
|
||||
startActivityDismissingKeyguard(intent, true /* onlyProvisioned */,
|
||||
true /* dismissShade */,
|
||||
false /* disallowEnterPictureInPictureWhileLaunching */,
|
||||
null /* callback */,
|
||||
0 /* flags */,
|
||||
animationController),
|
||||
delay);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -4071,7 +4101,8 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
|
||||
startActivityDismissingKeyguard(cameraIntent,
|
||||
false /* onlyProvisioned */, true /* dismissShade */,
|
||||
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0);
|
||||
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
|
||||
null /* animationController */);
|
||||
} else {
|
||||
if (!mDeviceInteractive) {
|
||||
// Avoid flickering of the scrim when we instant launch the camera and the bouncer
|
||||
@@ -4122,7 +4153,8 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
if (!mStatusBarKeyguardViewManager.isShowing()) {
|
||||
startActivityDismissingKeyguard(emergencyIntent,
|
||||
false /* onlyProvisioned */, true /* dismissShade */,
|
||||
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0);
|
||||
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
|
||||
null /* animationController */);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4504,8 +4536,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
&& mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
|
||||
mLockscreenUserManager.getCurrentUserId());
|
||||
|
||||
boolean animate =
|
||||
animationController != null && areLaunchAnimationsEnabled() && !isOccluded();
|
||||
boolean animate = animationController != null && areLaunchAnimationsEnabled();
|
||||
boolean collapse = !animate;
|
||||
executeActionDismissingKeyguard(() -> {
|
||||
try {
|
||||
|
||||
@@ -34,6 +34,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -46,6 +47,7 @@ import android.testing.TestableLooper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.FeatureFlagUtils;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
@@ -418,7 +420,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
|
||||
public void secondaryClick() {}
|
||||
|
||||
@Override
|
||||
public void longClick() {}
|
||||
public void longClick(@Nullable View view) {}
|
||||
|
||||
@Override
|
||||
public void userSwitch(int currentUser) {}
|
||||
|
||||
@@ -189,7 +189,7 @@ public class QSTileImplTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testLongClick_Metrics() {
|
||||
mTile.longClick();
|
||||
mTile.longClick(null /* view */);
|
||||
verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS)));
|
||||
assertEquals(1, mUiEventLoggerFake.numLogs());
|
||||
UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0);
|
||||
@@ -201,7 +201,7 @@ public class QSTileImplTest extends SysuiTestCase {
|
||||
public void testLongClick_log() {
|
||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
|
||||
|
||||
mTile.longClick();
|
||||
mTile.longClick(null /* view */);
|
||||
verify(mQsLogger).logTileLongClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user