Merge "Fixing AOSP tests using TAPL" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index cccf4e4..0477dab 100644
--- a/Android.mk
+++ b/Android.mk
@@ -182,7 +182,9 @@
endif
LOCAL_MODULE := Launcher3QuickStepLib
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ Launcher3CommonDepsLib \
+ SecondaryDisplayLauncherLib
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
diff --git a/go/quickstep/res/.keep b/go/quickstep/res/.keep
deleted file mode 100644
index e69de29..0000000
--- a/go/quickstep/res/.keep
+++ /dev/null
diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml
new file mode 100644
index 0000000..122fadf
--- /dev/null
+++ b/go/quickstep/res/layout/icon_recents_root_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+<!-- TODO(114136250): Remove this temporary placeholder view for Go recents -->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="Stub!"
+ android:textSize="40sp"/>
\ No newline at end of file
diff --git a/go/quickstep/res/layout/overview_panel.xml b/go/quickstep/res/layout/overview_panel.xml
new file mode 100644
index 0000000..601edce
--- /dev/null
+++ b/go/quickstep/res/layout/overview_panel.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+<fragment android:name="com.android.quickstep.IconRecentsFragment"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/low_ram_recents_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/go/quickstep/src/.keep b/go/quickstep/src/.keep
deleted file mode 100644
index e69de29..0000000
--- a/go/quickstep/src/.keep
+++ /dev/null
diff --git a/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java b/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java
new file mode 100644
index 0000000..facf0d2
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 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 com.android.quickstep;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+public class IconRecentsFragment extends Fragment {
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.icon_recents_root_view, container, false);
+ }
+}
\ No newline at end of file
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 882af59..af1b353 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -253,19 +253,24 @@
badge.draw(target);
}
+ private Bitmap createIconBitmap(Drawable icon, float scale) {
+ return createIconBitmap(icon, scale, mIconBitmapSize);
+ }
+
/**
+ * @param icon drawable that should be flattened to a bitmap
* @param scale the scale to apply before drawing {@param icon} on the canvas
*/
- private Bitmap createIconBitmap(Drawable icon, float scale) {
- Bitmap bitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,
- Bitmap.Config.ARGB_8888);
+ public Bitmap createIconBitmap(Drawable icon, float scale, int size) {
+ Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+
mCanvas.setBitmap(bitmap);
mOldBounds.set(icon.getBounds());
if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
- int offset = Math.max((int) Math.ceil(BLUR_FACTOR * mIconBitmapSize),
- Math.round(mIconBitmapSize * (1 - scale) / 2 ));
- icon.setBounds(offset, offset, mIconBitmapSize - offset, mIconBitmapSize - offset);
+ int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
+ Math.round(size * (1 - scale) / 2 ));
+ icon.setBounds(offset, offset, size - offset, size - offset);
icon.draw(mCanvas);
} else {
if (icon instanceof BitmapDrawable) {
@@ -275,8 +280,8 @@
bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
}
}
- int width = mIconBitmapSize;
- int height = mIconBitmapSize;
+ int width = size;
+ int height = size;
int intrinsicWidth = icon.getIntrinsicWidth();
int intrinsicHeight = icon.getIntrinsicHeight();
@@ -289,11 +294,11 @@
width = (int) (height * ratio);
}
}
- final int left = (mIconBitmapSize - width) / 2;
- final int top = (mIconBitmapSize - height) / 2;
+ final int left = (size - width) / 2;
+ final int top = (size - height) / 2;
icon.setBounds(left, top, left + width, top + height);
mCanvas.save();
- mCanvas.scale(scale, scale, mIconBitmapSize / 2, mIconBitmapSize / 2);
+ mCanvas.scale(scale, scale, size / 2, size / 2);
icon.draw(mCanvas);
mCanvas.restore();
diff --git a/quickstep/recents_ui_overrides/res/.keep b/quickstep/recents_ui_overrides/res/.keep
deleted file mode 100644
index e69de29..0000000
--- a/quickstep/recents_ui_overrides/res/.keep
+++ /dev/null
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
similarity index 100%
rename from quickstep/res/layout/overview_panel.xml
rename to quickstep/recents_ui_overrides/res/layout/overview_panel.xml
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index a1cee82..cb5152a 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -215,7 +215,8 @@
playIconAnimators(anim, v, windowTargetBounds, !isAllOpeningTargetTrs);
if (launcherClosing) {
Pair<AnimatorSet, Runnable> launcherContentAnimator =
- getLauncherContentAnimator(true /* isAppOpening */);
+ getLauncherContentAnimator(true /* isAppOpening */,
+ new float[] {0, mContentTransY});
anim.play(launcherContentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -350,17 +351,16 @@
*
* @param isAppOpening True when this is called when an app is opening.
* False when this is called when an app is closing.
+ * @param trans Array that contains the start and end translation values for the content.
*/
- private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening) {
+ private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
+ float[] trans) {
AnimatorSet launcherAnimator = new AnimatorSet();
Runnable endListener;
float[] alphas = isAppOpening
? new float[] {1, 0}
: new float[] {0, 1};
- float[] trans = isAppOpening
- ? new float[] {0, mContentTransY}
- : new float[] {-mContentTransY, 0};
if (mLauncher.isInState(ALL_APPS)) {
// All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
@@ -443,7 +443,16 @@
private void playIconAnimators(AnimatorSet appOpenAnimator, View v, Rect windowTargetBounds,
boolean toggleVisibility) {
final boolean isBubbleTextView = v instanceof BubbleTextView;
- mFloatingView = new View(mLauncher);
+ if (mFloatingView == null) {
+ mFloatingView = new View(mLauncher);
+ } else {
+ mFloatingView.setTranslationX(0);
+ mFloatingView.setTranslationY(0);
+ mFloatingView.setScaleX(1);
+ mFloatingView.setScaleY(1);
+ mFloatingView.setAlpha(1);
+ mFloatingView.setBackground(null);
+ }
if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
// Create a copy of the app icon
mFloatingView.setBackground(DrawableFactory.INSTANCE.get(mLauncher)
@@ -481,19 +490,17 @@
: viewLocationLeft;
LayoutParams lp = new LayoutParams(rect.width(), rect.height());
lp.ignoreInsets = true;
- lp.setMarginStart(viewLocationStart);
+ lp.leftMargin = viewLocationStart;
lp.topMargin = viewLocationTop;
mFloatingView.setLayoutParams(lp);
// Set the properties here already to make sure they'are available when running the first
// animation frame.
- mFloatingView.setLeft(viewLocationLeft);
- mFloatingView.setTop(viewLocationTop);
- mFloatingView.setRight(viewLocationLeft + rect.width());
- mFloatingView.setBottom(viewLocationTop + rect.height());
+ mFloatingView.layout(viewLocationLeft, viewLocationTop,
+ viewLocationLeft + rect.width(), viewLocationTop + rect.height());
// Swap the two views in place.
- ((ViewGroup) mDragLayer.getParent()).addView(mFloatingView);
+ ((ViewGroup) mDragLayer.getParent()).getOverlay().add(mFloatingView);
if (toggleVisibility) {
v.setVisibility(View.INVISIBLE);
}
@@ -562,7 +569,7 @@
((BubbleTextView) v).setStayPressed(false);
}
v.setVisibility(View.VISIBLE);
- ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
+ ((ViewGroup) mDragLayer.getParent()).getOverlay().remove(mFloatingView);
}
});
}
@@ -681,10 +688,13 @@
RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
- new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(),
+ new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(false /* fromUnlock */),
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
- // TODO: Transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+ definition.addRemoteAnimation(
+ WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+ new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(true /* fromUnlock */),
+ CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
}
}
@@ -697,7 +707,7 @@
* @return Runner that plays when user goes to Launcher
* ie. pressing home, swiping up from nav bar.
*/
- private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
+ private RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
return new LauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */) {
@Override
public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
@@ -723,7 +733,9 @@
if (anim == null) {
anim = new AnimatorSet();
- anim.play(getClosingWindowAnimators(targetCompats));
+ anim.play(fromUnlock
+ ? getUnlockWindowAnimator(targetCompats)
+ : getClosingWindowAnimators(targetCompats));
// Normally, we run the launcher content animation when we are transitioning
// home, but if home is already visible, then we don't want to animate the
@@ -737,7 +749,21 @@
|| mLauncher.isForceInvisible()) {
// Only register the content animation for cancellation when state changes
mLauncher.getStateManager().setCurrentAnimation(anim);
- createLauncherResumeAnimation(anim);
+ if (fromUnlock) {
+ Pair<AnimatorSet, Runnable> contentAnimator =
+ getLauncherContentAnimator(false /* isAppOpening */,
+ new float[] {mContentTransY, 0});
+ contentAnimator.first.setStartDelay(0);
+ anim.play(contentAnimator.first);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ contentAnimator.second.run();
+ }
+ });
+ } else {
+ createLauncherResumeAnimation(anim);
+ }
}
}
@@ -748,6 +774,31 @@
}
/**
+ * Animator that controls the transformations of the windows when unlocking the device.
+ */
+ private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] targets) {
+ SyncRtSurfaceTransactionApplierCompat surfaceApplier =
+ new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
+ ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
+ unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
+ float cornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
+ unlockAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ SurfaceParams[] params = new SurfaceParams[targets.length];
+ for (int i = targets.length - 1; i >= 0; i--) {
+ RemoteAnimationTargetCompat target = targets[i];
+ params[i] = new SurfaceParams(target.leash, 1f, null,
+ target.sourceContainerBounds,
+ RemoteAnimationProvider.getLayer(target, MODE_OPENING), cornerRadius);
+ }
+ surfaceApplier.scheduleApply(params);
+ }
+ });
+ return unlockAnimator;
+ }
+
+ /**
* Animator that controls the transformations of the windows the targets that are closing.
*/
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
@@ -801,7 +852,8 @@
private void createLauncherResumeAnimation(AnimatorSet anim) {
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
Pair<AnimatorSet, Runnable> contentAnimator =
- getLauncherContentAnimator(false /* isAppOpening */);
+ getLauncherContentAnimator(false /* isAppOpening */,
+ new float[] {-mContentTransY, 0});
contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
anim.play(contentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 1906286..25e0af2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
@@ -45,7 +46,11 @@
@Override
public void onStateEnabled(Launcher launcher) {
- AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ AbstractFloatingView.closeAllOpenViews(launcher);
+ } else {
+ AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW);
+ }
dispatchWindowStateChanged(launcher);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 753f73a..50af4a1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -49,7 +50,7 @@
private static final String TAG = "OverviewSwipeController";
// Progress after which the transition is assumed to be a success in case user does not fling
- private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+ public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
protected final T mActivity;
private final SwipeDetector mDetector;
@@ -231,6 +232,12 @@
mFlingBlockCheck.onEvent();
}
mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
+
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (mRecentsView.getCurrentPage() != 0 || isGoingUp) {
+ mRecentsView.redrawLiveTile(true);
+ }
+ }
return true;
}
@@ -267,6 +274,13 @@
anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
anim.setDuration(animationDuration);
anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ anim.addUpdateListener(valueAnimator -> {
+ if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) {
+ mRecentsView.redrawLiveTile(true);
+ }
+ });
+ }
anim.start();
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 4e79fed..0ef67c4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -44,6 +44,8 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
@@ -58,6 +60,9 @@
public class UiFactory {
+ private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
+ WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
+
public static TouchController[] createTouchControllers(Launcher launcher) {
boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
.isSwipeUpGestureEnabled();
@@ -175,10 +180,10 @@
LauncherState state = launcher.getStateManager().getState();
if (!OverviewInteractionState.INSTANCE.get(launcher).swipeGestureInitializing()) {
DeviceProfile profile = launcher.getDeviceProfile();
- WindowManagerWrapper.getInstance().setShelfHeight(
- (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
- && !profile.isVerticalBarLayout(),
- profile.hotseatBarSizePx);
+ boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
+ && !profile.isVerticalBarLayout();
+ UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
+ visible ? 1 : 0, profile.hotseatBarSizePx);
}
if (state == NORMAL) {
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 4646fd7f..8293083 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
@@ -44,6 +43,8 @@
import android.os.Handler;
import android.os.Looper;
import android.view.View;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
@@ -53,9 +54,9 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.TestProtocol;
-import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
@@ -77,9 +78,6 @@
import java.util.function.BiPredicate;
import java.util.function.Consumer;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
/**
* Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
*/
@@ -150,11 +148,13 @@
*/
int getContainerType();
+ boolean isInLiveTileMode();
+
class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
@Override
public LayoutListener createLayoutListener(Launcher activity) {
- return new LauncherLayoutListener(activity);
+ return LauncherLayoutListener.resetAndGet(activity);
}
@Override
@@ -295,8 +295,8 @@
AnimatorSet anim = new AnimatorSet();
if (!activity.getDeviceProfile().isVerticalBarLayout()) {
- AllAppsTransitionController controller = activity.getAllAppsController();
- ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(controller, ALL_APPS_PROGRESS,
+ Animator shiftAnim = new SpringObjectAnimator(activity.getAllAppsController(),
+ activity.getAllAppsController().getShiftRange(),
fromState.getVerticalProgress(activity),
endState.getVerticalProgress(activity));
shiftAnim.setInterpolator(LINEAR);
@@ -442,6 +442,13 @@
return launcher != null ? launcher.getStateManager().getState().containerType
: LauncherLogProto.ContainerType.APP;
}
+
+ @Override
+ public boolean isInLiveTileMode() {
+ Launcher launcher = getCreatedActivity();
+ return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
+ launcher.isStarted();
+ }
}
class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
@@ -627,6 +634,11 @@
public int getContainerType() {
return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
}
+
+ @Override
+ public boolean isInLiveTileMode() {
+ return false;
+ }
}
interface LayoutListener {
diff --git a/quickstep/src/com/android/quickstep/LongSwipeHelper.java b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
index 16214dd..80d37ae 100644
--- a/quickstep/src/com/android/quickstep/LongSwipeHelper.java
+++ b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_OVERSHOOT_DURATION;
@@ -41,7 +42,6 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
* Utility class to handle long swipe from an app.
@@ -113,7 +113,7 @@
* MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
- if (blockedFling && !toAllApps) {
+ if (blockedFling && !toAllApps && !QUICKSTEP_SPRINGS.get()) {
Interpolators.OvershootParams overshoot = new OvershootParams(currentFraction,
currentFraction, endProgress, velocityPxPerMs, (int) mMaxSwipeDistance);
duration = (overshoot.duration + duration);
@@ -145,7 +145,12 @@
ValueAnimator animator = mAnimator.getAnimationPlayer();
animator.setDuration(duration).setInterpolator(interpolator);
animator.setFloatValues(currentFraction, endProgress);
- animator.start();
+
+ if (QUICKSTEP_SPRINGS.get()) {
+ mAnimator.dispatchOnStartWithVelocity(endProgress, velocityPxPerMs);
+ } else {
+ animator.start();
+ }
}
private void onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback) {
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 95be188..e27af2a 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -21,11 +21,9 @@
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
-
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
-import static com.android.systemui.shared.system.ActivityManagerWrapper
- .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.annotation.TargetApi;
@@ -192,7 +190,7 @@
if (mPassedInitialSlop && mInteractionHandler != null) {
// Move
- mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
+ dispatchMotion(ev, displacement - mStartDisplacement);
}
break;
}
@@ -207,6 +205,14 @@
}
}
+ private void dispatchMotion(MotionEvent ev, float displacement) {
+ mInteractionHandler.updateDisplacement(displacement);
+ boolean isLandscape = isNavBarOnLeft() || isNavBarOnRight();
+ if (!isLandscape) {
+ mInteractionHandler.dispatchMotionEventToRecentsView(ev);
+ }
+ }
+
private void notifyGestureStarted() {
if (mInteractionHandler == null) {
return;
@@ -297,15 +303,16 @@
*/
private void finishTouchTracking(MotionEvent ev) {
if (mPassedInitialSlop && mInteractionHandler != null) {
- mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
+ dispatchMotion(ev, getDisplacement(ev) - mStartDisplacement);
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
- float velocity = isNavBarOnRight() ? mVelocityTracker.getXVelocity(mActivePointerId)
- : isNavBarOnLeft() ? -mVelocityTracker.getXVelocity(mActivePointerId)
+ float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+ float velocity = isNavBarOnRight() ? velocityX
+ : isNavBarOnLeft() ? -velocityX
: mVelocityTracker.getYVelocity(mActivePointerId);
- mInteractionHandler.onGestureEnded(velocity);
+ mInteractionHandler.onGestureEnded(velocity, velocityX);
} else {
// Since we start touch tracking on DOWN, we may reach this state without actually
// starting the gesture. In that case, just cleanup immediately.
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index da5c4fa..db0150e 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASK_STABILIZER;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -124,6 +125,12 @@
mActivityControlHelper = controlHelper;
mTouchInteractionLog = touchInteractionLog;
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (mRecentsView.getRunningTaskView() != null) {
+ mRecentsView.getRunningTaskView().setShowScreenshot(false);
+ }
+ }
+
if (mIsQuickSwitch) {
mShouldSwitchToNext = true;
mPrevProgressDelta = 0;
@@ -342,6 +349,7 @@
if (action != null) {
action.run();
}
+ mRecentsView.setEnableDrawingLiveTile(true);
}
public void onTaskRemoved(int taskId) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index 2f3cb5f..042afea 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -97,8 +97,8 @@
}
/**
- * @param onFinishComplete A callback that runs after the animation controller has finished
- * on the background thread.
+ * @param onFinishComplete A callback that runs on the main thread after the animation
+ * controller has finished on the background thread.
*/
public void finish(boolean toHome, Runnable onFinishComplete) {
if (!toHome) {
@@ -128,7 +128,7 @@
controller.finish(toHome);
if (onFinishComplete != null) {
- onFinishComplete.run();
+ mMainThreadExecutor.execute(onFinishComplete);
}
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 75ccba6..442b106 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -67,7 +67,6 @@
private float mWindowCornerRadius = -1;
-
private RecentsModel(Context context) {
mContext = context;
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index a42ee09..7a6b135 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -22,6 +22,7 @@
import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
import static android.view.MotionEvent.ACTION_UP;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.systemui.shared.system.ActivityManagerWrapper
.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
@@ -252,6 +253,11 @@
mOverviewCommandHelper.getActivityControlHelper().isResumed()) {
return OverviewTouchConsumer.newInstance(
mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog);
+ } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
+ mOverviewCommandHelper.getActivityControlHelper().isInLiveTileMode()) {
+ return OverviewTouchConsumer.newInstance(
+ mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog,
+ false /* waitForWindowAvailable */);
} else {
if (tracker == null) {
tracker = VelocityTracker.obtain();
@@ -298,9 +304,11 @@
private float mLastProgress = 0;
private boolean mStartPending = false;
private boolean mEndPending = false;
+ private boolean mWaitForWindowAvailable;
OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity,
- boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) {
+ boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog,
+ boolean waitForWindowAvailable) {
mActivityHelper = activityHelper;
mActivity = activity;
mTarget = activity.getDragLayer();
@@ -311,6 +319,8 @@
.getQuickScrubController();
mTouchInteractionLog = touchInteractionLog;
mTouchInteractionLog.setTouchConsumer(this);
+
+ mWaitForWindowAvailable = waitForWindowAvailable;
}
@Override
@@ -433,7 +443,11 @@
}
};
- mActivityHelper.executeOnWindowAvailable(mActivity, action);
+ if (mWaitForWindowAvailable) {
+ mActivityHelper.executeOnWindowAvailable(mActivity, action);
+ } else {
+ action.run();
+ }
}
@Override
@@ -461,12 +475,19 @@
public static TouchConsumer newInstance(ActivityControlHelper activityHelper,
boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) {
+ return newInstance(activityHelper, startingInActivityBounds, touchInteractionLog,
+ true /* waitForWindowAvailable */);
+ }
+
+ public static TouchConsumer newInstance(ActivityControlHelper activityHelper,
+ boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog,
+ boolean waitForWindowAvailable) {
BaseDraggingActivity activity = activityHelper.getCreatedActivity();
if (activity == null) {
return TouchConsumer.NO_OP;
}
return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds,
- touchInteractionLog);
+ touchInteractionLog, waitForWindowAvailable);
}
}
}
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index e951750..fd4585b 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -22,6 +22,8 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION;
import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_FROM_APP_START_DURATION;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
@@ -46,10 +48,12 @@
import android.os.UserHandle;
import android.util.Log;
import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnDrawListener;
import android.view.WindowManager;
import android.view.animation.Interpolator;
+
import androidx.annotation.AnyThread;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
@@ -109,7 +113,7 @@
// Interaction finish states
private static final int STATE_SCALED_CONTROLLER_RECENTS = 1 << 5;
- private static final int STATE_SCALED_CONTROLLER_APP = 1 << 6;
+ private static final int STATE_SCALED_CONTROLLER_LAST_TASK = 1 << 6;
private static final int STATE_HANDLER_INVALIDATED = 1 << 7;
private static final int STATE_GESTURE_STARTED_QUICKSTEP = 1 << 8;
@@ -127,7 +131,8 @@
private static final int STATE_SCREENSHOT_VIEW_SHOWN = 1 << 17;
private static final int STATE_RESUME_LAST_TASK = 1 << 18;
- private static final int STATE_ASSIST_DATA_RECEIVED = 1 << 19;
+ private static final int STATE_START_NEW_TASK = 1 << 19;
+ private static final int STATE_ASSIST_DATA_RECEIVED = 1 << 20;
private static final int LAUNCHER_UI_STATES =
@@ -153,7 +158,7 @@
"STATE_ACTIVITY_MULTIPLIER_COMPLETE",
"STATE_APP_CONTROLLER_RECEIVED",
"STATE_SCALED_CONTROLLER_RECENTS",
- "STATE_SCALED_CONTROLLER_APP",
+ "STATE_SCALED_CONTROLLER_LAST_TASK",
"STATE_HANDLER_INVALIDATED",
"STATE_GESTURE_STARTED_QUICKSTEP",
"STATE_GESTURE_STARTED_QUICKSCRUB",
@@ -166,6 +171,7 @@
"STATE_SCREENSHOT_CAPTURED",
"STATE_SCREENSHOT_VIEW_SHOWN",
"STATE_RESUME_LAST_TASK",
+ "STATE_START_NEW_TASK",
"STATE_ASSIST_DATA_RECEIVED",
};
@@ -173,7 +179,7 @@
public static final long MIN_SWIPE_DURATION = 80;
public static final long MIN_OVERSHOOT_DURATION = 120;
- public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
+ public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
private static final float SWIPE_DURATION_MULTIPLIER =
Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
@@ -190,6 +196,7 @@
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
// visible.
private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+ private boolean mDispatchedDownEvent;
// To avoid UI jump when gesture is started, we offset the animation by the threshold.
private float mShiftAtGestureStart = 0;
@@ -298,10 +305,12 @@
mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
this::sendRemoteAnimationsToAnimationFactory);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_APP,
+ mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_LAST_TASK,
this::resumeLastTaskForQuickstep);
mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
+ mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_APP_CONTROLLER_RECEIVED,
+ this::startNewTask);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_ACTIVITY_MULTIPLIER_COMPLETE
@@ -327,7 +336,7 @@
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED
- | STATE_SCALED_CONTROLLER_APP,
+ | STATE_SCALED_CONTROLLER_LAST_TASK,
this::notifyTransitionCancelled);
mStateCallback.addCallback(QUICK_SCRUB_START_UI_STATE, this::onQuickScrubStartUi);
@@ -339,9 +348,11 @@
mStateCallback.addCallback(LONG_SWIPE_ENTER_STATE, this::checkLongSwipeCanEnter);
mStateCallback.addCallback(LONG_SWIPE_START_STATE, this::checkLongSwipeCanStart);
- mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
- | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
- (b) -> mRecentsView.setRunningTaskHidden(!b));
+ if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
+ | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
+ (b) -> mRecentsView.setRunningTaskHidden(!b));
+ }
}
private void executeOnUiThread(Runnable action) {
@@ -410,6 +421,13 @@
SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, (applier) -> {
mSyncTransactionApplier = applier;
});
+ mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+ if (!mBgLongSwipeMode) {
+ updateFinalShift();
+ }
+ });
+ mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
+ mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
mQuickScrubController = mRecentsView.getQuickScrubController();
mLayoutListener = mActivityControlHelper.createLayoutListener(mActivity);
@@ -462,6 +480,7 @@
}
private void setupRecentsViewUi() {
+ mRecentsView.setEnableDrawingLiveTile(false);
mRecentsView.showTask(mRunningTaskId);
mRecentsView.setRunningTaskHidden(true);
mRecentsView.setRunningTaskIconScaledDown(true);
@@ -535,15 +554,39 @@
} else {
offsetX = res.getDimensionPixelSize(R.dimen.recents_page_spacing)
+ tempRect.rect.width();
- float distanceToReachEdge = mDp.widthPx / 2 + tempRect.rect.width() / 2 +
- res.getDimensionPixelSize(R.dimen.recents_page_spacing);
- float interpolation = Math.min(1, offsetX / distanceToReachEdge);
- scale = TaskView.getCurveScaleForInterpolation(interpolation);
+ scale = getTaskCurveScaleForOffsetX(offsetX, tempRect.rect.width());
}
mClipAnimationHelper.offsetTarget(scale, Utilities.isRtl(res) ? -offsetX : offsetX, offsetY,
QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR);
}
+ private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
+ float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 +
+ mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+ float interpolation = Math.min(1, offsetX / distanceToReachEdge);
+ return TaskView.getCurveScaleForInterpolation(interpolation);
+ }
+
+ @WorkerThread
+ public void dispatchMotionEventToRecentsView(MotionEvent event) {
+ if (mRecentsView == null) {
+ return;
+ }
+ // Pass the motion events to RecentsView to allow scrolling during swipe up.
+ if (mDispatchedDownEvent) {
+ mRecentsView.dispatchTouchEvent(event);
+ } else {
+ // The first event we dispatch should be ACTION_DOWN.
+ mDispatchedDownEvent = true;
+ MotionEvent downEvent = MotionEvent.obtain(event);
+ downEvent.setAction(MotionEvent.ACTION_DOWN);
+ int flags = downEvent.getEdgeFlags();
+ downEvent.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
+ mRecentsView.dispatchTouchEvent(downEvent);
+ downEvent.recycle();
+ }
+ }
+
@WorkerThread
public void updateDisplacement(float displacement) {
// We are moving in the negative x/y direction
@@ -588,11 +631,21 @@
RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
if (controller != null) {
+ float offsetX = 0;
+ if (mRecentsView != null && mInteractionType == INTERACTION_NORMAL) {
+ int startScroll = mRecentsView.getScrollForPage(mRecentsView.indexOfChild(
+ mRecentsView.getRunningTaskView()));
+ offsetX = startScroll - mRecentsView.getScrollX();
+ offsetX *= mRecentsView.getScaleX();
+ }
+ float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
+ mClipAnimationHelper.getTargetRect().width());
SyncRtSurfaceTransactionApplierCompat syncTransactionApplier
= Looper.myLooper() == mMainThreadHandler.getLooper()
? mSyncTransactionApplier
: null;
- mTransformParams.setProgress(shift).setSyncTransactionApplier(syncTransactionApplier);
+ mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale)
+ .setSyncTransactionApplier(syncTransactionApplier);
mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
mTransformParams);
@@ -608,6 +661,9 @@
private void updateFinalShiftUi() {
if (mRecentsAnimationWrapper.getController() != null && mLayoutListener != null) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mLayoutListener.open();
+ }
mLayoutListener.update(mCurrentShift.value > 1, mUiLongSwipeMode,
mClipAnimationHelper.getCurrentRectWithInsets(),
mClipAnimationHelper.getCurrentCornerRadius());
@@ -621,10 +677,17 @@
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
}
}
- // Update insets of the next previous task, as we might switch to it.
- TaskView nextTaskView = mRecentsView == null ? null : mRecentsView.getNextTaskView();
- if (mInteractionType == INTERACTION_NORMAL && nextTaskView != null) {
- nextTaskView.setFullscreenProgress(1 - mCurrentShift.value);
+ // Update insets of the adjacent tasks, as we might switch to them.
+ int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex();
+ if (mInteractionType == INTERACTION_NORMAL && runningTaskIndex >= 0) {
+ TaskView nextTaskView = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
+ TaskView prevTaskView = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
+ if (nextTaskView != null) {
+ nextTaskView.setFullscreenProgress(1 - mCurrentShift.value);
+ }
+ if (prevTaskView != null) {
+ prevTaskView.setFullscreenProgress(1 - mCurrentShift.value);
+ }
}
if (mLauncherTransitionController == null || mLauncherTransitionController
@@ -714,7 +777,7 @@
}
@WorkerThread
- public void onGestureEnded(float endVelocity) {
+ public void onGestureEnded(float endVelocity, float velocityX) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
@@ -723,9 +786,9 @@
mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
if (mBgLongSwipeMode) {
- executeOnUiThread(() -> onLongSwipeGestureFinishUi(endVelocity, isFling));
+ executeOnUiThread(() -> onLongSwipeGestureFinishUi(endVelocity, isFling, velocityX));
} else {
- handleNormalGestureEnd(endVelocity, isFling);
+ handleNormalGestureEnd(endVelocity, isFling, velocityX);
}
}
@@ -735,23 +798,30 @@
if (mLauncherTransitionController != null) {
mLauncherTransitionController.getAnimationPlayer().end();
}
- // Hide the task view, if not already hidden
- setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+ if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ // Hide the task view, if not already hidden
+ setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+ }
return OverviewTouchConsumer.newInstance(mActivityControlHelper, true,
mTouchInteractionLog);
}
- private void handleNormalGestureEnd(float endVelocity, boolean isFling) {
+ private void handleNormalGestureEnd(float endVelocity, boolean isFling, float velocityX) {
float velocityPxPerMs = endVelocity / 1000;
+ float velocityXPxPerMs = velocityX / 1000;
long duration = MAX_SWIPE_DURATION;
float currentShift = mCurrentShift.value;
final boolean goingToHome;
float endShift;
final float startShift;
Interpolator interpolator = DEACCEL;
+ final int nextPage = mRecentsView != null ? mRecentsView.getNextPage() : -1;
+ final int runningTaskIndex = mRecentsView != null ? mRecentsView.getRunningTaskIndex() : -1;
+ boolean goingToNewTask = mRecentsView != null && nextPage != runningTaskIndex;
+ final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW;
if (!isFling) {
- goingToHome = currentShift >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted;
+ goingToHome = reachedOverviewThreshold && mGestureStarted;
endShift = goingToHome ? 1 : 0;
long expectedDuration = Math.abs(Math.round((endShift - currentShift)
* MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
@@ -759,7 +829,9 @@
startShift = currentShift;
interpolator = goingToHome ? OVERSHOOT_1_2 : DEACCEL;
} else {
- goingToHome = endVelocity < 0;
+ // If user scrolled to a new task, only go to home (overview) if they already passed
+ // the overview threshold. Otherwise, we'll snap to the new task and launch it.
+ goingToHome = endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold);
endShift = goingToHome ? 1 : 0;
startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
* SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
@@ -786,9 +858,29 @@
}
if (goingToHome) {
mRecentsAnimationWrapper.enableTouchProxy();
+ } else if (goingToNewTask) {
+ // We aren't goingToHome, and user scrolled/flung to a new task; snap to the closest
+ // task in that direction and launch it (in startNewTask()).
+ int taskToLaunch = runningTaskIndex + (nextPage > runningTaskIndex ? 1 : - 1);
+ if (taskToLaunch >= mRecentsView.getTaskViewCount()) {
+ // Scrolled to Clear all button, snap back to current task and resume it.
+ mRecentsView.snapToPage(runningTaskIndex, Math.toIntExact(duration));
+ goingToNewTask = false;
+ } else {
+ float distance = Math.abs(mRecentsView.getScrollForPage(taskToLaunch)
+ - mRecentsView.getScrollX());
+ int durationX = (int) Math.abs(distance / velocityXPxPerMs);
+ if (durationX > MAX_SWIPE_DURATION) {
+ durationX = Math.toIntExact(MAX_SWIPE_DURATION);
+ }
+ interpolator = Interpolators.scrollInterpolatorForVelocity(velocityXPxPerMs);
+ mRecentsView.snapToPage(taskToLaunch, durationX, interpolator);
+ duration = Math.max(duration, durationX);
+ }
}
- animateToProgress(startShift, endShift, duration, interpolator, goingToHome);
+ animateToProgress(startShift, endShift, duration, interpolator, goingToHome,
+ goingToNewTask, velocityPxPerMs);
}
private void doLogGesture(boolean toLauncher) {
@@ -813,23 +905,28 @@
}
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
- private void animateToProgress(float start, float end, long duration,
- Interpolator interpolator, boolean goingToHome) {
+ private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
+ boolean goingToHome, boolean goingToNewTask, float velocityPxPerMs) {
mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
- interpolator, goingToHome));
+ interpolator, goingToHome, goingToNewTask, velocityPxPerMs));
}
private void animateToProgressInternal(float start, float end, long duration,
- Interpolator interpolator, boolean goingToHome) {
+ Interpolator interpolator, boolean goingToHome, boolean goingToNewTask,
+ float velocityPxPerMs) {
mIsGoingToHome = goingToHome;
ObjectAnimator anim = mCurrentShift.animateToValue(start, end).setDuration(duration);
anim.setInterpolator(interpolator);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
+ int recentsState = STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
+ | STATE_SCREENSHOT_VIEW_SHOWN;
setStateOnUiThread(mIsGoingToHome
- ? (STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
- | STATE_SCREENSHOT_VIEW_SHOWN) : STATE_SCALED_CONTROLLER_APP);
+ ? recentsState
+ : goingToNewTask
+ ? STATE_START_NEW_TASK
+ : STATE_SCALED_CONTROLLER_LAST_TASK);
}
});
anim.start();
@@ -854,7 +951,12 @@
mLauncherTransitionController.dispatchSetInterpolator(Interpolators.mapToProgress(
interpolator, adjustedStart, end));
mLauncherTransitionController.getAnimationPlayer().setDuration(adjustedDuration);
- mLauncherTransitionController.getAnimationPlayer().start();
+
+ if (QUICKSTEP_SPRINGS.get()) {
+ mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs);
+ } else {
+ mLauncherTransitionController.getAnimationPlayer().start();
+ }
}
});
}
@@ -872,6 +974,18 @@
mTouchInteractionLog.finishRecentsAnimation(false);
}
+ @UiThread
+ private void startNewTask() {
+ // Launch the task user scrolled to (mRecentsView.getNextPage()).
+ mRecentsAnimationWrapper.finish(true /* toHome */, () -> {
+ mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
+ result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
+ mMainThreadHandler);
+ });
+ mTouchInteractionLog.finishRecentsAnimation(false);
+ doLogGesture(false /* toLauncher */);
+ }
+
public void reset() {
if (mInteractionType != INTERACTION_QUICK_SCRUB) {
// Only invalidate the handler if we are not quick scrubbing, otherwise, it will be
@@ -889,6 +1003,10 @@
mActivityInitListener.unregister();
mTaskSnapshot = null;
+
+ if (mRecentsView != null) {
+ mRecentsView.setOnScrollChangeListener(null);
+ }
}
private void invalidateHandlerWithLauncher() {
@@ -913,57 +1031,66 @@
}
public void layoutListenerClosed() {
+ mRecentsView.setRunningTaskHidden(false);
if (mWasLauncherAlreadyVisible && mLauncherTransitionController != null) {
mLauncherTransitionController.setPlayFraction(1);
}
- mRecentsView.setRunningTaskHidden(false);
+ mRecentsView.setEnableDrawingLiveTile(true);
}
private void switchToScreenshot() {
- boolean finishTransitionPosted = false;
- RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
- if (controller != null) {
- // Update the screenshot of the task
- if (mTaskSnapshot == null) {
- mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
- }
- TaskView taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
- if (taskView != null) {
- // Defer finishing the animation until the next launcher frame with the
- // new thumbnail
- finishTransitionPosted = new WindowCallbacksCompat(taskView) {
-
- // The number of frames to defer until we actually finish the animation
- private int mDeferFrameCount = 2;
-
- @Override
- public void onPostDraw(Canvas canvas) {
- if (mDeferFrameCount > 0) {
- mDeferFrameCount--;
- // Workaround, detach and reattach to invalidate the root node for
- // another draw
- detach();
- attach();
- taskView.invalidate();
- return;
- }
-
- setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- detach();
- }
- }.attach();
- }
- }
- if (!finishTransitionPosted) {
- // If we haven't posted a draw callback, set the state immediately.
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ } else {
+ boolean finishTransitionPosted = false;
+ RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+ if (controller != null) {
+ // Update the screenshot of the task
+ if (mTaskSnapshot == null) {
+ mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
+ }
+ TaskView taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
+ if (taskView != null) {
+ // Defer finishing the animation until the next launcher frame with the
+ // new thumbnail
+ finishTransitionPosted = new WindowCallbacksCompat(taskView) {
+
+ // The number of frames to defer until we actually finish the animation
+ private int mDeferFrameCount = 2;
+
+ @Override
+ public void onPostDraw(Canvas canvas) {
+ if (mDeferFrameCount > 0) {
+ mDeferFrameCount--;
+ // Workaround, detach and reattach to invalidate the root node for
+ // another draw
+ detach();
+ attach();
+ taskView.invalidate();
+ return;
+ }
+
+ setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ detach();
+ }
+ }.attach();
+ }
+ }
+ if (!finishTransitionPosted) {
+ // If we haven't posted a draw callback, set the state immediately.
+ setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ }
}
}
private void finishCurrentTransitionToHome() {
- synchronized (mRecentsAnimationWrapper) {
- mRecentsAnimationWrapper.finish(true /* toHome */,
- () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ } else {
+ synchronized (mRecentsAnimationWrapper) {
+ mRecentsAnimationWrapper.finish(true /* toHome */,
+ () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+ }
}
mTouchInteractionLog.finishRecentsAnimation(true);
}
@@ -999,7 +1126,8 @@
long duration = FeatureFlags.QUICK_SWITCH.get()
? QUICK_SWITCH_FROM_APP_START_DURATION
: QUICK_SCRUB_FROM_APP_START_DURATION;
- animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, true /* goingToHome */);
+ animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, true /* goingToHome */,
+ false /* goingToNewTask */, 1f);
}
private void onQuickScrubStartUi() {
@@ -1012,7 +1140,6 @@
mLauncherTransitionController.getAnimationPlayer().end();
mLauncherTransitionController = null;
}
- mLayoutListener.finish();
mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false,
mTouchInteractionLog);
@@ -1025,6 +1152,7 @@
if (mQuickScrubBlocked) {
return;
}
+ mLayoutListener.finish();
mQuickScrubController.onFinishedTransitionToQuickScrub();
mRecentsView.animateUpRunningTaskIconScale();
@@ -1149,13 +1277,15 @@
mLongSwipeController = mActivityControlHelper.getLongSwipeController(
mActivity, mRunningTaskId);
onLongSwipeDisplacementUpdated();
- setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+ if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+ }
}
- private void onLongSwipeGestureFinishUi(float velocity, boolean isFling) {
+ private void onLongSwipeGestureFinishUi(float velocity, boolean isFling, float velocityX) {
if (!mUiLongSwipeMode || mLongSwipeController == null) {
mUiLongSwipeMode = false;
- handleNormalGestureEnd(velocity, isFling);
+ handleNormalGestureEnd(velocity, isFling, velocityX);
return;
}
mUiLongSwipeMode = false;
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index 431517a..ea73e2c 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -73,6 +73,9 @@
// if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
// app window coordinates.
private final RectF mSourceWindowClipInsets = new RectF();
+ // The insets to be used for clipping the app window. For live tile, we don't transform the clip
+ // relative to the target rect.
+ private final RectF mSourceWindowClipInsetsForLiveTile = new RectF();
// The bounds of launcher (not including insets) in device coordinates
public final Rect mHomeStackBounds = new Rect();
@@ -144,6 +147,7 @@
Math.max(scaledTargetRect.top, 0),
Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
+ mSourceWindowClipInsetsForLiveTile.set(mSourceWindowClipInsets);
mSourceRect.set(scaledTargetRect);
}
@@ -152,26 +156,32 @@
}
public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params) {
- RectF currentRect;
- mTmpRectF.set(mTargetRect);
- Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale);
- float offsetYProgress = mOffsetYInterpolator.getInterpolation(params.progress);
- float progress = mInterpolator.getInterpolation(params.progress);
- currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
+ if (params.currentRect == null) {
+ RectF currentRect;
+ mTmpRectF.set(mTargetRect);
+ Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale * params.offsetScale);
+ float offsetYProgress = mOffsetYInterpolator.getInterpolation(params.progress);
+ float progress = mInterpolator.getInterpolation(params.progress);
+ currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
+ currentRect.offset(params.offsetX, 0);
- synchronized (mTargetOffset) {
- // Stay lined up with the center of the target, since it moves for quick scrub.
- currentRect.offset(mTargetOffset.x * mOffsetScale * progress,
- mTargetOffset.y * offsetYProgress);
+ synchronized (mTargetOffset) {
+ // Stay lined up with the center of the target, since it moves for quick scrub.
+ currentRect.offset(mTargetOffset.x * mOffsetScale * progress,
+ mTargetOffset.y * offsetYProgress);
+ }
+
+ final RectF sourceWindowClipInsets = params.forLiveTile
+ ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets;
+ mClipRectF.left = sourceWindowClipInsets.left * progress;
+ mClipRectF.top = sourceWindowClipInsets.top * progress;
+ mClipRectF.right =
+ mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress);
+ mClipRectF.bottom =
+ mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress);
+ params.setCurrentRectAndTargetAlpha(currentRect, 1);
}
- mClipRectF.left = mSourceWindowClipInsets.left * progress;
- mClipRectF.top = mSourceWindowClipInsets.top * progress;
- mClipRectF.right =
- mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress);
- mClipRectF.bottom =
- mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress);
-
SurfaceParams[] surfaceParams = new SurfaceParams[targetSet.unfilteredApps.length];
for (int i = 0; i < targetSet.unfilteredApps.length; i++) {
RemoteAnimationTargetCompat app = targetSet.unfilteredApps[i];
@@ -180,23 +190,17 @@
float alpha = 1f;
int layer;
float cornerRadius = 0f;
- float scale = currentRect.width() / crop.width();
+ float scale = params.currentRect.width() / crop.width();
if (app.mode == targetSet.targetMode) {
if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
- mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
+ mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
mTmpMatrix.postTranslate(app.position.x, app.position.y);
mClipRectF.roundOut(crop);
- cornerRadius = Utilities.mapRange(progress, mWindowCornerRadius,
+ cornerRadius = Utilities.mapRange(params.progress, mWindowCornerRadius,
mTaskCornerRadius);
mCurrentCornerRadius = cornerRadius;
}
-
- if (app.isNotInRecents
- || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
- alpha = 1 - progress;
- }
-
- alpha = mTaskAlphaCallback.apply(app, alpha);
+ alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
} else {
crop = null;
@@ -209,7 +213,7 @@
cornerRadius / scale);
}
applySurfaceParams(params.syncTransactionApplier, surfaceParams);
- return currentRect;
+ return params.currentRect;
}
public RectF getCurrentRectWithInsets() {
@@ -353,14 +357,48 @@
public static class TransformParams {
float progress;
+ float offsetX;
+ float offsetScale;
+ @Nullable RectF currentRect;
+ float targetAlpha;
+ boolean forLiveTile;
+
SyncRtSurfaceTransactionApplierCompat syncTransactionApplier;
public TransformParams() {
progress = 0;
+ offsetX = 0;
+ offsetScale = 1;
+ currentRect = null;
+ targetAlpha = 0;
+ forLiveTile = false;
}
public TransformParams setProgress(float progress) {
this.progress = progress;
+ this.currentRect = null;
+ return this;
+ }
+
+ public TransformParams setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha) {
+ this.currentRect = currentRect;
+ this.targetAlpha = targetAlpha;
+ this.progress = 1;
+ return this;
+ }
+
+ public TransformParams setOffsetX(float offsetX) {
+ this.offsetX = offsetX;
+ return this;
+ }
+
+ public TransformParams setOffsetScale(float offsetScale) {
+ this.offsetScale = offsetScale;
+ return this;
+ }
+
+ public TransformParams setForLiveTile(boolean forLiveTile) {
+ this.forLiveTile = forLiveTile;
return this;
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
index 6ee305f..10283bf 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
@@ -53,6 +53,7 @@
private final RecentsView mParent;
private final View mIconView;
private final int[] mIconPos;
+ private final TaskView mTaskView;
private final TaskThumbnailView mThumbnailView;
@@ -65,6 +66,7 @@
public TaskViewDrawable(TaskView tv, RecentsView parent) {
mParent = parent;
+ mTaskView = tv;
mIconView = tv.getIconView();
mIconPos = new int[2];
mIconScale = mIconView.getScaleX();
@@ -139,4 +141,8 @@
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
+
+ public TaskView getTaskView() {
+ return mTaskView;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
index 8ec5361..e684889 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
@@ -16,6 +16,7 @@
package com.android.quickstep.views;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import android.graphics.Canvas;
@@ -39,23 +40,41 @@
public class LauncherLayoutListener extends AbstractFloatingView
implements Insettable, LayoutListener {
+ public static LauncherLayoutListener resetAndGet(Launcher launcher) {
+ LauncherRecentsView lrv = launcher.getOverviewPanel();
+ LauncherLayoutListener listener = lrv.mLauncherLayoutListener;
+ if (listener.isOpen()) {
+ listener.close(false);
+ }
+ listener.setHandler(null);
+ return listener;
+ }
+
private final Launcher mLauncher;
private final Paint mPaint = new Paint();
private WindowTransformSwipeHandler mHandler;
private RectF mCurrentRect;
private float mCornerRadius;
- public LauncherLayoutListener(Launcher launcher) {
+ private boolean mWillNotDraw;
+
+ /**
+ * package private
+ */
+ LauncherLayoutListener(Launcher launcher) {
super(launcher, null);
mLauncher = launcher;
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+ mWillNotDraw = willNotDraw();
+ super.setWillNotDraw(false);
}
@Override
public void update(boolean shouldFinish, boolean isLongSwipe, RectF currentRect,
- float cornerRadius) {
- if (shouldFinish) {
+ float cornerRadius) {
+ if (!ENABLE_QUICKSTEP_LIVE_TILE.get() && shouldFinish) {
finish();
return;
}
@@ -68,6 +87,12 @@
}
@Override
+ public void setWillNotDraw(boolean willNotDraw) {
+ // Prevent super call as that causes additional relayout.
+ mWillNotDraw = willNotDraw;
+ }
+
+ @Override
public void setHandler(WindowTransformSwipeHandler handler) {
mHandler = handler;
}
@@ -124,6 +149,8 @@
@Override
protected void onDraw(Canvas canvas) {
- canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+ if (!mWillNotDraw) {
+ canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 697bb4f..7389d65 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,10 +15,12 @@
*/
package com.android.quickstep.views;
+import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
import static com.android.launcher3.LauncherAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
@@ -32,6 +34,8 @@
import android.view.View;
import android.view.ViewDebug;
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
@@ -40,6 +44,7 @@
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
import com.android.quickstep.util.LayoutUtils;
/**
@@ -65,6 +70,9 @@
@ViewDebug.ExportedProperty(category = "launcher")
private float mTranslationYFactor;
+ private final TransformParams mTransformParams = new TransformParams();
+ final LauncherLayoutListener mLauncherLayoutListener;
+
public LauncherRecentsView(Context context) {
this(context, null);
}
@@ -76,11 +84,17 @@
public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setContentAlpha(0);
+ mLauncherLayoutListener = new LauncherLayoutListener(BaseActivity.fromContext(context));
}
@Override
protected void startHome() {
- mActivity.getStateManager().goToState(NORMAL);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ takeScreenshotAndFinishRecentsAnimation(true,
+ () -> mActivity.getStateManager().goToState(NORMAL));
+ } else {
+ mActivity.getStateManager().goToState(NORMAL);
+ }
}
@Override
@@ -92,6 +106,9 @@
public void setTranslationYFactor(float translationFactor) {
mTranslationYFactor = translationFactor;
setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ redrawLiveTile(false);
+ }
}
public float computeTranslationYForFactor(float translationYFactor) {
@@ -168,4 +185,45 @@
public boolean shouldUseMultiWindowTaskSizeStrategy() {
return mActivity.isInMultiWindowModeCompat();
}
+
+ @Override
+ public void scrollTo(int x, int y) {
+ super.scrollTo(x, y);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
+ redrawLiveTile(true);
+ }
+ }
+
+ @Override
+ public void redrawLiveTile(boolean mightNeedToRefill) {
+ AbstractFloatingView layoutListener = AbstractFloatingView.getTopOpenViewWithType(
+ mActivity, TYPE_QUICKSTEP_PREVIEW);
+ if (layoutListener != null && layoutListener.isOpen()) {
+ return;
+ }
+ if (mRecentsAnimationWrapper == null || mClipAnimationHelper == null) {
+ return;
+ }
+ TaskView taskView = getRunningTaskView();
+ if (taskView != null) {
+ taskView.getThumbnail().getGlobalVisibleRect(mTempRect);
+ int offsetX = (int) (mTaskWidth * taskView.getScaleX() * getScaleX()
+ - mTempRect.width());
+ int offsetY = (int) (mTaskHeight * taskView.getScaleY() * getScaleY()
+ - mTempRect.height());
+ if (((mCurrentPage != 0) || mightNeedToRefill) && offsetX > 0) {
+ mTempRect.right += offsetX;
+ }
+ if (mightNeedToRefill && offsetY > 0) {
+ mTempRect.top -= offsetY;
+ }
+ mTempRectF.set(mTempRect);
+ mTransformParams.setCurrentRectAndTargetAlpha(mTempRectF, taskView.getAlpha())
+ .setSyncTransactionApplier(mSyncTransactionApplier);
+ if (mRecentsAnimationWrapper.targetSet != null) {
+ mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
+ mTransformParams);
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 34b5748..4cfd8cb 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -17,14 +17,17 @@
package com.android.quickstep.views;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.uioverrides.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
-
+import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -40,6 +43,7 @@
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
@@ -60,11 +64,13 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ListView;
+
import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -77,6 +83,7 @@
import com.android.launcher3.util.Themes;
import com.android.quickstep.OverviewCallbacks;
import com.android.quickstep.QuickScrubController;
+import com.android.quickstep.RecentsAnimationWrapper;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
@@ -87,7 +94,10 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.WindowCallbacksCompat;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -97,7 +107,8 @@
*/
@TargetApi(Build.VERSION_CODES.P)
public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
- TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback {
+ TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
+ InvariantDeviceProfile.OnIDPChangeListener {
private static final String TAG = RecentsView.class.getSimpleName();
@@ -114,7 +125,14 @@
}
};
- private final Rect mTempRect = new Rect();
+ protected RecentsAnimationWrapper mRecentsAnimationWrapper;
+ protected ClipAnimationHelper mClipAnimationHelper;
+ protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
+ protected int mTaskWidth;
+ protected int mTaskHeight;
+ protected boolean mEnableDrawingLiveTile = false;
+ protected final Rect mTempRect = new Rect();
+ protected final RectF mTempRectF = new RectF();
private static final int DISMISS_TASK_DURATION = 300;
private static final int ADDITION_TASK_DURATION = 200;
@@ -136,6 +154,8 @@
// Keeps track of the previously known visible tasks for purposes of loading/unloading task data
private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
+ private final InvariantDeviceProfile mIdp;
+
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
@@ -278,6 +298,8 @@
mActivity = (T) BaseActivity.fromContext(context);
mQuickScrubController = new QuickScrubController(mActivity, this);
mModel = RecentsModel.INSTANCE.get(context);
+ mIdp = InvariantDeviceProfile.INSTANCE.get(context);
+
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
mClearAllButton.setOnClickListener(this::dismissAllTasks);
@@ -320,12 +342,23 @@
}
@Override
+ public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
+ if ((changeFlags & CHANGE_FLAG_ICON_PARAMS) == 0) {
+ return;
+ }
+ mModel.getIconCache().clear();
+ reset();
+ }
+
+ @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateTaskStackListenerState();
mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
+ mIdp.addOnChangeListener(this);
}
@Override
@@ -335,6 +368,8 @@
mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ mSyncTransactionApplier = null;
+ mIdp.removeOnChangeListener(this);
}
@Override
@@ -421,7 +456,8 @@
final boolean clearAllButtonDeadZoneConsumed =
mClearAllButton.getAlpha() == 1
&& mClearAllButtonDeadZoneRect.contains(x, y);
- if (!clearAllButtonDeadZoneConsumed
+ final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+ if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar
&& !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) {
mTouchDownToStartHome = true;
}
@@ -548,6 +584,8 @@
mInsets.set(insets);
DeviceProfile dp = mActivity.getDeviceProfile();
getTaskSize(dp, mTempRect);
+ mTaskWidth = mTempRect.width();
+ mTaskHeight = mTempRect.height();
// Keep this logic in sync with ActivityControlHelper.getTranslationYForQuickScrub.
mTempRect.top -= mTaskTopMargin;
@@ -681,11 +719,15 @@
protected abstract void startHome();
public void reset() {
+ setRunningTaskViewShowScreenshot(false);
mRunningTaskId = -1;
mRunningTaskTileHidden = false;
mIgnoreResetTaskId = -1;
mTaskListChangeId = -1;
+ mRecentsAnimationWrapper = null;
+ mClipAnimationHelper = null;
+
unloadVisibleTaskData();
setCurrentPage(0);
@@ -732,6 +774,11 @@
return getTaskView(mRunningTaskId);
}
+ public int getRunningTaskIndex() {
+ TaskView tv = getRunningTaskView();
+ return tv == null ? -1 : indexOfChild(tv);
+ }
+
/**
* Hides the tile associated with {@link #mRunningTaskId}
*/
@@ -752,17 +799,27 @@
setRunningTaskIconScaledDown(false);
setRunningTaskHidden(false);
+ setRunningTaskViewShowScreenshot(true);
mRunningTaskId = runningTaskId;
+ setRunningTaskViewShowScreenshot(false);
setRunningTaskIconScaledDown(runningTaskIconScaledDown);
setRunningTaskHidden(runningTaskTileHidden);
- TaskView tv = getRunningTaskView();
- setCurrentPage(tv == null ? 0 : indexOfChild(tv));
+ setCurrentPage(getRunningTaskIndex());
// Load the tasks (if the loading is already
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
}
+ private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ TaskView runningTaskView = getRunningTaskView();
+ if (runningTaskView != null) {
+ runningTaskView.setShowScreenshot(showScreenshot);
+ }
+ }
+ }
+
public void showNextTask() {
TaskView runningTaskView = getRunningTaskView();
if (runningTaskView == null) {
@@ -970,26 +1027,40 @@
}
mPendingAnimation = pendingAnimation;
- mPendingAnimation.addEndListener((onEndListener) -> {
- if (onEndListener.isSuccess) {
- if (shouldRemoveTask) {
- removeTask(taskView.getTask(), draggedIndex, onEndListener, true);
- }
- int pageToSnapTo = mCurrentPage;
- if (draggedIndex < pageToSnapTo || pageToSnapTo == (getTaskViewCount() - 1)) {
- pageToSnapTo -= 1;
- }
- removeView(taskView);
+ mPendingAnimation.addEndListener(new Consumer<PendingAnimation.OnEndListener>() {
+ @Override
+ public void accept(PendingAnimation.OnEndListener onEndListener) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
+ taskView.isRunningTask() && onEndListener.isSuccess) {
+ finishRecentsAnimation(true /* toHome */, () -> onEnd(onEndListener));
+ } else {
+ onEnd(onEndListener);
+ }
+ }
- if (getTaskViewCount() == 0) {
- removeView(mClearAllButton);
- startHome();
- } else {
- snapToPageImmediately(pageToSnapTo);
- }
- }
- resetTaskVisuals();
- mPendingAnimation = null;
+ private void onEnd(PendingAnimation.OnEndListener onEndListener) {
+ if (onEndListener.isSuccess) {
+ if (shouldRemoveTask) {
+ removeTask(taskView.getTask(), draggedIndex, onEndListener, true);
+ }
+
+ int pageToSnapTo = mCurrentPage;
+ if (draggedIndex < pageToSnapTo ||
+ pageToSnapTo == (getTaskViewCount() - 1)) {
+ pageToSnapTo -= 1;
+ }
+ removeView(taskView);
+
+ if (getTaskViewCount() == 0) {
+ removeView(mClearAllButton);
+ startHome();
+ } else {
+ snapToPageImmediately(pageToSnapTo);
+ }
+ }
+ resetTaskVisuals();
+ mPendingAnimation = null;
+ }
});
return pendingAnimation;
}
@@ -1337,20 +1408,38 @@
ObjectAnimator drawableAnim =
ObjectAnimator.ofFloat(drawable, TaskViewDrawable.PROGRESS, 1, 0);
drawableAnim.setInterpolator(LINEAR);
- drawableAnim.addUpdateListener((animator) -> {
- // Once we pass a certain threshold, update the sysui flags to match the target tasks'
- // flags
- mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
- animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
- ? targetSysUiFlags
- : 0);
+ drawableAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ TransformParams mParams = new TransformParams();
- // Passing the threshold from taskview to fullscreen app will vibrate
- final boolean passed = animator.getAnimatedFraction() >= MIN_PROGRESS_FOR_OVERVIEW;
- if (passed != passedOverviewThreshold[0]) {
- passedOverviewThreshold[0] = passed;
- performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ // Once we pass a certain threshold, update the sysui flags to match the target
+ // tasks' flags
+ mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
+ animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
+ ? targetSysUiFlags
+ : 0);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (mRecentsAnimationWrapper.targetSet != null
+ && drawable.getTaskView().isRunningTask()) {
+ mParams.setProgress(1 - animator.getAnimatedFraction())
+ .setSyncTransactionApplier(mSyncTransactionApplier)
+ .setForLiveTile(true);
+ drawable.getClipAnimationHelper().applyTransform(
+ mRecentsAnimationWrapper.targetSet, mParams);
+ } else {
+ redrawLiveTile(true);
+ }
+ }
+
+ // Passing the threshold from taskview to fullscreen app will vibrate
+ final boolean passed = animator.getAnimatedFraction() >=
+ SUCCESS_TRANSITION_PROGRESS;
+ if (passed != passedOverviewThreshold[0]) {
+ passedOverviewThreshold[0] = passed;
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ }
}
});
@@ -1449,4 +1538,74 @@
protected boolean isPageOrderFlipped() {
return true;
}
+
+ public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) {
+ mEnableDrawingLiveTile = enableDrawingLiveTile;
+ }
+
+ public void redrawLiveTile(boolean mightNeedToRefill) { }
+
+ public void setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper) {
+ mRecentsAnimationWrapper = recentsAnimationWrapper;
+ }
+
+ public void setClipAnimationHelper(ClipAnimationHelper clipAnimationHelper) {
+ mClipAnimationHelper = clipAnimationHelper;
+ }
+
+ public void finishRecentsAnimation(boolean toHome, Runnable onFinishComplete) {
+ if (mRecentsAnimationWrapper == null) {
+ if (onFinishComplete != null) {
+ onFinishComplete.run();
+ }
+ return;
+ }
+
+ mRecentsAnimationWrapper.finish(toHome, onFinishComplete);
+ }
+
+ public void takeScreenshotAndFinishRecentsAnimation(boolean toHome, Runnable onFinishComplete) {
+ if (mRecentsAnimationWrapper == null || getRunningTaskView() == null) {
+ if (onFinishComplete != null) {
+ onFinishComplete.run();
+ }
+ return;
+ }
+
+ RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+ if (controller != null) {
+ // Update the screenshot of the task
+ ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId);
+ TaskView taskView = updateThumbnail(mRunningTaskId, taskSnapshot);
+ if (taskView != null) {
+ taskView.setShowScreenshot(true);
+ // Defer finishing the animation until the next launcher frame with the
+ // new thumbnail
+ new WindowCallbacksCompat(taskView) {
+
+ // The number of frames to defer until we actually finish the animation
+ private int mDeferFrameCount = 2;
+
+ @Override
+ public void onPostDraw(Canvas canvas) {
+ if (mDeferFrameCount > 0) {
+ mDeferFrameCount--;
+ // Workaround, detach and reattach to invalidate the root node for
+ // another draw
+ detach();
+ attach();
+ taskView.invalidate();
+ return;
+ }
+
+ detach();
+ mRecentsAnimationWrapper.finish(toHome, () -> {
+ onFinishComplete.run();
+ mRunningTaskId = -1;
+ });
+ }
+ }.attach();
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 667165b..bea646a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,6 +16,7 @@
package com.android.quickstep.views;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
import android.animation.Animator;
@@ -206,7 +207,13 @@
R.layout.task_view_menu_option, this, false);
menuOption.setIconAndLabelFor(
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
- menuOptionView.setOnClickListener(onClickListener);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ menuOptionView.setOnClickListener(
+ view -> mTaskView.getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
+ () -> onClickListener.onClick(view)));
+ } else {
+ menuOptionView.setOnClickListener(onClickListener);
+ }
mOptionLayout.addView(menuOptionView);
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index c2403a3..e5bed54 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -16,6 +16,7 @@
package com.android.quickstep.views;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
import android.content.Context;
@@ -29,6 +30,8 @@
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Shader;
import android.util.AttributeSet;
@@ -76,6 +79,8 @@
private final boolean mIsDarkTextTheme;
private final Paint mPaint = new Paint();
private final Paint mBackgroundPaint = new Paint();
+ private final Paint mClearPaint = new Paint();
+ private final Paint mDimmingPaintAfterClearing = new Paint();
private final Matrix mMatrix = new Matrix();
@@ -105,6 +110,8 @@
mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
mPaint.setFilterBitmap(true);
mBackgroundPaint.setColor(Color.WHITE);
+ mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ mDimmingPaintAfterClearing.setColor(Color.BLACK);
mActivity = BaseActivity.fromContext(context);
mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
}
@@ -213,6 +220,15 @@
public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
float cornerRadius) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
+ canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
+ canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
+ mDimmingPaintAfterClearing);
+ return;
+ }
+ }
+
// Draw the background in all cases, except when the thumbnail data is opaque
final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
|| mThumbnailData == null;
@@ -233,8 +249,13 @@
}
}
+ protected TaskView getTaskView() {
+ return (TaskView) getParent();
+ }
+
private void updateThumbnailPaintFilter() {
int mul = (int) ((1 - mDimAlpha * mDimAlphaMultiplier) * 255);
+ mDimmingPaintAfterClearing.setAlpha(255 - mul);
if (mBitmapShader != null) {
ColorFilter filter = getColorFilter(mul, mIsDarkTextTheme, mSaturation);
mPaint.setColorFilter(filter);
@@ -242,6 +263,7 @@
} else {
mPaint.setColorFilter(null);
mPaint.setColor(Color.argb(255, mul, mul, mul));
+ mBackgroundPaint.setColorFilter(null);
}
invalidate();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index bb6f514..456a022 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.BaseActivity.fromContext;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -46,9 +47,11 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.PendingAnimation;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
@@ -88,6 +91,7 @@
public static final long SCALE_ICON_DURATION = 120;
private static final long DIM_ANIM_DURATION = 700;
+ private static final long TASK_LAUNCH_ANIM_DURATION = 200;
public static final Property<TaskView, Float> ZOOM_SCALE =
new FloatProperty<TaskView>("zoomScale") {
@@ -119,7 +123,7 @@
new FloatProperty<TaskView>("focusTransition") {
@Override
public void setValue(TaskView taskView, float v) {
- taskView.setIconAndDimTransitionProgress(v);
+ taskView.setIconAndDimTransitionProgress(v, false /* invert */);
}
@Override
@@ -158,6 +162,8 @@
private Animator mIconAndDimAnimator;
private float mFocusTransitionProgress = 1;
+ private boolean mShowScreenshot;
+
// The current background requests to load the task thumbnail and icon
private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest;
private TaskIconCache.IconLoadRequest mIconLoadRequest;
@@ -176,7 +182,15 @@
if (getTask() == null) {
return;
}
- launchTask(true /* animate */);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (isRunningTask()) {
+ createLaunchAnimationForRunningTask().start();
+ } else {
+ launchTask(true /* animate */);
+ }
+ } else {
+ launchTask(true /* animate */);
+ }
fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss(
Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
@@ -219,6 +233,19 @@
return mSnapshotView.getTaskOverlay();
}
+ public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
+ final PendingAnimation pendingAnimation =
+ getRecentsView().createTaskLauncherAnimation(this, TASK_LAUNCH_ANIM_DURATION);
+ pendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
+ AnimatorPlaybackController currentAnimation = AnimatorPlaybackController
+ .wrap(pendingAnimation.anim, TASK_LAUNCH_ANIM_DURATION, null);
+ currentAnimation.setEndAction(() -> {
+ pendingAnimation.finish(true, Touch.SWIPE);
+ launchTask(false);
+ });
+ return currentAnimation;
+ }
+
public void launchTask(boolean animate) {
launchTask(animate, (result) -> {
if (!result) {
@@ -229,6 +256,21 @@
public void launchTask(boolean animate, Consumer<Boolean> resultCallback,
Handler resultCallbackHandler) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ if (isRunningTask()) {
+ getRecentsView().finishRecentsAnimation(false,
+ () -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
+ } else {
+ getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
+ () -> launchTaskInternal(animate, resultCallback, resultCallbackHandler));
+ }
+ } else {
+ launchTaskInternal(animate, resultCallback, resultCallbackHandler);
+ }
+ }
+
+ private void launchTaskInternal(boolean animate, Consumer<Boolean> resultCallback,
+ Handler resultCallbackHandler) {
if (mTask != null) {
final ActivityOptions opts;
if (animate) {
@@ -316,11 +358,17 @@
}
}
- private void setIconAndDimTransitionProgress(float progress) {
+ private void setIconAndDimTransitionProgress(float progress, boolean invert) {
+ if (invert) {
+ progress = 1 - progress;
+ }
mFocusTransitionProgress = progress;
mSnapshotView.setDimAlphaMultipler(progress);
- float scale = FAST_OUT_SLOW_IN.getInterpolation(Utilities.boundToRange(
- progress * DIM_ANIM_DURATION / SCALE_ICON_DURATION, 0, 1));
+ float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION;
+ float lowerClamp = invert ? 1f - iconScalePercentage : 0;
+ float upperClamp = invert ? 1 : iconScalePercentage;
+ float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp)
+ .getInterpolation(progress);
mIconView.setScaleX(scale);
mIconView.setScaleY(scale);
}
@@ -341,10 +389,14 @@
}
protected void setIconScaleAndDim(float iconScale) {
+ setIconScaleAndDim(iconScale, false);
+ }
+
+ private void setIconScaleAndDim(float iconScale, boolean invert) {
if (mIconAndDimAnimator != null) {
mIconAndDimAnimator.cancel();
}
- setIconAndDimTransitionProgress(iconScale);
+ setIconAndDimTransitionProgress(iconScale, invert);
}
public void resetVisualProperties() {
@@ -501,7 +553,7 @@
return super.performAccessibilityAction(action, arguments);
}
- private RecentsView getRecentsView() {
+ public RecentsView getRecentsView() {
return (RecentsView) getParent();
}
@@ -524,7 +576,8 @@
}
mFullscreenProgress = progress;
boolean isFullscreen = mFullscreenProgress > 0;
- mIconView.setVisibility(isFullscreen ? INVISIBLE : VISIBLE);
+ setIconScaleAndDim(progress, true /* invert */);
+ mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
setClipChildren(!isFullscreen);
setClipToPadding(!isFullscreen);
getThumbnail().invalidate();
@@ -533,4 +586,19 @@
public float getFullscreenProgress() {
return mFullscreenProgress;
}
+
+ public boolean isRunningTask() {
+ return this == getRecentsView().getRunningTaskView();
+ }
+
+ public void setShowScreenshot(boolean showScreenshot) {
+ mShowScreenshot = showScreenshot;
+ }
+
+ public boolean showScreenshot() {
+ if (!isRunningTask()) {
+ return true;
+ }
+ return mShowScreenshot;
+ }
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 5c842a5..3c0ef79 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -30,12 +30,16 @@
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
import android.util.Xml;
import android.view.Display;
import android.view.WindowManager;
import com.android.launcher3.util.ConfigMonitor;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.Themes;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -44,6 +48,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
public class InvariantDeviceProfile {
@@ -91,6 +96,8 @@
public int fillResIconDpi;
public float iconTextSize;
+ private SparseArray<TypedValue> mExtraAttrs;
+
/**
* Number of icons inside the hotseat area.
*/
@@ -122,6 +129,7 @@
numHotseatIcons = p.numHotseatIcons;
defaultLayoutId = p.defaultLayoutId;
demoModeLayoutId = p.demoModeLayoutId;
+ mExtraAttrs = p.mExtraAttrs;
}
@TargetApi(23)
@@ -171,6 +179,8 @@
demoModeLayoutId = closestProfile.demoModeLayoutId;
numFolderRows = closestProfile.numFolderRows;
numFolderColumns = closestProfile.numFolderColumns;
+ mExtraAttrs = closestProfile.extraAttrs;
+
if (!closestProfile.name.equals(gridName)) {
Utilities.getPrefs(context).edit()
.putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
@@ -210,10 +220,19 @@
}
}
+ @Nullable
+ public TypedValue getAttrValue(int attr) {
+ return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
+ }
+
public void addOnChangeListener(OnIDPChangeListener listener) {
mChangeListeners.add(listener);
}
+ public void removeOnChangeListener(OnIDPChangeListener listener) {
+ mChangeListeners.remove(listener);
+ }
+
private void killProcess(Context context) {
Log.e("ConfigMonitor", "restarting launcher");
android.os.Process.killProcess(android.os.Process.myPid());
@@ -436,6 +455,8 @@
private final int defaultLayoutId;
private final int demoModeLayoutId;
+ private final SparseArray<TypedValue> extraAttrs;
+
GridOption(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.GridDisplayOption);
@@ -454,6 +475,9 @@
numFolderColumns = a.getInt(
R.styleable.GridDisplayOption_numFolderColumns, numColumns);
a.recycle();
+
+ extraAttrs = Themes.createValueMap(context, attrs,
+ IntArray.wrap(R.styleable.GridDisplayOption));
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index ffbf34c..962c25b 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,7 +1,6 @@
package com.android.launcher3.allapps;
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
@@ -15,9 +14,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.util.Property;
-import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
@@ -29,10 +26,15 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.anim.SpringObjectAnimator.SpringProperty;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+
/**
* Handles AllApps view transition.
* 1) Slides all apps view using direct manipulation
@@ -59,6 +61,53 @@
}
};
+ public static final FloatPropertyCompat<AllAppsTransitionController> ALL_APPS_PROGRESS_SPRING
+ = new FloatPropertyCompat<AllAppsTransitionController>("allAppsProgressSpring") {
+ @Override
+ public float getValue(AllAppsTransitionController controller) {
+ return controller.mProgress;
+ }
+
+ @Override
+ public void setValue(AllAppsTransitionController controller, float progress) {
+ controller.setProgress(progress);
+ }
+ };
+
+ /**
+ * Property that either sets the progress directly or animates the progress via a spring.
+ */
+ public static class AllAppsSpringProperty extends
+ SpringProperty<AllAppsTransitionController, Float> {
+
+ SpringAnimation mSpring;
+ boolean useSpring = false;
+
+ public AllAppsSpringProperty(SpringAnimation spring) {
+ super(Float.class, "allAppsSpringProperty");
+ mSpring = spring;
+ }
+
+ @Override
+ public Float get(AllAppsTransitionController controller) {
+ return controller.getProgress();
+ }
+
+ @Override
+ public void set(AllAppsTransitionController controller, Float progress) {
+ if (useSpring) {
+ mSpring.animateToFinalPosition(progress);
+ } else {
+ controller.setProgress(progress);
+ }
+ }
+
+ @Override
+ public void switchToSpring() {
+ useSpring = true;
+ }
+ }
+
private AllAppsContainerView mAppsView;
private ScrimView mScrimView;
@@ -174,8 +223,8 @@
Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW
? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
: FAST_OUT_SLOW_IN;
- ObjectAnimator anim =
- ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, mProgress, targetProgress);
+ Animator anim = new SpringObjectAnimator(this, 1f / mShiftRange, mProgress,
+ targetProgress);
anim.setDuration(config.duration);
anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
anim.addListener(getProgressAnimatorListener());
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 819c843..62f59e4 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.anim;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
@@ -23,10 +24,16 @@
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
+import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
/**
* Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
@@ -37,6 +44,9 @@
*/
public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
+ private static final String TAG = "AnimatorPlaybackCtrler";
+ private static boolean DEBUG = false;
+
public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
return wrap(anim, duration, null);
}
@@ -60,6 +70,7 @@
private final long mDuration;
protected final AnimatorSet mAnim;
+ private Set<SpringAnimation> mSprings;
protected float mCurrentFraction;
private Runnable mEndAction;
@@ -67,6 +78,9 @@
protected boolean mTargetCancelled = false;
protected Runnable mOnCancelRunnable;
+ private OnAnimationEndDispatcher mEndListener;
+ private DynamicAnimation.OnAnimationEndListener mSpringEndListener;
+
protected AnimatorPlaybackController(AnimatorSet anim, long duration,
Runnable onCancelRunnable) {
mAnim = anim;
@@ -75,7 +89,8 @@
mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
mAnimationPlayer.setInterpolator(LINEAR);
- mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
+ mEndListener = new OnAnimationEndDispatcher();
+ mAnimationPlayer.addListener(mEndListener);
mAnimationPlayer.addUpdateListener(this);
mAnim.addListener(new AnimatorListenerAdapter() {
@@ -99,6 +114,15 @@
mTargetCancelled = false;
}
});
+
+ mSprings = new HashSet<>();
+ mSpringEndListener = (animation, canceled, value, velocity1) -> {
+ if (canceled) {
+ mEndListener.onAnimationCancel(mAnimationPlayer);
+ } else {
+ mEndListener.onAnimationEnd(mAnimationPlayer);
+ }
+ };
}
public AnimatorSet getTarget() {
@@ -180,6 +204,29 @@
}
}
+ /**
+ * Starts playback and sets the spring.
+ */
+ public void dispatchOnStartWithVelocity(float end, float velocity) {
+ if (!QUICKSTEP_SPRINGS.get()) {
+ dispatchOnStart();
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "dispatchOnStartWithVelocity#end=" + end + ", velocity=" + velocity);
+
+ for (Animator a : mAnim.getChildAnimations()) {
+ if (a instanceof SpringObjectAnimator) {
+ if (DEBUG) Log.d(TAG, "Found springAnimator=" + a);
+ SpringObjectAnimator springAnimator = (SpringObjectAnimator) a;
+ mSprings.add(springAnimator.getSpring());
+ springAnimator.startSpring(end, velocity, mSpringEndListener);
+ }
+ }
+
+ dispatchOnStart();
+ }
+
public void dispatchOnStart() {
dispatchOnStartRecursively(mAnim);
}
@@ -282,6 +329,18 @@
}
}
+ private boolean isAnySpringRunning() {
+ for (SpringAnimation spring : mSprings) {
+ if (spring.isRunning()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Only dispatches the on end actions once the animator and all springs have completed running.
+ */
private class OnAnimationEndDispatcher extends AnimationSuccessListener {
@Override
@@ -291,9 +350,12 @@
@Override
public void onAnimationSuccess(Animator animator) {
- dispatchOnEndRecursively(mAnim);
- if (mEndAction != null) {
- mEndAction.run();
+ // We wait for the spring (if any) to finish running before completing the end callback.
+ if (mSprings.isEmpty() || !isAnySpringRunning()) {
+ dispatchOnEndRecursively(mAnim);
+ if (mEndAction != null) {
+ mEndAction.run();
+ }
}
}
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
new file mode 100644
index 0000000..b2b931d
--- /dev/null
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2019 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 com.android.launcher3.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.util.Property;
+
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.allapps.AllAppsTransitionController.AllAppsSpringProperty;
+
+import java.util.ArrayList;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+/**
+ * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
+ * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
+ */
+public class SpringObjectAnimator extends ValueAnimator {
+
+ private static final String TAG = "SpringObjectAnimator";
+ private static boolean DEBUG = false;
+
+ private AllAppsTransitionController mObject;
+ private ObjectAnimator mObjectAnimator;
+ private float[] mValues;
+
+ private SpringAnimation mSpring;
+ private AllAppsSpringProperty mProperty;
+
+ private ArrayList<AnimatorListener> mListeners;
+ private boolean mSpringEnded = false;
+ private boolean mAnimatorEnded = false;
+ private boolean mEnded = false;
+
+ private static final float SPRING_DAMPING_RATIO = 0.9f;
+ private static final float SPRING_STIFFNESS = 600f;
+
+ public SpringObjectAnimator(AllAppsTransitionController object, float minimumVisibleChange,
+ float... values) {
+ mObject = object;
+ mSpring = new SpringAnimation(object, AllAppsTransitionController.ALL_APPS_PROGRESS_SPRING);
+ mSpring.setMinimumVisibleChange(minimumVisibleChange);
+ mSpring.setSpring(new SpringForce(0)
+ .setDampingRatio(SPRING_DAMPING_RATIO)
+ .setStiffness(SPRING_STIFFNESS));
+ mSpring.setStartVelocity(0.01f);
+ mProperty = new AllAppsSpringProperty(mSpring);
+ mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values);
+ mValues = values;
+ mListeners = new ArrayList<>();
+ setFloatValues(values);
+
+ mObjectAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mAnimatorEnded = false;
+ mEnded = false;
+ for (AnimatorListener l : mListeners) {
+ l.onAnimationStart(animation);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimatorEnded = true;
+ tryEnding();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ for (AnimatorListener l : mListeners) {
+ l.onAnimationCancel(animation);
+ }
+ mSpring.animateToFinalPosition(mObject.getProgress());
+ }
+ });
+
+ mSpring.addUpdateListener((animation, value, velocity) -> mSpringEnded = false);
+ mSpring.addEndListener((animation, canceled, value, velocity) -> {
+ mSpringEnded = true;
+ tryEnding();
+ });
+ }
+
+ private void tryEnding() {
+ if (DEBUG) {
+ Log.d(TAG, "tryEnding#mAnimatorEnded=" + mAnimatorEnded + ", mSpringEnded="
+ + mSpringEnded + ", mEnded=" + mEnded);
+ }
+
+ if (mAnimatorEnded && mSpringEnded && !mEnded) {
+ for (AnimatorListener l : mListeners) {
+ l.onAnimationEnd(this);
+ }
+ mEnded = true;
+ }
+ }
+
+ public SpringAnimation getSpring() {
+ return mSpring;
+ }
+
+ /**
+ * Initializes and sets up the spring to take over controlling the object.
+ */
+ void startSpring(float end, float velocity, OnAnimationEndListener endListener) {
+ // Cancel the spring so we can set new start velocity and final position. We need to remove
+ // the listener since the spring is not actually ending.
+ mSpring.removeEndListener(endListener);
+ mSpring.cancel();
+ mSpring.addEndListener(endListener);
+
+ mProperty.switchToSpring();
+
+ mSpring.setStartVelocity(velocity);
+ mSpring.animateToFinalPosition(end == 0 ? mValues[0] : mValues[1]);
+ }
+
+ @Override
+ public void addListener(AnimatorListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public ArrayList<AnimatorListener> getListeners() {
+ return mListeners;
+ }
+
+ @Override
+ public void removeAllListeners() {
+ mListeners.clear();
+ }
+
+ @Override
+ public void removeListener(AnimatorListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void addPauseListener(AnimatorPauseListener listener) {
+ mObjectAnimator.addPauseListener(listener);
+ }
+
+ @Override
+ public void cancel() {
+ mSpring.animateToFinalPosition(mObject.getProgress());
+ mObjectAnimator.cancel();
+ }
+
+ @Override
+ public void end() {
+ mObjectAnimator.end();
+ }
+
+ @Override
+ public long getDuration() {
+ return mObjectAnimator.getDuration();
+ }
+
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mObjectAnimator.getInterpolator();
+ }
+
+ @Override
+ public long getStartDelay() {
+ return mObjectAnimator.getStartDelay();
+ }
+
+ @Override
+ public long getTotalDuration() {
+ return mObjectAnimator.getTotalDuration();
+ }
+
+ @Override
+ public boolean isPaused() {
+ return mObjectAnimator.isPaused();
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mObjectAnimator.isRunning();
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mObjectAnimator.isStarted();
+ }
+
+ @Override
+ public void pause() {
+ mObjectAnimator.pause();
+ }
+
+ @Override
+ public void removePauseListener(AnimatorPauseListener listener) {
+ mObjectAnimator.removePauseListener(listener);
+ }
+
+ @Override
+ public void resume() {
+ mObjectAnimator.resume();
+ }
+
+ @Override
+ public ValueAnimator setDuration(long duration) {
+ return mObjectAnimator.setDuration(duration);
+ }
+
+ @Override
+ public void setInterpolator(TimeInterpolator value) {
+ mObjectAnimator.setInterpolator(value);
+ }
+
+ @Override
+ public void setStartDelay(long startDelay) {
+ mObjectAnimator.setStartDelay(startDelay);
+ }
+
+ @Override
+ public void setTarget(Object target) {
+ mObjectAnimator.setTarget(target);
+ }
+
+ @Override
+ public void start() {
+ mObjectAnimator.start();
+ }
+
+ @Override
+ public void setCurrentFraction(float fraction) {
+ mObjectAnimator.setCurrentFraction(fraction);
+ }
+
+ @Override
+ public void setCurrentPlayTime(long playTime) {
+ mObjectAnimator.setCurrentPlayTime(playTime);
+ }
+
+ public static abstract class SpringProperty<T, V> extends Property<T, V> {
+
+ public SpringProperty(Class<V> type, String name) {
+ super(type, name);
+ }
+
+ abstract public void switchToSpring();
+ }
+
+}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 03fdc64..1b1b152 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -98,6 +98,12 @@
public static final TogglableFlag ENABLE_TASK_STABILIZER = new TogglableFlag(
"ENABLE_TASK_STABILIZER", false, "Stable task list across fast task switches");
+ public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS",
+ false, "Enable springs for quickstep animations");
+
+ public static final TogglableFlag ENABLE_QUICKSTEP_LIVE_TILE = new TogglableFlag(
+ "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
+
public static void initialize(Context context) {
// Avoid the disk read for user builds
if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/folder/FolderShape.java b/src/com/android/launcher3/folder/FolderShape.java
index f7cdb77..4b06dda 100644
--- a/src/com/android/launcher3/folder/FolderShape.java
+++ b/src/com/android/launcher3/folder/FolderShape.java
@@ -49,6 +49,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.Themes;
import org.xmlpull.v1.XmlPullParser;
@@ -387,6 +388,8 @@
final int depth = parser.getDepth();
int[] radiusAttr = new int[] {R.attr.folderIconRadius};
+ IntArray keysToIgnore = new IntArray(0);
+
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
@@ -396,7 +399,7 @@
FolderShape shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
a.recycle();
- shape.mAttrs = Themes.createValueMap(context, attrs);
+ shape.mAttrs = Themes.createValueMap(context, attrs, keysToIgnore);
result.add(shape);
}
}
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 66f9dbf..6fac31e2 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -16,16 +16,9 @@
package com.android.launcher3.graphics;
-import static android.content.Intent.ACTION_SCREEN_OFF;
-import static android.content.Intent.ACTION_USER_PRESENT;
-
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.ObjectAnimator;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -96,20 +89,6 @@
}
};
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (ACTION_SCREEN_OFF.equals(action)) {
- mAnimateScrimOnNextDraw = true;
- } else if (ACTION_USER_PRESENT.equals(action)) {
- // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where
- // the user unlocked and the Launcher is not in the foreground.
- mAnimateScrimOnNextDraw = false;
- }
- }
- };
-
private static final int DARK_SCRIM_COLOR = 0x55000000;
private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100;
private static final int ALPHA_MASK_HEIGHT_DP = 500;
@@ -225,20 +204,11 @@
public void onViewAttachedToWindow(View view) {
mWallpaperColorInfo.addOnChangeListener(this);
onExtractedColorsChanged(mWallpaperColorInfo);
-
- if (mTopScrim != null) {
- IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
- filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
- mRoot.getContext().registerReceiver(mReceiver, filter);
- }
}
@Override
public void onViewDetachedFromWindow(View view) {
mWallpaperColorInfo.removeOnChangeListener(this);
- if (mTopScrim != null) {
- mRoot.getContext().unregisterReceiver(mReceiver);
- }
}
@Override
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 9c4a4ea..65103f6 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -29,6 +29,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.UiThreadHelper;
/**
* Utility class to manage launcher rotation
@@ -154,7 +155,7 @@
}
if (activityFlags != mLastActivityFlags) {
mLastActivityFlags = activityFlags;
- mActivity.setRequestedOrientation(activityFlags);
+ UiThreadHelper.setOrientationAsync(mActivity, activityFlags);
}
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index a7bd243..bb14328 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -45,6 +46,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -428,8 +430,8 @@
maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
targetState, velocity, fling);
- mCurrentAnimation.dispatchOnStart();
- if (fling && targetState == LauncherState.ALL_APPS) {
+ mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, velocity);
+ if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) {
mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
}
anim.start();
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index e3ab1a5..675e2f4 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -112,16 +112,19 @@
* Creates a map for attribute-name to value for all the values in {@param attrs} which can be
* held in memory for later use.
*/
- public static SparseArray<TypedValue> createValueMap(Context context, AttributeSet attrSet) {
+ public static SparseArray<TypedValue> createValueMap(Context context, AttributeSet attrSet,
+ IntArray keysToIgnore) {
int count = attrSet.getAttributeCount();
- int[] attrNames = new int[count];
+ IntArray attrNameArray = new IntArray(count);
for (int i = 0; i < count; i++) {
- attrNames[i] = attrSet.getAttributeNameResource(i);
+ attrNameArray.add(attrSet.getAttributeNameResource(i));
}
+ attrNameArray.removeAllValues(keysToIgnore);
- SparseArray<TypedValue> result = new SparseArray<>(count);
+ int[] attrNames = attrNameArray.toArray();
+ SparseArray<TypedValue> result = new SparseArray<>(attrNames.length);
TypedArray ta = context.obtainStyledAttributes(attrSet, attrNames);
- for (int i = 0; i < count; i++) {
+ for (int i = 0; i < attrNames.length; i++) {
TypedValue tv = new TypedValue();
ta.getValue(i, tv);
result.put(attrNames[i], tv);
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index 27140a1..cc442f9 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.util;
+import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
@@ -33,6 +34,8 @@
private static Handler sHandler;
private static final int MSG_HIDE_KEYBOARD = 1;
+ private static final int MSG_SET_ORIENTATION = 2;
+ private static final int MSG_RUN_COMMAND = 3;
public static Looper getBackgroundLooper() {
if (sHandlerThread == null) {
@@ -55,6 +58,15 @@
Message.obtain(getHandler(context), MSG_HIDE_KEYBOARD, token).sendToTarget();
}
+ public static void setOrientationAsync(Activity activity, int orientation) {
+ Message.obtain(getHandler(activity), MSG_SET_ORIENTATION, orientation, 0, activity)
+ .sendToTarget();
+ }
+
+ public static void runAsyncCommand(Context context, AsyncCommand command, int arg1, int arg2) {
+ Message.obtain(getHandler(context), MSG_RUN_COMMAND, arg1, arg2, command).sendToTarget();
+ }
+
private static class UiCallbacks implements Handler.Callback {
private final InputMethodManager mIMM;
@@ -69,8 +81,19 @@
case MSG_HIDE_KEYBOARD:
mIMM.hideSoftInputFromWindow((IBinder) message.obj, 0);
return true;
+ case MSG_SET_ORIENTATION:
+ ((Activity) message.obj).setRequestedOrientation(message.arg1);
+ return true;
+ case MSG_RUN_COMMAND:
+ ((AsyncCommand) message.obj).execute(message.arg1, message.arg2);
+ return true;
}
return false;
}
}
+
+ public interface AsyncCommand {
+
+ void execute(int arg1, int arg2);
+ }
}