1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.uioverrides.states; 17 18 import static android.view.View.VISIBLE; 19 20 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 21 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 22 import static com.android.launcher3.LauncherState.HINT_STATE; 23 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; 24 import static com.android.launcher3.LauncherState.NORMAL; 25 import static com.android.launcher3.LauncherState.OVERVIEW; 26 import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; 27 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator; 28 import static com.android.launcher3.anim.Interpolators.ACCEL; 29 import static com.android.launcher3.anim.Interpolators.DEACCEL; 30 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; 31 import static com.android.launcher3.anim.Interpolators.DEACCEL_3; 32 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 33 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; 34 import static com.android.launcher3.anim.Interpolators.INSTANT; 35 import static com.android.launcher3.anim.Interpolators.LINEAR; 36 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; 37 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; 38 import static com.android.launcher3.anim.Interpolators.clampToProgress; 39 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; 40 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; 41 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; 42 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE; 43 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE; 44 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE; 45 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; 46 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE; 47 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X; 48 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y; 49 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; 50 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE; 51 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE; 52 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE; 53 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; 54 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; 55 56 import android.animation.Animator; 57 import android.animation.AnimatorSet; 58 import android.animation.ValueAnimator; 59 import android.view.View; 60 import android.view.animation.Interpolator; 61 62 import com.android.launcher3.CellLayout; 63 import com.android.launcher3.Hotseat; 64 import com.android.launcher3.Launcher; 65 import com.android.launcher3.LauncherState; 66 import com.android.launcher3.LauncherState.ScaleAndTranslation; 67 import com.android.launcher3.Workspace; 68 import com.android.launcher3.allapps.AllAppsContainerView; 69 import com.android.launcher3.allapps.AllAppsTransitionController; 70 import com.android.launcher3.statemanager.StateManager; 71 import com.android.launcher3.states.StateAnimationConfig; 72 import com.android.launcher3.uioverrides.QuickstepLauncher; 73 import com.android.quickstep.SysUINavigationMode; 74 import com.android.quickstep.util.RecentsAtomicAnimationFactory; 75 import com.android.quickstep.views.RecentsView; 76 77 /** 78 * Animation factory for quickstep specific transitions 79 */ 80 public class QuickstepAtomicAnimationFactory extends 81 RecentsAtomicAnimationFactory<Launcher, LauncherState> { 82 83 // Scale recents takes before animating in 84 private static final float RECENTS_PREPARE_SCALE = 1.33f; 85 86 public static final int INDEX_SHELF_ANIM = RecentsAtomicAnimationFactory.NEXT_INDEX + 0; 87 public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 88 RecentsAtomicAnimationFactory.NEXT_INDEX + 1; 89 90 private static final int MY_ANIM_COUNT = 2; 91 protected static final int NEXT_INDEX = RecentsAtomicAnimationFactory.NEXT_INDEX 92 + MY_ANIM_COUNT; 93 94 // Due to use of physics, duration may differ between devices so we need to calculate and 95 // cache the value. 96 private int mHintToNormalDuration = -1; 97 98 public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300; 99 QuickstepAtomicAnimationFactory(QuickstepLauncher activity)100 public QuickstepAtomicAnimationFactory(QuickstepLauncher activity) { 101 super(activity, MY_ANIM_COUNT); 102 } 103 104 @Override createStateElementAnimation(int index, float... values)105 public Animator createStateElementAnimation(int index, float... values) { 106 switch (index) { 107 case INDEX_SHELF_ANIM: { 108 AllAppsTransitionController aatc = mActivity.getAllAppsController(); 109 Animator springAnim = aatc.createSpringAnimation(values); 110 111 if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) { 112 // Translate hotseat with the shelf until reaching overview. 113 float overviewProgress = OVERVIEW.getVerticalProgress(mActivity); 114 ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mActivity); 115 float shiftRange = aatc.getShiftRange(); 116 if (values.length == 1) { 117 values = new float[] {aatc.getProgress(), values[0]}; 118 } 119 ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values); 120 hotseatAnim.addUpdateListener(anim -> { 121 float progress = (Float) anim.getAnimatedValue(); 122 if (progress >= overviewProgress || mActivity.isInState(BACKGROUND_APP)) { 123 float hotseatShift = (progress - overviewProgress) * shiftRange; 124 mActivity.getHotseat().setTranslationY(hotseatShift + sat.translationY); 125 } 126 }); 127 hotseatAnim.setInterpolator(LINEAR); 128 hotseatAnim.setDuration(springAnim.getDuration()); 129 130 AnimatorSet anim = new AnimatorSet(); 131 anim.play(hotseatAnim); 132 anim.play(springAnim); 133 return anim; 134 } 135 136 return springAnim; 137 } 138 case INDEX_PAUSE_TO_OVERVIEW_ANIM: { 139 StateAnimationConfig config = new StateAnimationConfig(); 140 config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW; 141 142 config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); 143 config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3); 144 if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) { 145 config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); 146 config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); 147 } 148 149 StateManager<LauncherState> stateManager = mActivity.getStateManager(); 150 return stateManager.createAtomicAnimation( 151 stateManager.getCurrentStableState(), OVERVIEW, config); 152 } 153 default: 154 return super.createStateElementAnimation(index, values); 155 } 156 } 157 158 @Override prepareForAtomicAnimation(LauncherState fromState, LauncherState toState, StateAnimationConfig config)159 public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState, 160 StateAnimationConfig config) { 161 if (toState == NORMAL && fromState == OVERVIEW) { 162 config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL); 163 config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL); 164 config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL); 165 config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f)); 166 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL); 167 config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7); 168 Workspace workspace = mActivity.getWorkspace(); 169 170 // Start from a higher workspace scale, but only if we're invisible so we don't jump. 171 boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE; 172 if (isWorkspaceVisible) { 173 CellLayout currentChild = (CellLayout) workspace.getChildAt( 174 workspace.getCurrentPage()); 175 isWorkspaceVisible = currentChild.getVisibility() == VISIBLE 176 && currentChild.getShortcutsAndWidgets().getAlpha() > 0; 177 } 178 if (!isWorkspaceVisible) { 179 workspace.setScaleX(0.92f); 180 workspace.setScaleY(0.92f); 181 } 182 Hotseat hotseat = mActivity.getHotseat(); 183 boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0; 184 if (!isHotseatVisible) { 185 hotseat.setScaleX(0.92f); 186 hotseat.setScaleY(0.92f); 187 if (ENABLE_OVERVIEW_ACTIONS.get()) { 188 AllAppsContainerView qsbContainer = mActivity.getAppsView(); 189 View qsb = qsbContainer.getSearchView(); 190 boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0; 191 if (!qsbVisible) { 192 qsbContainer.setScaleX(0.92f); 193 qsbContainer.setScaleY(0.92f); 194 } 195 } 196 } 197 } else if (toState == NORMAL && fromState == OVERVIEW_PEEK) { 198 // Keep fully visible until the very end (when overview is offscreen) to make invisible. 199 config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME); 200 } else if (toState == OVERVIEW_PEEK && fromState == NORMAL) { 201 config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT); 202 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); 203 config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN); 204 } else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) { 205 if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) { 206 config.setInterpolator(ANIM_WORKSPACE_SCALE, 207 fromState == NORMAL ? ACCEL : OVERSHOOT_1_2); 208 config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL); 209 } else { 210 config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2); 211 212 // Scale up the recents, if it is not coming from the side 213 RecentsView overview = mActivity.getOverviewPanel(); 214 if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) { 215 SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); 216 } 217 } 218 config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); 219 config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2); 220 config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); 221 config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2); 222 Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get() 223 && removeShelfFromOverview(mActivity) 224 ? OVERSHOOT_1_2 225 : OVERSHOOT_1_7; 226 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator); 227 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator); 228 config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); 229 } else if (fromState == HINT_STATE && toState == NORMAL) { 230 config.setInterpolator(ANIM_DEPTH, DEACCEL_3); 231 if (mHintToNormalDuration == -1) { 232 ValueAnimator va = getSpringScaleAnimator(mActivity, mActivity.getWorkspace(), 233 toState.getWorkspaceScaleAndTranslation(mActivity).scale); 234 mHintToNormalDuration = (int) va.getDuration(); 235 } 236 config.duration = Math.max(config.duration, mHintToNormalDuration); 237 } 238 } 239 } 240