diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index e003b2e1d68ac..055fe373156ad 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -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); /** diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt index e8bdb6755f97d..5af8dab764a12 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt @@ -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) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt index a23722433a4db..a5494adedd1db 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt @@ -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) + } } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index c9f2401fd16a9..4623714891589 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -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(); diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java index ecbb70eb0f039..5507ffabc3a0c 100644 --- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java @@ -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( diff --git a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt index 10b36e9eb85a0..bc451137d7f53 100644 --- a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt @@ -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 + } } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt index cee71013413d5..e6035f35fe717 100644 --- a/packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt @@ -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() } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index d3ae9320d91d8..4fd8fe7b921be 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -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. - * + *
* 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);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index a45b131902c85..abe321979a1a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -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;
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index e8fc245be1235..1cb2683654413 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -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