Merge "Removing pressing home button twice" into ub-launcher3-qt-dev
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index ab4b64c..3c55854 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -78,7 +78,7 @@
public IconNormalizer getNormalizer() {
if (mNormalizer == null) {
- mNormalizer = new IconNormalizer(mContext, mIconBitmapSize);
+ mNormalizer = new IconNormalizer(mIconBitmapSize);
}
return mNormalizer;
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
index 11d5eef..3e818a5 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
@@ -16,6 +16,9 @@
package com.android.launcher3.icons;
import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.RegionIterator;
import android.util.Log;
import java.io.ByteArrayOutputStream;
@@ -60,4 +63,14 @@
return null;
}
}
+
+ public static int getArea(Region r) {
+ RegionIterator itr = new RegionIterator(r);
+ int area = 0;
+ Rect tempRect = new Rect();
+ while (itr.next(tempRect)) {
+ area += tempRect.width() * tempRect.height();
+ }
+ return area;
+ }
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
index 05908df..4a2a7cf 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
@@ -16,14 +16,18 @@
package com.android.launcher3.icons;
+import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.Region;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import java.nio.ByteBuffer;
@@ -57,7 +61,7 @@
private final Canvas mCanvas;
private final byte[] mPixels;
- private final Rect mAdaptiveIconBounds;
+ private final RectF mAdaptiveIconBounds;
private float mAdaptiveIconScale;
// for each y, stores the position of the leftmost x and the rightmost x
@@ -66,7 +70,7 @@
private final Rect mBounds;
/** package private **/
- IconNormalizer(Context context, int iconBitmapSize) {
+ IconNormalizer(int iconBitmapSize) {
// Use twice the icon size as maximum size to avoid scaling down twice.
mMaxSize = iconBitmapSize * 2;
mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
@@ -75,11 +79,53 @@
mLeftBorder = new float[mMaxSize];
mRightBorder = new float[mMaxSize];
mBounds = new Rect();
- mAdaptiveIconBounds = new Rect();
+ mAdaptiveIconBounds = new RectF();
mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
}
+ private static float getScale(float hullArea, float boundingArea, float fullArea) {
+ float hullByRect = hullArea / boundingArea;
+ float scaleRequired;
+ if (hullByRect < CIRCLE_AREA_BY_RECT) {
+ scaleRequired = MAX_CIRCLE_AREA_FACTOR;
+ } else {
+ scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
+ }
+
+ float areaScale = hullArea / fullArea;
+ // Use sqrt of the final ratio as the images is scaled across both width and height.
+ return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
+ }
+
+ /**
+ * @param d Should be AdaptiveIconDrawable
+ * @param size Canvas size to use
+ */
+ @TargetApi(Build.VERSION_CODES.O)
+ public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) {
+ Rect tmpBounds = new Rect(d.getBounds());
+ d.setBounds(0, 0, size, size);
+
+ Path path = ((AdaptiveIconDrawable) d).getIconMask();
+ Region region = new Region();
+ region.setPath(path, new Region(0, 0, size, size));
+
+ Rect hullBounds = region.getBounds();
+ int hullArea = GraphicsUtils.getArea(region);
+
+ if (outBounds != null) {
+ float sizeF = size;
+ outBounds.set(
+ hullBounds.left / sizeF,
+ hullBounds.top / sizeF,
+ 1 - (hullBounds.right / sizeF),
+ 1 - (hullBounds.bottom / sizeF));
+ }
+ d.setBounds(tmpBounds);
+ return getScale(hullArea, hullArea, size * size);
+ }
+
/**
* Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
* matches the design guidelines for a launcher icon.
@@ -96,12 +142,13 @@
*/
public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
- if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
- if (outBounds != null) {
- outBounds.set(mAdaptiveIconBounds);
- }
- return mAdaptiveIconScale;
+ if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
+ mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
}
+ if (outBounds != null) {
+ outBounds.set(mAdaptiveIconBounds);
+ }
+ return mAdaptiveIconScale;
}
int width = d.getIntrinsicWidth();
int height = d.getIntrinsicHeight();
@@ -184,16 +231,6 @@
area += mRightBorder[y] - mLeftBorder[y] + 1;
}
- // Area of the rectangle required to fit the convex hull
- float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
- float hullByRect = area / rectArea;
-
- float scaleRequired;
- if (hullByRect < CIRCLE_AREA_BY_RECT) {
- scaleRequired = MAX_CIRCLE_AREA_FACTOR;
- } else {
- scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
- }
mBounds.left = leftX;
mBounds.right = rightX;
@@ -205,15 +242,10 @@
1 - ((float) mBounds.right) / width,
1 - ((float) mBounds.bottom) / height);
}
- float areaScale = area / (width * height);
- // Use sqrt of the final ratio as the images is scaled across both width and height.
- float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
- if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
- mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
- mAdaptiveIconScale = scale;
- mAdaptiveIconBounds.set(mBounds);
- }
- return scale;
+
+ // Area of the rectangle required to fit the convex hull
+ float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
+ return getScale(area, rectArea, width * height);
}
/**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 624b3dc..5e77e0a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -68,7 +68,7 @@
* @param wasVisible true if it was visible before
*/
boolean onActivityReady(T activity, Boolean wasVisible) {
- activity.<RecentsView>getOverviewPanel().setCurrentTask(mTargetTaskId);
+ activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
ActivityControlHelper.AnimationFactory factory =
mHelper.prepareRecentsUI(activity, wasVisible,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index 7fc5d50..db377b0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -21,7 +21,6 @@
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.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
@@ -44,6 +43,8 @@
import android.view.ViewConfiguration;
import android.view.WindowManager;
+import androidx.annotation.UiThread;
+
import com.android.launcher3.R;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.util.Preconditions;
@@ -62,8 +63,6 @@
import java.util.function.Consumer;
-import androidx.annotation.UiThread;
-
/**
* Input consumer for handling events originating from an activity other than Launcher
*/
@@ -108,7 +107,6 @@
// Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
private float mStartDisplacement;
- private float mStartDisplacementX;
private Handler mMainThreadHandler;
private Runnable mCancelRecentsAnimationRunnable = () -> {
@@ -227,7 +225,6 @@
if (Math.abs(displacement) > mDragSlop) {
mPassedDragSlop = true;
mStartDisplacement = displacement;
- mStartDisplacementX = displacementX;
}
}
}
@@ -244,19 +241,20 @@
if (!mPassedDragSlop) {
mPassedDragSlop = true;
mStartDisplacement = displacement;
- mStartDisplacementX = displacementX;
}
notifyGestureStarted();
}
}
- if (mPassedDragSlop && mInteractionHandler != null) {
- // Move
- mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
+ if (mInteractionHandler != null) {
+ if (mPassedDragSlop) {
+ // Move
+ mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
+ }
if (mMode == Mode.NO_BUTTON) {
- float horizontalDist = Math.abs(displacementX - mStartDisplacementX);
- float upDist = -(displacement - mStartDisplacement);
+ float horizontalDist = Math.abs(displacementX);
+ float upDist = -displacement;
boolean isLikelyToStartNewTask = horizontalDist > upDist;
mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
|| isLikelyToStartNewTask);
@@ -382,7 +380,7 @@
mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
if (mSwipeSharedState.canGestureBeContinued) {
- mInteractionHandler.cancelCurrentAnimation();
+ mInteractionHandler.cancelCurrentAnimation(mSwipeSharedState);
} else {
mInteractionHandler.reset();
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index ced9afa..96d2dca 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -59,6 +59,10 @@
mInputProxySupplier = inputProxySupplier;
}
+ public boolean hasTargets() {
+ return targetSet != null && targetSet.hasTargets();
+ }
+
@UiThread
public synchronized void setController(SwipeAnimationTargetSet targetSet) {
Preconditions.assertUIThread();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index f393387..194d073 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -38,6 +38,8 @@
public boolean canGestureBeContinued;
public boolean goingToLauncher;
+ public boolean recentsAnimationFinishInterrupted;
+ public int nextRunningTaskId = -1;
public SwipeSharedState(OverviewComponentObserver overviewComponentObserver) {
mOverviewComponentObserver = overviewComponentObserver;
@@ -114,9 +116,22 @@
}
}
+ /**
+ * Called when a recents animation has finished, but was interrupted before the next task was
+ * launched. The given {@param runningTaskId} should be used as the running task for the
+ * continuing input consumer.
+ */
+ public void setRecentsAnimationFinishInterrupted(int runningTaskId) {
+ recentsAnimationFinishInterrupted = true;
+ nextRunningTaskId = runningTaskId;
+ mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets();
+ }
+
public void clearAllState() {
clearListenerState();
canGestureBeContinued = false;
+ recentsAnimationFinishInterrupted = false;
+ nextRunningTaskId = -1;
goingToLauncher = false;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index f95f9c2..d0ea73a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -150,6 +150,7 @@
@Override
public void onUpdate(float percent) {
+ // TODO: Take into account the current fullscreen progress for animating the insets
params.setProgress(1 - percent);
RectF taskBounds = inOutHelper.applyTransform(targetSet, params);
if (!skipViewChanges) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index b25865e..61ae880 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -26,6 +26,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import android.annotation.TargetApi;
+import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.KeyguardManager;
import android.app.Service;
@@ -456,7 +457,7 @@
return new DeviceLockedInputConsumer(this);
}
- RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
+ final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
if (!useSharedState) {
mSwipeSharedState.clearAllState();
}
@@ -465,8 +466,15 @@
mOverviewComponentObserver.getActivityControlHelper();
InputConsumer base;
- if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
+ if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher
+ && !mSwipeSharedState.recentsAnimationFinishInterrupted) {
base = InputConsumer.NO_OP;
+ } else if (mSwipeSharedState.recentsAnimationFinishInterrupted) {
+ // If the finish animation was interrupted, then continue using the other activity input
+ // consumer but with the next task as the running task
+ RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+ info.id = mSwipeSharedState.nextRunningTaskId;
+ base = createOtherActivityInputConsumer(event, info);
} else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 9c6102e..936f0aa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -256,7 +256,9 @@
private AnimationFactory mAnimationFactory = (t) -> { };
private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
+ private boolean mCanceled;
private boolean mWasLauncherAlreadyVisible;
+ private int mFinishingRecentsAnimationForNewTaskId = -1;
private boolean mPassedOverviewThreshold;
private boolean mGestureStarted;
@@ -412,7 +414,6 @@
mRecentsAnimationWrapper.runOnInit(() ->
mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
});
- mRecentsView.setEnableFreeScroll(false);
mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
if (mGestureEndTarget != HOME) {
@@ -500,10 +501,7 @@
if (mContinuingLastGesture) {
return;
}
- mRecentsView.setEnableDrawingLiveTile(false);
- mRecentsView.showTask(mRunningTaskId);
- mRecentsView.setRunningTaskHidden(true);
- mRecentsView.setRunningTaskIconScaledDown(true);
+ mRecentsView.onGestureAnimationStart(mRunningTaskId);
}
private void launcherFrameDrawn() {
@@ -679,7 +677,7 @@
int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex();
if (runningTaskIndex >= 0) {
for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
- if (i != runningTaskIndex) {
+ if (i != runningTaskIndex || !mRecentsAnimationWrapper.hasTargets()) {
mRecentsView.getTaskViewAt(i).setFullscreenProgress(1 - mCurrentShift.value);
}
}
@@ -838,9 +836,15 @@
Interpolator interpolator = DEACCEL;
final boolean goingToNewTask;
if (mRecentsView != null) {
- final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
- final int taskToLaunch = mRecentsView.getNextPage();
- goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
+ if (!mRecentsAnimationWrapper.hasTargets()) {
+ // If there are no running tasks, then we can assume that this is a continuation of
+ // the last gesture, but after the recents animation has finished
+ goingToNewTask = true;
+ } else {
+ final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
+ final int taskToLaunch = mRecentsView.getNextPage();
+ goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
+ }
} else {
goingToNewTask = false;
}
@@ -1061,7 +1065,9 @@
RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect);
if (isFloatingIconView) {
- anim.addAnimatorListener((FloatingIconView) floatingView);
+ FloatingIconView fiv = (FloatingIconView) floatingView;
+ anim.addAnimatorListener(fiv);
+ fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
}
AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
@@ -1122,14 +1128,22 @@
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
true /* freezeTaskList */);
} else {
+ int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
+ mFinishingRecentsAnimationForNewTaskId = taskId;
mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
- mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(
- false /* animate */, true /* freezeTaskList */);
+ if (!mCanceled) {
+ TaskView nextTask = mRecentsView.getTaskView(taskId);
+ if (nextTask != null) {
+ nextTask.launchTask(false /* animate */, true /* freezeTaskList */);
+ doLogGesture(NEW_TASK);
+ }
+ reset();
+ }
+ mCanceled = false;
+ mFinishingRecentsAnimationForNewTaskId = -1;
});
}
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
- doLogGesture(NEW_TASK);
- reset();
}
public void reset() {
@@ -1140,12 +1154,26 @@
* Cancels any running animation so that the active target can be overriden by a new swipe
* handle (in case of quick switch).
*/
- public void cancelCurrentAnimation() {
+ public void cancelCurrentAnimation(SwipeSharedState sharedState) {
+ mCanceled = true;
mCurrentShift.cancelAnimation();
if (mLauncherTransitionController != null && mLauncherTransitionController
.getAnimationPlayer().isStarted()) {
mLauncherTransitionController.getAnimationPlayer().cancel();
}
+
+ if (mFinishingRecentsAnimationForNewTaskId != -1) {
+ // If we are canceling mid-starting a new task, switch to the screenshot since the
+ // recents animation has finished
+ switchToScreenshot();
+ TaskView newRunningTaskView = mRecentsView.getTaskView(
+ mFinishingRecentsAnimationForNewTaskId);
+ int newRunningTaskId = newRunningTaskView != null
+ ? newRunningTaskView.getTask().key.id
+ : -1;
+ mRecentsView.setCurrentTask(newRunningTaskId);
+ sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
+ }
}
private void invalidateHandler() {
@@ -1167,11 +1195,7 @@
mLauncherTransitionController = null;
}
- mRecentsView.setEnableFreeScroll(true);
- mRecentsView.setRunningTaskIconScaledDown(false);
- mRecentsView.setOnScrollChangeListener(null);
- mRecentsView.setRunningTaskHidden(false);
- mRecentsView.setEnableDrawingLiveTile(true);
+ mRecentsView.onGestureAnimationEnd();
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
@@ -1192,6 +1216,9 @@
private void switchToScreenshot() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ } else if (!mRecentsAnimationWrapper.hasTargets()) {
+ // If there are no targets, then we don't need to capture anything
+ setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else {
boolean finishTransitionPosted = false;
SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
@@ -1239,6 +1266,9 @@
private void finishCurrentTransitionToRecents() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ } else if (!mRecentsAnimationWrapper.hasTargets()) {
+ // If there are no targets, then there is nothing to finish
+ setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
synchronized (mRecentsAnimationWrapper) {
mRecentsAnimationWrapper.finish(true /* toRecents */,
@@ -1265,10 +1295,7 @@
}
mActivityControlHelper.onSwipeUpComplete(mActivity);
mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
-
- // Animate the first icon.
- mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation());
- mRecentsView.setSwipeDownShouldLaunchApp(true);
+ mRecentsView.onSwipeUpAnimationSuccess();
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index 7159e7c..40b9c4d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -113,6 +113,15 @@
mCurrentCenterY = mStartRect.centerY();
}
+ public void onTargetPositionChanged() {
+ if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
+ mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
+ }
+ if (mRectYAnim != null && mRectYAnim.getTargetPosition() != mTargetRect.centerY()) {
+ mRectYAnim.updatePosition(mCurrentCenterY, mTargetRect.centerY());
+ }
+ }
+
public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
mOnUpdateListeners.add(onUpdateListener);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index 83973fa..f5a9e8a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -53,6 +53,20 @@
this.mOnFinishListener = onFinishListener;
}
+ public boolean hasTargets() {
+ return unfilteredApps.length != 0;
+ }
+
+ /**
+ * Clones the target set without any actual targets. Used only when continuing a gesture after
+ * the actual recents animation has finished.
+ */
+ public SwipeAnimationTargetSet cloneWithoutTargets() {
+ return new SwipeAnimationTargetSet(controller, new RemoteAnimationTargetCompat[0],
+ homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
+ mOnFinishListener);
+ }
+
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
mOnFinishListener.accept(this);
BACKGROUND_EXECUTOR.execute(() -> {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index b5f90a5..525ead8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -744,9 +744,7 @@
public abstract void startHome();
public void reset() {
- setRunningTaskViewShowScreenshot(false);
- mRunningTaskId = -1;
- mRunningTaskTileHidden = false;
+ setCurrentTask(-1);
mIgnoreResetTaskId = -1;
mTaskListChangeId = -1;
@@ -757,6 +755,15 @@
setCurrentPage(0);
}
+ public @Nullable TaskView getRunningTaskView() {
+ return getTaskView(mRunningTaskId);
+ }
+
+ public int getRunningTaskIndex() {
+ TaskView tv = getRunningTaskView();
+ return tv == null ? -1 : indexOfChild(tv);
+ }
+
/**
* Reloads the view if anything in recents changed.
*/
@@ -767,14 +774,50 @@
}
/**
- * Ensures that the first task in the view represents {@param task} and reloads the view
- * if needed. This allows the swipe-up gesture to assume that the first tile always
- * corresponds to the correct task.
- * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
- * is called.
- * Also scrolls the view to this task
+ * Called when a gesture from an app is starting.
*/
- public void showTask(int runningTaskId) {
+ public void onGestureAnimationStart(int runningTaskId) {
+ // This needs to be called before the other states are set since it can create the task view
+ showCurrentTask(runningTaskId);
+ setEnableFreeScroll(false);
+ setEnableDrawingLiveTile(false);
+ setRunningTaskHidden(true);
+ setRunningTaskIconScaledDown(true);
+ }
+
+ /**
+ * Called only when a swipe-up gesture from an app has completed. Only called after
+ * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
+ */
+ public void onSwipeUpAnimationSuccess() {
+ if (getRunningTaskView() != null) {
+ float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get()
+ ? mLiveTileOverlay.cancelIconAnimation()
+ : 0f;
+ animateUpRunningTaskIconScale(startProgress);
+ }
+ setSwipeDownShouldLaunchApp(true);
+ }
+
+ /**
+ * Called when a gesture from an app has finished.
+ */
+ public void onGestureAnimationEnd() {
+ setEnableFreeScroll(true);
+ setEnableDrawingLiveTile(true);
+ setOnScrollChangeListener(null);
+ setRunningTaskViewShowScreenshot(true);
+ setRunningTaskHidden(false);
+ animateUpRunningTaskIconScale();
+ }
+
+ /**
+ * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}.
+ *
+ * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
+ * is called. Also scrolls the view to this task.
+ */
+ public void showCurrentTask(int runningTaskId) {
if (getChildCount() == 0) {
// Add an empty view for now until the task plan is loaded and applied
final TaskView taskView = mTaskViewPool.getView();
@@ -789,16 +832,33 @@
new ComponentName("", ""), false);
taskView.bind(mTmpRunningTask);
}
+
+ boolean runningTaskTileHidden = mRunningTaskTileHidden;
setCurrentTask(runningTaskId);
+ setCurrentPage(getRunningTaskIndex());
+ setRunningTaskViewShowScreenshot(false);
+ setRunningTaskHidden(runningTaskTileHidden);
+
+ // Reload the task list
+ mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
}
- public @Nullable TaskView getRunningTaskView() {
- return getTaskView(mRunningTaskId);
- }
+ /**
+ * Sets the running task id, cleaning up the old running task if necessary.
+ * @param runningTaskId
+ */
+ public void setCurrentTask(int runningTaskId) {
+ if (mRunningTaskId == runningTaskId) {
+ return;
+ }
- public int getRunningTaskIndex() {
- TaskView tv = getRunningTaskView();
- return tv == null ? -1 : indexOfChild(tv);
+ if (mRunningTaskId != -1) {
+ // Reset the state on the old running task view
+ setRunningTaskIconScaledDown(false);
+ setRunningTaskViewShowScreenshot(true);
+ setRunningTaskHidden(false);
+ }
+ mRunningTaskId = runningTaskId;
}
/**
@@ -812,27 +872,6 @@
}
}
- /**
- * Similar to {@link #showTask(int)} but does not put any restrictions on the first tile.
- */
- public void setCurrentTask(int runningTaskId) {
- boolean runningTaskTileHidden = mRunningTaskTileHidden;
- boolean runningTaskIconScaledDown = mRunningTaskIconScaledDown;
-
- setRunningTaskIconScaledDown(false);
- setRunningTaskHidden(false);
- setRunningTaskViewShowScreenshot(true);
- mRunningTaskId = runningTaskId;
- setRunningTaskViewShowScreenshot(false);
- setRunningTaskIconScaledDown(runningTaskIconScaledDown);
- setRunningTaskHidden(runningTaskTileHidden);
-
- 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();
@@ -1520,7 +1559,9 @@
public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
protected void onTaskLaunched(boolean success) {
- resetTaskVisuals();
+ if (success) {
+ resetTaskVisuals();
+ }
}
@Override
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index a57b97a..62915f2 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -46,6 +46,8 @@
LOCAL_JAVA_LIBRARIES := \
LauncherRoboTests
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
LOCAL_TEST_PACKAGE := Launcher3
LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src \
diff --git a/robolectric_tests/res/values/overlayable_icons_test.xml b/robolectric_tests/res/values/overlayable_icons_test.xml
new file mode 100644
index 0000000..5144e52
--- /dev/null
+++ b/robolectric_tests/res/values/overlayable_icons_test.xml
@@ -0,0 +1,36 @@
+<!--
+ 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.
+-->
+<resources>
+ <!-- overlayable_icons references all of the drawables in this package
+ that are being overlayed by resource overlays. If you remove/rename
+ any of these resources, you must also change the resource overlay icons.-->
+ <array name="overlayable_icons">
+ <item>@drawable/ic_corp</item>
+ <item>@drawable/ic_drag_handle</item>
+ <item>@drawable/ic_hourglass_top</item>
+ <item>@drawable/ic_info_no_shadow</item>
+ <item>@drawable/ic_install_no_shadow</item>
+ <item>@drawable/ic_palette</item>
+ <item>@drawable/ic_pin</item>
+ <item>@drawable/ic_remove_no_shadow</item>
+ <item>@drawable/ic_setting</item>
+ <item>@drawable/ic_smartspace_preferences</item>
+ <item>@drawable/ic_split_screen</item>
+ <item>@drawable/ic_uninstall_no_shadow</item>
+ <item>@drawable/ic_warning</item>
+ <item>@drawable/ic_widget</item>
+ </array>
+</resources>
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 3e7f67b..819a551 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -42,7 +42,7 @@
import android.view.Display;
import android.view.WindowManager;
-import com.android.launcher3.folder.FolderShape;
+import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.MainThreadInitializedObject;
@@ -304,7 +304,7 @@
changeFlags |= CHANGE_FLAG_ICON_PARAMS;
}
if (!iconShapePath.equals(oldProfile.iconShapePath)) {
- FolderShape.init(context);
+ IconShape.init(context);
}
apply(context, changeFlags);
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index b24f660..f5040b3 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -553,6 +553,8 @@
cancelAnimation();
if (reapplyNeeded) {
reapplyState();
+ // Dispatch on transition end, so that any transient property is cleared.
+ onStateTransitionEnd(mState);
}
mConfig.setAnimation(anim, null);
}
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 93df025..95ee687 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -19,7 +19,7 @@
import android.content.Context;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderShape;
+import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -38,6 +38,6 @@
FileLog.setDir(context.getApplicationContext().getFilesDir());
FeatureFlags.initialize(context);
SessionCommitReceiver.applyDefaultUserPrefs(context);
- FolderShape.init(context);
+ IconShape.init(context);
}
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index b640430..80e17c9 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1072,6 +1072,10 @@
public void setEnableFreeScroll(boolean freeScroll) {
+ if (mFreeScroll == freeScroll) {
+ return;
+ }
+
boolean wasFreeScroll = mFreeScroll;
mFreeScroll = freeScroll;
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
index 45d49e8..bb0f855 100644
--- a/src/com/android/launcher3/anim/FlingSpringAnim.java
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -36,6 +36,8 @@
private final FlingAnimation mFlingAnim;
private SpringAnimation mSpringAnim;
+ private float mTargetPosition;
+
public <K> FlingSpringAnim(K object, FloatPropertyCompat<K> property, float startPosition,
float targetPosition, float startVelocity, OnAnimationEndListener onEndListener) {
mFlingAnim = new FlingAnimation(object, property)
@@ -44,10 +46,12 @@
.setStartVelocity(startVelocity)
.setMinValue(Math.min(startPosition, targetPosition))
.setMaxValue(Math.max(startPosition, targetPosition));
+ mTargetPosition = targetPosition;
+
mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
mSpringAnim = new SpringAnimation(object, property)
.setStartVelocity(velocity)
- .setSpring(new SpringForce(targetPosition)
+ .setSpring(new SpringForce(mTargetPosition)
.setStiffness(SPRING_STIFFNESS)
.setDampingRatio(SPRING_DAMPING));
mSpringAnim.addEndListener(onEndListener);
@@ -55,6 +59,19 @@
}));
}
+ public float getTargetPosition() {
+ return mTargetPosition;
+ }
+
+ public void updatePosition(float startPosition, float targetPosition) {
+ mFlingAnim.setMinValue(Math.min(startPosition, targetPosition))
+ .setMaxValue(Math.max(startPosition, targetPosition));
+ mTargetPosition = targetPosition;
+ if (mSpringAnim != null) {
+ mSpringAnim.animateToFinalPosition(mTargetPosition);
+ }
+ }
+
public void start() {
mFlingAnim.start();
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 2461e28..2450039 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -19,7 +19,7 @@
import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-import static com.android.launcher3.folder.FolderShape.getShape;
+import static com.android.launcher3.graphics.IconShape.getShape;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.Animator;
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index 46df77a..09e8276 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -16,7 +16,7 @@
package com.android.launcher3.folder;
-import static com.android.launcher3.folder.FolderShape.getShape;
+import static com.android.launcher3.graphics.IconShape.getShape;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.Animator;
@@ -311,7 +311,7 @@
public Path getClipPath() {
mPath.reset();
- getShape().addShape(mPath, getOffsetX(), getOffsetY(), getScaledRadius());
+ getShape().addToPath(mPath, getOffsetX(), getOffsetY(), getScaledRadius());
return mPath;
}
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index ce83a17..c9566cb 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -16,15 +16,15 @@
package com.android.launcher3.graphics;
+import static com.android.launcher3.graphics.IconShape.getShapePath;
+
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Path;
import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Process;
@@ -34,7 +34,6 @@
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -47,16 +46,8 @@
public class DrawableFactory implements ResourceBasedOverride {
public static final MainThreadInitializedObject<DrawableFactory> INSTANCE =
- new MainThreadInitializedObject<>(c -> {
- DrawableFactory factory = Overrides.getObject(DrawableFactory.class,
- c.getApplicationContext(), R.string.drawable_factory_class);
- factory.mContext = c;
- return factory;
- });
-
-
- private Context mContext;
- private Path mPreloadProgressPath;
+ new MainThreadInitializedObject<>(c -> Overrides.getObject(DrawableFactory.class,
+ c.getApplicationContext(), R.string.drawable_factory_class));
protected final UserHandle mMyUser = Process.myUserHandle();
protected final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>();
@@ -66,7 +57,7 @@
*/
public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
FastBitmapDrawable drawable = info.usingLowResIcon()
- ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context)
+ ? new PlaceHolderIconDrawable(info, getShapePath(), context)
: new FastBitmapDrawable(info);
drawable.setIsDisabled(info.isDisabled());
return drawable;
@@ -74,7 +65,7 @@
public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) {
return info.isLowRes()
- ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context)
+ ? new PlaceHolderIconDrawable(info, getShapePath(), context)
: new FastBitmapDrawable(info);
}
@@ -82,29 +73,7 @@
* Returns a FastBitmapDrawable with the icon.
*/
public PreloadIconDrawable newPendingIcon(Context context, ItemInfoWithIcon info) {
- return new PreloadIconDrawable(info, getPreloadProgressPath(), context);
- }
-
- protected Path getPreloadProgressPath() {
- if (mPreloadProgressPath != null) {
- return mPreloadProgressPath;
- }
- if (Utilities.ATLEAST_OREO) {
- // Load the path from Mask Icon
- AdaptiveIconDrawable icon = (AdaptiveIconDrawable)
- mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper);
- icon.setBounds(0, 0,
- PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE);
- mPreloadProgressPath = icon.getIconMask();
- } else {
-
- // Create a circle static from top center and going clockwise.
- Path p = new Path();
- p.moveTo(PreloadIconDrawable.PATH_SIZE / 2, 0);
- p.addArc(0, 0, PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE, -90, 360);
- mPreloadProgressPath = p;
- }
- return mPreloadProgressPath;
+ return new PreloadIconDrawable(info, getShapePath(), context);
}
/**
diff --git a/src/com/android/launcher3/folder/FolderShape.java b/src/com/android/launcher3/graphics/IconShape.java
similarity index 86%
rename from src/com/android/launcher3/folder/FolderShape.java
rename to src/com/android/launcher3/graphics/IconShape.java
index ec6078e..88e4452 100644
--- a/src/com/android/launcher3/folder/FolderShape.java
+++ b/src/com/android/launcher3/graphics/IconShape.java
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.folder;
+package com.android.launcher3.graphics;
+
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -31,7 +33,6 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
-import android.graphics.RegionIterator;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
@@ -45,6 +46,8 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.icons.GraphicsUtils;
+import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ClipPathView;
@@ -59,22 +62,39 @@
import androidx.annotation.Nullable;
/**
- * Abstract representation of the shape of a folder icon
+ * Abstract representation of the shape of an icon shape
*/
-public abstract class FolderShape {
+public abstract class IconShape {
- private static FolderShape sInstance = new Circle();
+ private static IconShape sInstance = new Circle();
+ private static Path sShapePath;
+ private static float sNormalizationScale = ICON_VISIBLE_AREA_FACTOR;
- public static FolderShape getShape() {
+ public static final int DEFAULT_PATH_SIZE = 100;
+
+ public static IconShape getShape() {
return sInstance;
}
+ public static Path getShapePath() {
+ if (sShapePath == null) {
+ Path p = new Path();
+ getShape().addToPath(p, 0, 0, DEFAULT_PATH_SIZE * 0.5f);
+ sShapePath = p;
+ }
+ return sShapePath;
+ }
+
+ public static float getNormalizationScale() {
+ return sNormalizationScale;
+ }
+
private SparseArray<TypedValue> mAttrs;
public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
Paint paint);
- public abstract void addShape(Path path, float offsetX, float offsetY, float radius);
+ public abstract void addToPath(Path path, float offsetX, float offsetY, float radius);
public abstract <T extends View & ClipPathView> Animator createRevealAnimator(T target,
Rect startRect, Rect endRect, float endRadius, boolean isReversed);
@@ -87,7 +107,7 @@
/**
* Abstract shape where the reveal animation is a derivative of a round rect animation
*/
- private static abstract class SimpleRectShape extends FolderShape {
+ private static abstract class SimpleRectShape extends IconShape {
@Override
public final <T extends View & ClipPathView> Animator createRevealAnimator(T target,
@@ -107,7 +127,7 @@
/**
* Abstract shape which draws using {@link Path}
*/
- private static abstract class PathShape extends FolderShape {
+ private static abstract class PathShape extends IconShape {
private final Path mTmpPath = new Path();
@@ -115,7 +135,7 @@
public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
Paint paint) {
mTmpPath.reset();
- addShape(mTmpPath, offsetX, offsetY, radius);
+ addToPath(mTmpPath, offsetX, offsetY, radius);
canvas.drawPath(mTmpPath, paint);
}
@@ -166,7 +186,7 @@
}
@Override
- public void addShape(Path path, float offsetX, float offsetY, float radius) {
+ public void addToPath(Path path, float offsetX, float offsetY, float radius) {
path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW);
}
@@ -196,7 +216,7 @@
}
@Override
- public void addShape(Path path, float offsetX, float offsetY, float radius) {
+ public void addToPath(Path path, float offsetX, float offsetY, float radius) {
float cx = radius + offsetX;
float cy = radius + offsetY;
float cr = radius * mRadiusRatio;
@@ -223,7 +243,7 @@
}
@Override
- public void addShape(Path p, float offsetX, float offsetY, float r1) {
+ public void addToPath(Path p, float offsetX, float offsetY, float r1) {
float r2 = r1 * mRadiusRatio;
float cx = r1 + offsetX;
float cy = r1 + offsetY;
@@ -274,7 +294,7 @@
}
@Override
- public void addShape(Path p, float offsetX, float offsetY, float r) {
+ public void addToPath(Path p, float offsetX, float offsetY, float r) {
float cx = r + offsetX;
float cy = r + offsetY;
float control = r - r * mRadiusRatio;
@@ -358,7 +378,7 @@
pickBestShape(context);
}
- private static FolderShape getShapeDefinition(String type, float radius) {
+ private static IconShape getShapeDefinition(String type, float radius) {
switch (type) {
case "Circle":
return new Circle();
@@ -373,8 +393,8 @@
}
}
- private static List<FolderShape> getAllShapes(Context context) {
- ArrayList<FolderShape> result = new ArrayList<>();
+ private static List<IconShape> getAllShapes(Context context) {
+ ArrayList<IconShape> result = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.folder_shapes)) {
// Find the root tag
@@ -393,7 +413,7 @@
if (type == XmlPullParser.START_TAG) {
AttributeSet attrs = Xml.asAttributeSet(parser);
TypedArray a = context.obtainStyledAttributes(attrs, radiusAttr);
- FolderShape shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
+ IconShape shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
a.recycle();
shape.mAttrs = Themes.createValueMap(context, attrs, keysToIgnore);
@@ -409,7 +429,7 @@
@TargetApi(Build.VERSION_CODES.O)
protected static void pickBestShape(Context context) {
// Pick any large size
- int size = 200;
+ final int size = 200;
Region full = new Region(0, 0, size, size);
Region iconR = new Region();
@@ -420,23 +440,17 @@
Path shapePath = new Path();
Region shapeR = new Region();
- Rect tempRect = new Rect();
// Find the shape with minimum area of divergent region.
int minArea = Integer.MAX_VALUE;
- FolderShape closestShape = null;
- for (FolderShape shape : getAllShapes(context)) {
+ IconShape closestShape = null;
+ for (IconShape shape : getAllShapes(context)) {
shapePath.reset();
- shape.addShape(shapePath, 0, 0, size / 2f);
+ shape.addToPath(shapePath, 0, 0, size / 2f);
shapeR.setPath(shapePath, full);
shapeR.op(iconR, Op.XOR);
- RegionIterator itr = new RegionIterator(shapeR);
- int area = 0;
-
- while (itr.next(tempRect)) {
- area += tempRect.width() * tempRect.height();
- }
+ int area = GraphicsUtils.getArea(shapeR);
if (area < minArea) {
minArea = area;
closestShape = shape;
@@ -446,5 +460,10 @@
if (closestShape != null) {
sInstance = closestShape;
}
+
+ // Initialize shape properties
+ drawable.setBounds(0, 0, DEFAULT_PATH_SIZE, DEFAULT_PATH_SIZE);
+ sShapePath = new Path(drawable.getIconMask());
+ sNormalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null);
}
}
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index d3a7955..cc4c2ef 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -17,6 +17,8 @@
package com.android.launcher3.graphics;
+import static com.android.launcher3.graphics.IconShape.DEFAULT_PATH_SIZE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -55,8 +57,6 @@
}
};
- public static final int PATH_SIZE = 100;
-
private static final float PROGRESS_WIDTH = 7;
private static final float PROGRESS_GAP = 2;
private static final int MAX_PAINT_ALPHA = 255;
@@ -123,14 +123,14 @@
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mTmpMatrix.setScale(
- (bounds.width() - 2 * PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE,
- (bounds.height() - 2 * PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE);
+ (bounds.width() - 2 * PROGRESS_WIDTH - 2 * PROGRESS_GAP) / DEFAULT_PATH_SIZE,
+ (bounds.height() - 2 * PROGRESS_WIDTH - 2 * PROGRESS_GAP) / DEFAULT_PATH_SIZE);
mTmpMatrix.postTranslate(
bounds.left + PROGRESS_WIDTH + PROGRESS_GAP,
bounds.top + PROGRESS_WIDTH + PROGRESS_GAP);
mProgressPath.transform(mTmpMatrix, mScaledTrackPath);
- float scale = bounds.width() / PATH_SIZE;
+ float scale = bounds.width() / DEFAULT_PATH_SIZE;
mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(),
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 020e380..787c93c 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -25,7 +25,6 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
-import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
@@ -42,6 +41,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
import com.android.launcher3.BubbleTextView;
@@ -54,7 +54,7 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.FolderShape;
+import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.popup.SystemShortcut;
@@ -67,16 +67,19 @@
* A view that is created to look like another view with the purpose of creating fluid animations.
*/
@TargetApi(Build.VERSION_CODES.Q)
-public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView {
+public class FloatingIconView extends View implements
+ Animator.AnimatorListener, ClipPathView, OnGlobalLayoutListener {
public static final float SHAPE_PROGRESS_DURATION = 0.15f;
private static final int FADE_DURATION_MS = 200;
private static final Rect sTmpRect = new Rect();
+ private static final RectF sTmpRectF = new RectF();
private static final Object[] sTmpObjArray = new Object[1];
private Runnable mEndRunnable;
private CancellationSignal mLoadIconSignal;
+ private final Launcher mLauncher;
private final int mBlurSizeOutline;
private boolean mIsVerticalBarLayout = false;
@@ -91,6 +94,10 @@
private Path mClipPath;
private float mTaskCornerRadius;
+ private View mOriginalIcon;
+ private RectF mPositionOut;
+ private Runnable mOnTargetChangeRunnable;
+
private final Rect mFinalDrawableBounds = new Rect();
private final Rect mBgDrawableBounds = new Rect();
private float mBgDrawableStartScale = 1f;
@@ -99,12 +106,24 @@
private AnimatorSet mFadeAnimatorSet;
private ListenerView mListenerView;
- private FloatingIconView(Context context) {
- super(context);
-
- mBlurSizeOutline = context.getResources().getDimensionPixelSize(
+ private FloatingIconView(Launcher launcher) {
+ super(launcher);
+ mLauncher = launcher;
+ mBlurSizeOutline = getResources().getDimensionPixelSize(
R.dimen.blur_size_medium_outline);
- mListenerView = new ListenerView(context, null);
+ mListenerView = new ListenerView(launcher, null);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ getViewTreeObserver().addOnGlobalLayoutListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ super.onDetachedFromWindow();
}
/**
@@ -144,7 +163,7 @@
mTaskCornerRadius = cornerRadius;
if (mIsAdaptiveIcon && shapeRevealProgress >= 0) {
if (mRevealAnimator == null) {
- mRevealAnimator = (ValueAnimator) FolderShape.getShape().createRevealAnimator(this,
+ mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(this,
mStartRevealRect, mEndRevealRect, mTaskCornerRadius / scale, !isOpening);
mRevealAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -188,21 +207,28 @@
* @param v The view to copy
* @param positionOut Rect that will hold the size and position of v.
*/
- private void matchPositionOf(Launcher launcher, View v, RectF positionOut) {
- mRotation = getLocationBoundsForView(launcher, v, positionOut);
+ private void matchPositionOf(View v, RectF positionOut) {
+ float rotation = getLocationBoundsForView(v, positionOut);
final LayoutParams lp = new LayoutParams(
Math.round(positionOut.width()),
Math.round(positionOut.height()));
- lp.ignoreInsets = true;
-
- // Position the floating view exactly on top of the original
- lp.leftMargin = Math.round(positionOut.left);
- lp.topMargin = Math.round(positionOut.top);
+ updatePosition(rotation, positionOut, lp);
setLayoutParams(lp);
+ }
+
+ private void updatePosition(float rotation, RectF position, LayoutParams lp) {
+ mRotation = rotation;
+ mPositionOut.set(position);
+ lp.ignoreInsets = true;
+ // Position the floating view exactly on top of the original
+ lp.leftMargin = Math.round(position.left);
+ lp.topMargin = Math.round(position.top);
+
// Set the properties here already to make sure they are available when running the first
// animation frame.
layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
+ lp.height);
+
}
/**
@@ -210,7 +236,7 @@
* - For DeepShortcutView, we return the bounds of the icon view.
* - For BubbleTextView, we return the icon bounds.
*/
- private float getLocationBoundsForView(Launcher launcher, View v, RectF outRect) {
+ private float getLocationBoundsForView(View v, RectF outRect) {
boolean ignoreTransform = true;
if (v instanceof DeepShortcutView) {
v = ((DeepShortcutView) v).getBubbleText();
@@ -235,7 +261,7 @@
float[] points = new float[] {iconBounds.left, iconBounds.top, iconBounds.right,
iconBounds.bottom};
float[] rotation = new float[] {0};
- Utilities.getDescendantCoordRelativeToAncestor(v, launcher.getDragLayer(), points,
+ Utilities.getDescendantCoordRelativeToAncestor(v, mLauncher.getDragLayer(), points,
false, ignoreTransform, rotation);
outRect.set(
Math.min(points[0], points[2]),
@@ -246,7 +272,7 @@
}
@WorkerThread
- private void getIcon(Launcher launcher, View v, ItemInfo info, boolean isOpening,
+ private void getIcon(View v, ItemInfo info, boolean isOpening,
Runnable onIconLoadedRunnable, CancellationSignal loadIconSignal) {
final LayoutParams lp = (LayoutParams) getLayoutParams();
Drawable drawable = null;
@@ -263,7 +289,7 @@
}
} else {
if (supportsAdaptiveIcons) {
- drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
+ drawable = Utilities.getFullDrawable(mLauncher, info, lp.width, lp.height,
false, sTmpObjArray);
if (!(drawable instanceof AdaptiveIconDrawable)) {
// The drawable we get back is not an adaptive icon, so we need to use the
@@ -275,7 +301,7 @@
// Similar to DragView, we simply use the BubbleTextView icon here.
drawable = btvIcon;
} else {
- drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
+ drawable = Utilities.getFullDrawable(mLauncher, info, lp.width, lp.height,
false, sTmpObjArray);
}
}
@@ -328,7 +354,7 @@
mStartRevealRect.inset(mBlurSizeOutline, mBlurSizeOutline);
}
- float aspectRatio = launcher.getDeviceProfile().aspectRatio;
+ float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
if (mIsVerticalBarLayout) {
lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
} else {
@@ -395,7 +421,7 @@
Rect bounds = new Rect(0, 0, lp.width + mBlurSizeOutline, lp.height + mBlurSizeOutline);
bounds.inset(mBlurSizeOutline / 2, mBlurSizeOutline / 2);
- try (LauncherIcons li = LauncherIcons.obtain(Launcher.fromContext(getContext()))) {
+ try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null));
}
@@ -452,6 +478,23 @@
@Override
public void onAnimationRepeat(Animator animator) {}
+ @Override
+ public void onGlobalLayout() {
+ if (mOriginalIcon.isAttachedToWindow() && mPositionOut != null) {
+ float rotation = getLocationBoundsForView(mOriginalIcon, sTmpRectF);
+ if (rotation != mRotation || !sTmpRectF.equals(mPositionOut)) {
+ updatePosition(rotation, sTmpRectF, (LayoutParams) getLayoutParams());
+ if (mOnTargetChangeRunnable != null) {
+ mOnTargetChangeRunnable.run();
+ }
+ }
+ }
+ }
+
+ public void setOnTargetChangeListener(Runnable onTargetChangeListener) {
+ mOnTargetChangeRunnable = onTargetChangeListener;
+ }
+
/**
* Creates a floating icon view for {@param originalView}.
* @param originalView The view to copy
@@ -468,8 +511,10 @@
FloatingIconView view = recycle != null ? recycle : new FloatingIconView(launcher);
view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout();
+ view.mOriginalIcon = originalView;
+ view.mPositionOut = positionOut;
// Match the position of the original view.
- view.matchPositionOf(launcher, originalView, positionOut);
+ view.matchPositionOf(originalView, positionOut);
// Get the drawable on the background thread
// Must be called after matchPositionOf so that we know what size to load.
@@ -482,7 +527,7 @@
};
CancellationSignal loadIconSignal = view.mLoadIconSignal;
new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
- view.getIcon(launcher, originalView, (ItemInfo) originalView.getTag(), isOpening,
+ view.getIcon(originalView, (ItemInfo) originalView.getTag(), isOpening,
onIconLoaded, loadIconSignal);
});
}
@@ -580,7 +625,10 @@
if (mFadeAnimatorSet != null) {
mFadeAnimatorSet.cancel();
}
+ mPositionOut = null;
mFadeAnimatorSet = null;
mListenerView.setListener(null);
+ mOriginalIcon = null;
+ mOnTargetChangeRunnable = null;
}
}