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
This commit is contained in:
Nick Chameyev 2021-07-26 14:56:50 +01:00
parent 4b878f5318
commit 5e8dbe77e8
7 changed files with 394 additions and 4 deletions

View File

@ -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

View File

@ -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<O
MAIN_EXECUTOR.execute(() -> 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));

View File

@ -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);
}
}
}

View File

@ -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<ScreenListener> 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);
}
}

View File

@ -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<ViewGroup, Boolean> mOriginalClipToPadding = new HashMap<>();
private final Map<ViewGroup, Boolean> 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);
}
}
}
}

View File

@ -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<BubbleTextView, Float> 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;
}

View File

@ -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();