diff --git a/androidx-lib/build.gradle b/androidx-lib/build.gradle new file mode 100644 index 0000000000..d7946b5d32 --- /dev/null +++ b/androidx-lib/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace "androidx.dynamicanimation.animation" + sourceSets { + main { + java.srcDirs = ['src'] + } + } +} + +addFrameworkJar('framework-14.jar') +compileOnlyCommonJars() diff --git a/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java b/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java new file mode 100644 index 0000000000..45f6d9349c --- /dev/null +++ b/androidx-lib/src/androidx/dynamicanimation/animation/AnimationHandler.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2017 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. + */ + +package androidx.dynamicanimation.animation; + +import android.animation.ValueAnimator; +import android.os.Build; +import android.os.Looper; +import android.os.SystemClock; +import android.view.Choreographer; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.annotation.RestrictTo; +import androidx.annotation.VisibleForTesting; +import androidx.collection.SimpleArrayMap; + +import java.util.ArrayList; + +/** + * This custom handler handles the timing pulse that is shared by all active ValueAnimators. + * This approach ensures that the setting of animation values will happen on the + * same thread that animations start on, and that all animations will share the same times for + * calculating their values, which makes synchronizing animations possible. + * + * The handler uses the Choreographer by default for doing periodic callbacks. A custom + * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that + * may be independent of UI frame update. This could be useful in testing. + */ +public class AnimationHandler { + /** + * Callbacks that receives notifications for animation timing + */ + interface AnimationFrameCallback { + /** + * Run animation based on the frame time. + * + * @param frameTime The frame start time + */ + boolean doAnimationFrame(long frameTime); + } + + /** + * This class is responsible for interacting with the available frame provider by either + * registering frame callback or posting runnable, and receiving a callback for when a + * new frame has arrived. This dispatcher class then notifies all the on-going animations of + * the new frame, so that they can update animation values as needed. + */ + private class AnimationCallbackDispatcher { + /** + * Notifies all the on-going animations of the new frame. + */ + void dispatchAnimationFrame() { + mCurrentFrameTime = SystemClock.uptimeMillis(); + AnimationHandler.this.doAnimationFrame(mCurrentFrameTime); + if (mAnimationCallbacks.size() > 0) { + mScheduler.postFrameCallback(mRunnable); + } + } + } + + private static final ThreadLocal sAnimatorHandler = new ThreadLocal<>(); + + /** + * Internal per-thread collections used to avoid set collisions as animations start and end + * while being processed. + */ + private final SimpleArrayMap mDelayedCallbackStartTime = + new SimpleArrayMap<>(); + @SuppressWarnings("WeakerAccess") /* synthetic access */ + final ArrayList mAnimationCallbacks = new ArrayList<>(); + private final AnimationCallbackDispatcher mCallbackDispatcher = + new AnimationCallbackDispatcher(); + private final Runnable mRunnable = () -> mCallbackDispatcher.dispatchAnimationFrame(); + private FrameCallbackScheduler mScheduler; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + long mCurrentFrameTime = 0; + private boolean mListDirty = false; + @RestrictTo(RestrictTo.Scope.LIBRARY) + @VisibleForTesting + public float mDurationScale = 1.0f; + @Nullable + @RestrictTo(RestrictTo.Scope.LIBRARY) + @VisibleForTesting + public DurationScaleChangeListener mDurationScaleChangeListener; + + static AnimationHandler getInstance() { + if (sAnimatorHandler.get() == null) { + AnimationHandler handler = new AnimationHandler( + new FrameCallbackScheduler16()); + sAnimatorHandler.set(handler); + } + return sAnimatorHandler.get(); + } + + /** + * The constructor of the AnimationHandler with {@link FrameCallbackScheduler} which is handle + * running the given Runnable on the next frame. + * + * @param scheduler The scheduler for this handler to run the given runnable. + */ + public AnimationHandler(@NonNull FrameCallbackScheduler scheduler) { + mScheduler = scheduler; + } + + /** + * Register to get a callback on the next frame after the delay. + */ + void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) { + if (mAnimationCallbacks.size() == 0) { + mScheduler.postFrameCallback(mRunnable); + if (Build.VERSION.SDK_INT >= 33) { + mDurationScale = ValueAnimator.getDurationScale(); + if (mDurationScaleChangeListener == null) { + mDurationScaleChangeListener = new DurationScaleChangeListener33(); + } + mDurationScaleChangeListener.register(); + } + } + if (!mAnimationCallbacks.contains(callback)) { + mAnimationCallbacks.add(callback); + } + + if (delay > 0) { + mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay)); + } + } + /** + * Removes the given callback from the list, so it will no longer be called for frame related + * timing. + */ + void removeCallback(AnimationFrameCallback callback) { + mDelayedCallbackStartTime.remove(callback); + int id = mAnimationCallbacks.indexOf(callback); + if (id >= 0) { + mAnimationCallbacks.set(id, null); + mListDirty = true; + } + } + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + void doAnimationFrame(long frameTime) { + long currentTime = SystemClock.uptimeMillis(); + for (int i = 0; i < mAnimationCallbacks.size(); i++) { + final AnimationFrameCallback callback = mAnimationCallbacks.get(i); + if (callback == null) { + continue; + } + if (isCallbackDue(callback, currentTime)) { + callback.doAnimationFrame(frameTime); + } + } + cleanUpList(); + } + + /** + * Returns whether the current thread is the same thread as the animation handler + * frame scheduler. + * + * @return true the current thread is the same thread as the animation handler frame scheduler. + */ + boolean isCurrentThread() { + return mScheduler.isCurrentThread(); + } + + /** + * Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay + * so that they can start getting frame callbacks. + * + * @return true if they have passed the initial delay or have no delay, false otherwise. + */ + private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) { + Long startTime = mDelayedCallbackStartTime.get(callback); + if (startTime == null) { + return true; + } + if (startTime < currentTime) { + mDelayedCallbackStartTime.remove(callback); + return true; + } + return false; + } + + private void cleanUpList() { + if (mListDirty) { + for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) { + if (mAnimationCallbacks.get(i) == null) { + mAnimationCallbacks.remove(i); + } + } + // Unregister duration scale listener if there are no current animations. + if (mAnimationCallbacks.size() == 0) { + if (Build.VERSION.SDK_INT >= 33) { + mDurationScaleChangeListener.unregister(); + } + } + mListDirty = false; + } + } + + /** + * Gets the FrameCallbackScheduler in this handler. + * + * @return The FrameCallbackScheduler in this handler + */ + @NonNull + FrameCallbackScheduler getScheduler() { + return mScheduler; + } + + /** + * Default provider of timing pulse that uses Choreographer for frame callbacks. + */ + static final class FrameCallbackScheduler16 implements FrameCallbackScheduler { + + private final Choreographer mChoreographer = Choreographer.getInstance(); + private final Looper mLooper = Looper.myLooper(); + + @Override + public void postFrameCallback(@NonNull Runnable frameCallback) { + mChoreographer.postFrameCallback(time -> frameCallback.run()); + } + + @Override + public boolean isCurrentThread() { + return Thread.currentThread() == mLooper.getThread(); + } + } + + /** + * Returns the system-wide scaling factor for animations. + */ + @VisibleForTesting + public float getDurationScale() { + return mDurationScale; + } + + /** + * T+ listener for changes to the system-wide scaling factor for Animator-based animations. + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @RequiresApi(api = 33) + @VisibleForTesting + public class DurationScaleChangeListener33 implements DurationScaleChangeListener { + ValueAnimator.DurationScaleChangeListener mListener; + + @Override + public boolean register() { + if (mListener == null) { + mListener = scale -> AnimationHandler.this.mDurationScale = scale; + return ValueAnimator.registerDurationScaleChangeListener(mListener); + } + return true; + } + + @Override + public boolean unregister() { + boolean unregistered = ValueAnimator.unregisterDurationScaleChangeListener(mListener); + mListener = null; + return unregistered; + } + } + + /** + * listener for changes to the system-wide scaling factor for Animator-based animations. + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @VisibleForTesting + public interface DurationScaleChangeListener { + /** + * Registers a duration scale change listener. + * @return true if a listener is registered or one is already registered. + */ + boolean register(); + + /** + * Unregisters a duration scale change listener. + * @return true if a listener is unregistered. + */ + boolean unregister(); + } +} diff --git a/androidx-lib/src/androidx/dynamicanimation/animation/DynamicAnimation.java b/androidx-lib/src/androidx/dynamicanimation/animation/DynamicAnimation.java new file mode 100644 index 0000000000..6ed0e9a604 --- /dev/null +++ b/androidx-lib/src/androidx/dynamicanimation/animation/DynamicAnimation.java @@ -0,0 +1,846 @@ +/* + * Copyright (C) 2017 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. + */ + +package androidx.dynamicanimation.animation; + +import android.annotation.SuppressLint; +import android.util.AndroidRuntimeException; +import android.view.Choreographer; +import android.view.View; + +import androidx.annotation.FloatRange; +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; +import androidx.annotation.VisibleForTesting; +import androidx.core.view.ViewCompat; + +import java.util.ArrayList; + +/** + * This class is the base class of physics-based animations. It manages the animation's + * lifecycle such as {@link #start()} and {@link #cancel()}. This base class also handles the common + * setup for all the subclass animations. For example, DynamicAnimation supports adding + * {@link OnAnimationEndListener} and {@link OnAnimationUpdateListener} so that the important + * animation events can be observed through the callbacks. The start conditions for any subclass of + * DynamicAnimation can be set using {@link #setStartValue(float)} and + * {@link #setStartVelocity(float)}. + * + * @param subclass of DynamicAnimation + */ +public abstract class DynamicAnimation> + implements AnimationHandler.AnimationFrameCallback { + + /** + * ViewProperty holds the access of a property of a {@link View}. When an animation is + * created with a {@link ViewProperty} instance, the corresponding property value of the view + * will be updated through this ViewProperty instance. + */ + public abstract static class ViewProperty extends FloatPropertyCompat { + private ViewProperty(String name) { + super(name); + } + } + + /** + * View's translationX property. + */ + public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") { + @Override + public void setValue(View view, float value) { + view.setTranslationX(value); + } + + @Override + public float getValue(View view) { + return view.getTranslationX(); + } + }; + + /** + * View's translationY property. + */ + public static final ViewProperty TRANSLATION_Y = new ViewProperty("translationY") { + @Override + public void setValue(View view, float value) { + view.setTranslationY(value); + } + + @Override + public float getValue(View view) { + return view.getTranslationY(); + } + }; + + /** + * View's translationZ property. + */ + public static final ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") { + @Override + public void setValue(View view, float value) { + ViewCompat.setTranslationZ(view, value); + } + + @Override + public float getValue(View view) { + return ViewCompat.getTranslationZ(view); + } + }; + + /** + * View's scaleX property. + */ + public static final ViewProperty SCALE_X = new ViewProperty("scaleX") { + @Override + public void setValue(View view, float value) { + view.setScaleX(value); + } + + @Override + public float getValue(View view) { + return view.getScaleX(); + } + }; + + /** + * View's scaleY property. + */ + public static final ViewProperty SCALE_Y = new ViewProperty("scaleY") { + @Override + public void setValue(View view, float value) { + view.setScaleY(value); + } + + @Override + public float getValue(View view) { + return view.getScaleY(); + } + }; + + /** + * View's rotation property. + */ + public static final ViewProperty ROTATION = new ViewProperty("rotation") { + @Override + public void setValue(View view, float value) { + view.setRotation(value); + } + + @Override + public float getValue(View view) { + return view.getRotation(); + } + }; + + /** + * View's rotationX property. + */ + public static final ViewProperty ROTATION_X = new ViewProperty("rotationX") { + @Override + public void setValue(View view, float value) { + view.setRotationX(value); + } + + @Override + public float getValue(View view) { + return view.getRotationX(); + } + }; + + /** + * View's rotationY property. + */ + public static final ViewProperty ROTATION_Y = new ViewProperty("rotationY") { + @Override + public void setValue(View view, float value) { + view.setRotationY(value); + } + + @Override + public float getValue(View view) { + return view.getRotationY(); + } + }; + + /** + * View's x property. + */ + public static final ViewProperty X = new ViewProperty("x") { + @Override + public void setValue(View view, float value) { + view.setX(value); + } + + @Override + public float getValue(View view) { + return view.getX(); + } + }; + + /** + * View's y property. + */ + public static final ViewProperty Y = new ViewProperty("y") { + @Override + public void setValue(View view, float value) { + view.setY(value); + } + + @Override + public float getValue(View view) { + return view.getY(); + } + }; + + /** + * View's z property. + */ + public static final ViewProperty Z = new ViewProperty("z") { + @Override + public void setValue(View view, float value) { + ViewCompat.setZ(view, value); + } + + @Override + public float getValue(View view) { + return ViewCompat.getZ(view); + } + }; + + /** + * View's alpha property. + */ + public static final ViewProperty ALPHA = new ViewProperty("alpha") { + @Override + public void setValue(View view, float value) { + view.setAlpha(value); + } + + @Override + public float getValue(View view) { + return view.getAlpha(); + } + }; + + // Properties below are not RenderThread compatible + /** + * View's scrollX property. + */ + public static final ViewProperty SCROLL_X = new ViewProperty("scrollX") { + @Override + public void setValue(View view, float value) { + view.setScrollX((int) value); + } + + @Override + public float getValue(View view) { + return view.getScrollX(); + } + }; + + /** + * View's scrollY property. + */ + public static final ViewProperty SCROLL_Y = new ViewProperty("scrollY") { + @Override + public void setValue(View view, float value) { + view.setScrollY((int) value); + } + + @Override + public float getValue(View view) { + return view.getScrollY(); + } + }; + + /** + * The minimum visible change in pixels that can be visible to users. + */ + @SuppressLint("MinMaxConstant") + public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f; + /** + * The minimum visible change in degrees that can be visible to users. + */ + @SuppressLint("MinMaxConstant") + public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f; + /** + * The minimum visible change in alpha that can be visible to users. + */ + @SuppressLint("MinMaxConstant") + public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f; + /** + * The minimum visible change in scale that can be visible to users. + */ + @SuppressLint("MinMaxConstant") + public static final float MIN_VISIBLE_CHANGE_SCALE = 1f / 500f; + + // Use the max value of float to indicate an unset state. + private static final float UNSET = Float.MAX_VALUE; + + // Multiplier to the min visible change value for value threshold + private static final float THRESHOLD_MULTIPLIER = 0.75f; + + // Internal tracking for velocity. + float mVelocity = 0; + + // Internal tracking for value. + float mValue = UNSET; + + // Tracks whether start value is set. If not, the animation will obtain the value at the time + // of starting through the getter and use that as the starting value of the animation. + boolean mStartValueIsSet = false; + + // Target to be animated. + final Object mTarget; + + // View property id. + final FloatPropertyCompat mProperty; + + // Package private tracking of animation lifecycle state. Visible to subclass animations. + boolean mRunning = false; + + // Min and max values that defines the range of the animation values. + float mMaxValue = Float.MAX_VALUE; + float mMinValue = -mMaxValue; + + // Last frame time. Always gets reset to -1 at the end of the animation. + private long mLastFrameTime = 0; + + private float mMinVisibleChange; + + // List of end listeners + private final ArrayList mEndListeners = new ArrayList<>(); + + // List of update listeners + private final ArrayList mUpdateListeners = new ArrayList<>(); + + // Animation handler used to schedule updates for this animation. + private AnimationHandler mAnimationHandler; + + // Internal state for value/velocity pair. + static class MassState { + float mValue; + float mVelocity; + } + + /** + * Creates a dynamic animation with the given FloatValueHolder instance. + * + * @param floatValueHolder the FloatValueHolder instance to be animated. + */ + DynamicAnimation(final FloatValueHolder floatValueHolder) { + mTarget = null; + mProperty = new FloatPropertyCompat("FloatValueHolder") { + @Override + public float getValue(Object object) { + return floatValueHolder.getValue(); + } + + @Override + public void setValue(Object object, float value) { + floatValueHolder.setValue(value); + } + }; + mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS; + } + + /** + * Creates a dynamic animation to animate the given property for the given {@link View} + * + * @param object the Object whose property is to be animated + * @param property the property to be animated + */ + + DynamicAnimation(K object, FloatPropertyCompat property) { + mTarget = object; + mProperty = property; + if (mProperty == ROTATION || mProperty == ROTATION_X + || mProperty == ROTATION_Y) { + mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES; + } else if (mProperty == ALPHA) { + mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA; + } else if (mProperty == SCALE_X || mProperty == SCALE_Y) { + mMinVisibleChange = MIN_VISIBLE_CHANGE_SCALE; + } else { + mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS; + } + } + + /** + * Sets the start value of the animation. If start value is not set, the animation will get + * the current value for the view's property, and use that as the start value. + * + * @param startValue start value for the animation + * @return the Animation whose start value is being set + */ + @SuppressWarnings("unchecked") + public T setStartValue(float startValue) { + mValue = startValue; + mStartValueIsSet = true; + return (T) this; + } + + /** + * Start velocity of the animation. Default velocity is 0. Unit: change in property per + * second (e.g. pixels per second, scale/alpha value change per second). + * + *

Note when using a fixed value as the start velocity (as opposed to getting the velocity + * through touch events), it is recommended to define such a value in dp/second and convert it + * to pixel/second based on the density of the screen to achieve a consistent look across + * different screens. + * + *

To convert from dp/second to pixel/second: + *

+     * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
+     *         getResources().getDisplayMetrics());
+     * 
+ * + * @param startVelocity start velocity of the animation + * @return the Animation whose start velocity is being set + */ + @SuppressWarnings("unchecked") + public T setStartVelocity(float startVelocity) { + mVelocity = startVelocity; + return (T) this; + } + + /** + * Sets the max value of the animation. Animations will not animate beyond their max value. + * Whether or not animation will come to an end when max value is reached is dependent on the + * child animation's implementation. + * + * @param max maximum value of the property to be animated + * @return the Animation whose max value is being set + */ + @SuppressWarnings("unchecked") + public T setMaxValue(float max) { + // This max value should be checked and handled in the subclass animations, instead of + // assuming the end of the animations when the max/min value is hit in the base class. + // The reason is that hitting max/min value may just be a transient state, such as during + // the spring oscillation. + mMaxValue = max; + return (T) this; + } + + /** + * Sets the min value of the animation. Animations will not animate beyond their min value. + * Whether or not animation will come to an end when min value is reached is dependent on the + * child animation's implementation. + * + * @param min minimum value of the property to be animated + * @return the Animation whose min value is being set + */ + @SuppressWarnings("unchecked") + public T setMinValue(float min) { + mMinValue = min; + return (T) this; + } + + /** + * Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener + * is {@code null} or has already been added to the list of listeners for the animation, no op. + * + * @param listener the listener to be added + * @return the animation to which the listener is added + */ + @SuppressWarnings("unchecked") + public T addEndListener(OnAnimationEndListener listener) { + if (!mEndListeners.contains(listener)) { + mEndListeners.add(listener); + } + return (T) this; + } + + /** + * Removes the end listener from the animation, so as to stop receiving animation end callbacks. + * + * @param listener the listener to be removed + */ + public void removeEndListener(OnAnimationEndListener listener) { + removeEntry(mEndListeners, listener); + } + + /** + * Adds an update listener to the animation for receiving per-frame animation update callbacks. + * If the listener is {@code null} or has already been added to the list of listeners for the + * animation, no op. + * + *

Note that update listener should only be added before the start of the animation. + * + * @param listener the listener to be added + * @return the animation to which the listener is added + * @throws UnsupportedOperationException if the update listener is added after the animation has + * started + */ + @SuppressWarnings("unchecked") + public T addUpdateListener(OnAnimationUpdateListener listener) { + if (isRunning()) { + // Require update listener to be added before the animation, such as when we start + // the animation, we know whether the animation is RenderThread compatible. + throw new UnsupportedOperationException("Error: Update listeners must be added before" + + "the animation."); + } + if (!mUpdateListeners.contains(listener)) { + mUpdateListeners.add(listener); + } + return (T) this; + } + + /** + * Removes the update listener from the animation, so as to stop receiving animation update + * callbacks. + * + * @param listener the listener to be removed + */ + public void removeUpdateListener(OnAnimationUpdateListener listener) { + removeEntry(mUpdateListeners, listener); + } + + + /** + * This method sets the minimal change of animation value that is visible to users, which helps + * determine a reasonable threshold for the animation's termination condition. It is critical + * to set the minimal visible change for custom properties (i.e. non-ViewPropertys) + * unless the custom property is in pixels. + * + *

For custom properties, this minimum visible change defaults to change in pixel + * (i.e. {@link #MIN_VISIBLE_CHANGE_PIXELS}. It is recommended to adjust this value that is + * reasonable for the property to be animated. A general rule of thumb to calculate such a value + * is: minimum visible change = range of custom property value / corresponding pixel range. For + * example, if the property to be animated is a progress (from 0 to 100) that corresponds to a + * 200-pixel change. Then the min visible change should be 100 / 200. (i.e. 0.5). + * + *

It's not necessary to call this method when animating {@link ViewProperty}s, as the + * minimum visible change will be derived from the property. For example, if the property to be + * animated is in pixels (i.e. {@link #TRANSLATION_X}, {@link #TRANSLATION_Y}, + * {@link #TRANSLATION_Z}, @{@link #SCROLL_X} or {@link #SCROLL_Y}), the default minimum visible + * change is 1 (pixel). For {@link #ROTATION}, {@link #ROTATION_X} or {@link #ROTATION_Y}, the + * animation will use {@link #MIN_VISIBLE_CHANGE_ROTATION_DEGREES} as the min visible change, + * which is 1/10. Similarly, the minimum visible change for alpha ( + * i.e. {@link #MIN_VISIBLE_CHANGE_ALPHA} is defined as 1 / 256. + * + * @param minimumVisibleChange minimum change in property value that is visible to users + * @return the animation whose min visible change is being set + * @throws IllegalArgumentException if the given threshold is not positive + */ + @SuppressWarnings("unchecked") + public T setMinimumVisibleChange(@FloatRange(from = 0.0, fromInclusive = false) + float minimumVisibleChange) { + if (minimumVisibleChange <= 0) { + throw new IllegalArgumentException("Minimum visible change must be positive."); + } + mMinVisibleChange = minimumVisibleChange; + setValueThreshold(minimumVisibleChange * THRESHOLD_MULTIPLIER); + return (T) this; + } + + /** + * Returns the minimum change in the animation property that could be visibly different to + * users. + * + * @return minimum change in property value that is visible to users + */ + public float getMinimumVisibleChange() { + return mMinVisibleChange; + } + + /** + * Remove {@code null} entries from the list. + */ + private static void removeNullEntries(ArrayList list) { + // Clean up null entries + for (int i = list.size() - 1; i >= 0; i--) { + if (list.get(i) == null) { + list.remove(i); + } + } + } + + /** + * Remove an entry from the list by marking it {@code null} and clean up later. + */ + private static void removeEntry(ArrayList list, T entry) { + int id = list.indexOf(entry); + if (id >= 0) { + list.set(id, null); + } + } + + /****************Animation Lifecycle Management***************/ + + /** + * Starts an animation. If the animation has already been started, no op. Note that calling + * {@link #start()} will not immediately set the property value to start value of the animation. + * The property values will be changed at each animation pulse, which happens before the draw + * pass. As a result, the changes will be reflected in the next frame, the same as if the values + * were set immediately. This method should only be called on main thread. + * + * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler + * is created on the same thread as the first call to start/cancel an animation. All the + * subsequent animation lifecycle manipulations need to be on that same thread, until the + * AnimationHandler is reset (using [setAnimationHandler]). + * + * @throws AndroidRuntimeException if this method is not called on the same thread as the + * animation handler + */ + @MainThread + public void start() { + if (!getAnimationHandler().isCurrentThread()) { + throw new AndroidRuntimeException("Animations may only be started on the same thread " + + "as the animation handler"); + } + if (!mRunning) { + startAnimationInternal(); + } + } + + /** + * Cancels the on-going animation. If the animation hasn't started, no op. + * + * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler + * is created on the same thread as the first call to start/cancel an animation. All the + * subsequent animation lifecycle manipulations need to be on that same thread, until the + * AnimationHandler is reset (using [setAnimationHandler]). + * + * @throws AndroidRuntimeException if this method is not called on the same thread as the + * animation handler + */ + @MainThread + public void cancel() { + if (!getAnimationHandler().isCurrentThread()) { + throw new AndroidRuntimeException("Animations may only be canceled from the same " + + "thread as the animation handler"); + } + if (mRunning) { + endAnimationInternal(true); + } + } + + /** + * Returns whether the animation is currently running. + * + * @return {@code true} if the animation is currently running, {@code false} otherwise + */ + public boolean isRunning() { + return mRunning; + } + + /************************** Private APIs below ********************************/ + + // This gets called when the animation is started, to finish the setup of the animation + // before the animation pulsing starts. + private void startAnimationInternal() { + if (!mRunning) { + mRunning = true; + if (!mStartValueIsSet) { + mValue = getPropertyValue(); + } + // Sanity check: + if (mValue > mMaxValue || mValue < mMinValue) { + throw new IllegalArgumentException("Starting value need to be in between min" + + " value and max value"); + } + getAnimationHandler().addAnimationFrameCallback(this, 0); + } + } + + /** + * This gets call on each frame of the animation. Animation value and velocity are updated + * in this method based on the new frame time. The property value of the view being animated + * is then updated. The animation's ending conditions are also checked in this method. Once + * the animation reaches equilibrium, the animation will come to its end, and end listeners + * will be notified, if any. + * + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @Override + public boolean doAnimationFrame(long frameTime) { + if (mLastFrameTime == 0) { + // First frame. + mLastFrameTime = frameTime; + setPropertyValue(mValue); + return false; + } + long deltaT = frameTime - mLastFrameTime; + mLastFrameTime = frameTime; + float durationScale = getAnimationHandler().getDurationScale(); + deltaT = durationScale == 0.0f ? Integer.MAX_VALUE : (long) (deltaT / durationScale); + boolean finished = updateValueAndVelocity(deltaT); + // Clamp value & velocity. + mValue = Math.min(mValue, mMaxValue); + mValue = Math.max(mValue, mMinValue); + + setPropertyValue(mValue); + + if (finished) { + endAnimationInternal(false); + } + return finished; + } + + /** + * Updates the animation state (i.e. value and velocity). This method is package private, so + * subclasses can override this method to calculate the new value and velocity in their custom + * way. + * + * @param deltaT time elapsed in millisecond since last frame + * @return whether the animation has finished + */ + abstract boolean updateValueAndVelocity(long deltaT); + + /** + * Internal method to reset the animation states when animation is finished/canceled. + */ + private void endAnimationInternal(boolean canceled) { + mRunning = false; + getAnimationHandler().removeCallback(this); + mLastFrameTime = 0; + mStartValueIsSet = false; + for (int i = 0; i < mEndListeners.size(); i++) { + if (mEndListeners.get(i) != null) { + mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity); + } + } + removeNullEntries(mEndListeners); + } + + /** + * Updates the property value through the corresponding setter. + */ + @SuppressWarnings("unchecked") + void setPropertyValue(float value) { + mProperty.setValue(mTarget, value); + for (int i = 0; i < mUpdateListeners.size(); i++) { + if (mUpdateListeners.get(i) != null) { + mUpdateListeners.get(i).onAnimationUpdate(this, mValue, mVelocity); + } + } + removeNullEntries(mUpdateListeners); + } + + /** + * Returns the default threshold. + */ + float getValueThreshold() { + return mMinVisibleChange * THRESHOLD_MULTIPLIER; + } + + /** + * Obtain the property value through the corresponding getter. + */ + @SuppressWarnings("unchecked") + private float getPropertyValue() { + return mProperty.getValue(mTarget); + } + + /** + * Returns the {@link AnimationHandler} used to schedule updates for this animator. + * + * @return the {@link AnimationHandler} for this animator. + */ + @NonNull + @VisibleForTesting + public AnimationHandler getAnimationHandler() { + return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance(); + } + + /** + * Returns the {@link FrameCallbackScheduler} used to schedule updates for this animator. + * + * If not already set using {@link #setScheduler(FrameCallbackScheduler)}, this is initialized + * to an {@link FrameCallbackScheduler} that uses the caller thread's {@link Choreographer}. + * Otherwise, return the scheduler set via the previous + * {@link #setScheduler(FrameCallbackScheduler)} call. + * + * @return the {@link FrameCallbackScheduler} for this animator. + */ + @NonNull + public FrameCallbackScheduler getScheduler() { + return mAnimationHandler != null ? mAnimationHandler.getScheduler() + : AnimationHandler.getInstance().getScheduler(); + } + + /** + * Sets the frame scheduler used to schedule updates for this animator. Note this should + * be called before animation running, as it would lead to discontinuity in animations. + * + * @param scheduler The {@link FrameCallbackScheduler} that will be used to schedule updates + * for this animator. + * @throws AndroidRuntimeException if this method called when animation running + */ + public void setScheduler(@NonNull FrameCallbackScheduler scheduler) { + if (mAnimationHandler != null && mAnimationHandler.getScheduler() == scheduler) { + return; + } + if (mRunning) { + throw new AndroidRuntimeException("Animations are still running and the animation" + + "handler should not be set at this timming"); + } + + mAnimationHandler = new AnimationHandler(scheduler); + } + + /****************Sub class animations**************/ + /** + * Returns the acceleration at the given value with the given velocity. + */ + abstract float getAcceleration(float value, float velocity); + + /** + * Returns whether the animation has reached equilibrium. + */ + abstract boolean isAtEquilibrium(float value, float velocity); + + /** + * Updates the default value threshold for the animation based on the property to be animated. + */ + abstract void setValueThreshold(float threshold); + + /** + * An animation listener that receives end notifications from an animation. + */ + public interface OnAnimationEndListener { + /** + * Notifies the end of an animation. Note that this callback will be invoked not only when + * an animation reach equilibrium, but also when the animation is canceled. + * + * @param animation animation that has ended or was canceled + * @param canceled whether the animation has been canceled + * @param value the final value when the animation stopped + * @param velocity the final velocity when the animation stopped + */ + void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity); + } + + /** + * Implementors of this interface can add themselves as update listeners + * to an DynamicAnimation instance to receive callbacks on every animation + * frame, after the current frame's values have been calculated for that + * DynamicAnimation. + */ + public interface OnAnimationUpdateListener { + + /** + * Notifies the occurrence of another frame of the animation. + * + * @param animation animation that the update listener is added to + * @param value the current value of the animation + * @param velocity the current velocity of the animation + */ + void onAnimationUpdate(DynamicAnimation animation, float value, float velocity); + } +} diff --git a/androidx-lib/src/androidx/dynamicanimation/animation/FlingAnimation.java b/androidx-lib/src/androidx/dynamicanimation/animation/FlingAnimation.java new file mode 100644 index 0000000000..e42d06a66d --- /dev/null +++ b/androidx-lib/src/androidx/dynamicanimation/animation/FlingAnimation.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2017 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. + */ + +package androidx.dynamicanimation.animation; + +import androidx.annotation.FloatRange; + +/** + *

Fling animation is an animation that continues an initial momentum (most often from gesture + * velocity) and gradually slows down. The fling animation will come to a stop when the velocity of + * the animation is below the threshold derived from {@link #setMinimumVisibleChange(float)}, + * or when the value of the animation has gone beyond the min or max value defined via + * {@link DynamicAnimation#setMinValue(float)} or {@link DynamicAnimation#setMaxValue(float)}. + * It is recommended to restrict the fling animation with min and/or max value, such that the + * animation can end when it goes beyond screen bounds, thus preserving CPU cycles and resources. + * + *

For example, you can create a fling animation that animates the translationX of a view: + *

+ * FlingAnimation flingAnim = new FlingAnimation(view, DynamicAnimation.TRANSLATION_X)
+ *         // Sets the start velocity to -2000 (pixel/s)
+ *         .setStartVelocity(-2000)
+ *         // Optional but recommended to set a reasonable min and max range for the animation.
+ *         // In this particular case, we set the min and max to -200 and 2000 respectively.
+ *         .setMinValue(-200).setMaxValue(2000);
+ * flingAnim.start();
+ * 
+ */ +public final class FlingAnimation extends DynamicAnimation { + + private final DragForce mFlingForce = new DragForce(); + + /** + *

This creates a FlingAnimation that animates a {@link FloatValueHolder} instance. During + * the animation, the {@link FloatValueHolder} instance will be updated via + * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date + * animation value via {@link FloatValueHolder#getValue()}. + * + *

Note: changing the value in the {@link FloatValueHolder} via + * {@link FloatValueHolder#setValue(float)} outside of the animation during an + * animation run will not have any effect on the on-going animation. + * + * @param floatValueHolder the property to be animated + */ + public FlingAnimation(FloatValueHolder floatValueHolder) { + super(floatValueHolder); + mFlingForce.setValueThreshold(getValueThreshold()); + } + + /** + * This creates a FlingAnimation that animates the property of the given object. + * + * @param object the Object whose property will be animated + * @param property the property to be animated + * @param the class on which the property is declared + */ + public FlingAnimation(K object, FloatPropertyCompat property) { + super(object, property); + mFlingForce.setValueThreshold(getValueThreshold()); + } + + /** + * Sets the friction for the fling animation. The greater the friction is, the sooner the + * animation will slow down. When not set, the friction defaults to 1. + * + * @param friction the friction used in the animation + * @return the animation whose friction will be scaled + * @throws IllegalArgumentException if the input friction is not positive + */ + public FlingAnimation setFriction( + @FloatRange(from = 0.0, fromInclusive = false) float friction) { + if (friction <= 0) { + throw new IllegalArgumentException("Friction must be positive"); + } + mFlingForce.setFrictionScalar(friction); + return this; + } + + /** + * Returns the friction being set on the animation via {@link #setFriction(float)}. If the + * friction has not been set, the default friction of 1 will be returned. + * + * @return friction being used in the animation + */ + public float getFriction() { + return mFlingForce.getFrictionScalar(); + } + + /** + * Sets the min value of the animation. When a fling animation reaches the min value, the + * animation will end immediately. Animations will not animate beyond the min value. + * + * @param minValue minimum value of the property to be animated + * @return the Animation whose min value is being set + */ + @Override + public FlingAnimation setMinValue(float minValue) { + super.setMinValue(minValue); + return this; + } + + /** + * Sets the max value of the animation. When a fling animation reaches the max value, the + * animation will end immediately. Animations will not animate beyond the max value. + * + * @param maxValue maximum value of the property to be animated + * @return the Animation whose max value is being set + */ + @Override + public FlingAnimation setMaxValue(float maxValue) { + super.setMaxValue(maxValue); + return this; + } + + /** + * Start velocity of the animation. Default velocity is 0. Unit: pixel/second + * + *

A non-zero start velocity is required for a FlingAnimation. If no start velocity is + * set through {@link #setStartVelocity(float)}, the start velocity defaults to 0. In that + * case, the fling animation will consider itself done in the next frame. + * + *

Note when using a fixed value as the start velocity (as opposed to getting the velocity + * through touch events), it is recommended to define such a value in dp/second and convert it + * to pixel/second based on the density of the screen to achieve a consistent look across + * different screens. + * + *

To convert from dp/second to pixel/second: + *

+     * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
+     *         getResources().getDisplayMetrics());
+     * 
+ * + * @param startVelocity start velocity of the animation in pixel/second + * @return the Animation whose start velocity is being set + */ + @Override + public FlingAnimation setStartVelocity(float startVelocity) { + super.setStartVelocity(startVelocity); + return this; + } + + @Override + boolean updateValueAndVelocity(long deltaT) { + + MassState state = mFlingForce.updateValueAndVelocity(mValue, mVelocity, deltaT); + mValue = state.mValue; + mVelocity = state.mVelocity; + + // When the animation hits the max/min value, consider animation done. + if (mValue < mMinValue) { + mValue = mMinValue; + return true; + } + if (mValue > mMaxValue) { + mValue = mMaxValue; + return true; + } + + if (isAtEquilibrium(mValue, mVelocity)) { + return true; + } + return false; + } + + @Override + float getAcceleration(float value, float velocity) { + return mFlingForce.getAcceleration(value, velocity); + } + + @Override + boolean isAtEquilibrium(float value, float velocity) { + return value >= mMaxValue + || value <= mMinValue + || mFlingForce.isAtEquilibrium(value, velocity); + } + + @Override + void setValueThreshold(float threshold) { + mFlingForce.setValueThreshold(threshold); + } + + static final class DragForce implements Force { + + private static final float DEFAULT_FRICTION = -4.2f; + + // This multiplier is used to calculate the velocity threshold given a certain value + // threshold. The idea is that if it takes >= 1 frame to move the value threshold amount, + // then the velocity is a reasonable threshold. + private static final float VELOCITY_THRESHOLD_MULTIPLIER = 1000f / 16f; + private float mFriction = DEFAULT_FRICTION; + private float mVelocityThreshold; + + // Internal state to hold a value/velocity pair. + private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState(); + + void setFrictionScalar(float frictionScalar) { + mFriction = frictionScalar * DEFAULT_FRICTION; + } + + float getFrictionScalar() { + return mFriction / DEFAULT_FRICTION; + } + + MassState updateValueAndVelocity(float value, float velocity, long deltaT) { + mMassState.mVelocity = (float) (velocity * Math.exp((deltaT / 1000f) * mFriction)); + mMassState.mValue = (float) (value + (mMassState.mVelocity - velocity) / mFriction); + if (isAtEquilibrium(mMassState.mValue, mMassState.mVelocity)) { + mMassState.mVelocity = 0f; + } + return mMassState; + } + + @Override + public float getAcceleration(float position, float velocity) { + return velocity * mFriction; + } + + @Override + public boolean isAtEquilibrium(float value, float velocity) { + return Math.abs(velocity) < mVelocityThreshold; + } + + void setValueThreshold(float threshold) { + mVelocityThreshold = threshold * VELOCITY_THRESHOLD_MULTIPLIER; + } + } + +} diff --git a/androidx-lib/src/androidx/dynamicanimation/animation/FloatPropertyCompat.java b/androidx-lib/src/androidx/dynamicanimation/animation/FloatPropertyCompat.java new file mode 100644 index 0000000000..adb666676f --- /dev/null +++ b/androidx-lib/src/androidx/dynamicanimation/animation/FloatPropertyCompat.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 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. + */ + +package androidx.dynamicanimation.animation; + +import android.util.FloatProperty; + +import androidx.annotation.RequiresApi; + +/** + *

FloatPropertyCompat is an abstraction that can be used to represent a mutable float value that + * is held in a host object. To access this float value, {@link #setValue(Object, float)} and getter + * {@link #getValue(Object)} need to be implemented. Both the setter and the getter take the + * primitive float type and avoids autoboxing and other overhead associated with the + * Float class. + * + *

For API 24 and later, {@link FloatProperty} instances can be converted to + * {@link FloatPropertyCompat} through + * {@link FloatPropertyCompat#createFloatPropertyCompat(FloatProperty)}. + * + * @param the class on which the Property is declared + */ +public abstract class FloatPropertyCompat { + final String mPropertyName; + + /** + * A constructor that takes an identifying name. + */ + public FloatPropertyCompat(String name) { + mPropertyName = name; + } + + /** + * Create a {@link FloatPropertyCompat} wrapper for a {@link FloatProperty} object. The new + * {@link FloatPropertyCompat} instance will access and modify the property value of + * {@link FloatProperty} through the {@link FloatProperty} instance's setter and getter. + * + * @param property FloatProperty instance to be wrapped + * @param the class on which the Property is declared + * @return a new {@link FloatPropertyCompat} wrapper for the given {@link FloatProperty} object + */ + @RequiresApi(24) + public static FloatPropertyCompat createFloatPropertyCompat( + final FloatProperty property) { + return new FloatPropertyCompat(property.getName()) { + @Override + public float getValue(T object) { + return property.get(object); + } + + @Override + public void setValue(T object, float value) { + property.setValue(object, value); + } + }; + } + + /** + * Returns the current value that this property represents on the given object. + * + * @param object object which this property represents + * @return the current property value of the given object + */ + public abstract float getValue(T object); + + /** + * Sets the value on object which this property represents. + * + * @param object object which this property represents + * @param value new value of the property + */ + public abstract void setValue(T object, float value); +} diff --git a/androidx-lib/src/androidx/dynamicanimation/animation/FloatValueHolder.java b/androidx-lib/src/androidx/dynamicanimation/animation/FloatValueHolder.java new file mode 100644 index 0000000000..bd25f7bcb3 --- /dev/null +++ b/androidx-lib/src/androidx/dynamicanimation/animation/FloatValueHolder.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017 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. + */ + +package androidx.dynamicanimation.animation; + +/** + *

FloatValueHolder holds a float value. FloatValueHolder provides a setter and a getter ( + * i.e. {@link #setValue(float)} and {@link #getValue()}) to access this float value. Animations can + * be performed on a FloatValueHolder instance. During each frame of the animation, the + * FloatValueHolder will have its value updated via {@link #setValue(float)}. The caller can + * obtain the up-to-date animation value via {@link FloatValueHolder#getValue()}. + * + *

Here is an example for creating a {@link FlingAnimation} with a FloatValueHolder: + *

+ * // Create a fling animation with an initial velocity of 5000 (pixel/s) and an initial position
+ * // of 20f.
+ * FloatValueHolder floatValueHolder = new FloatValueHolder(20f);
+ * FlingAnimation anim = new FlingAnimation(floatValueHolder).setStartVelocity(5000);
+ * anim.start();
+ * 
+ * + * @see SpringAnimation#SpringAnimation(FloatValueHolder) + * @see FlingAnimation#FlingAnimation(FloatValueHolder) + */ + +public class FloatValueHolder { + private float mValue = 0.0f; + + /** + * Constructs a holder for a float value that is initialized to 0. + */ + public FloatValueHolder() { + } + + /** + * Constructs a holder for a float value that is initialized to the input value. + * + * @param value the value to initialize the value held in the FloatValueHolder + */ + public FloatValueHolder(float value) { + setValue(value); + } + + /** + * Sets the value held in the FloatValueHolder instance. + * + * @param value float value held in the FloatValueHolder instance + */ + public void setValue(float value) { + mValue = value; + } + + /** + * Returns the float value held in the FloatValueHolder instance. + * + * @return float value held in the FloatValueHolder instance + */ + public float getValue() { + return mValue; + } +} diff --git a/androidx-lib/src/androidx/dynamicanimation/animation/Force.java b/androidx-lib/src/androidx/dynamicanimation/animation/Force.java new file mode 100644 index 0000000000..609d97bf25 --- /dev/null +++ b/androidx-lib/src/androidx/dynamicanimation/animation/Force.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 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. + */ + +package androidx.dynamicanimation.animation; + +/** + * Hide this for now, in case we want to change the API. + */ +interface Force { + // Acceleration based on position. + float getAcceleration(float position, float velocity); + + boolean isAtEquilibrium(float value, float velocity); +} diff --git a/androidx-lib/src/androidx/dynamicanimation/animation/FrameCallbackScheduler.java b/androidx-lib/src/androidx/dynamicanimation/animation/FrameCallbackScheduler.java new file mode 100644 index 0000000000..9d3e165c5a --- /dev/null +++ b/androidx-lib/src/androidx/dynamicanimation/animation/FrameCallbackScheduler.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 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. + */ + +package androidx.dynamicanimation.animation; + + +import androidx.annotation.NonNull; + +/** + * A scheduler that runs the given Runnable on the next frame. + */ +public interface FrameCallbackScheduler { + /** + * Callbacks on new frame arrived. + * + * @param frameCallback The runnable of new frame should be posted + */ + void postFrameCallback(@NonNull Runnable frameCallback); + + /** + * Returns whether the current thread is the same as the thread that the scheduler is + * running on. + * + * @return true if the scheduler is running on the same thread as the current thread. + */ + boolean isCurrentThread(); +} diff --git a/androidx-lib/src/androidx/dynamicanimation/animation/SpringAnimation.java b/androidx-lib/src/androidx/dynamicanimation/animation/SpringAnimation.java new file mode 100644 index 0000000000..75b798f041 --- /dev/null +++ b/androidx-lib/src/androidx/dynamicanimation/animation/SpringAnimation.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2017 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. + */ + +package androidx.dynamicanimation.animation; + +import android.util.AndroidRuntimeException; + +import androidx.annotation.MainThread; + +/** + * SpringAnimation is an animation that is driven by a {@link SpringForce}. The spring force defines + * the spring's stiffness, damping ratio, as well as the rest position. Once the SpringAnimation is + * started, on each frame the spring force will update the animation's value and velocity. + * The animation will continue to run until the spring force reaches equilibrium. If the spring used + * in the animation is undamped, the animation will never reach equilibrium. Instead, it will + * oscillate forever. + * + *
+ *

Developer Guides

+ *
+ * + *

To create a simple {@link SpringAnimation} that uses the default {@link SpringForce}:

+ *
+ * // Create an animation to animate view's X property, set the rest position of the
+ * // default spring to 0, and start the animation with a starting velocity of 5000 (pixel/s).
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0)
+ *         .setStartVelocity(5000);
+ * anim.start();
+ * 
+ * + *

Alternatively, a {@link SpringAnimation} can take a pre-configured {@link SpringForce}, and + * use that to drive the animation.

+ *
+ * // Create a low stiffness, low bounce spring at position 0.
+ * SpringForce spring = new SpringForce(0)
+ *         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ *         .setStiffness(SpringForce.STIFFNESS_LOW);
+ * // Create an animation to animate view's scaleY property, and start the animation using
+ * // the spring above and a starting value of 0.5. Additionally, constrain the range of value for
+ * // the animation to be non-negative, effectively preventing any spring overshoot.
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.SCALE_Y)
+ *         .setMinValue(0).setSpring(spring).setStartValue(1);
+ * anim.start();
+ * 
+ */ +public final class SpringAnimation extends DynamicAnimation { + + private SpringForce mSpring = null; + private float mPendingPosition = UNSET; + private static final float UNSET = Float.MAX_VALUE; + private boolean mEndRequested = false; + + /** + *

This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During + * the animation, the {@link FloatValueHolder} instance will be updated via + * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date + * animation value via {@link FloatValueHolder#getValue()}. + * + *

Note: changing the value in the {@link FloatValueHolder} via + * {@link FloatValueHolder#setValue(float)} outside of the animation during an + * animation run will not have any effect on the on-going animation. + * + * @param floatValueHolder the property to be animated + */ + public SpringAnimation(FloatValueHolder floatValueHolder) { + super(floatValueHolder); + } + + /** + *

This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During + * the animation, the {@link FloatValueHolder} instance will be updated via + * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date + * animation value via {@link FloatValueHolder#getValue()}. + * + * A Spring will be created with the given final position and default stiffness and damping + * ratio. This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}. + * + *

Note: changing the value in the {@link FloatValueHolder} via + * {@link FloatValueHolder#setValue(float)} outside of the animation during an + * animation run will not have any effect on the on-going animation. + * + * @param floatValueHolder the property to be animated + * @param finalPosition the final position of the spring to be created. + */ + public SpringAnimation(FloatValueHolder floatValueHolder, float finalPosition) { + super(floatValueHolder); + mSpring = new SpringForce(finalPosition); + } + + /** + * This creates a SpringAnimation that animates the property of the given object. + * Note, a spring will need to setup through {@link #setSpring(SpringForce)} before + * the animation starts. + * + * @param object the Object whose property will be animated + * @param property the property to be animated + * @param the class on which the Property is declared + */ + public SpringAnimation(K object, FloatPropertyCompat property) { + super(object, property); + } + + /** + * This creates a SpringAnimation that animates the property of the given object. A Spring will + * be created with the given final position and default stiffness and damping ratio. + * This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}. + * + * @param object the Object whose property will be animated + * @param property the property to be animated + * @param finalPosition the final position of the spring to be created. + * @param the class on which the Property is declared + */ + public SpringAnimation(K object, FloatPropertyCompat property, + float finalPosition) { + super(object, property); + mSpring = new SpringForce(finalPosition); + } + + /** + * Returns the spring that the animation uses for animations. + * + * @return the spring that the animation uses for animations + */ + public SpringForce getSpring() { + return mSpring; + } + + /** + * Uses the given spring as the force that drives this animation. If this spring force has its + * parameters re-configured during the animation, the new configuration will be reflected in the + * animation immediately. + * + * @param force a pre-defined spring force that drives the animation + * @return the animation that the spring force is set on + */ + public SpringAnimation setSpring(SpringForce force) { + mSpring = force; + return this; + } + + @MainThread + @Override + public void start() { + sanityCheck(); + mSpring.setValueThreshold(getValueThreshold()); + super.start(); + } + + /** + * Updates the final position of the spring. + *

+ * When the animation is running, calling this method would assume the position change of the + * spring as a continuous movement since last frame, which yields more accurate results than + * changing the spring position directly through {@link SpringForce#setFinalPosition(float)}. + *

+ * If the animation hasn't started, calling this method will change the spring position, and + * immediately start the animation. + * + * @param finalPosition rest position of the spring + */ + public void animateToFinalPosition(float finalPosition) { + if (isRunning()) { + mPendingPosition = finalPosition; + } else { + if (mSpring == null) { + mSpring = new SpringForce(finalPosition); + } + mSpring.setFinalPosition(finalPosition); + start(); + } + } + + /** + * Cancels the on-going animation. If the animation hasn't started, no op. Note that this method + * should only be called on main thread. + * + * @throws AndroidRuntimeException if this method is not called on the main thread + */ + @MainThread + @Override + public void cancel() { + super.cancel(); + if (mPendingPosition != UNSET) { + if (mSpring == null) { + mSpring = new SpringForce(mPendingPosition); + } else { + mSpring.setFinalPosition(mPendingPosition); + } + mPendingPosition = UNSET; + } + } + + /** + * Skips to the end of the animation. If the spring is undamped, an + * {@link IllegalStateException} will be thrown, as the animation would never reach to an end. + * It is recommended to check {@link #canSkipToEnd()} before calling this method. If animation + * is not running, no-op. + * + * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler + * is created on the same thread as the first call to start/cancel an animation. All the + * subsequent animation lifecycle manipulations need to be on that same thread, until the + * AnimationHandler is reset (using [setAnimationHandler]). + * + * @throws IllegalStateException if the spring is undamped (i.e. damping ratio = 0) + * @throws AndroidRuntimeException if this method is not called on the same thread as the + * animation handler + */ + public void skipToEnd() { + if (!canSkipToEnd()) { + throw new UnsupportedOperationException("Spring animations can only come to an end" + + " when there is damping"); + } + if (!getAnimationHandler().isCurrentThread()) { + throw new AndroidRuntimeException("Animations may only be started on the same thread " + + "as the animation handler"); + } + if (mRunning) { + mEndRequested = true; + } + } + + /** + * Queries whether the spring can eventually come to the rest position. + * + * @return {@code true} if the spring is damped, otherwise {@code false} + */ + public boolean canSkipToEnd() { + return mSpring.mDampingRatio > 0; + } + + /************************ Below are private APIs *************************/ + + private void sanityCheck() { + if (mSpring == null) { + throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final" + + " position or a spring force needs to be set."); + } + double finalPosition = mSpring.getFinalPosition(); + if (finalPosition > mMaxValue) { + throw new UnsupportedOperationException("Final position of the spring cannot be greater" + + " than the max value."); + } else if (finalPosition < mMinValue) { + throw new UnsupportedOperationException("Final position of the spring cannot be less" + + " than the min value."); + } + } + + @Override + boolean updateValueAndVelocity(long deltaT) { + // If user had requested end, then update the value and velocity to end state and consider + // animation done. + if (mEndRequested) { + if (mPendingPosition != UNSET) { + mSpring.setFinalPosition(mPendingPosition); + mPendingPosition = UNSET; + } + mValue = mSpring.getFinalPosition(); + mVelocity = 0; + mEndRequested = false; + return true; + } + + if (mPendingPosition != UNSET) { + // Approximate by considering half of the time spring position stayed at the old + // position, half of the time it's at the new position. + MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2); + mSpring.setFinalPosition(mPendingPosition); + mPendingPosition = UNSET; + + massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2); + mValue = massState.mValue; + mVelocity = massState.mVelocity; + + } else { + MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT); + mValue = massState.mValue; + mVelocity = massState.mVelocity; + } + + mValue = Math.max(mValue, mMinValue); + mValue = Math.min(mValue, mMaxValue); + + if (isAtEquilibrium(mValue, mVelocity)) { + mValue = mSpring.getFinalPosition(); + mVelocity = 0f; + return true; + } + return false; + } + + @Override + float getAcceleration(float value, float velocity) { + return mSpring.getAcceleration(value, velocity); + } + + @Override + boolean isAtEquilibrium(float value, float velocity) { + return mSpring.isAtEquilibrium(value, velocity); + } + + @Override + void setValueThreshold(float threshold) { + } +} diff --git a/androidx-lib/src/androidx/dynamicanimation/animation/SpringForce.java b/androidx-lib/src/androidx/dynamicanimation/animation/SpringForce.java new file mode 100644 index 0000000000..fa3f91c853 --- /dev/null +++ b/androidx-lib/src/androidx/dynamicanimation/animation/SpringForce.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2017 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. + */ + +package androidx.dynamicanimation.animation; + +import androidx.annotation.FloatRange; +import androidx.annotation.RestrictTo; + +/** + * Spring Force defines the characteristics of the spring being used in the animation. + *

+ * By configuring the stiffness and damping ratio, callers can create a spring with the look and + * feel suits their use case. Stiffness corresponds to the spring constant. The stiffer the spring + * is, the harder it is to stretch it, the faster it undergoes dampening. + *

+ * Spring damping ratio describes how oscillations in a system decay after a disturbance. + * When damping ratio > 1* (i.e. over-damped), the object will quickly return to the rest position + * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will + * return to equilibrium within the shortest amount of time. When damping ratio is less than 1 + * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without any + * damping (i.e. damping ratio = 0), the mass will oscillate forever. + */ +public final class SpringForce implements Force { + /** + * Stiffness constant for extremely stiff spring. + */ + public static final float STIFFNESS_HIGH = 10_000f; + /** + * Stiffness constant for medium stiff spring. This is the default stiffness for spring force. + */ + public static final float STIFFNESS_MEDIUM = 1500f; + /** + * Stiffness constant for a spring with low stiffness. + */ + public static final float STIFFNESS_LOW = 200f; + /** + * Stiffness constant for a spring with very low stiffness. + */ + public static final float STIFFNESS_VERY_LOW = 50f; + + /** + * Damping ratio for a very bouncy spring. Note for under-damped springs + * (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring. + */ + public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f; + /** + * Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring + * force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio, + * the more bouncy the spring. + */ + public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f; + /** + * Damping ratio for a spring with low bounciness. Note for under-damped springs + * (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness. + */ + public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f; + /** + * Damping ratio for a spring with no bounciness. This damping ratio will create a critically + * damped spring that returns to equilibrium within the shortest amount of time without + * oscillating. + */ + public static final float DAMPING_RATIO_NO_BOUNCY = 1f; + + // This multiplier is used to calculate the velocity threshold given a certain value threshold. + // The idea is that if it takes >= 1 frame to move the value threshold amount, then the velocity + // is a reasonable threshold. + private static final double VELOCITY_THRESHOLD_MULTIPLIER = 1000.0 / 16.0; + + // Natural frequency + double mNaturalFreq = Math.sqrt(STIFFNESS_MEDIUM); + // Damping ratio. + double mDampingRatio = DAMPING_RATIO_MEDIUM_BOUNCY; + + // Value to indicate an unset state. + private static final double UNSET = Double.MAX_VALUE; + + // Indicates whether the spring has been initialized + private boolean mInitialized = false; + + // Threshold for velocity and value to determine when it's reasonable to assume that the spring + // is approximately at rest. + private double mValueThreshold; + private double mVelocityThreshold; + + // Intermediate values to simplify the spring function calculation per frame. + private double mGammaPlus; + private double mGammaMinus; + private double mDampedFreq; + + // Final position of the spring. This must be set before the start of the animation. + private double mFinalPosition = UNSET; + + // Internal state to hold a value/velocity pair. + private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState(); + + /** + * Creates a spring force. Note that final position of the spring must be set through + * {@link #setFinalPosition(float)} before the spring animation starts. + */ + public SpringForce() { + // No op. + } + + /** + * Creates a spring with a given final rest position. + * + * @param finalPosition final position of the spring when it reaches equilibrium + */ + public SpringForce(float finalPosition) { + mFinalPosition = finalPosition; + } + + /** + * Sets the stiffness of a spring. The more stiff a spring is, the more force it applies to + * the object attached when the spring is not at the final position. Default stiffness is + * {@link #STIFFNESS_MEDIUM}. + * + * @param stiffness non-negative stiffness constant of a spring + * @return the spring force that the given stiffness is set on + * @throws IllegalArgumentException if the given spring stiffness is not positive + */ + public SpringForce setStiffness( + @FloatRange(from = 0.0, fromInclusive = false) float stiffness) { + if (stiffness <= 0) { + throw new IllegalArgumentException("Spring stiffness constant must be positive."); + } + mNaturalFreq = Math.sqrt(stiffness); + // All the intermediate values need to be recalculated. + mInitialized = false; + return this; + } + + /** + * Gets the stiffness of the spring. + * + * @return the stiffness of the spring + */ + public float getStiffness() { + return (float) (mNaturalFreq * mNaturalFreq); + } + + /** + * Spring damping ratio describes how oscillations in a system decay after a disturbance. + *

+ * When damping ratio > 1 (over-damped), the object will quickly return to the rest position + * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will + * return to equilibrium within the shortest amount of time. When damping ratio is less than 1 + * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without + * any damping (i.e. damping ratio = 0), the mass will oscillate forever. + *

+ * Default damping ratio is {@link #DAMPING_RATIO_MEDIUM_BOUNCY}. + * + * @param dampingRatio damping ratio of the spring, it should be non-negative + * @return the spring force that the given damping ratio is set on + * @throws IllegalArgumentException if the {@param dampingRatio} is negative. + */ + public SpringForce setDampingRatio(@FloatRange(from = 0.0) float dampingRatio) { + if (dampingRatio < 0) { + throw new IllegalArgumentException("Damping ratio must be non-negative"); + } + mDampingRatio = dampingRatio; + // All the intermediate values need to be recalculated. + mInitialized = false; + return this; + } + + /** + * Returns the damping ratio of the spring. + * + * @return damping ratio of the spring + */ + public float getDampingRatio() { + return (float) mDampingRatio; + } + + /** + * Sets the rest position of the spring. + * + * @param finalPosition rest position of the spring + * @return the spring force that the given final position is set on + */ + public SpringForce setFinalPosition(float finalPosition) { + mFinalPosition = finalPosition; + return this; + } + + /** + * Returns the rest position of the spring. + * + * @return rest position of the spring + */ + public float getFinalPosition() { + return (float) mFinalPosition; + } + + /*********************** Below are private APIs *********************/ + + /** + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @Override + public float getAcceleration(float lastDisplacement, float lastVelocity) { + + lastDisplacement -= getFinalPosition(); + + double k = mNaturalFreq * mNaturalFreq; + double c = 2 * mNaturalFreq * mDampingRatio; + + return (float) (-k * lastDisplacement - c * lastVelocity); + } + + /** + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + @Override + public boolean isAtEquilibrium(float value, float velocity) { + if (Math.abs(velocity) < mVelocityThreshold + && Math.abs(value - getFinalPosition()) < mValueThreshold) { + return true; + } + return false; + } + + /** + * Initialize the string by doing the necessary pre-calculation as well as some sanity check + * on the setup. + * + * @throws IllegalStateException if the final position is not yet set by the time the spring + * animation has started + */ + private void init() { + if (mInitialized) { + return; + } + + if (mFinalPosition == UNSET) { + throw new IllegalStateException("Error: Final position of the spring must be" + + " set before the animation starts"); + } + + if (mDampingRatio > 1) { + // Over damping + mGammaPlus = -mDampingRatio * mNaturalFreq + + mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1); + mGammaMinus = -mDampingRatio * mNaturalFreq + - mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1); + } else if (mDampingRatio >= 0 && mDampingRatio < 1) { + // Under damping + mDampedFreq = mNaturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio); + } + + mInitialized = true; + } + + /** + * Internal only call for Spring to calculate the spring position/velocity using + * an analytical approach. + */ + DynamicAnimation.MassState updateValues(double lastDisplacement, double lastVelocity, + long timeElapsed) { + init(); + + double deltaT = timeElapsed / 1000d; // unit: seconds + lastDisplacement -= mFinalPosition; + double displacement; + double currentVelocity; + if (mDampingRatio > 1) { + // Overdamped + double coeffA = lastDisplacement - (mGammaMinus * lastDisplacement - lastVelocity) + / (mGammaMinus - mGammaPlus); + double coeffB = (mGammaMinus * lastDisplacement - lastVelocity) + / (mGammaMinus - mGammaPlus); + displacement = coeffA * Math.pow(Math.E, mGammaMinus * deltaT) + + coeffB * Math.pow(Math.E, mGammaPlus * deltaT); + currentVelocity = coeffA * mGammaMinus * Math.pow(Math.E, mGammaMinus * deltaT) + + coeffB * mGammaPlus * Math.pow(Math.E, mGammaPlus * deltaT); + } else if (mDampingRatio == 1) { + // Critically damped + double coeffA = lastDisplacement; + double coeffB = lastVelocity + mNaturalFreq * lastDisplacement; + displacement = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT); + currentVelocity = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT) + * -mNaturalFreq + coeffB * Math.pow(Math.E, -mNaturalFreq * deltaT); + } else { + // Underdamped + double cosCoeff = lastDisplacement; + double sinCoeff = (1 / mDampedFreq) * (mDampingRatio * mNaturalFreq + * lastDisplacement + lastVelocity); + displacement = Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT) + * (cosCoeff * Math.cos(mDampedFreq * deltaT) + + sinCoeff * Math.sin(mDampedFreq * deltaT)); + currentVelocity = displacement * -mNaturalFreq * mDampingRatio + + Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT) + * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT) + + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT)); + } + + mMassState.mValue = (float) (displacement + mFinalPosition); + mMassState.mVelocity = (float) currentVelocity; + return mMassState; + } + + /** + * This threshold defines how close the animation value needs to be before the animation can + * finish. This default value is based on the property being animated, e.g. animations on alpha, + * scale, translation or rotation would have different thresholds. This value should be small + * enough to avoid visual glitch of "jumping to the end". But it shouldn't be so small that + * animations take seconds to finish. + * + * @param threshold the difference between the animation value and final spring position that + * is allowed to end the animation when velocity is very low + */ + void setValueThreshold(double threshold) { + mValueThreshold = Math.abs(threshold); + mVelocityThreshold = mValueThreshold * VELOCITY_THRESHOLD_MULTIPLIER; + } +} diff --git a/build.gradle b/build.gradle index 5cb172930f..656379d2c0 100644 --- a/build.gradle +++ b/build.gradle @@ -7,11 +7,11 @@ plugins { id 'com.android.library' version "8.7.0" apply false id 'com.android.test' version '8.7.0' apply false id 'androidx.baselineprofile' version '1.3.2' + id 'org.jetbrains.kotlin.kapt' version "1.9.20" apply false id 'org.jetbrains.kotlin.android' version "2.0.20" id 'org.jetbrains.kotlin.plugin.compose' version "2.0.20" id 'org.jetbrains.kotlin.plugin.parcelize' version "2.0.20" id 'org.jetbrains.kotlin.plugin.serialization' version "2.0.20" - id "com.google.devtools.ksp" version "2.0.20-1.0.25" id 'com.google.protobuf' version "0.9.4" id 'app.cash.licensee' version "1.11.0" id 'dev.rikka.tools.refine' version "4.4.0" @@ -19,6 +19,9 @@ plugins { id 'com.diffplug.spotless' version '6.25.0' } +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + allprojects { plugins.withType(AndroidBasePlugin).configureEach { apply plugin: 'org.gradle.android.cache-fix' @@ -359,7 +362,7 @@ dependencies { implementation 'androidx.profileinstaller:profileinstaller:1.4.1' baselineProfile projects.baselineProfile - implementation "androidx.dynamicanimation:dynamicanimation:1.0.0" + implementation projects.androidxLib implementation "androidx.recyclerview:recyclerview:1.3.2" implementation "androidx.preference:preference-ktx:1.2.1" @@ -405,9 +408,14 @@ dependencies { implementation "com.squareup.retrofit2:converter-kotlinx-serialization:$retrofitVersion" def roomVersion = '2.6.1' + + implementation "androidx.room:room-runtime:$roomVersion" + annotationProcessor "androidx.room:room-compiler:$roomVersion" + implementation "androidx.room:room-runtime:$roomVersion" implementation "androidx.room:room-ktx:$roomVersion" - ksp "androidx.room:room-compiler:$roomVersion" + //noinspection KaptUsageInsteadOfKsp + kapt "androidx.room:room-compiler:$roomVersion" def core_version = "1.13.1" implementation "androidx.core:core:$core_version" @@ -430,12 +438,15 @@ dependencies { implementation("com.github.android:renderscript-intrinsics-replacement-toolkit:b6363490c3") } -ksp { - arg("room.schemaLocation", "$projectDir/schemas") - arg("room.generateKotlin", "true") - arg("room.incremental", "true") +kapt { + arguments { + arg("room.schemaLocation", "$projectDir/schemas") + arg("room.generateKotlin", "true") + arg("room.incremental", "true") + } } + spotless { java { target("compatLib/**/src/**/*.java") diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index d5792e7001..c4ec714aa5 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -22,8 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_OPEN; @@ -43,7 +41,6 @@ import static com.android.launcher3.BaseActivity.INVISIBLE_ALL; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS; import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION; -import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; import static com.android.launcher3.LauncherState.ALL_APPS; @@ -56,18 +53,15 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAU import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; -import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE; import static com.android.launcher3.util.DisplayController.isTransientTaskbar; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR; import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import static com.android.launcher3.views.FloatingIconView.getFloatingIconView; import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; -import static com.android.quickstep.util.AnimUtils.clampToDuration; -import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback; +import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME; +import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME_FALLBACK; import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; @@ -80,8 +74,8 @@ import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; @@ -92,12 +86,10 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.IBinder; -import android.os.IRemoteCallback; import android.os.Looper; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; -import android.provider.Settings.Global; import android.util.Pair; import android.util.Size; import android.view.CrossWindowBlurListeners; @@ -120,12 +112,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; -import com.android.internal.jank.Cuj; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorListeners; -import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.model.data.ItemInfo; @@ -150,7 +140,6 @@ import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig; import com.android.quickstep.util.RectFSpringAnim.TaskbarHotseatSpringConfig; -import com.android.quickstep.util.ScalingWorkspaceRevealAnim; import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.util.SurfaceTransaction; import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties; @@ -159,18 +148,17 @@ import com.android.quickstep.util.TaskRestartedDuringLaunchListener; import com.android.quickstep.util.WorkspaceRevealAnim; import com.android.quickstep.views.FloatingWidgetView; import com.android.quickstep.views.RecentsView; -import com.android.systemui.animation.ActivityTransitionAnimator; -import com.android.systemui.animation.DelegateTransitionAnimatorController; +import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.DelegateLaunchAnimatorController; import com.android.systemui.animation.LaunchableView; import com.android.systemui.animation.RemoteAnimationDelegate; -import com.android.systemui.animation.RemoteAnimationRunnerCompat; import com.android.systemui.shared.system.BlurUtils; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; import com.android.wm.shell.startingsurface.IStartingWindowListener; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -223,8 +211,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener // TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with // unlock animation // is solved. - private static final int TASKBAR_TO_HOME_DURATION_FAST = 300; - private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000; + public static final int TASKBAR_TO_HOME_DURATION = 300; protected static final int CONTENT_SCALE_DURATION = 350; protected static final int CONTENT_SCRIM_DURATION = 350; @@ -241,8 +228,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener private final float mClosingWindowTransY; private final float mMaxShadowRadius; - private final StartingWindowListener mStartingWindowListener = new StartingWindowListener(this); - private ContentObserver mAnimationRemovalObserver=new ContentObserver(ORDERED_BG_EXECUTOR.getHandler()){@Override public void onChange(boolean selfChange){mAreAnimationsEnabled=Global.getFloat(mLauncher.getContentResolver(),Global.ANIMATOR_DURATION_SCALE,1f)>0||Global.getFloat(mLauncher.getContentResolver(),Global.TRANSITION_ANIMATION_SCALE,1f)>0;}}; + private final StartingWindowListener mStartingWindowListener = new StartingWindowListener(); private DeviceProfile mDeviceProfile; @@ -255,18 +241,23 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener private RemoteAnimationFactory mWallpaperOpenTransitionRunner; private RemoteTransition mLauncherOpenTransition; - private final RemoteAnimationCoordinateTransfer mCoordinateTransfer; - private LauncherBackAnimationController mBackAnimationController; - private final AnimatorListenerAdapter mForceInvisibleListener=new AnimatorListenerAdapter(){@Override public void onAnimationStart(Animator animation){mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS);} + private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); + } - @Override public void onAnimationEnd(Animator animation){mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS);}}; + @Override + public void onAnimationEnd(Animator animation) { + mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); + } + }; // Pairs of window starting type and starting window background color for // starting tasks // Will never be larger than MAX_NUM_TASKS private LinkedHashMap> mTaskStartParams; - private boolean mAreAnimationsEnabled = true; private final Interpolator mOpeningXInterpolator; private final Interpolator mOpeningInterpolator; @@ -278,16 +269,14 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mDeviceProfile = mLauncher.getDeviceProfile(); mBackAnimationController = LawnchairApp.isAtleastT() ? new LauncherBackAnimationController(mLauncher, this) : null; - checkAndMonitorIfAnimationsAreEnabled(); - Resources res = mLauncher.getResources(); mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y); mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius); mLauncher.addOnDeviceProfileChangeListener(this); - if (ENABLE_SHELL_STARTING_SURFACE) { - mTaskStartParams = new LinkedHashMap<>(MAX_NUM_TASKS) { + if (supportsSSplashScreen()) { + mTaskStartParams = new LinkedHashMap>(MAX_NUM_TASKS) { @Override protected boolean removeEldestEntry(Entry> entry) { return size() > MAX_NUM_TASKS; @@ -304,7 +293,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x); mOpeningInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.emphasized_interpolator); - mCoordinateTransfer = new RemoteAnimationCoordinateTransfer(mLauncher); } @Override @@ -321,17 +309,17 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener boolean fromRecents = isLaunchingFromRecents(v, null /* targets */); RunnableList onEndCallback = new RunnableList(); - // Handle the case where an already visible task is launched which results in no - // transition - TaskRestartedDuringLaunchListener restartedListener = new TaskRestartedDuringLaunchListener(); + // Handle the case where an already visible task is launched which results in no transition + TaskRestartedDuringLaunchListener restartedListener = + new TaskRestartedDuringLaunchListener(); restartedListener.register(onEndCallback::executeAllAndDestroy); onEndCallback.add(restartedListener::unregister); mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback); ItemInfo tag = (ItemInfo) v.getTag(); if (tag != null && tag.shouldUseBackgroundAnimation()) { - ContainerAnimationRunner containerAnimationRunner = ContainerAnimationRunner.from( - v, mLauncher, mStartingWindowListener, onEndCallback); + ContainerAnimationRunner containerAnimationRunner = ContainerAnimationRunner.from(v, + mStartingWindowListener, onEndCallback); if (containerAnimationRunner != null) { mAppLaunchRunner = containerAnimationRunner; } @@ -372,8 +360,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * not */ protected boolean isLaunchingFromRecents(@NonNull View v, - @Nullable RemoteAnimationTarget[] targets) { - return mLauncher.getStateManager().getState().isRecentsViewVisible + @Nullable RemoteAnimationTarget[] targets) { + return mLauncher.getStateManager().getState().overviewUi && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null; } @@ -386,9 +374,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * @param launcherClosing true if the launcher app is closing */ protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, - @NonNull RemoteAnimationTarget[] appTargets, - @NonNull RemoteAnimationTarget[] wallpaperTargets, - @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) { + @NonNull RemoteAnimationTarget[] appTargets, + @NonNull RemoteAnimationTarget[] wallpaperTargets, + @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) { TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets, nonAppTargets, launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(), mLauncher.getDepthController()); @@ -416,10 +404,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * @param launcherClosing true if launcher is closing */ private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, - @NonNull RemoteAnimationTarget[] appTargets, - @NonNull RemoteAnimationTarget[] wallpaperTargets, - @NonNull RemoteAnimationTarget[] nonAppTargets, - boolean launcherClosing) { + @NonNull RemoteAnimationTarget[] appTargets, + @NonNull RemoteAnimationTarget[] wallpaperTargets, + @NonNull RemoteAnimationTarget[] nonAppTargets, + boolean launcherClosing) { // Set the state animation first so that any state listeners are called // before our internal listeners. mLauncher.getStateManager().setCurrentAnimation(anim); @@ -463,7 +451,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * figure out where the floating view should animate to. */ private Rect getWindowTargetBounds(@NonNull RemoteAnimationTarget[] appTargets, - int rotationChange) { + int rotationChange) { RemoteAnimationTarget target = null; for (RemoteAnimationTarget t : appTargets) { if (t.mode != MODE_OPENING) @@ -512,7 +500,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * @param skipAllAppsScale True if we want to avoid scaling All Apps */ private Pair getLauncherContentAnimator(boolean isAppOpening, - int startDelay, boolean skipAllAppsScale) { + int startDelay, boolean skipAllAppsScale) { AnimatorSet launcherAnimator = new AnimatorSet(); Runnable endListener; @@ -644,7 +632,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * @return listener to run when the animation ends */ protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, - float[] alphas, float[] scales) { + float[] alphas, float[] scales) { RecentsView overview = mLauncher.getOverviewPanel(); ObjectAnimator alpha = ObjectAnimator.ofFloat(overview, RecentsView.CONTENT_ALPHA, alphas); @@ -671,10 +659,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * icons. */ private Animator getOpeningWindowAnimators(View v, - RemoteAnimationTarget[] appTargets, - RemoteAnimationTarget[] wallpaperTargets, - RemoteAnimationTarget[] nonAppTargets, - boolean launcherClosing) { + RemoteAnimationTarget[] appTargets, + RemoteAnimationTarget[] wallpaperTargets, + RemoteAnimationTarget[] nonAppTargets, + boolean launcherClosing) { int rotationChange = getRotationChange(appTargets); Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange); boolean appTargetsAreTranslucent = areAllTargetsTranslucent(appTargets); @@ -699,7 +687,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mDragLayer.getLocationOnScreen(dragLayerBounds); final boolean hasSplashScreen; - if (ENABLE_SHELL_STARTING_SURFACE) { + if (supportsSSplashScreen()) { int taskId = openingTargets.getFirstAppTargetTaskId(); Pair defaultParams = Pair.create(STARTING_WINDOW_TYPE_NONE, 0); Pair taskParams = mTaskStartParams.getOrDefault(taskId, defaultParams); @@ -763,35 +751,34 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius; MultiValueUpdateListener listener = new MultiValueUpdateListener() { - FloatProp mDx = new FloatProp(0, prop.dX, mOpeningXInterpolator); - FloatProp mDy = new FloatProp(0, prop.dY, mOpeningInterpolator); + FloatProp mDx = new FloatProp(0, prop.dX, 0, APP_LAUNCH_DURATION, + mOpeningXInterpolator); + FloatProp mDy = new FloatProp(0, prop.dY, 0, APP_LAUNCH_DURATION, + mOpeningInterpolator); FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale, - prop.finalAppIconScale, mOpeningInterpolator); + prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, mOpeningInterpolator); FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f, - clampToDuration(LINEAR, APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, - APP_LAUNCH_DURATION)); + APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, LINEAR); - FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, - mOpeningInterpolator); - FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, - mOpeningInterpolator); + FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0, + APP_LAUNCH_DURATION, mOpeningInterpolator); + FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0, + APP_LAUNCH_DURATION, mOpeningInterpolator); FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd, - mOpeningInterpolator); + 0, APP_LAUNCH_DURATION, mOpeningInterpolator); FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd, - mOpeningInterpolator); - FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, - mOpeningInterpolator); - FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, - mOpeningInterpolator); + 0, APP_LAUNCH_DURATION, mOpeningInterpolator); + FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0, + APP_LAUNCH_DURATION, mOpeningInterpolator); + FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0, + APP_LAUNCH_DURATION, mOpeningInterpolator); - FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration( - NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION, - APP_LAUNCH_DURATION)); - FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration( - NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN, - ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION)); + FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION, + NAV_FADE_OUT_INTERPOLATOR); + FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, + ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); @Override public void onUpdate(float percent, boolean initOnly) { @@ -882,8 +869,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } else { tmpPos.set(target.position.x, target.position.y); } - final Rect crop = new Rect( - Utilities.ATLEAST_R ? target.screenSpaceBounds : target.sourceContainerBounds); + final Rect crop = new Rect(Utilities.ATLEAST_R ? target.screenSpaceBounds : target.sourceContainerBounds); crop.offsetTo(0, 0); if ((rotationChange % 2) == 1) { @@ -933,9 +919,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } private Animator getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v, - RemoteAnimationTarget[] appTargets, - RemoteAnimationTarget[] wallpaperTargets, - RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) { + RemoteAnimationTarget[] appTargets, + RemoteAnimationTarget[] wallpaperTargets, + RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) { Rect windowTargetBounds = getWindowTargetBounds(appTargets, getRotationChange(appTargets)); boolean appTargetsAreTranslucent = areAllTargetsTranslucent(appTargets); @@ -947,7 +933,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener RemoteAnimationTarget openingTarget = openingTargets.getFirstAppTarget(); int fallbackBackgroundColor = 0; - if (openingTarget != null && ENABLE_SHELL_STARTING_SURFACE) { + if (openingTarget != null && supportsSSplashScreen()) { fallbackBackgroundColor = mTaskStartParams.containsKey(openingTarget.taskId) ? mTaskStartParams.get(openingTarget.taskId).second : 0; @@ -988,36 +974,37 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener appAnimator.addUpdateListener(new MultiValueUpdateListener() { float mAppWindowScale = 1; - final FloatProp mWidgetForegroundAlpha = new FloatProp(1, 0, clampToDuration( - LINEAR, 0, WIDGET_CROSSFADE_DURATION_MILLIS / 2, APP_LAUNCH_DURATION)); - - final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0, 1, - clampToDuration(LINEAR, 0, 75, APP_LAUNCH_DURATION)); - final FloatProp mPreviewAlpha = new FloatProp(0, 1, clampToDuration( - LINEAR, + final FloatProp mWidgetForegroundAlpha = new FloatProp(1 /* start */, + 0 /* end */, 0 /* delay */, + WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR); + final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0 /* start */, + 1 /* end */, 0 /* delay */, 75 /* duration */, LINEAR); + final FloatProp mPreviewAlpha = new FloatProp(0 /* start */, 1 /* end */, WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */, - WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, - APP_LAUNCH_DURATION)); + WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR); final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, + 0 /* start */, APP_LAUNCH_DURATION, mOpeningInterpolator); + final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, APP_LAUNCH_DURATION, mOpeningInterpolator); - final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, mOpeningInterpolator); // Window & widget background positioning bounds final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(), - windowTargetBounds.centerX(), mOpeningXInterpolator); + windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_DURATION, + mOpeningXInterpolator); final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(), - windowTargetBounds.centerY(), mOpeningInterpolator); + windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION, + mOpeningInterpolator); final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(), - windowTargetBounds.width(), mOpeningInterpolator); + windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION, + mOpeningInterpolator); final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(), - windowTargetBounds.height(), mOpeningInterpolator); + windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION, + mOpeningInterpolator); - final FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration( - NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION, - APP_LAUNCH_DURATION)); - final FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration( - NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN, - ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION)); + final FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION, + NAV_FADE_OUT_INTERPOLATOR); + final FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, + ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); @Override public void onUpdate(float percent, boolean initOnly) { @@ -1087,47 +1074,45 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener LaunchDepthController depthController = new LaunchDepthController(mLauncher); ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController.stateDepth, - MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher)) + MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher)) .setDuration(APP_LAUNCH_DURATION); - // caching - // optimizations on the SurfaceFlinger side: - // - There won't be texture allocation overhead, because EffectLayers don't have - // buffers - ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); - SurfaceControl parent = viewRootImpl != null - ? viewRootImpl.getSurfaceControl() - : null; - SurfaceControl dimLayer = new SurfaceControl.Builder() - .setName("Blur layer") - .setParent(parent) - .setOpaque(false) - .setHidden(false) - .setEffectLayer() - .build(); + if (allowBlurringLauncher) { + // Create a temporary effect layer, that lives on top of launcher, so we can + // apply + // the blur to it. The EffectLayer will be fullscreen, which will help with + // caching + // optimizations on the SurfaceFlinger side: + // - Results would be able to be cached as a texture + // - There won't be texture allocation overhead, because EffectLayers don't have + // buffers + ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); + SurfaceControl parent = viewRootImpl != null + ? viewRootImpl.getSurfaceControl() + : null; + SurfaceControl dimLayer = new SurfaceControl.Builder() + .setName("Blur layer") + .setParent(parent) + .setOpaque(false) + .setHidden(false) + .setEffectLayer() + .build(); + + backgroundRadiusAnim.addListener( + AnimatorListeners.forEndCallback(() -> new SurfaceControl.Transaction().remove(dimLayer).apply())); + } backgroundRadiusAnim.addListener( - AnimatorListeners.forEndCallback(() -> new SurfaceControl.Transaction().remove(dimLayer).apply())); - } + AnimatorListeners.forEndCallback(depthController::dispose)); - backgroundRadiusAnim.addListener(AnimatorListeners.forEndCallback(()-> - - { - // reset the depth to match the main depth controller's depth - depthController.stateDepth - .setValue(mLauncher.getDepthController().stateDepth.getValue()); - depthController.dispose(); - })); - - return backgroundRadiusAnim; + return backgroundRadiusAnim; } /** * Registers remote animations used when closing apps to home screen. */ public void registerRemoteAnimations() { - if (SEPARATE_RECENTS_ACTIVITY.get() || !Utilities.ATLEAST_S) - return; + if (SEPARATE_RECENTS_ACTIVITY.get() || !Utilities.ATLEAST_S) return; if (hasControlRemoteAppTransitionPermission()) { RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); addRemoteAnimations(definition); @@ -1165,8 +1150,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * Registers remote animations used when closing apps to home screen. */ public void registerRemoteTransitions() { - if (SEPARATE_RECENTS_ACTIVITY.get() || !Utilities.ATLEAST_T) - return; + if (SEPARATE_RECENTS_ACTIVITY.get() || !Utilities.ATLEAST_T) return; if (ENABLE_SHELL_TRANSITIONS && LawnchairQuickstepCompat.ATLEAST_U) SystemUiProxy.INSTANCE.get(mLauncher).shareTransactionQueue(); @@ -1192,7 +1176,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener .registerRemoteTransition(mLauncherOpenTransition, homeCheck); } if (mBackAnimationController != null) { - mBackAnimationController.registerComponentCallbacks(); mBackAnimationController.registerBackCallbacks(mHandler); } } @@ -1201,10 +1184,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener unregisterRemoteAnimations(); unregisterRemoteTransitions(); if (Utilities.ATLEAST_T) { - mLauncher.removeOnDeviceProfileChangeListener(this); + mStartingWindowListener.setTransitionManager(null); SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null); - ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver() - .unregisterContentObserver(mAnimationRemovalObserver)); } } @@ -1232,38 +1213,27 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener if (SEPARATE_RECENTS_ACTIVITY.get()) { return; } - if (mLauncherOpenTransition == null) - return; - SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition( - mLauncherOpenTransition); - mLauncherOpenTransition = null; - mWallpaperOpenTransitionRunner = null; + if (hasControlRemoteAppTransitionPermission()) { + if (mLauncherOpenTransition == null) return; + SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition( + mLauncherOpenTransition); + mLauncherOpenTransition = null; + mWallpaperOpenTransitionRunner = null; + } if (mBackAnimationController != null) { mBackAnimationController.unregisterBackCallbacks(); - mBackAnimationController.unregisterComponentCallbacks(); mBackAnimationController = null; } } - private void checkAndMonitorIfAnimationsAreEnabled() { - ORDERED_BG_EXECUTOR.execute(() -> { - mAnimationRemovalObserver.onChange(true); - mLauncher.getContentResolver().registerContentObserver(Global.getUriFor( - Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver); - mLauncher.getContentResolver().registerContentObserver(Global.getUriFor( - Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver); - - }); - } - private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) { for (RemoteAnimationTarget target : targets) { if (!Utilities.ATLEAST_S) { return target.mode == mode && target.taskId == mLauncher.getTaskId(); } if (target.mode == mode && target.taskInfo != null - // Compare component name instead of task-id because transitions will promote - // the target up to the root task while getTaskId returns the leaf. + // Compare component name instead of task-id because transitions will promote + // the target up to the root task while getTaskId returns the leaf. && target.taskInfo.topActivity != null && target.taskInfo.topActivity.equals(mLauncher.getComponentName())) { return true; @@ -1277,7 +1247,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener for (RemoteAnimationTarget target : targets) { if (target.mode == MODE_CLOSING) { numTargets++; - if (numTargets > 1 || target.windowConfiguration.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) { + if (numTargets > 1 || target.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_MULTI_WINDOW) { return true; } } @@ -1298,7 +1269,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * device. */ private Animator getUnlockWindowAnimator(RemoteAnimationTarget[] appTargets, - RemoteAnimationTarget[] wallpaperTargets) { + RemoteAnimationTarget[] wallpaperTargets) { SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1); unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS); @@ -1311,8 +1282,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener RemoteAnimationTarget target = appTargets[i]; transaction.forSurface(target.leash) .setAlpha(1f) - .setWindowCrop( - Utilities.ATLEAST_R ? target.screenSpaceBounds : target.sourceContainerBounds) + .setWindowCrop(Utilities.ATLEAST_R ? target.screenSpaceBounds : target.sourceContainerBounds) .setCornerRadius(cornerRadius); } surfaceApplier.scheduleApply(transaction); @@ -1339,8 +1309,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * app targets */ private @Nullable View findLauncherView(RemoteAnimationTarget[] appTargets) { - if (!Utilities.ATLEAST_S) - return null; + if (!Utilities.ATLEAST_S) return null; for (RemoteAnimationTarget appTarget : appTargets) { if (appTarget.mode == MODE_CLOSING) { View launcherView = findLauncherView(appTarget); @@ -1418,8 +1387,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener * workspace. */ protected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation, - RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS, - RectF closingWindowStartRectF, float startWindowCornerRadius) { + RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS, + RectF closingWindowStartRect, float startWindowCornerRadius) { FloatingIconView floatingIconView = null; FloatingWidgetView floatingWidget = null; RectF targetRect = new RectF(); @@ -1444,7 +1413,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener (LauncherAppWidgetHostView) launcherView, targetRect, windowSize, mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher), isTransluscent, fallbackBackgroundColor); - } else if (launcherView != null && mAreAnimationsEnabled) { + } else if (launcherView != null) { floatingIconView = getFloatingIconView(mLauncher, launcherView, null, mLauncher.getTaskbarUIController() == null ? null @@ -1458,16 +1427,13 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener boolean useTaskbarHotseatParams = mDeviceProfile.isTaskbarPresent && isInHotseat; RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams - ? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRectF, targetRect) - : new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRectF, - targetRect)); + ? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRect, targetRect) + : new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRect, + targetRect)); // Hook up floating views to the closing window animators. - // note the coordinate of closingWindowStartRect is based on launcher - Rect closingWindowStartRect = new Rect(); - closingWindowStartRectF.round(closingWindowStartRect); - Rect closingWindowOriginalRect = - new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx); + final int rotationChange = getRotationChange(targets); + Rect windowTargetBounds = getWindowTargetBounds(targets, rotationChange); if (floatingIconView != null) { anim.addAnimatorListener(floatingIconView); floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged); @@ -1479,7 +1445,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION; RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect, - closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) { + windowTargetBounds, startWindowCornerRadius) { @Override public void onUpdate(RectF currentRectF, float progress) { finalFloatingIconView.update(1f, currentRectF, progress, windowAlphaThreshold, @@ -1497,9 +1463,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener final float floatingWidgetAlpha = isTransluscent ? 0 : 1; FloatingWidgetView finalFloatingWidget = floatingWidget; RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect, - closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) { - - @Override + windowTargetBounds, startWindowCornerRadius) { + @Override public void onUpdate(RectF currentRectF, float progress) { final float fallbackBackgroundAlpha = 1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE); @@ -1515,8 +1480,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener // If no floating icon or widget is present, animate the to the default window // target rect. anim.addOnUpdateListener(new SpringAnimRunner( - targets, targetRect, closingWindowStartRect, closingWindowOriginalRect, - startWindowCornerRadius)); + targets, targetRect, windowTargetBounds, startWindowCornerRadius)); } // Use a fixed velocity to start the animation. @@ -1546,10 +1510,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius; closingAnimator.setDuration(duration); closingAnimator.addUpdateListener(new MultiValueUpdateListener() { - FloatProp mDy = new FloatProp(0, mClosingWindowTransY, DECELERATE_1_7); - FloatProp mScale = new FloatProp(1f, 1f, DECELERATE_1_7); - FloatProp mAlpha = new FloatProp(1f, 0f, clampToDuration(LINEAR, 25, 125, duration)); - FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, DECELERATE_1_7); + FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DECELERATE_1_7); + FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DECELERATE_1_7); + FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR); + FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration, + DECELERATE_1_7); @Override public void onUpdate(float percent, boolean initOnly) { @@ -1674,21 +1639,16 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener || mLauncher.getWorkspace().isOverlayShown() || shouldPlayFallbackClosingAnimation(appTargets); - boolean playWorkspaceReveal = !fromPredictiveBack; + boolean playWorkspaceReveal = true; boolean skipAllAppsScale = false; if (fromUnlock) { anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets)); } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get() && !playFallBackAnimation) { - PointF velocity; - if (enableScalingRevealHomeAnimation()) { - velocity = new PointF(); - } else { - // Use a fixed velocity to start the animation. - float velocityPxPerS = DynamicResource.provider(mLauncher) - .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s); - velocity = new PointF(0, -velocityPxPerS); - } + // Use a fixed velocity to start the animation. + float velocityPxPerS = DynamicResource.provider(mLauncher) + .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s); + PointF velocity = new PointF(0, -velocityPxPerS); rectFSpringAnim = getClosingWindowAnimators( anim, appTargets, launcherView, velocity, startRect, startWindowCornerRadius); @@ -1697,15 +1657,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener // layout bounds. skipAllAppsScale = true; } else if (!fromPredictiveBack) { - if (enableScalingRevealHomeAnimation()) { - anim.play( - new ScalingWorkspaceRevealAnim( - mLauncher, rectFSpringAnim, - rectFSpringAnim.getTargetRect()).getAnimators()); - } else { - anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y, - true /* animateOverviewScrim */, launcherView).getAnimators()); - } + anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y, + true /* animateOverviewScrim */, launcherView).getAnimators()); if (!areAllTargetsTranslucent(appTargets)) { anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth, @@ -1728,30 +1681,14 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener // targets list because it is already visible). In that case, we force // invisibility on touch down, and only reset it after the animation to home // is initialized. - if (launcherIsForceInvisibleOrOpening || fromPredictiveBack) { + if (launcherIsForceInvisibleOrOpening) { addCujInstrumentation(anim, playFallBackAnimation - ? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK - : Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME); - - AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - AccessibilityManagerCompat.sendTestProtocolEventToTest( - mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE); - } - }; - - if (fromPredictiveBack && rectFSpringAnim != null) { - rectFSpringAnim.addAnimatorListener(endListener); - } else { - anim.addListener(endListener); - } - + ? CUJ_APP_CLOSE_TO_HOME_FALLBACK + : CUJ_APP_CLOSE_TO_HOME); // Only register the content animation for cancellation when state changes mLauncher.getStateManager().setCurrentAnimation(anim); - if (mLauncher.isInState(LauncherState.ALL_APPS) && !fromPredictiveBack) { + if (mLauncher.isInState(LauncherState.ALL_APPS)) { Pair contentAnimator = getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY, skipAllAppsScale); @@ -1762,22 +1699,16 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener contentAnimator.second.run(); } }); - } else if (playWorkspaceReveal) { - anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators()); + } else { + if (playWorkspaceReveal) { + anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators()); + } } } return new Pair(rectFSpringAnim, anim); } - public static int getTaskbarToHomeDuration() { - if (enableScalingRevealHomeAnimation()) { - return TASKBAR_TO_HOME_DURATION_SLOW; - } else { - return TASKBAR_TO_HOME_DURATION_FAST; - } - } - /** * Remote animation runner for animation from the app to Launcher, including * recents. @@ -1792,10 +1723,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener @Override public void onAnimationStart(int transit, - RemoteAnimationTarget[] appTargets, - RemoteAnimationTarget[] wallpaperTargets, - RemoteAnimationTarget[] nonAppTargets, - LauncherAnimationRunner.AnimationResult result) { + RemoteAnimationTarget[] appTargets, + RemoteAnimationTarget[] wallpaperTargets, + RemoteAnimationTarget[] nonAppTargets, + LauncherAnimationRunner.AnimationResult result) { if (mLauncher.isDestroyed()) { AnimatorSet anim = new AnimatorSet(); anim.play(getFallbackClosingWindowAnimators(appTargets)); @@ -1809,18 +1740,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } RectF windowTargetBounds = new RectF(getWindowTargetBounds(appTargets, getRotationChange(appTargets))); - - final RectF resolveRectF = new RectF(windowTargetBounds); - for (RemoteAnimationTarget t : appTargets) { - if (t.mode == MODE_CLOSING) { - transferRectToTargetCoordinate( - t, windowTargetBounds, true, resolveRectF); - break; - } - } - Pair pair = createWallpaperOpenAnimations( - appTargets, wallpaperTargets, mFromUnlock, resolveRectF, + appTargets, wallpaperTargets, mFromUnlock, windowTargetBounds, QuickStepContract.getWindowCornerRadius(mLauncher), false /* fromPredictiveBack */); @@ -1845,10 +1766,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener @Override public void onAnimationStart(int transit, - RemoteAnimationTarget[] appTargets, - RemoteAnimationTarget[] wallpaperTargets, - RemoteAnimationTarget[] nonAppTargets, - LauncherAnimationRunner.AnimationResult result) { + RemoteAnimationTarget[] appTargets, + RemoteAnimationTarget[] wallpaperTargets, + RemoteAnimationTarget[] nonAppTargets, + LauncherAnimationRunner.AnimationResult result) { AnimatorSet anim = new AnimatorSet(); boolean launcherClosing = launcherIsATargetWithMode(appTargets, MODE_CLOSING); @@ -1858,18 +1779,19 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener if (launchingFromWidget) { composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); - addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET); + addCujInstrumentation( + anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_WIDGET); skipFirstFrame = true; } else if (launchingFromRecents) { composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); addCujInstrumentation( - anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS); + anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS); skipFirstFrame = true; } else { composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); - addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON); + addCujInstrumentation(anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON); skipFirstFrame = false; } @@ -1901,18 +1823,18 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } @Nullable - private static ContainerAnimationRunner from(View v, Launcher launcher, - StartingWindowListener startingWindowListener, RunnableList onEndCallback) { + private static ContainerAnimationRunner from( + View v, StartingWindowListener startingWindowListener, RunnableList onEndCallback) { View viewToUse = findLaunchableViewWithBackground(v); if (viewToUse == null) { - return null; + viewToUse = v; } // The CUJ is logged by the click handler, so we don't log it inside the // animation // library. - ActivityTransitionAnimator.Controller controllerDelegate = ActivityTransitionAnimator.Controller - .fromView(viewToUse, null /* cujType */); + ActivityLaunchAnimator.Controller controllerDelegate = ActivityLaunchAnimator.Controller.fromView(viewToUse, + null /* cujType */); if (controllerDelegate == null) { return null; @@ -1921,31 +1843,25 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener // This wrapper allows us to override the default value, telling the controller // that the // current window is below the animating window. - ActivityTransitionAnimator.Controller controller = new DelegateTransitionAnimatorController( - controllerDelegate) { + ActivityLaunchAnimator.Controller controller = new DelegateLaunchAnimatorController(controllerDelegate) { @Override public boolean isBelowAnimatingWindow() { return true; } }; - ActivityTransitionAnimator.Callback callback = task -> { - final int backgroundColor = startingWindowListener.mBackgroundColor == Color.TRANSPARENT - ? launcher.getScrimView().getBackgroundColor() - : startingWindowListener.mBackgroundColor; - return ColorUtils.setAlphaComponent(backgroundColor, 255); - }; + ActivityLaunchAnimator.Callback callback = task -> ColorUtils.setAlphaComponent( + startingWindowListener.getBackgroundColor(), 255); - ActivityTransitionAnimator.Listener listener = new ActivityTransitionAnimator.Listener() { + ActivityLaunchAnimator.Listener listener = new ActivityLaunchAnimator.Listener() { @Override - public void onTransitionAnimationEnd() { + public void onLaunchAnimationEnd() { onEndCallback.executeAllAndDestroy(); } }; return new ContainerAnimationRunner( - new ActivityTransitionAnimator.AnimationDelegate( - MAIN_EXECUTOR, controller, callback, listener)); + new ActivityLaunchAnimator.AnimationDelegate(controller, callback, listener)); } /** @@ -1970,8 +1886,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener @Override public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets, - RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, - LauncherAnimationRunner.AnimationResult result) { + RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, + LauncherAnimationRunner.AnimationResult result) { mDelegate.onAnimationStart( transit, appTargets, wallpaperTargets, nonAppTargets, result); } @@ -2006,8 +1922,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener public final float iconAlphaStart; AnimOpenProperties(Resources r, DeviceProfile dp, Rect windowTargetBounds, - RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop, - boolean hasSplashScreen, boolean hasDifferentAppIcon) { + RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop, + boolean hasSplashScreen, boolean hasDifferentAppIcon) { // Scale the app icon to take up the entire screen. This simplifies the math // when // animating the app window position / scale. @@ -2052,70 +1968,24 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } } - private static class StartingWindowListener extends IStartingWindowListener.Stub { - private final WeakReference mTransitionManagerRef; + private class StartingWindowListener extends IStartingWindowListener.Stub { + private QuickstepTransitionManager mTransitionManager; private int mBackgroundColor; - private StartingWindowListener(QuickstepTransitionManager transitionManager) { - mTransitionManagerRef = new WeakReference<>(transitionManager); + public void setTransitionManager(QuickstepTransitionManager transitionManager) { + mTransitionManager = transitionManager; } @Override public void onTaskLaunching(int taskId, int supportedType, int color) { - QuickstepTransitionManager transitionManager = mTransitionManagerRef.get(); - if (transitionManager != null) { - transitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color)); - } + mTransitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color)); mBackgroundColor = color; } - } - /** - * Transfer the rectangle to another coordinate if needed. - * - * @param toLauncher which one is the anchor of this transfer, if true then - * transfer from - * animation target to launcher, false transfer from launcher - * to animation - * target. - */ - public void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect, - boolean toLauncher, RectF resultRect) { - mCoordinateTransfer.transferRectToTargetCoordinate( - target, currentRect, toLauncher, resultRect); - } - - private static class RemoteAnimationCoordinateTransfer { - private final QuickstepLauncher mLauncher; - private final Rect mDisplayRect = new Rect(); - private final Rect mTmpResult = new Rect(); - - RemoteAnimationCoordinateTransfer(QuickstepLauncher launcher) { - mLauncher = launcher; - } - - void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect, - boolean toLauncher, RectF resultRect) { - final int taskRotation = target.windowConfiguration.getRotation(); - final DeviceProfile profile = mLauncher.getDeviceProfile(); - - final int rotationDelta = toLauncher - ? android.util.RotationUtils.deltaRotation(taskRotation, profile.rotationHint) - : android.util.RotationUtils.deltaRotation(profile.rotationHint, taskRotation); - if (rotationDelta != ROTATION_0) { - // Get original display size when task is on top but with different rotation - if (rotationDelta % 2 != 0 && toLauncher && (profile.rotationHint == ROTATION_0 - || profile.rotationHint == ROTATION_180)) { - mDisplayRect.set(0, 0, profile.heightPx, profile.widthPx); - } else { - mDisplayRect.set(0, 0, profile.widthPx, profile.heightPx); - } - currentRect.round(mTmpResult); - android.util.RotationUtils.rotateBounds(mTmpResult, mDisplayRect, rotationDelta); - resultRect.set(mTmpResult); - } else { - resultRect.set(currentRect); - } + public int getBackgroundColor() { + return mBackgroundColor == Color.TRANSPARENT + ? mLauncher.getScrimView().getBackgroundColor() + : mBackgroundColor; } } @@ -2126,58 +1996,21 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener private final RemoteAnimationTarget[] mAppTargets; private final Matrix mMatrix = new Matrix(); private final Point mTmpPos = new Point(); - private final RectF mCurrentRectF = new RectF(); + private final Rect mCurrentRect = new Rect(); private final float mStartRadius; private final float mEndRadius; private final SurfaceTransactionApplier mSurfaceApplier; - private final Rect mWindowStartBounds = new Rect(); - private final Rect mWindowOriginalBounds = new Rect(); + private final Rect mWindowTargetBounds = new Rect(); private final Rect mTmpRect = new Rect(); - /** - * Constructor for SpringAnimRunner - * - * @param appTargets the list of opening/closing apps - * @param targetRect target rectangle - * @param closingWindowStartRect start position of the window when the spring - * animation - * is started. In the predictive back to home - * case this - * will be smaller than - * closingWindowOriginalRect because - * the window is already scaled by the user - * gesture - * @param closingWindowOriginalRect Original unscaled window rect - * @param startWindowCornerRadius corner radius of window at the start - * position - */ SpringAnimRunner(RemoteAnimationTarget[] appTargets, RectF targetRect, - Rect closingWindowStartRect, Rect closingWindowOriginalRect, - float startWindowCornerRadius) { + Rect windowTargetBounds, float startWindowCornerRadius) { mAppTargets = appTargets; mStartRadius = startWindowCornerRadius; mEndRadius = Math.max(1, targetRect.width()) / 2f; mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer); - mWindowStartBounds.set(closingWindowStartRect); - mWindowOriginalBounds.set(closingWindowOriginalRect); - - // transfer the coordinate based on animation target. - if (mAppTargets != null) { - for (RemoteAnimationTarget t : mAppTargets) { - if (t.mode == MODE_CLOSING) { - final RectF transferRect = new RectF(mWindowStartBounds); - final RectF result = new RectF(); - transferRectToTargetCoordinate(t, transferRect, false, result); - result.round(mWindowStartBounds); - - transferRect.set(closingWindowOriginalRect); - transferRectToTargetCoordinate(t, transferRect, false, result); - result.round(mWindowOriginalBounds); - break; - } - } - } + mWindowTargetBounds.set(windowTargetBounds); } public float getCornerRadius(float progress) { @@ -2198,32 +2031,31 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } if (target.mode == MODE_CLOSING) { - transferRectToTargetCoordinate(target, currentRectF, false, mCurrentRectF); + currentRectF.round(mCurrentRect); // Scale the target window to match the currentRectF. final float scale; // We need to infer the crop (we crop the window to match the currentRectF). - if (mWindowStartBounds.height() > mWindowStartBounds.width()) { - scale = Math.min(1f, mCurrentRectF.width() / mWindowOriginalBounds.width()); + if (mWindowTargetBounds.height() > mWindowTargetBounds.width()) { + scale = Math.min(1f, currentRectF.width() / mWindowTargetBounds.width()); - int unscaledHeight = (int) (mCurrentRectF.height() * (1f / scale)); - int croppedHeight = mWindowStartBounds.height() - unscaledHeight; - mTmpRect.set(0, 0, mWindowOriginalBounds.width(), - mWindowStartBounds.height() - croppedHeight); + int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale)); + int croppedHeight = mWindowTargetBounds.height() - unscaledHeight; + mTmpRect.set(0, 0, mWindowTargetBounds.width(), + mWindowTargetBounds.height() - croppedHeight); } else { - scale = Math.min(1f, mCurrentRectF.height() - / mWindowOriginalBounds.height()); + scale = Math.min(1f, currentRectF.height() / mWindowTargetBounds.height()); - int unscaledWidth = (int) (mCurrentRectF.width() * (1f / scale)); - int croppedWidth = mWindowStartBounds.width() - unscaledWidth; - mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth, - mWindowOriginalBounds.height()); + int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale)); + int croppedWidth = mWindowTargetBounds.width() - unscaledWidth; + mTmpRect.set(0, 0, mWindowTargetBounds.width() - croppedWidth, + mWindowTargetBounds.height()); } // Match size and position of currentRect. mMatrix.setScale(scale, scale); - mMatrix.postTranslate(mCurrentRectF.left, mCurrentRectF.top); + mMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top); builder.setMatrix(mMatrix) .setWindowCrop(mTmpRect) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java index bb6468c1c5..cb9f24ae80 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -61,8 +61,7 @@ import java.util.HashMap; import java.util.StringJoiner; /** - * Track LauncherState, RecentsAnimation, resumed state for task bar in one - * place here and animate + * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate * the task bar accordingly. */ public class TaskbarLauncherStateController { @@ -74,14 +73,12 @@ public class TaskbarLauncherStateController { public static final int FLAG_VISIBLE = 1 << 0; /** - * A external transition / animation is running that will result in FLAG_VISIBLE - * being set. + * A external transition / animation is running that will result in FLAG_VISIBLE being set. **/ public static final int FLAG_TRANSITION_TO_VISIBLE = 1 << 1; /** - * Set while the launcher state machine is performing a state transition, see - * {@link + * Set while the launcher state machine is performing a state transition, see {@link * StateManager.StateListener}. */ public static final int FLAG_LAUNCHER_IN_STATE_TRANSITION = 1 << 2; @@ -94,14 +91,11 @@ public class TaskbarLauncherStateController { private static final int FLAG_AWAKE = 1 << 3; /** - * Captures whether the launcher was active at the time the FLAG_AWAKE was - * cleared. + * Captures whether the launcher was active at the time the FLAG_AWAKE was cleared. * Always cleared when FLAG_AWAKE is set. *

- * FLAG_RESUMED will be cleared when the device is asleep, since all apps get - * paused at this - * point. Thus, this flag indicates whether the launcher will be shown when the - * device wakes up + * FLAG_RESUMED will be cleared when the device is asleep, since all apps get paused at this + * point. Thus, this flag indicates whether the launcher will be shown when the device wakes up * again. */ private static final int FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE = 1 << 4; @@ -109,23 +103,18 @@ public class TaskbarLauncherStateController { /** * Whether the device is currently locked. *

    - *
  • While locked, the taskbar is always stashed. - *
  • - *
  • Navbar animations on FLAG_DEVICE_LOCKED transitions will get special - * treatment.
  • + *
  • While locked, the taskbar is always stashed.
  • + *
  • Navbar animations on FLAG_DEVICE_LOCKED transitions will get special treatment.
  • *
*/ private static final int FLAG_DEVICE_LOCKED = 1 << 5; /** - * Whether the complete taskbar is completely hidden (neither visible stashed or - * unstashed). - * This is tracked to allow a nice transition of the taskbar before SysUI forces - * it away by + * Whether the complete taskbar is completely hidden (neither visible stashed or unstashed). + * This is tracked to allow a nice transition of the taskbar before SysUI forces it away by * hiding the inset. * - * This flag is predominanlty set while FLAG_DEVICE_LOCKED is set, thus the - * taskbar's invisible + * This flag is predominanlty set while FLAG_DEVICE_LOCKED is set, thus the taskbar's invisible * resting state while hidden is stashed. */ private static final int FLAG_TASKBAR_HIDDEN = 1 << 6; @@ -141,15 +130,14 @@ public class TaskbarLauncherStateController { /** * Delay for the taskbar fade-in. * - * Helps to avoid visual noise when unlocking successfully via SFPS, and the - * device transitions - * to launcher directly. The delay avoids the navbar to become briefly visible. - * The duration + * Helps to avoid visual noise when unlocking successfully via SFPS, and the device transitions + * to launcher directly. The delay avoids the navbar to become briefly visible. The duration * is the same as in SysUI, see http://shortn/_uNSbDoRUSr. */ private static final long TASKBAR_SHOW_DELAY_MS = 250; - private final AnimatedFloat mIconAlignment = new AnimatedFloat(this::onIconAlignmentRatioChanged); + private final AnimatedFloat mIconAlignment = + new AnimatedFloat(this::onIconAlignmentRatioChanged); private TaskbarControllers mControllers; private AnimatedFloat mTaskbarBackgroundAlpha; @@ -163,8 +151,7 @@ public class TaskbarLauncherStateController { private LauncherState mLauncherState = LauncherState.NORMAL; private boolean mSkipNextRecentsAnimEnd; - // Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime - // (milliseconds). + // Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds). private long mLastUnlockTimeMs = 0; private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener; @@ -178,12 +165,23 @@ public class TaskbarLauncherStateController { private boolean mIsQsbInline; - private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener=new DeviceProfile.OnDeviceProfileChangeListener(){@Override public void onDeviceProfileChanged(DeviceProfile dp){if(mIsQsbInline&&!dp.isQsbInline){ - // We only modify QSB alpha if isQsbInline = true. If we switch to a DP - // where isQsbInline = false, then we need to reset the alpha. - mLauncher.getHotseat().setQsbAlpha(1f);}mIsQsbInline=dp.isQsbInline;TaskbarLauncherStateController.this.updateIconAlphaForHome(mIconAlphaForHome.getValue());}}; + private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = + new DeviceProfile.OnDeviceProfileChangeListener() { + @Override + public void onDeviceProfileChanged(DeviceProfile dp) { + if (mIsQsbInline && !dp.isQsbInline) { + // We only modify QSB alpha if isQsbInline = true. If we switch to a DP + // where isQsbInline = false, then we need to reset the alpha. + mLauncher.getHotseat().setQsbAlpha(1f); + } + mIsQsbInline = dp.isQsbInline; + TaskbarLauncherStateController.this.updateIconAlphaForHome( + mIconAlphaForHome.getValue()); + } + }; - private final StateManager.StateListener mStateListener = new StateManager.StateListener() { + private final StateManager.StateListener mStateListener = + new StateManager.StateListener() { @Override public void onStateTransitionStart(LauncherState toState) { @@ -203,23 +201,18 @@ public class TaskbarLauncherStateController { } } } - } - } - @Override - public void onStateTransitionComplete(LauncherState finalState) { - mLauncherState = finalState; - updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false); - applyState(); - updateOverviewDragState(finalState); - } - - }; + @Override + public void onStateTransitionComplete(LauncherState finalState) { + mLauncherState = finalState; + updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false); + applyState(); + updateOverviewDragState(finalState); + } + }; /** - * Callback for when launcher state transition completes after user swipes to - * home. - * + * Callback for when launcher state transition completes after user swipes to home. * @param finalState The final state of the transition. */ public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) { @@ -232,10 +225,7 @@ public class TaskbarLauncherStateController { } } - /** - * Initializes the controller instance, and applies the initial state - * immediately. - */ + /** Initializes the controller instance, and applies the initial state immediately. */ public void init(TaskbarControllers controllers, QuickstepLauncher launcher, @SystemUiStateFlags long sysuiStateFlags) { mCanSyncViews = false; @@ -256,7 +246,7 @@ public class TaskbarLauncherStateController { mLauncher.getStateManager().addStateListener(mStateListener); mLauncherState = launcher.getStateManager().getState(); - updateStateForSysuiFlags(sysuiStateFlags, /* applyState */ false); + updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false); applyState(0); @@ -280,8 +270,7 @@ public class TaskbarLauncherStateController { /** * Creates a transition animation to the launcher activity. * - * Warning: the resulting animation must be played, since this method has side - * effects on this + * Warning: the resulting animation must be played, since this method has side effects on this * controller's state. */ public Animator createAnimToLauncher(@NonNull LauncherState toState, @@ -290,8 +279,7 @@ public class TaskbarLauncherStateController { // If going home, align the icons to hotseat AnimatorSet animatorSet = new AnimatorSet(); - // Update stashed flags first to ensure goingToUnstashedLauncherState() returns - // correctly. + // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly. TaskbarStashController stashController = mControllers.taskbarStashController; stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, toState.isTaskbarStashed(mLauncher)); @@ -306,12 +294,12 @@ public class TaskbarLauncherStateController { if (mTaskBarRecentsAnimationListener != null) { mTaskBarRecentsAnimationListener.endGestureStateOverride( - !mLauncher.isInState(LauncherState.OVERVIEW), false /* canceled */); + !mLauncher.isInState(LauncherState.OVERVIEW), false /*canceled*/); } mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks); callbacks.addListener(mTaskBarRecentsAnimationListener); - ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener( - () -> mTaskBarRecentsAnimationListener.endGestureStateOverride(true, false /* canceled */)); + ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() -> + mTaskBarRecentsAnimationListener.endGestureStateOverride(true, false /*canceled*/)); ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchCancelledRunnable(() -> { updateStateForUserFinishedToApp(false /* finishedToApp */); @@ -325,8 +313,7 @@ public class TaskbarLauncherStateController { public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) { if (!shouldDelayLauncherStateAnim && mShouldDelayLauncherStateAnim) { - // Animate the animation we have delayed immediately. This is usually triggered - // when + // Animate the animation we have delayed immediately. This is usually triggered when // the user has released their finger. applyState(); } @@ -358,14 +345,10 @@ public class TaskbarLauncherStateController { updateStateForFlag(FLAG_DEVICE_LOCKED, SystemUiFlagUtils.isLocked(systemUiStateFlags)); - // Taskbar is hidden whenever the device is dreaming. The dreaming state - // includes the - // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING - // only kicks in - // when the device is asleep, the second condition extends ensures that the - // transition from - // and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the - // taskbar + // Taskbar is hidden whenever the device is dreaming. The dreaming state includes the + // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in + // when the device is asleep, the second condition extends ensures that the transition from + // and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar // hide/reveal animation timings. boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING) || (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE; @@ -377,15 +360,15 @@ public class TaskbarLauncherStateController { } /** - * Updates overview drag state on various controllers based on - * {@link #mLauncherState}. + * Updates overview drag state on various controllers based on {@link #mLauncherState}. * * @param launcherState The current state launcher is in */ private void updateOverviewDragState(LauncherState launcherState) { - boolean disallowLongClick = FeatureFlags.enableSplitContextually() - ? mLauncher.isSplitSelectionActive() - : launcherState == LauncherState.OVERVIEW_SPLIT_SELECT; + boolean disallowLongClick = + FeatureFlags.enableSplitContextually() + ? mLauncher.isSplitSelectionActive() + : launcherState == LauncherState.OVERVIEW_SPLIT_SELECT; com.android.launcher3.taskbar.Utilities.setOverviewDragState( mControllers, launcherState.disallowTaskbarGlobalDrag(), disallowLongClick, launcherState.allowTaskbarInitialSplitSelection()); @@ -394,8 +377,7 @@ public class TaskbarLauncherStateController { /** * Updates the proper flag to change the state of the task bar. * - * Note that this only updates the flag. {@link #applyState()} needs to be - * called separately. + * Note that this only updates the flag. {@link #applyState()} needs to be called separately. * * @param flag The flag to update. * @param enabled Whether to enable the flag @@ -489,8 +471,7 @@ public class TaskbarLauncherStateController { handleOpenFloatingViews = true; } if (mLauncherState == LauncherState.OVERVIEW) { - // Calling to update the insets in - // TaskbarInsetController#updateInsetsTouchability + // Calling to update the insets in TaskbarInsetController#updateInsetsTouchability mControllers.taskbarActivityContext.notifyUpdateLayoutParams(); } } @@ -501,7 +482,8 @@ public class TaskbarLauncherStateController { public void onAnimationStart(Animator animation) { mIsAnimatingToLauncher = isInLauncher; - TaskbarStashController stashController = mControllers.taskbarStashController; + TaskbarStashController stashController = + mControllers.taskbarStashController; if (DEBUG) { Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher); } @@ -538,83 +520,124 @@ public class TaskbarLauncherStateController { // Stash the transient taskbar once the taskbar is not visible. This reduces // visual noise when unlocking the device afterwards. animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + TaskbarStashController stashController = + mControllers.taskbarStashController; + stashController.updateAndAnimateTransientTaskbar( + /* stash */ true, /* bubblesShouldFollow */ true); + } + }); + } else { + // delay the fade in animation a bit to reduce visual noise when waking up a device + // with a fingerprint reader. This should only be done when the device was woken + // up via fingerprint reader, however since this information is currently not + // available, opting to always delay the fade-in a bit. + long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs; + taskbarVisibility.setStartDelay( + Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs)); + } + animatorSet.play(taskbarVisibility); + } - @Override - public void onAnimationEnd(Animator animation) { - TaskbarStashController stashController = mControllers.taskbarStashController; - stashController.updateAndAnimateTransientTaskbar( - /* stash */ true, /* bubblesShouldFollow */ true); - }});}else{ + float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1; - // delay the fade in animation a bit to reduce visual noise when waking up a - // device - // with a fingerprint reader. This should only be done when the device was woken - // up via fingerprint reader, however since this information is currently not - // available, opting to always delay the fade-in a bit. - long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - - mLastUnlockTimeMs;taskbarVisibility.setStartDelay(Math.max(0,TASKBAR_SHOW_DELAY_MS-durationSinceLastUnlockMs));}animatorSet.play(taskbarVisibility);} + // Don't animate if background has reached desired value. + if (mTaskbarBackgroundAlpha.isAnimating() + || mTaskbarBackgroundAlpha.value != backgroundAlpha) { + mTaskbarBackgroundAlpha.cancelAnimation(); + if (DEBUG) { + Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - " + + mTaskbarBackgroundAlpha.value + + " -> " + backgroundAlpha + ": " + duration); + } - float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1; + boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat; + boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat; + boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat; - // Don't animate if background has reached desired value. - if(mTaskbarBackgroundAlpha.isAnimating()||mTaskbarBackgroundAlpha.value!=backgroundAlpha){mTaskbarBackgroundAlpha.cancelAnimation();if(DEBUG){Log.d(TAG,"onStateChangeApplied - taskbarBackgroundAlpha - "+mTaskbarBackgroundAlpha.value+" -> "+backgroundAlpha+": "+duration);} + float startDelay = 0; + // We want to delay the background from fading in so that the icons have time to move + // into the bounds of the background before it appears. + if (isInLauncherIconNotAligned) { + startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT; + } else if (notInLauncherIconNotAligned) { + startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT; + } + float newDuration = duration - startDelay; + if (isInLauncherIconIsAligned) { + // Make the background fade out faster so that it is gone by the time the + // icons move outside of the bounds of the background. + newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT; + } + Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha + .animateToValue(backgroundAlpha) + .setDuration((long) newDuration); + taskbarBackgroundAlpha.setStartDelay((long) startDelay); + animatorSet.play(taskbarBackgroundAlpha); + } - boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat; - boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat; - boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat; + float cornerRoundness = isInLauncher ? 0 : 1; - float startDelay = 0; - // We want to delay the background from fading in so that the icons have time to - // move - // into the bounds of the background before it appears. - if(isInLauncherIconNotAligned){startDelay=duration*TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT;}else if(notInLauncherIconNotAligned){startDelay=duration*TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT;} - float newDuration = duration - startDelay;if(isInLauncherIconIsAligned){ - // Make the background fade out faster so that it is gone by the time the - // icons move outside of the bounds of the background. - newDuration=duration*TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT;} - Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha - .animateToValue(backgroundAlpha) - .setDuration( - (long) newDuration);taskbarBackgroundAlpha.setStartDelay((long)startDelay);animatorSet.play(taskbarBackgroundAlpha);} + // Don't animate if corner roundness has reached desired value. + if (mTaskbarCornerRoundness.isAnimating() + || mTaskbarCornerRoundness.value != cornerRoundness) { + mTaskbarCornerRoundness.cancelAnimation(); + if (DEBUG) { + Log.d(TAG, "onStateChangeApplied - taskbarCornerRoundness - " + + mTaskbarCornerRoundness.value + + " -> " + cornerRoundness + ": " + duration); + } + animatorSet.play(mTaskbarCornerRoundness.animateToValue(cornerRoundness)); + } - float cornerRoundness = isInLauncher ? 0 : 1; + // Keep isUnlockTransition in sync with its counterpart in + // TaskbarStashController#createAnimToIsStashed. + boolean isUnlockTransition = + hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED); + if (isUnlockTransition) { + // When transitioning to unlocked, ensure the hotseat is fully visible from the + // beginning. The hotseat itself is animated by LauncherUnlockAnimationController. + mIconAlignment.cancelAnimation(); + // updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual + // change in value + mIconAlignment.updateValue(toAlignment); - // Don't animate if corner roundness has reached desired value. - if(mTaskbarCornerRoundness.isAnimating()||mTaskbarCornerRoundness.value!=cornerRoundness){mTaskbarCornerRoundness.cancelAnimation();if(DEBUG){Log.d(TAG,"onStateChangeApplied - taskbarCornerRoundness - "+mTaskbarCornerRoundness.value+" -> "+cornerRoundness+": "+duration);}animatorSet.play(mTaskbarCornerRoundness.animateToValue(cornerRoundness));} + // Make sure FLAG_IN_APP is set when launching applications from keyguard. + if (!isInLauncher) { + mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true); + mControllers.taskbarStashController.applyState(0); + } + } else if (mIconAlignment.isAnimatingToValue(toAlignment) + || mIconAlignment.isSettledOnValue(toAlignment)) { + // Already at desired value, but make sure we run the callback at the end. + animatorSet.addListener(AnimatorListeners.forEndCallback( + this::onIconAlignmentRatioChanged)); + } else { + mIconAlignment.cancelAnimation(); + ObjectAnimator iconAlignAnim = mIconAlignment + .animateToValue(toAlignment) + .setDuration(duration); + if (DEBUG) { + Log.d(TAG, "onStateChangeApplied - iconAlignment - " + + mIconAlignment.value + + " -> " + toAlignment + ": " + duration); + } + animatorSet.play(iconAlignAnim); + } - // Keep isUnlockTransition in sync with its counterpart in - // TaskbarStashController#createAnimToIsStashed. - boolean isUnlockTransition = hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) - && !hasAnyFlag(FLAG_DEVICE_LOCKED);if(isUnlockTransition){ - // When transitioning to unlocked, ensure the hotseat is fully visible from the - // beginning. The hotseat itself is animated by - // LauncherUnlockAnimationController. - mIconAlignment.cancelAnimation(); - // updateValue ensures onIconAlignmentRatioChanged will be called if there is an - // actual - // change in value - mIconAlignment.updateValue(toAlignment); + animatorSet.setInterpolator(EMPHASIZED); - // Make sure FLAG_IN_APP is set when launching applications from keyguard. - if(!isInLauncher){mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP,true);mControllers.taskbarStashController.applyState(0);}}else if(mIconAlignment.isAnimatingToValue(toAlignment)||mIconAlignment.isSettledOnValue(toAlignment)){ - // Already at desired value, but make sure we run the callback at the end. - animatorSet.addListener(AnimatorListeners.forEndCallback(this::onIconAlignmentRatioChanged));}else{mIconAlignment.cancelAnimation(); - ObjectAnimator iconAlignAnim = mIconAlignment - .animateToValue(toAlignment) - .setDuration( - duration);if(DEBUG){Log.d(TAG,"onStateChangeApplied - iconAlignment - "+mIconAlignment.value+" -> "+toAlignment+": "+duration); - }animatorSet.play(iconAlignAnim);} - - animatorSet.setInterpolator(EMPHASIZED); - - if(start){animatorSet.start();}return animatorSet;} + if (start) { + animatorSet.start(); + } + return animatorSet; + } /** - * Whether the taskbar is aligned with the hotseat in the current/target - * launcher state. + * Whether the taskbar is aligned with the hotseat in the current/target launcher state. * - * This refers to the intended state - a transition to this state might be in - * progress. + * This refers to the intended state - a transition to this state might be in progress. */ public boolean isTaskbarAlignedWithHotseat() { return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher); @@ -628,7 +651,8 @@ public class TaskbarLauncherStateController { boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher); boolean willStashVisually = isInStashedState && mControllers.taskbarStashController.supportsVisualStashing(); - boolean isTaskbarAlignedWithHotseat = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher); + boolean isTaskbarAlignedWithHotseat = + mLauncherState.isTaskbarAlignedWithHotseat(mLauncher); return isTaskbarAlignedWithHotseat && !willStashVisually; } else { return false; @@ -673,12 +697,9 @@ public class TaskbarLauncherStateController { animatorSet.play(stashAnimator); } - // Translate back to 0 at a shorter or same duration as the icon alignment - // animation. - // This ensures there is no jump after switching to hotseat, e.g. when swiping - // up from - // overview to home. When not in app, we do duration / 2 just to make it feel - // snappier. + // Translate back to 0 at a shorter or same duration as the icon alignment animation. + // This ensures there is no jump after switching to hotseat, e.g. when swiping up from + // overview to home. When not in app, we do duration / 2 just to make it feel snappier. long resetDuration = mControllers.taskbarStashController.isInApp() ? duration : duration / 2; @@ -735,7 +756,7 @@ public class TaskbarLauncherStateController { boolean hotseatVisible = alpha == 0 || mControllers.taskbarActivityContext.isPhoneMode() || (!mControllers.uiController.isHotseatIconOnTopWhenAligned() - && mIconAlignment.value > 0); + && mIconAlignment.value > 0); /* * Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets * should not be visible at the same time. @@ -757,26 +778,23 @@ public class TaskbarLauncherStateController { @Override public void onRecentsAnimationCanceled(HashMap thumbnailDatas) { boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW); - endGestureStateOverride(!isInOverview, true /* canceled */); + endGestureStateOverride(!isInOverview, true /*canceled*/); } @Override public void onRecentsAnimationFinished(RecentsAnimationController controller) { - endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /* canceled */); + endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /*canceled*/); } /** * Handles whatever cleanup is needed after the recents animation is completed. - * NOTE: If {@link #mSkipNextRecentsAnimEnd} is set and we're coming from a - * non-cancelled + * NOTE: If {@link #mSkipNextRecentsAnimEnd} is set and we're coming from a non-cancelled * path, this will not call {@link #updateStateForUserFinishedToApp(boolean)} * - * @param finishedToApp {@code true} if the recents animation finished to - * showing an app and + * @param finishedToApp {@code true} if the recents animation finished to showing an app and * not workspace or overview - * @param canceled {@code true} if the recents animation was canceled - * instead of finishing - * to completion + * @param canceled {@code true} if the recents animation was canceled instead of finishing + * to completion */ private void endGestureStateOverride(boolean finishedToApp, boolean canceled) { mCallbacks.removeListener(this); @@ -793,7 +811,6 @@ public class TaskbarLauncherStateController { /** * Updates the visible state immediately to ensure a seamless handoff. - * * @param finishedToApp True iff user is in an app. */ private void updateStateForUserFinishedToApp(boolean finishedToApp) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java index 69a7d96f08..2730be1b57 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java @@ -31,7 +31,6 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; @@ -62,13 +61,13 @@ import java.util.stream.Collectors; import java.util.stream.Stream; /** - * Implements interfaces required to show and allow interacting with a - * PopupContainerWithArrow. + * Implements interfaces required to show and allow interacting with a PopupContainerWithArrow. * Controls the long-press menu on Taskbar and AllApps icons. */ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskbarController { - private static final SystemShortcut.Factory APP_INFO = SystemShortcut.AppInfo::new; + private static final SystemShortcut.Factory + APP_INFO = SystemShortcut.AppInfo::new; private final TaskbarActivityContext mContext; private final PopupDataProvider mPopupDataProvider; @@ -162,12 +161,7 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba .filter(Objects::nonNull) .collect(Collectors.toList()); - if (FeatureFlags.showMaterialUPopup(icon.getContext())) { - container = (PopupContainerWithArrow) context.getLayoutInflater().inflate( - R.layout.popup_container_material_u, context.getDragLayer(), false); - container.populateAndShowRowsMaterialU(icon, deepShortcutCount, systemShortcuts); - } else { - container = (PopupContainerWithArrow) context.getLayoutInflater().inflate( + container = (PopupContainerWithArrow) context.getLayoutInflater().inflate( R.layout.popup_container, context.getDragLayer(), false); container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts); @@ -187,11 +181,11 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba // Create a Stream of all applicable system shortcuts private Stream getSystemShortcuts() { - // append split options to APP_INFO shortcut, the order here will reflect in the - // popup + // append split options to APP_INFO shortcut, the order here will reflect in the popup return Stream.concat( Stream.of(APP_INFO), - mControllers.uiController.getSplitMenuOptions()); + mControllers.uiController.getSplitMenuOptions() + ); } @Override @@ -206,8 +200,7 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba protected final Point mIconLastTouchPos = new Point(); - TaskbarPopupItemDragHandler() { - } + TaskbarPopupItemDragHandler() {} @Override public boolean onTouch(View view, MotionEvent ev) { @@ -225,8 +218,7 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba @Override public boolean onLongClick(View v) { // Return early if not the correct view - if (!(v.getParent() instanceof DeepShortcutView)) - return false; + if (!(v.getParent() instanceof DeepShortcutView)) return false; DeepShortcutView sv = (DeepShortcutView) v.getParent(); sv.setWillDrawIcon(false); @@ -244,12 +236,9 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba } /** - * Creates a factory function representing a single "split position" menu item - * ("Split left," + * Creates a factory function representing a single "split position" menu item ("Split left," * "Split right," or "Split top"). - * - * @param position A SplitPositionOption representing whether we are splitting - * top, left, or + * @param position A SplitPositionOption representing whether we are splitting top, left, or * right. * @return A factory function to be used in populating the long-press menu. */ @@ -259,47 +248,43 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba originalView, position, mAllowInitialSplitSelection); } - /** - * A single menu item ("Split left," "Split right," or "Split top") that - * executes a split + /** + * A single menu item ("Split left," "Split right," or "Split top") that executes a split * from the taskbar, as if the user performed a drag and drop split. * Includes an onClick method that initiates the actual split. */ private static class TaskbarSplitShortcut extends - SplitShortcut { - /** - * If {@code true}, clicking this shortcut will not attempt to start a split app - * directly, - * but be the first app in split selection mode - */ - private final boolean mAllowInitialSplitSelection; + SplitShortcut { + /** + * If {@code true}, clicking this shortcut will not attempt to start a split app directly, + * but be the first app in split selection mode + */ + private final boolean mAllowInitialSplitSelection; - TaskbarSplitShortcut(BaseTaskbarContext context, ItemInfo itemInfo, View originalView, + TaskbarSplitShortcut(BaseTaskbarContext context, ItemInfo itemInfo, View originalView, SplitPositionOption position, boolean allowInitialSplitSelection) { - super(position.iconResId, position.textResId, context, itemInfo, originalView, - position); - mAllowInitialSplitSelection = allowInitialSplitSelection; - } + super(position.iconResId, position.textResId, context, itemInfo, originalView, + position); + mAllowInitialSplitSelection = allowInitialSplitSelection; + } @Override public void onClick(View view) { - // Add callbacks depending on what type of Taskbar context we're in (Taskbar or - // AllApps) + // Add callbacks depending on what type of Taskbar context we're in (Taskbar or AllApps) mTarget.onSplitScreenMenuButtonClicked(); AbstractFloatingView.closeAllOpenViews(mTarget); - // Depending on what app state we're in, we either want to initiate the split - // screen + // Depending on what app state we're in, we either want to initiate the split screen // staging process or immediately launch a split with an existing app. // - Initiate the split screen staging process - if (mAllowInitialSplitSelection) { - super.onClick(view); - return; - } + if (mAllowInitialSplitSelection) { + super.onClick(view); + return; + } // - Immediately launch split with the running app - Pair instanceIds = LogUtils - .getShellShareableInstanceId(); + Pair instanceIds = + LogUtils.getShellShareableInstanceId(); mTarget.getStatsLogManager().logger() .withItemInfo(mItemInfo) .withInstanceId(instanceIds.second) @@ -329,3 +314,4 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba } } } + diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 674dce7b61..2168f7a318 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -16,6 +16,7 @@ package com.android.launcher3.uioverrides; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.os.Trace.TRACE_TAG_APP; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; @@ -78,10 +79,12 @@ import android.graphics.Rect; import android.graphics.RectF; import android.hardware.display.DisplayManager; import android.media.permission.SafeCloseable; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.SystemProperties; +import android.os.Trace; import android.util.AttributeSet; import android.view.Display; import android.view.HapticFeedbackConstants; @@ -94,9 +97,11 @@ import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedDispatcher; import android.window.RemoteTransition; import android.window.SplashScreen; + import androidx.annotation.BinderThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.android.app.viewcapture.ViewCaptureFactory; import com.android.launcher3.AbstractFloatingView; @@ -106,7 +111,6 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherState; -import com.android.launcher3.OnBackPressedHandler; import com.android.launcher3.QuickstepAccessibilityDelegate; import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.R; @@ -207,12 +211,12 @@ import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Stream; -import app.lawnchair.compat.LawnchairQuickstepCompat; public class QuickstepLauncher extends Launcher implements RecentsViewContainer { - private static final boolean TRACE_LAYOUTS = SystemProperties.getBoolean("persist.debug.trace_layouts", false); - private static final String TRACE_RELAYOUT_CLASS = SystemProperties.get("persist.debug.trace_request_layout_class", - null); + private static final boolean TRACE_LAYOUTS = + SystemProperties.getBoolean("persist.debug.trace_layouts", false); + private static final String TRACE_RELAYOUT_CLASS = + SystemProperties.get("persist.debug.trace_request_layout_class", null); public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false; protected static final String RING_APPEAR_ANIMATION_PREFIX = "RingAppearAnimation\t"; @@ -229,13 +233,13 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer // Will be updated when dragging from taskbar. private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider; private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController; + private SplitSelectStateController mSplitSelectStateController; private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController; private SplitToWorkspaceController mSplitToWorkspaceController; /** - * If Launcher restarted while in the middle of an Overview split select, it - * needs this data to + * If Launcher restarted while in the middle of an Overview split select, it needs this data to * recover. In all other cases this will remain null. */ private PendingSplitSelectInfo mPendingSplitSelectInfo = null; @@ -244,6 +248,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer private DesktopRecentsTransitionController mDesktopRecentsTransitionController; private SafeCloseable mViewCapture; + private boolean mEnableWidgetDepth; private boolean mIsPredictiveBackToHomeInProgress; @@ -255,16 +260,17 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override protected void setupViews() { super.setupViews(); + mActionsView = findViewById(R.id.overview_actions_view); - RecentsView overviewPanel = getOverviewPanel(); + RecentsView overviewPanel = getOverviewPanel(); SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this); - mSplitSelectStateController = new SplitSelectStateController(this, mHandler, getStateManager(), - getDepthController(), getStatsLogManager(), - systemUiProxy, RecentsModel.INSTANCE.get(this), - () -> onStateBack()); + mSplitSelectStateController = + new SplitSelectStateController(this, mHandler, getStateManager(), + getDepthController(), getStatsLogManager(), + systemUiProxy, RecentsModel.INSTANCE.get(this), + () -> onStateBack()); RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(asContext()); - // TODO(b/337863494): Explore use of the same OverviewComponentObserver across - // launcher + // TODO(b/337863494): Explore use of the same OverviewComponentObserver across launcher OverviewComponentObserver overviewComponentObserver = new OverviewComponentObserver( asContext(), deviceState); if (enableDesktopWindowingMode()) { @@ -280,9 +286,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer mSplitSelectStateController); mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize()); mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this)); + mAppTransitionManager = buildAppTransitionManager(); mAppTransitionManager.registerRemoteAnimations(); mAppTransitionManager.registerRemoteTransitions(); + mTISBindHelper = new TISBindHelper(this, this::onTISConnected); mDepthController = new DepthController(this); if (enableDesktopWindowingMode()) { @@ -292,26 +300,28 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer overviewComponentObserver); } mHotseatPredictionController = new HotseatPredictionController(this); + mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true); - getWorkspace().addOverlayCallback( - progress -> onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX)); + getWorkspace().addOverlayCallback(progress -> + onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX)); addBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler()); } @Override public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info, InstanceId instanceId) { - // If the app launch is from any of the surfaces in AllApps then add the - // InstanceId from + // If the app launch is from any of the surfaces in AllApps then add the InstanceId from // LiveSearchManager to recreate the AllApps session on the server side. if (mAllAppsSessionLogId != null && ALL_APPS.equals( getStateManager().getCurrentStableState())) { instanceId = mAllAppsSessionLogId; } + StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId); + if (mAllAppsPredictions != null && (info.itemType == ITEM_TYPE_APPLICATION - || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) { + || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) { int count = mAllAppsPredictions.items.size(); for (int i = 0; i < count; i++) { ItemInfo targetInfo = mAllAppsPredictions.items.get(i); @@ -321,9 +331,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer logger.withRank(i); break; } + } } logger.log(LAUNCHER_APP_LAUNCH_TAP); + mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId); } @@ -355,8 +367,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer } /** - * Builds the {@link QuickstepTransitionManager} instance to use for managing - * transitions. + * Builds the {@link QuickstepTransitionManager} instance to use for managing transitions. */ protected QuickstepTransitionManager buildAppTransitionManager() { return new QuickstepTransitionManager(this); @@ -370,11 +381,10 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) { - // Only pause is taskbar controller is not present until the transition (if it - // exists) ends + // Only pause is taskbar controller is not present until the transition (if it exists) ends mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null); - PredictionRowView predictionRowView = getAppsView().getFloatingHeaderView() - .findFixedRowByType(PredictionRowView.class); + PredictionRowView predictionRowView = + getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class); // Pause the prediction row updates until the transition (if it exists) ends. predictionRowView.setPredictionUiUpdatePaused(true); RunnableList result = super.startActivitySafely(v, intent, item); @@ -401,11 +411,13 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer if ((changeBits & ACTIVITY_STATE_STARTED) != 0) { mDepthController.setActivityStarted(isStarted()); } + if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) { if (!FeatureFlags.enableHomeTransitionListener() && mTaskbarUIController != null) { mTaskbarUIController.onLauncherVisibilityChanged(hasBeenResumed()); } } + super.onActivityFlagsChanged(changeBits); if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) { @@ -454,11 +466,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer if (!mDeviceProfile.isTablet || mSplitSelectStateController.isSplitSelectActive()) { return Collections.emptyList(); } - RecentsView recentsView = (RecentsView) getOverviewPanel(); - // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from - // workspace. - List positions = recentsView.getPagedOrientationHandler().getSplitPositionOptions( - mDeviceProfile); + RecentsView recentsView = getOverviewPanel(); + // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace. + List positions = + recentsView.getPagedOrientationHandler().getSplitPositionOptions( + mDeviceProfile); List> splitShortcuts = new ArrayList<>(); for (SplitPositionOption position : positions) { splitShortcuts.add(getSplitSelectShortcutByPosition(position)); @@ -467,15 +479,15 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer } /** - * Recents logic that triggers when launcher state changes or launcher activity - * stops/resumes. + * Recents logic that triggers when launcher state changes or launcher activity stops/resumes. */ private void onStateOrResumeChanging(boolean inTransition) { LauncherState state = getStateManager().getState(); boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0; if (started) { DeviceProfile profile = getDeviceProfile(); - boolean willUserBeActive = (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0; + boolean willUserBeActive = + (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0; boolean visible = (state == NORMAL || state == OVERVIEW) && (willUserBeActive || isUserActive()) && !profile.isVerticalBarLayout(); @@ -491,8 +503,9 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer public void bindExtraContainerItems(FixedContainerItems item) { if (item.containerId == Favorites.CONTAINER_PREDICTION) { mAllAppsPredictions = item; - PredictionRowView predictionRowView = getAppsView().getFloatingHeaderView().findFixedRowByType( - PredictionRowView.class); + PredictionRowView predictionRowView = + getAppsView().getFloatingHeaderView().findFixedRowByType( + PredictionRowView.class); predictionRowView.setPredictedApps(item.items); } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) { mHotseatPredictionController.setPredictedItems(item); @@ -519,6 +532,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null); mUnfoldTransitionProgressProvider.destroy(); } + mTISBindHelper.onDestroy(); if (mLauncherUnfoldAnimationController != null) { @@ -544,6 +558,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer public void onStateSetEnd(LauncherState state) { super.onStateSetEnd(state); handlePendingActivityRequest(); + switch (state.ordinal) { case HINT_STATE_ORDINAL: { Workspace workspace = getWorkspace(); @@ -559,7 +574,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer break; } case OVERVIEW_STATE_ORDINAL: { - RecentsView rv = (RecentsView) getOverviewPanel(); + RecentsView rv = getOverviewPanel(); sendCustomAccessibilityEvent( rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null); break; @@ -581,12 +596,14 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer } break; } + } } @Override public TouchController[] createTouchControllers() { NavigationMode mode = DisplayController.getNavigationMode(this); + ArrayList list = new ArrayList<>(); list.add(getDragController()); BiConsumer splitAnimator = (animatorSet, duration) -> @@ -616,9 +633,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer list.add(new PortraitStatesTouchController(this)); break; } + if (!getDeviceProfile().isMultiWindowMode) { list.add(new StatusBarTouchController(this)); } + list.add(new LauncherTaskViewController(this)); return list.toArray(new TouchController[list.size()]); } @@ -630,8 +649,8 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override protected LauncherWidgetHolder createAppWidgetHolder() { - final QuickstepHolderFactory factory = (QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory - .newFactory(this); + final QuickstepHolderFactory factory = + (QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory.newFactory(this); return factory.newInstance(this, appWidgetId -> getWorkspace().removeWidget(appWidgetId), new QuickstepInteractionHandler(this)); @@ -657,10 +676,8 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer } getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE); QuickstepOnboardingPrefs.setup(this); - if (Utilities.ATLEAST_U) { - View.setTraceLayoutSteps(TRACE_LAYOUTS); - View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS); - } + View.setTraceLayoutSteps(TRACE_LAYOUTS); + View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS); } @Override @@ -674,8 +691,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override public void startSplitSelection(SplitSelectSource splitSelectSource) { RecentsView recentsView = getOverviewPanel(); - // Check if there is already an instance of this app running, if so, initiate - // the split + // Check if there is already an instance of this app running, if so, initiate the split // using that. mSplitSelectStateController.findLastActiveTasksAndRunCallback( Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()), @@ -691,13 +707,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer } else { recentsView.initiateSplitSelect(splitSelectSource); } - }); + } + ); } - /** - * TODO(b/266482558) Migrate into SplitSelectStateController or someplace split - * specific. - */ + /** TODO(b/266482558) Migrate into SplitSelectStateController or someplace split specific. */ private void startSplitToHome(SplitSelectSource source) { AbstractFloatingView.closeAllOpenViews(this); int splitPlaceholderSize = getResources().getDimensionPixelSize( @@ -705,13 +719,16 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer int splitPlaceholderInset = getResources().getDimensionPixelSize( R.dimen.split_placeholder_inset); Rect tempRect = new Rect(); + mSplitSelectStateController.setInitialTaskSelect(source.intent, source.position.stagePosition, source.itemInfo, source.splitEvent, source.alreadyRunningTaskId); + RecentsView recentsView = getOverviewPanel(); recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds( splitPlaceholderSize, splitPlaceholderInset, getDeviceProfile(), mSplitSelectStateController.getActiveSplitStagePosition(), tempRect); + PendingAnimation anim = new PendingAnimation(TABLET_HOME_TO_SPLIT.getDuration()); RectF startingTaskRect = new RectF(); final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(this, @@ -759,6 +776,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override protected void onResume() { super.onResume(); + if (mLauncherUnfoldAnimationController != null) { mLauncherUnfoldAnimationController.onResume(); } @@ -769,6 +787,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer if (mLauncherUnfoldAnimationController != null) { mLauncherUnfoldAnimationController.onPause(); } + super.onPause(); if (enableSplitContextually()) { @@ -799,10 +818,8 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override public void onEnterAnimationComplete() { super.onEnterAnimationComplete(); - // After the transition to home, enable the high-res thumbnail loader if it - // wasn't enabled - // as a part of quickstep, so that high-res thumbnails can load the next time we - // enter + // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled + // as a part of quickstep, so that high-res thumbnails can load the next time we enter // overview RecentsModel.INSTANCE.get(this).getThumbnailCache() .getHighResLoadingState().setVisible(true); @@ -876,50 +893,51 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer OnBackInvokedDispatcher.PRIORITY_DEFAULT, new OnBackAnimationCallback() { - @Nullable - OnBackPressedHandler mActiveOnBackPressedHandler; + @Nullable OnBackAnimationCallback mActiveOnBackAnimationCallback; @Override public void onBackStarted(@NonNull BackEvent backEvent) { - if (mActiveOnBackPressedHandler != null) { - mActiveOnBackPressedHandler.onBackCancelled(); + if (mActiveOnBackAnimationCallback != null) { + mActiveOnBackAnimationCallback.onBackCancelled(); } - mActiveOnBackPressedHandler = getOnBackPressedHandler(); - mActiveOnBackPressedHandler.onBackStarted(); + mActiveOnBackAnimationCallback = getOnBackAnimationCallback(); + mActiveOnBackAnimationCallback.onBackStarted(backEvent); } + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @Override public void onBackInvoked() { - // Recreate mActiveOnBackPressedHandler if necessary to avoid NPE + // Recreate mActiveOnBackAnimationCallback if necessary to avoid NPE // because: // 1. b/260636433: In 3-button-navigation mode, onBackStarted() is not // called on ACTION_DOWN before onBackInvoked() is called in ACTION_UP. // 2. Launcher#onBackPressed() will call onBackInvoked() without calling // onBackInvoked() beforehand. - if (mActiveOnBackPressedHandler == null) { - mActiveOnBackPressedHandler = getOnBackPressedHandler(); + if (mActiveOnBackAnimationCallback == null) { + mActiveOnBackAnimationCallback = getOnBackAnimationCallback(); } - mActiveOnBackPressedHandler.onBackInvoked(); - mActiveOnBackPressedHandler = null; + mActiveOnBackAnimationCallback.onBackInvoked(); + mActiveOnBackAnimationCallback = null; TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked"); } + @Override public void onBackProgressed(@NonNull BackEvent backEvent) { if (!FeatureFlags.IS_STUDIO_BUILD - && mActiveOnBackPressedHandler == null) { + && mActiveOnBackAnimationCallback == null) { return; } - mActiveOnBackPressedHandler.onBackProgressed(backEvent.getProgress()); + mActiveOnBackAnimationCallback.onBackProgressed(backEvent); } @Override public void onBackCancelled() { if (!FeatureFlags.IS_STUDIO_BUILD - && mActiveOnBackPressedHandler == null) { + && mActiveOnBackAnimationCallback == null) { return; } - mActiveOnBackPressedHandler.onBackCancelled(); - mActiveOnBackPressedHandler = null; + mActiveOnBackAnimationCallback.onBackCancelled(); + mActiveOnBackAnimationCallback = null; } }); } @@ -988,11 +1006,9 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer private void handlePendingActivityRequest() { if (mPendingActivityRequestCode != -1 && isInState(NORMAL) && ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) { - // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to - // Launcher. + // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher. onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null); - // ProxyActivityStarter is started with clear task to reset the task after which - // it + // ProxyActivityStarter is started with clear task to reset the task after which it // removes the task itself. startActivity(ProxyActivityStarter.getLaunchIntent(this, null)); } @@ -1046,8 +1062,10 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer "Trying to create getRemoteTransitionProgress when the transition " + "is disabled")); mUnfoldTransitionProgressProvider = remoteUnfoldTransitionProgressProvider; + SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener( remoteUnfoldTransitionProgressProvider); + initUnfoldAnimationController(mUnfoldTransitionProgressProvider, unfoldComponent.getRotationChangeProvider()); } @@ -1058,7 +1076,8 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer /* launcher= */ this, getWindowManager(), progressProvider, - rotationChangeProvider); + rotationChangeProvider + ); } public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) { @@ -1127,15 +1146,13 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override public float[] getNormalOverviewScaleAndOffset() { return DisplayController.getNavigationMode(this).hasGestures - ? new float[] { 1, 1 } - : new float[] { 1.1f, NO_OFFSET }; + ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET}; } @Override public void finishBindingItems(IntSet pagesBoundFirst) { super.finishBindingItems(pagesBoundFirst); - // Instantiate and initialize WellbeingModel now that its loading won't - // interfere with + // Instantiate and initialize WellbeingModel now that its loading won't interfere with // populating workspace. // TODO: Find a better place for this WellbeingModel.INSTANCE.get(this); @@ -1148,29 +1165,24 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { - ActivityOptionsWrapper activityOptions = mAppTransitionManager.hasControlRemoteAppTransitionPermission() - ? mAppTransitionManager.getActivityLaunchOptions(v) - : super.getActivityLaunchOptions(v, item); - if (mLastTouchUpTime > 0 && LawnchairQuickstepCompat.ATLEAST_S) { + ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(v); + if (mLastTouchUpTime > 0) { activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER, mLastTouchUpTime); } if (item != null && (item.animationType == DEFAULT_NO_ICON - || item.animationType == VIEW_BACKGROUND) && LawnchairQuickstepCompat.ATLEAST_T) { + || item.animationType == VIEW_BACKGROUND)) { activityOptions.options.setSplashScreenStyle( SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); } else { - if (LawnchairQuickstepCompat.ATLEAST_T) { - activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); - } + activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); } activityOptions.options.setLaunchDisplayId( (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId() : Display.DEFAULT_DISPLAY); - Utilities.allowBGLaunch(activityOptions.options); - if (LawnchairQuickstepCompat.ATLEAST_T) { - addLaunchCookie(item, activityOptions.options); - } + activityOptions.options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + addLaunchCookie(item, activityOptions.options); return activityOptions; } @@ -1251,19 +1263,16 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) { super.onDisplayInfoChanged(context, info, flags); - // When changing screens, force moving to rest state similar to - // StatefulActivity.onStop, as + // When changing screens, force moving to rest state similar to StatefulActivity.onStop, as // StatefulActivity isn't called consistently. if ((flags & CHANGE_ACTIVE_SCREEN) != 0) { - // Do not animate moving to rest state, as it can clash with - // Launcher#onIdpChanged - // where reapplyUi calls StateManager's reapplyState during the state change - // animation, - // and cancel the state change unexpectedly. The screen will be off during - // screen + // Do not animate moving to rest state, as it can clash with Launcher#onIdpChanged + // where reapplyUi calls StateManager's reapplyState during the state change animation, + // and cancel the state change unexpectedly. The screen will be off during screen // transition, hiding the unanimated transition. getStateManager().moveToRestState(/* isAnimated = */false); } + if ((flags & CHANGE_NAVIGATION_MODE) != 0) { getDragLayer().recreateControllers(); if (mActionsView != null) { @@ -1275,16 +1284,12 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - // If Launcher shuts downs during split select, we save some extra data in the - // recovery - // bundle to allow graceful recovery. The normal LauncherState restore mechanism - // doesn't - // work in this case because restoring straight to OverviewSplitSelect without - // staging data, - // or before the tasks themselves have loaded into Overview, causes a crash. So - // we tell - // Launcher to first restore into Overview state, wait for the relevant tasks - // and icons to + + // If Launcher shuts downs during split select, we save some extra data in the recovery + // bundle to allow graceful recovery. The normal LauncherState restore mechanism doesn't + // work in this case because restoring straight to OverviewSplitSelect without staging data, + // or before the tasks themselves have loaded into Overview, causes a crash. So we tell + // Launcher to first restore into Overview state, wait for the relevant tasks and icons to // load in, and then proceed to OverviewSplitSelect. if (isInState(OVERVIEW_SPLIT_SELECT)) { // Launcher will restart in Overview and then transition to OverviewSplitSelect. @@ -1292,18 +1297,17 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer new PendingSplitSelectInfo( mSplitSelectStateController.getInitialTaskId(), mSplitSelectStateController.getActiveSplitStagePosition(), - mSplitSelectStateController.getSplitEvent()))); + mSplitSelectStateController.getSplitEvent()) + )); outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal); } } /** - * When Launcher restarts, it sometimes needs to recover to a split selection - * state. + * When Launcher restarts, it sometimes needs to recover to a split selection state. * This function checks if such a recovery is needed. - * * @return a boolean representing whether the launcher is waiting to recover to - * OverviewSplitSelect state. + * OverviewSplitSelect state. */ public boolean hasPendingSplitSelectInfo() { return mPendingSplitSelectInfo != null; @@ -1317,8 +1321,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer } /** - * When the launcher has successfully recovered to OverviewSplitSelect state, - * this function + * When the launcher has successfully recovered to OverviewSplitSelect state, this function * deletes the recovery data, returning it to a null state. */ public void finishSplitSelectRecovery() { @@ -1350,6 +1353,8 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer @Override public void dispatchDeviceProfileChanged() { super.dispatchDeviceProfileChanged(); + Trace.instantForTrack(TRACE_TAG_APP, "QuickstepLauncher#DeviceProfileChanged", + getDeviceProfile().toSmallString()); SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx); TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager(); if (taskbarManager != null) { @@ -1397,6 +1402,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer return (mTaskbarUIController != null && mTaskbarUIController.isBubbleBarEnabled()); } + @Override + public boolean hasBubbles() { + return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles()); + } + @NonNull public TISBindHelper getTISBindHelper() { return mTISBindHelper; @@ -1424,6 +1434,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer } @Override + protected boolean isRecentsModal() { return mContainer.isInState(OVERVIEW_MODAL_TASK); } @@ -1441,8 +1452,8 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer } RecentsView recentsView = getOverviewPanel(); writer.println("\nQuickstepLauncher:"); - writer.println(prefix + "\tmOrientationState: " - + (recentsView == null ? "recentsNull" : recentsView.getPagedViewOrientedState())); + writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" : + recentsView.getPagedViewOrientedState())); if (recentsView != null) { recentsView.getSplitSelectController().dump(prefix, writer); } diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java index 7088fae6ef..b720382597 100644 --- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java +++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java @@ -71,20 +71,16 @@ import java.lang.ref.WeakReference; /** * Controls the animation of swiping back and returning to launcher. * - * This is a two part animation. The first part is an animation that tracks - * gesture location to - * scale and move the leaving app window. Once the gesture is committed, the - * second part takes over + * This is a two part animation. The first part is an animation that tracks gesture location to + * scale and move the leaving app window. Once the gesture is committed, the second part takes over * the app window and plays the rest of app close transitions in one go. * * This animation is used only for apps that enable back dispatching via * {@link android.window.OnBackInvokedDispatcher}. The controller registers - * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches - * when a back + * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back * navigation to launcher starts. * - * Apps using the legacy back dispatching will keep triggering the - * WALLPAPER_OPEN remote + * Apps using the legacy back dispatching will keep triggering the WALLPAPER_OPEN remote * transition registered in {@link QuickstepTransitionManager}. * */ @@ -119,15 +115,21 @@ public class LauncherBackAnimationController { private boolean mBackInProgress = false; private OnBackInvokedCallbackStub mBackCallback; private IRemoteAnimationFinishedCallback mAnimationFinishedCallback; - private BackProgressAnimator mProgressAnimator; + private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); private SurfaceControl mScrimLayer; private ValueAnimator mScrimAlphaAnimator; private float mScrimAlpha; private boolean mOverridingStatusBarFlags; - private final ComponentCallbacks mComponentCallbacks=new ComponentCallbacks(){@Override public void onConfigurationChanged(Configuration newConfig){loadResources();} + private final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() { + @Override + public void onConfigurationChanged(Configuration newConfig) { + loadResources(); + } - @Override public void onLowMemory(){}}; + @Override + public void onLowMemory() {} + }; public LauncherBackAnimationController( QuickstepLauncher launcher, @@ -137,107 +139,24 @@ public class LauncherBackAnimationController { loadResources(); mWindowScaleMarginX = mLauncher.getResources().getDimensionPixelSize( R.dimen.swipe_back_window_scale_x_margin); - try { - mProgressAnimator = new BackProgressAnimator(); - } catch (Throwable throwable) { - // ignore - } } /** - * Registers {@link IOnBackInvokedCallback} to receive back dispatches from - * shell. - * + * Registers {@link IOnBackInvokedCallback} to receive back dispatches from shell. * @param handler Handler to the thread to run the animations on. */ public void registerBackCallbacks(Handler handler) { - try { - mBackCallback = new IOnBackInvokedCallback.Stub() { - @Override - public void onBackStarted(BackMotionEvent backMotionEvent) { - startBack(backMotionEvent); - handler.post(() -> { - if (mProgressAnimator == null) { - return; - } - mProgressAnimator.onBackStarted(backMotionEvent, event -> { - mBackProgress = event.getProgress(); - // TODO: Update once the interpolation curve spec is finalized. - mBackProgress = 1 - (1 - mBackProgress) * (1 - mBackProgress) * (1 - - mBackProgress); - updateBackProgress(mBackProgress, event); - }); - }); - } - - @Override - public void onBackProgressed(BackMotionEvent backMotionEvent) { - handler.post(() -> { - if (mProgressAnimator == null) { - return; - } - mProgressAnimator.onBackProgressed(backMotionEvent); - }); - } - - @Override - public void onBackCancelled() { - handler.post(() -> { - if (mProgressAnimator == null) { - return; - } - mProgressAnimator.onBackCancelled( - LauncherBackAnimationController.this::resetPositionAnimated); - }); - } - - @Override - public void onBackInvoked() { - handler.post(() -> { - startTransition(); - if (mProgressAnimator == null) { - return; - } - mProgressAnimator.reset(); - }); - } - }; - - final IRemoteAnimationRunner runner = new IRemoteAnimationRunner.Stub() { - @Override - public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, - IRemoteAnimationFinishedCallback finishedCallback) { - for (final RemoteAnimationTarget target : apps) { - if (MODE_CLOSING == target.mode) { - mBackTarget = target; - break; - } - } - mAnimationFinishedCallback = finishedCallback; - } - - void onAnimationCancelled(boolean isKeyguardOccluded) { - } - - public void onAnimationCancelled() { - } - }; - - SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, runner); - - } catch (Throwable t) { - // Ignore - } - + mBackCallback = new OnBackInvokedCallbackStub(handler, mProgressAnimator, + mProgressInterpolator, this); + SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, + new RemoteAnimationRunnerStub(this)); } private static class OnBackInvokedCallbackStub extends IOnBackInvokedCallback.Stub { private Handler mHandler; private BackProgressAnimator mProgressAnimator; private final Interpolator mProgressInterpolator; - // LauncherBackAnimationController has strong reference to Launcher activity, - // the binder + // LauncherBackAnimationController has strong reference to Launcher activity, the binder // callback should not hold strong reference to it to avoid memory leak. private WeakReference mControllerRef; @@ -295,7 +214,8 @@ public class LauncherBackAnimationController { controller.startBack(backEvent); mProgressAnimator.onBackStarted(backEvent, event -> { float backProgress = event.getProgress(); - controller.mBackProgress = mProgressInterpolator.getInterpolation(backProgress); + controller.mBackProgress = + mProgressInterpolator.getInterpolation(backProgress); controller.updateBackProgress(controller.mBackProgress, event); }); } @@ -310,8 +230,7 @@ public class LauncherBackAnimationController { private static class RemoteAnimationRunnerStub extends IRemoteAnimationRunner.Stub { - // LauncherBackAnimationController has strong reference to Launcher activity, - // the binder + // LauncherBackAnimationController has strong reference to Launcher activity, the binder // callback should not hold strong reference to it to avoid memory leak. private WeakReference mControllerRef; @@ -339,8 +258,7 @@ public class LauncherBackAnimationController { } @Override - public void onAnimationCancelled() { - } + public void onAnimationCancelled() {} } private void onCancelFinished() { @@ -353,22 +271,16 @@ public class LauncherBackAnimationController { if (mBackCallback != null) { SystemUiProxy.INSTANCE.get(mLauncher).clearBackToLauncherCallback(mBackCallback); } - if (mProgressAnimator != null) { - mProgressAnimator.reset(); - } + mProgressAnimator.reset(); mBackCallback = null; } private void startBack(BackMotionEvent backEvent) { - // in case we're still animating an onBackCancelled event, let's remove the - // finish- - // callback from the progress animator to prevent calling finishAnimation() - // before + // in case we're still animating an onBackCancelled event, let's remove the finish- + // callback from the progress animator to prevent calling finishAnimation() before // restarting a new animation - // Side note: startBack is never called during the post-commit phase if the back - // gesture - // was committed (not cancelled). BackAnimationController prevents that. - // Therefore we + // Side note: startBack is never called during the post-commit phase if the back gesture + // was committed (not cancelled). BackAnimationController prevents that. Therefore we // don't have to handle that case. mProgressAnimator.removeOnBackCancelledFinishCallback(); @@ -391,7 +303,7 @@ public class LauncherBackAnimationController { mStartRect.inset(0, 0, 0, appTarget.contentInsets.bottom); mLauncherTargetView = mQuickstepTransitionManager.findLauncherView( - new RemoteAnimationTarget[] { mBackTarget }); + new RemoteAnimationTarget[]{ mBackTarget }); setLauncherTargetViewVisible(false); mCurrentRect.set(mStartRect); if (mScrimLayer == null) { @@ -425,8 +337,7 @@ public class LauncherBackAnimationController { .build(); final float[] colorComponents = new float[] { 0f, 0f, 0f }; mScrimAlpha = (isDarkTheme) - ? MAX_SCRIM_ALPHA_DARK - : MAX_SCRIM_ALPHA_LIGHT; + ? MAX_SCRIM_ALPHA_DARK : MAX_SCRIM_ALPHA_LIGHT; mTransaction .setColor(mScrimLayer, colorComponents) .setAlpha(mScrimLayer, mScrimAlpha) @@ -515,11 +426,9 @@ public class LauncherBackAnimationController { if (taskbarUIController != null) { taskbarUIController.onLauncherVisibilityChanged(true); } - // TODO: Catch the moment when launcher becomes visible after the top app - // un-occludes - // launcher and start animating afterwards. Currently we occasionally get a - // flicker from - // animating when launcher is still invisible. + // TODO: Catch the moment when launcher becomes visible after the top app un-occludes + // launcher and start animating afterwards. Currently we occasionally get a flicker from + // animating when launcher is still invisible. if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) { mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS); mLauncher.getStateManager().moveToRestState(); @@ -528,8 +437,7 @@ public class LauncherBackAnimationController { setLauncherTargetViewVisible(true); // Explicitly close opened floating views (which is typically called from - // Launcher#onResumed, but in the predictive back flow launcher is not resumed - // until + // Launcher#onResumed, but in the predictive back flow launcher is not resumed until // the transition is fully finished.) AbstractFloatingView.closeAllOpenViewsExcept(mLauncher, false, TYPE_REBIND_SAFE); float cornerRadius = Utilities.mapRange( @@ -538,13 +446,14 @@ public class LauncherBackAnimationController { mQuickstepTransitionManager.transferRectToTargetCoordinate( mBackTarget, mCurrentRect, true, resolveRectF); - Pair pair = mQuickstepTransitionManager.createWallpaperOpenAnimations( - new RemoteAnimationTarget[] { mBackTarget }, - new RemoteAnimationTarget[0], - false /* fromUnlock */, - resolveRectF, - cornerRadius, - mBackInProgress /* fromPredictiveBack */); + Pair pair = + mQuickstepTransitionManager.createWallpaperOpenAnimations( + new RemoteAnimationTarget[]{mBackTarget}, + new RemoteAnimationTarget[0], + false /* fromUnlock */, + resolveRectF, + cornerRadius, + mBackInProgress /* fromPredictiveBack */); startTransitionAnimations(pair.first, pair.second); mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL); customizeStatusBarAppearance(true); @@ -567,16 +476,21 @@ public class LauncherBackAnimationController { // We don't call customizeStatusBarAppearance here to prevent the status bar update with // the legacy appearance. It should be refreshed after the transition done. mOverridingStatusBarFlags = false; + if (mAnimationFinishedCallback != null) { + try { + mAnimationFinishedCallback.onAnimationFinished(); + } catch (RemoteException e) { Log.w("ShellBackPreview", "Failed call onBackAnimationFinished", e); - }mAnimationFinishedCallback=null;if(mScrimAlphaAnimator!=null&&mScrimAlphaAnimator.isRunning()) - - { - mScrimAlphaAnimator.cancel(); - mScrimAlphaAnimator = null; - }if(mScrimLayer!=null) - { - removeScrimLayer(); - } + } + mAnimationFinishedCallback = null; + } + if (mScrimAlphaAnimator != null && mScrimAlphaAnimator.isRunning()) { + mScrimAlphaAnimator.cancel(); + mScrimAlphaAnimator = null; + } + if (mScrimLayer != null) { + removeScrimLayer(); + } } private void startTransitionAnimations(RectFSpringAnim springAnim, AnimatorSet anim) { @@ -590,22 +504,26 @@ public class LauncherBackAnimationController { mSpringAnimationInProgress = false; tryFinishBackAnimation(); } - }); + } + ); } anim.addListener(new AnimatorListenerAdapter() { - - @Override + @Override public void onAnimationEnd(Animator animation) { mAnimatorSetInProgress = false; tryFinishBackAnimation(); - }});if(mScrimLayer==null){ - // Scrim hasn't been attached yet. Let's attach it. - addScrimLayer();}mScrimAlphaAnimator=new ValueAnimator().ofFloat(1,0);mScrimAlphaAnimator.addUpdateListener(animation->{ - - float value = (Float) animation - .getAnimatedValue();if(mScrimLayer!=null&&mScrimLayer.isValid()){mTransaction.setAlpha(mScrimLayer,value*mScrimAlpha); - - applyTransaction(); + } + }); + if (mScrimLayer == null) { + // Scrim hasn't been attached yet. Let's attach it. + addScrimLayer(); + } + mScrimAlphaAnimator = new ValueAnimator().ofFloat(1, 0); + mScrimAlphaAnimator.addUpdateListener(animation -> { + float value = (Float) animation.getAnimatedValue(); + if (mScrimLayer != null && mScrimLayer.isValid()) { + mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha); + applyTransaction(); } }); mScrimAlphaAnimator.addListener(new AnimatorListenerAdapter() { @@ -621,29 +539,28 @@ public class LauncherBackAnimationController { private void loadResources() { mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows( mLauncher.getResources()) - ? mLauncher.getResources().getDimensionPixelSize( - R.dimen.swipe_back_window_corner_radius) - : 0; + ? mLauncher.getResources().getDimensionPixelSize( + R.dimen.swipe_back_window_corner_radius) + : 0; mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher); mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mLauncher); } /** - * Called when launcher is destroyed. Unregisters component callbacks to avoid - * memory leaks. + * Called when launcher is destroyed. Unregisters component callbacks to avoid memory leaks. */ public void unregisterComponentCallbacks() { mLauncher.unregisterComponentCallbacks(mComponentCallbacks); } /** - * Registers component callbacks with the launcher to receive configuration - * change events. + * Registers component callbacks with the launcher to receive configuration change events. */ public void registerComponentCallbacks() { mLauncher.registerComponentCallbacks(mComponentCallbacks); } + private void resetScrim() { removeScrimLayer(); mScrimAlpha = 0; @@ -661,8 +578,9 @@ public class LauncherBackAnimationController { } mOverridingStatusBarFlags = overridingStatusBarFlags; - final boolean isBackgroundDark = (mLauncher.getWindow().getDecorView().getSystemUiVisibility() - & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) == 0; + final boolean isBackgroundDark = + (mLauncher.getWindow().getDecorView().getSystemUiVisibility() + & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) == 0; final AppearanceRegion region = mOverridingStatusBarFlags ? new AppearanceRegion(!isBackgroundDark ? APPEARANCE_LIGHT_STATUS_BARS : 0, mBackTarget.windowConfiguration.getBounds()) diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 27a6312778..d7c7857653 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -18,7 +18,6 @@ package com.android.quickstep.views; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; @@ -36,7 +35,6 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU; import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType; import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.Flags.enableAdditionalHomeAnimations; import static com.android.launcher3.Flags.enableGridOnlyOverview; import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; @@ -239,120 +237,214 @@ import java.util.Optional; import java.util.function.Consumer; import java.util.stream.Collectors; -import app.lawnchair.LawnchairApp; -import app.lawnchair.compat.LawnchairQuickstepCompat; -import app.lawnchair.theme.color.tokens.ColorTokens; -import app.lawnchair.util.OverScrollerCompat; -import app.lawnchair.util.RecentHelper; - /** * A list of recent tasks. - * * @param : the container that should host recents view - * @param : the type of base state that will be used + * @param : the type of base state that will be used */ -public abstract class RecentsView> - extends PagedView implements Insettable, + +public abstract class RecentsView> extends PagedView implements Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, TaskVisualsChangeListener { private static final String TAG = "RecentsView"; private static final boolean DEBUG = false; - public static final FloatProperty CONTENT_ALPHA=new FloatProperty("contentAlpha"){@Override public void setValue(RecentsView view,float v){view.setContentAlpha(v);} + public static final FloatProperty CONTENT_ALPHA = + new FloatProperty("contentAlpha") { + @Override + public void setValue(RecentsView view, float v) { + view.setContentAlpha(v); + } - @Override public Float get(RecentsView view){return view.getContentAlpha();}}; + @Override + public Float get(RecentsView view) { + return view.getContentAlpha(); + } + }; - public static final FloatProperty FULLSCREEN_PROGRESS=new FloatProperty("fullscreenProgress"){@Override public void setValue(RecentsView recentsView,float v){recentsView.setFullscreenProgress(v);} + public static final FloatProperty FULLSCREEN_PROGRESS = + new FloatProperty("fullscreenProgress") { + @Override + public void setValue(RecentsView recentsView, float v) { + recentsView.setFullscreenProgress(v); + } - @Override public Float get(RecentsView recentsView){return recentsView.mFullscreenProgress;}}; + @Override + public Float get(RecentsView recentsView) { + return recentsView.mFullscreenProgress; + } + }; - public static final FloatProperty TASK_MODALNESS=new FloatProperty("taskModalness"){@Override public void setValue(RecentsView recentsView,float v){recentsView.setTaskModalness(v);} + public static final FloatProperty TASK_MODALNESS = + new FloatProperty("taskModalness") { + @Override + public void setValue(RecentsView recentsView, float v) { + recentsView.setTaskModalness(v); + } - @Override public Float get(RecentsView recentsView){return recentsView.mTaskModalness;}}; + @Override + public Float get(RecentsView recentsView) { + return recentsView.mTaskModalness; + } + }; - public static final FloatProperty ADJACENT_PAGE_HORIZONTAL_OFFSET=new FloatProperty("adjacentPageHorizontalOffset"){@Override public void setValue(RecentsView recentsView,float v){if(recentsView.mAdjacentPageHorizontalOffset!=v){recentsView.mAdjacentPageHorizontalOffset=v;recentsView.updatePageOffsets();}} + public static final FloatProperty ADJACENT_PAGE_HORIZONTAL_OFFSET = + new FloatProperty("adjacentPageHorizontalOffset") { + @Override + public void setValue(RecentsView recentsView, float v) { + if (recentsView.mAdjacentPageHorizontalOffset != v) { + recentsView.mAdjacentPageHorizontalOffset = v; + recentsView.updatePageOffsets(); + } + } - @Override public Float get(RecentsView recentsView){return recentsView.mAdjacentPageHorizontalOffset;}}; + @Override + public Float get(RecentsView recentsView) { + return recentsView.mAdjacentPageHorizontalOffset; + } + }; - public static final int SCROLL_VIBRATION_PRIMITIVE = Utilities.ATLEAST_S - ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK - : -1; + public static final int SCROLL_VIBRATION_PRIMITIVE = + Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1; public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f; - public static final VibrationEffect SCROLL_VIBRATION_FALLBACK = VibrationConstants.EFFECT_TEXTURE_TICK; + public static final VibrationEffect SCROLL_VIBRATION_FALLBACK = + VibrationConstants.EFFECT_TEXTURE_TICK; public static final int UNBOUND_TASK_VIEW_ID = -1; /** - * Can be used to tint the color of the RecentsView to simulate a scrim that can - * views + * Can be used to tint the color of the RecentsView to simulate a scrim that can views * excluded from. Really should be a proper scrim. * TODO(b/187528071): Remove this and replace with a real scrim. */ - private static final FloatProperty COLOR_TINT=new FloatProperty("colorTint"){@Override public void setValue(RecentsView recentsView,float v){recentsView.setColorTint(v);} + private static final FloatProperty COLOR_TINT = + new FloatProperty("colorTint") { + @Override + public void setValue(RecentsView recentsView, float v) { + recentsView.setColorTint(v); + } - @Override public Float get(RecentsView recentsView){return recentsView.getColorTint();}}; + @Override + public Float get(RecentsView recentsView) { + return recentsView.getColorTint(); + } + }; /** - * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance - * property, they - * are currently both used to apply secondary translation. Should their use - * cases change to be - * more specific, we'd want to create a similar FloatProperty just for a - * TaskView's + * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they + * are currently both used to apply secondary translation. Should their use cases change to be + * more specific, we'd want to create a similar FloatProperty just for a TaskView's * offsetX/Y property */ - public static final FloatProperty TASK_SECONDARY_TRANSLATION=new FloatProperty("taskSecondaryTranslation"){@Override public void setValue(RecentsView recentsView,float v){recentsView.setTaskViewsResistanceTranslation(v);} + public static final FloatProperty TASK_SECONDARY_TRANSLATION = + new FloatProperty("taskSecondaryTranslation") { + @Override + public void setValue(RecentsView recentsView, float v) { + recentsView.setTaskViewsResistanceTranslation(v); + } - @Override public Float get(RecentsView recentsView){return recentsView.mTaskViewsSecondaryTranslation;}}; + @Override + public Float get(RecentsView recentsView) { + return recentsView.mTaskViewsSecondaryTranslation; + } + }; /** - * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance - * property, they - * are currently both used to apply secondary translation. Should their use - * cases change to be - * more specific, we'd want to create a similar FloatProperty just for a - * TaskView's + * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they + * are currently both used to apply secondary translation. Should their use cases change to be + * more specific, we'd want to create a similar FloatProperty just for a TaskView's * offsetX/Y property */ - public static final FloatProperty TASK_PRIMARY_SPLIT_TRANSLATION=new FloatProperty("taskPrimarySplitTranslation"){@Override public void setValue(RecentsView recentsView,float v){recentsView.setTaskViewsPrimarySplitTranslation(v);} + public static final FloatProperty TASK_PRIMARY_SPLIT_TRANSLATION = + new FloatProperty("taskPrimarySplitTranslation") { + @Override + public void setValue(RecentsView recentsView, float v) { + recentsView.setTaskViewsPrimarySplitTranslation(v); + } - @Override public Float get(RecentsView recentsView){return recentsView.mTaskViewsPrimarySplitTranslation;}}; + @Override + public Float get(RecentsView recentsView) { + return recentsView.mTaskViewsPrimarySplitTranslation; + } + }; - public static final FloatProperty TASK_SECONDARY_SPLIT_TRANSLATION=new FloatProperty("taskSecondarySplitTranslation"){@Override public void setValue(RecentsView recentsView,float v){recentsView.setTaskViewsSecondarySplitTranslation(v);}@Override public Float get(RecentsView recentsView){return recentsView.mTaskViewsSecondarySplitTranslation;}}; + public static final FloatProperty TASK_SECONDARY_SPLIT_TRANSLATION = + new FloatProperty("taskSecondarySplitTranslation") { + @Override + public void setValue(RecentsView recentsView, float v) { + recentsView.setTaskViewsSecondarySplitTranslation(v); + } + + @Override + public Float get(RecentsView recentsView) { + return recentsView.mTaskViewsSecondarySplitTranslation; + } + }; + + /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */ + public static final FloatProperty RECENTS_SCALE_PROPERTY = + new FloatProperty("recentsScale") { + @Override + public void setValue(RecentsView view, float scale) { + view.setScaleX(scale); + view.setScaleY(scale); + if (enableRefactorTaskThumbnail()) { + view.mRecentsViewData.getScale().setValue(scale); + } + view.mLastComputedTaskStartPushOutDistance = null; + view.mLastComputedTaskEndPushOutDistance = null; + view.runActionOnRemoteHandles(new Consumer() { + @Override + public void accept(RemoteTargetHandle remoteTargetHandle) { + remoteTargetHandle.getTaskViewSimulator().recentsViewScale.value = + scale; + } + }); + view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation); + view.updateTaskViewsSnapshotRadius(); + view.updatePageOffsets(); + } + + @Override + public Float get(RecentsView view) { + return view.getScaleX(); + } + }; /** - * Same as normal SCALE_PROPERTY, but also updates page offsets that depend on - * this scale. - */ - public static final FloatProperty RECENTS_SCALE_PROPERTY=new FloatProperty("recentsScale"){@Override public void setValue(RecentsView view,float scale){view.setScaleX(scale);view.setScaleY(scale);if(enableRefactorTaskThumbnail()){view.mRecentsViewData.getScale().setValue(scale);}view.mLastComputedTaskStartPushOutDistance=null;view.mLastComputedTaskEndPushOutDistance=null;view.runActionOnRemoteHandles(new Consumer(){@Override public void accept(RemoteTargetHandle remoteTargetHandle){remoteTargetHandle.getTaskViewSimulator().recentsViewScale.value=scale;}});view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);view.updateTaskViewsSnapshotRadius();view.updatePageOffsets();} - - @Override public Float get(RecentsView view){return view.getScaleX();}}; - - /** - * Same as normal SCALE_PROPERTY, but also updates page offsets that depend on - * this scale. - */ - public static final FloatProperty RECENTS_SCALE_PROPERTY=new FloatProperty("recentsScale"){@Override public void setValue(RecentsView view,float scale){view.setScaleX(scale);view.setScaleY(scale);view.mLastComputedTaskStartPushOutDistance=null;view.mLastComputedTaskEndPushOutDistance=null;view.runActionOnRemoteHandles(new Consumer(){@Override public void accept(RemoteTargetHandle remoteTargetHandle){remoteTargetHandle.getTaskViewSimulator().recentsViewScale.value=scale;}});view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);view.updateTaskViewsSnapshotRadius();view.updatePageOffsets();} - - @Override public Float get(RecentsView view){return view.getScaleX();}}; - - /** - * Progress of Recents view from carousel layout to grid layout. If Recents is - * not shown as a + * Progress of Recents view from carousel layout to grid layout. If Recents is not shown as a * grid, then the value remains 0. */ - public static final FloatProperty RECENTS_GRID_PROGRESS=new FloatProperty("recentsGrid"){@Override public void setValue(RecentsView view,float gridProgress){view.setGridProgress(gridProgress);} + public static final FloatProperty RECENTS_GRID_PROGRESS = + new FloatProperty("recentsGrid") { + @Override + public void setValue(RecentsView view, float gridProgress) { + view.setGridProgress(gridProgress); + } - @Override public Float get(RecentsView view){return view.mGridProgress;}}; + @Override + public Float get(RecentsView view) { + return view.mGridProgress; + } + }; /** - * Alpha of the task thumbnail splash, where being in BackgroundAppState has a - * value of 1, and + * Alpha of the task thumbnail splash, where being in BackgroundAppState has a value of 1, and * being in any other state has a value of 0. */ - public static final FloatProperty TASK_THUMBNAIL_SPLASH_ALPHA=new FloatProperty("taskThumbnailSplashAlpha"){@Override public void setValue(RecentsView view,float taskThumbnailSplashAlpha){view.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);} + public static final FloatProperty TASK_THUMBNAIL_SPLASH_ALPHA = + new FloatProperty("taskThumbnailSplashAlpha") { + @Override + public void setValue(RecentsView view, float taskThumbnailSplashAlpha) { + view.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); + } - @Override public Float get(RecentsView view){return view.mTaskThumbnailSplashAlpha;}}; + @Override + public Float get(RecentsView view) { + return view.mTaskThumbnailSplashAlpha; + } + }; // OverScroll constants private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270; @@ -385,8 +477,7 @@ public abstract class RecentsView mScrollListeners = new ArrayList<>(); - // The threshold at which we update the SystemUI flags when animating from the - // task into the app + // The threshold at which we update the SystemUI flags when animating from the task into the app public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f; protected final CONTAINER_TYPE mContainer; @@ -424,14 +513,12 @@ public abstract class RecentsView(()->PackageManagerWrapper.getInstance().getActivityInfo(taskKey.getComponent(),taskKey.userId)==null,MAIN_EXECUTOR,apkRemoved->{if(apkRemoved){dismissTask(taskId);}else{mModel.isTaskRemoved(taskKey.id,taskRemoved->{if(taskRemoved){dismissTask(taskId);}},RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter()));}}));}}; + TaskView taskView = getTaskViewByTaskId(taskId); + if (taskView == null) { + return; + } + Task.TaskKey taskKey = taskView.getFirstTask().key; + UI_HELPER_EXECUTOR.execute(new CancellableTask<>( + () -> PackageManagerWrapper.getInstance() + .getActivityInfo(taskKey.getComponent(), taskKey.userId) == null, + MAIN_EXECUTOR, + apkRemoved -> { + if (apkRemoved) { + dismissTask(taskId); + } else { + mModel.isTaskRemoved(taskKey.id, taskRemoved -> { + if (taskRemoved) { + dismissTask(taskId); + } + }, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter())); + } + })); + } + }; - private final PinnedStackAnimationListener mIPipAnimationListener = new PinnedStackAnimationListener(); + private final PinnedStackAnimationListener mIPipAnimationListener = + new PinnedStackAnimationListener(); private int mPipCornerRadius; private int mPipShadowRadius; - // Used to keep track of the last requested task list id, so that we do not - // request to load the + // Used to keep track of the last requested task list id, so that we do not request to load the // tasks again if we have already requested it and the task list has not changed private int mTaskListChangeId = -1; // Only valid until the launcher state changes to NORMAL /** - * ID for the current running TaskView view, unique amongst TaskView instances. - * ID's are set - * through {@link #getTaskViewFromPool(boolean)} and incremented by - * {@link #mTaskViewIdCount} + * ID for the current running TaskView view, unique amongst TaskView instances. ID's are set + * through {@link #getTaskViewFromPool(boolean)} and incremented by {@link #mTaskViewIdCount} */ protected int mRunningTaskViewId = -1; private int mTaskViewIdCount; + protected boolean mRunningTaskTileHidden; + @Nullable + private Task[] mTmpRunningTasks; + protected int mFocusedTaskViewId = -1; private boolean mTaskIconScaledDown = false; private boolean mRunningTaskShowScreenshot = false; @@ -540,10 +671,8 @@ public abstract class RecentsView { Animator animatorFade = getStateManager().createStateElementAnimation( RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM, 1f, 0f); @@ -775,8 +903,7 @@ public abstract class RecentsView remoteTargetHandle.getTransformParams() .setSyncTransactionApplier(mSyncTransactionApplier)); RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); @@ -1027,10 +1146,8 @@ public abstract class RecentsView remoteTargetHandle.getTransformParams() .setSyncTransactionApplier(null)); executeSideTaskLaunchCallback(); @@ -1050,11 +1167,9 @@ public abstract class RecentsView 0 @@ -1374,8 +1474,7 @@ public abstract class RecentsView