1 /* 2 * Copyright (C) 2022 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 17 package com.android.quickstep.util; 18 19 import static com.android.app.animation.Interpolators.clampToProgress; 20 import static com.android.launcher3.LauncherState.NORMAL; 21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 22 23 import android.animation.AnimatorSet; 24 import android.os.BinderUtils; 25 import android.os.Bundle; 26 import android.os.IBinder; 27 import android.os.IRemoteCallback; 28 import android.view.animation.Interpolator; 29 30 import androidx.annotation.NonNull; 31 import androidx.lifecycle.DefaultLifecycleObserver; 32 import androidx.lifecycle.LifecycleOwner; 33 34 import com.android.launcher3.logging.StatsLogManager; 35 import com.android.launcher3.statemanager.BaseState; 36 import com.android.launcher3.statemanager.StateManager; 37 import com.android.launcher3.states.StateAnimationConfig; 38 import com.android.launcher3.util.RunnableList; 39 import com.android.launcher3.views.ActivityContext; 40 import com.android.quickstep.views.RecentsViewContainer; 41 42 /** 43 * Utility class containing methods to help manage animations, interpolators, and timings. 44 */ 45 public class AnimUtils { 46 private static final int DURATION_DEFAULT_SPLIT_DISMISS = 350; 47 48 /** 49 * Fetches device-specific timings for the Overview > Split animation 50 * (splitscreen initiated from Overview). 51 */ getDeviceOverviewToSplitTimings(boolean isTablet)52 public static SplitAnimationTimings getDeviceOverviewToSplitTimings(boolean isTablet) { 53 return isTablet 54 ? SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT 55 : SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT; 56 } 57 58 /** 59 * Fetches device-specific timings for the Split > Confirm animation 60 * (splitscreen confirmed by selecting a second app). 61 */ getDeviceSplitToConfirmTimings(boolean isTablet)62 public static SplitAnimationTimings getDeviceSplitToConfirmTimings(boolean isTablet) { 63 return isTablet 64 ? SplitAnimationTimings.TABLET_SPLIT_TO_CONFIRM 65 : SplitAnimationTimings.PHONE_SPLIT_TO_CONFIRM; 66 } 67 68 /** 69 * Fetches device-specific timings for the app pair launch animation. 70 */ getDeviceAppPairLaunchTimings(boolean isTablet)71 public static SplitAnimationTimings getDeviceAppPairLaunchTimings(boolean isTablet) { 72 return isTablet 73 ? SplitAnimationTimings.TABLET_APP_PAIR_LAUNCH 74 : SplitAnimationTimings.PHONE_APP_PAIR_LAUNCH; 75 } 76 77 /** 78 * Synchronizes the timing for the split dismiss animation to the current transition to 79 * NORMAL (launcher home/workspace) 80 */ goToNormalStateWithSplitDismissal(@onNull StateManager stateManager, @NonNull RecentsViewContainer container, @NonNull StatsLogManager.LauncherEvent exitReason, @NonNull SplitAnimationController animationController)81 public static void goToNormalStateWithSplitDismissal(@NonNull StateManager stateManager, 82 @NonNull RecentsViewContainer container, 83 @NonNull StatsLogManager.LauncherEvent exitReason, 84 @NonNull SplitAnimationController animationController) { 85 StateAnimationConfig config = new StateAnimationConfig(); 86 BaseState startState = stateManager.getState(); 87 long duration = startState.getTransitionDuration(container, false /*isToState*/); 88 if (duration == 0) { 89 // Case where we're in contextual on workspace (NORMAL), which by default has 0 90 // transition duration 91 duration = DURATION_DEFAULT_SPLIT_DISMISS; 92 } 93 config.duration = duration; 94 AnimatorSet stateAnim = stateManager.createAtomicAnimation( 95 startState, NORMAL, config); 96 AnimatorSet dismissAnim = animationController 97 .createPlaceholderDismissAnim(container, exitReason, duration); 98 stateAnim.play(dismissAnim); 99 stateManager.setCurrentAnimation(stateAnim, NORMAL); 100 stateAnim.start(); 101 } 102 103 /** 104 * Returns a IRemoteCallback which completes the provided list as a result or when the owner 105 * is destroyed 106 */ completeRunnableListCallback( RunnableList list, ActivityContext owner)107 public static IRemoteCallback completeRunnableListCallback( 108 RunnableList list, ActivityContext owner) { 109 DefaultLifecycleObserver destroyObserver = new DefaultLifecycleObserver() { 110 @Override 111 public void onDestroy(@NonNull LifecycleOwner owner) { 112 list.executeAllAndClear(); 113 } 114 }; 115 MAIN_EXECUTOR.execute(() -> owner.getLifecycle().addObserver(destroyObserver)); 116 list.add(() -> owner.getLifecycle().removeObserver(destroyObserver)); 117 118 return new IRemoteCallback.Stub() { 119 @Override 120 public void sendResult(Bundle bundle) { 121 MAIN_EXECUTOR.execute(list::executeAllAndDestroy); 122 } 123 124 @Override 125 public IBinder asBinder() { 126 return BinderUtils.wrapLifecycle(this, owner.getOwnerCleanupSet()); 127 } 128 }; 129 } 130 131 /** 132 * Returns a function that runs the given interpolator such that the entire progress is set 133 * between the given duration. That is, we set the interpolation to 0 until startDelay and reach 134 * 1 by (startDelay + duration). 135 */ 136 public static Interpolator clampToDuration(Interpolator interpolator, float startDelay, 137 float duration, float totalDuration) { 138 return clampToProgress(interpolator, startDelay / totalDuration, 139 (startDelay + duration) / totalDuration); 140 } 141 } 142