From 5e8dbe77e81260bdd41a5ec8d2a6962d6319be73 Mon Sep 17 00:00:00 2001 From: Nick Chameyev Date: Mon, 26 Jul 2021 14:56:50 +0100 Subject: [PATCH] Add unfold animation to launcher icons and widgets Adds unfold animation to launcher which translates icons and widgets from the center to the sides on foldable devices. Bug: 193794541 Test: manual Change-Id: I9d6e018a0451d342f02dddea47bc180781c31d43 --- .../uioverrides/QuickstepLauncher.java | 54 +++++++ .../quickstep/TouchInteractionService.java | 7 + .../LauncherUnfoldAnimationController.java | 122 +++++++++++++++ .../util/ProxyScreenStatusProvider.java | 51 +++++++ ...UnfoldMoveFromCenterWorkspaceAnimator.java | 140 ++++++++++++++++++ src/com/android/launcher3/BubbleTextView.java | 13 +- .../widget/NavigableAppWidgetHostView.java | 11 +- 7 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java create mode 100644 quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java create mode 100644 quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 2009cd75d9..069af96d22 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -36,9 +36,13 @@ import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SY import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceStateManager; import android.view.HapticFeedbackConstants; import android.view.View; +import androidx.annotation.Nullable; + import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; @@ -75,9 +79,14 @@ import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskUtils; +import com.android.quickstep.util.LauncherUnfoldAnimationController; +import com.android.quickstep.util.ProxyScreenStatusProvider; import com.android.quickstep.util.QuickstepOnboardingPrefs; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; +import com.android.unfold.UnfoldTransitionFactory; +import com.android.unfold.UnfoldTransitionProgressProvider; +import com.android.unfold.config.UnfoldTransitionConfig; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -97,10 +106,51 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { private FixedContainerItems mAllAppsPredictions; private HotseatPredictionController mHotseatPredictionController; + @Nullable + private LauncherUnfoldAnimationController mLauncherUnfoldAnimationController; + @Override protected void setupViews() { super.setupViews(); mHotseatPredictionController = new HotseatPredictionController(this); + + final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this); + if (config.isEnabled()) { + final UnfoldTransitionProgressProvider unfoldTransitionProgressProvider = + UnfoldTransitionFactory.createUnfoldTransitionProgressProvider( + this, + config, + ProxyScreenStatusProvider.INSTANCE, + getSystemService(DeviceStateManager.class), + getSystemService(SensorManager.class), + getMainThreadHandler(), + getMainExecutor() + ); + + mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController( + this, + getWindowManager(), + unfoldTransitionProgressProvider + ); + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (mLauncherUnfoldAnimationController != null) { + mLauncherUnfoldAnimationController.onResume(); + } + } + + @Override + protected void onPause() { + if (mLauncherUnfoldAnimationController != null) { + mLauncherUnfoldAnimationController.onPause(); + } + + super.onPause(); } @Override @@ -231,6 +281,10 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { public void onDestroy() { super.onDestroy(); mHotseatPredictionController.destroy(); + + if (mLauncherUnfoldAnimationController != null) { + mLauncherUnfoldAnimationController.onDestroy(); + } } @Override diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 61622eebda..4979206791 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -100,6 +100,7 @@ import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.util.ProtoTracer; +import com.android.quickstep.util.ProxyScreenStatusProvider; import com.android.quickstep.util.SplitScreenBounds; import com.android.systemui.plugins.OverscrollPlugin; import com.android.systemui.plugins.PluginListener; @@ -268,6 +269,12 @@ public class TouchInteractionService extends Service implements PluginListener SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb)); } + @BinderThread + @Override + public void onScreenTurnedOn() { + MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn); + } + @Override public void onRotationProposal(int rotation, boolean isValid) { executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid)); diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java new file mode 100644 index 0000000000..ea1ece86d4 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021 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.util; + +import android.view.ViewTreeObserver; +import android.view.WindowManager; + +import com.android.launcher3.Launcher; +import com.android.unfold.UnfoldTransitionProgressProvider; +import com.android.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener; + +/** + * Controls animations that are happening during unfolding foldable devices + */ +public class LauncherUnfoldAnimationController { + + private final Launcher mLauncher; + private final UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider; + private final UnfoldMoveFromCenterWorkspaceAnimator mMoveFromCenterWorkspaceAnimation; + + private final AnimationListener mAnimationListener = new AnimationListener(); + + private boolean mIsTransitionRunning = false; + private boolean mIsReadyToPlayAnimation = false; + + public LauncherUnfoldAnimationController( + Launcher launcher, + WindowManager windowManager, + UnfoldTransitionProgressProvider unfoldTransitionProgressProvider) { + mLauncher = launcher; + mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider; + mMoveFromCenterWorkspaceAnimation = new UnfoldMoveFromCenterWorkspaceAnimator(launcher, + windowManager); + mUnfoldTransitionProgressProvider.addCallback(mAnimationListener); + } + + /** + * Called when launcher is resumed + */ + public void onResume() { + final ViewTreeObserver obs = mLauncher.getWorkspace().getViewTreeObserver(); + obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (obs.isAlive()) { + onPreDrawAfterResume(); + obs.removeOnPreDrawListener(this); + } + return true; + } + }); + } + + /** + * Called when launcher activity is paused + */ + public void onPause() { + mIsReadyToPlayAnimation = false; + + if (mIsTransitionRunning) { + mIsTransitionRunning = false; + mMoveFromCenterWorkspaceAnimation.onTransitionFinished(); + } + } + + /** + * Called when launcher activity is destroyed + */ + public void onDestroy() { + mUnfoldTransitionProgressProvider.removeCallback(mAnimationListener); + } + + /** + * Called after performing layouting of the views after configuration change + */ + private void onPreDrawAfterResume() { + mIsReadyToPlayAnimation = true; + + if (mIsTransitionRunning) { + mMoveFromCenterWorkspaceAnimation.onTransitionStarted(); + } + } + + private class AnimationListener implements TransitionProgressListener { + + @Override + public void onTransitionStarted() { + mIsTransitionRunning = true; + + if (mIsReadyToPlayAnimation) { + mMoveFromCenterWorkspaceAnimation.onTransitionStarted(); + } + } + + @Override + public void onTransitionFinished() { + if (mIsReadyToPlayAnimation) { + mMoveFromCenterWorkspaceAnimation.onTransitionFinished(); + } + + mIsTransitionRunning = false; + } + + @Override + public void onTransitionProgress(float progress) { + mMoveFromCenterWorkspaceAnimation.onTransitionProgress(progress); + } + } +} diff --git a/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java new file mode 100644 index 0000000000..9eda8f4db1 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 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.util; + +import androidx.annotation.NonNull; + +import com.android.unfold.updates.screen.ScreenStatusProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * Screen status provider implementation that exposes methods to provide screen + * status updates to listeners. It is used to receive screen turned on event from + * SystemUI to Launcher. + */ +public class ProxyScreenStatusProvider implements ScreenStatusProvider { + + public static final ProxyScreenStatusProvider INSTANCE = new ProxyScreenStatusProvider(); + private final List mListeners = new ArrayList<>(); + + /** + * Called when the screen is on and ready (windows are drawn and screen blocker is removed) + */ + public void onScreenTurnedOn() { + mListeners.forEach(ScreenListener::onScreenTurnedOn); + } + + @Override + public void addCallback(@NonNull ScreenListener listener) { + mListeners.add(listener); + } + + @Override + public void removeCallback(@NonNull ScreenListener listener) { + mListeners.remove(listener); + } +} diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java new file mode 100644 index 0000000000..126e0449e7 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 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.util; + +import android.annotation.NonNull; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.CellLayout; +import com.android.launcher3.Hotseat; +import com.android.launcher3.Launcher; +import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.Workspace; +import com.android.launcher3.widget.NavigableAppWidgetHostView; +import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator; +import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier; +import com.android.unfold.UnfoldTransitionProgressProvider; + +import java.util.HashMap; +import java.util.Map; + +/** + * Animation that moves launcher icons and widgets from center to the sides (final position) + */ +public class UnfoldMoveFromCenterWorkspaceAnimator + implements UnfoldTransitionProgressProvider.TransitionProgressListener { + + private final Launcher mLauncher; + private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimation; + + private final Map mOriginalClipToPadding = new HashMap<>(); + private final Map mOriginalClipChildren = new HashMap<>(); + + public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager) { + mLauncher = launcher; + mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager, + new WorkspaceViewsTranslationApplier()); + } + + @Override + public void onTransitionStarted() { + mMoveFromCenterAnimation.updateDisplayProperties(); + + Workspace workspace = mLauncher.getWorkspace(); + Hotseat hotseat = mLauncher.getHotseat(); + + // App icons and widgets + workspace + .forEachVisiblePage(page -> { + final CellLayout cellLayout = (CellLayout) page; + ShortcutAndWidgetContainer itemsContainer = cellLayout + .getShortcutsAndWidgets(); + disableClipping(cellLayout); + + for (int i = 0; i < itemsContainer.getChildCount(); i++) { + View child = itemsContainer.getChildAt(i); + mMoveFromCenterAnimation.registerViewForAnimation(child); + } + }); + + disableClipping(workspace); + + // Hotseat icons + ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets(); + disableClipping(hotseat); + + for (int i = 0; i < hotseatIcons.getChildCount(); i++) { + View child = hotseatIcons.getChildAt(i); + mMoveFromCenterAnimation.registerViewForAnimation(child); + } + + onTransitionProgress(0f); + } + + @Override + public void onTransitionProgress(float progress) { + mMoveFromCenterAnimation.onTransitionProgress(progress); + } + + @Override + public void onTransitionFinished() { + mMoveFromCenterAnimation.onTransitionFinished(); + mMoveFromCenterAnimation.clearRegisteredViews(); + + restoreClipping(mLauncher.getWorkspace()); + mLauncher.getWorkspace().forEachVisiblePage(page -> restoreClipping((CellLayout) page)); + restoreClipping(mLauncher.getHotseat()); + + mOriginalClipChildren.clear(); + mOriginalClipToPadding.clear(); + } + + private void disableClipping(ViewGroup view) { + mOriginalClipToPadding.put(view, view.getClipToPadding()); + mOriginalClipChildren.put(view, view.getClipChildren()); + view.setClipToPadding(false); + view.setClipChildren(false); + } + + private void restoreClipping(ViewGroup view) { + final Boolean originalClipToPadding = mOriginalClipToPadding.get(view); + if (originalClipToPadding != null) { + view.setClipToPadding(originalClipToPadding); + } + final Boolean originalClipChildren = mOriginalClipChildren.get(view); + if (originalClipChildren != null) { + view.setClipChildren(originalClipChildren); + } + } + + private static class WorkspaceViewsTranslationApplier implements TranslationApplier { + + @Override + public void apply(@NonNull View view, float x, float y) { + if (view instanceof NavigableAppWidgetHostView) { + ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y); + } else if (view instanceof BubbleTextView) { + ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y); + } else { + view.setTranslationX(x); + view.setTranslationY(y); + } + } + } +} diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index f800cf6276..a90fda2865 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -90,6 +90,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private final PointF mTranslationForReorderBounce = new PointF(0, 0); private final PointF mTranslationForReorderPreview = new PointF(0, 0); + private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0); + private float mScaleForReorderBounce = 1f; private static final Property DOT_SCALE_PROPERTY @@ -799,8 +801,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } private void updateTranslation() { - super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x); - super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y); + super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x + + mTranslationForMoveFromCenterAnimation.x); + super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y + + mTranslationForMoveFromCenterAnimation.y); } public void setReorderBounceOffset(float x, float y) { @@ -833,6 +837,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, return mScaleForReorderBounce; } + public void setTranslationForMoveFromCenterAnimation(float x, float y) { + mTranslationForMoveFromCenterAnimation.set(x, y); + updateTranslation(); + } + public View getView() { return this; } diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java index d12fe74637..241c93734c 100644 --- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java @@ -49,6 +49,8 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView */ private final PointF mTranslationForCentering = new PointF(0, 0); + private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0); + private final PointF mTranslationForReorderBounce = new PointF(0, 0); private final PointF mTranslationForReorderPreview = new PointF(0, 0); private float mScaleForReorderBounce = 1f; @@ -167,9 +169,9 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView private void updateTranslation() { super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x - + mTranslationForCentering.x); + + mTranslationForCentering.x + mTranslationForMoveFromCenterAnimation.x); super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y - + mTranslationForCentering.y); + + mTranslationForCentering.y + mTranslationForMoveFromCenterAnimation.y); } public void setTranslationForCentering(float x, float y) { @@ -177,6 +179,11 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView updateTranslation(); } + public void setTranslationForMoveFromCenterAnimation(float x, float y) { + mTranslationForMoveFromCenterAnimation.set(x, y); + updateTranslation(); + } + public void setReorderBounceOffset(float x, float y) { mTranslationForReorderBounce.set(x, y); updateTranslation();