1 /* 2 * Copyright (C) 2021 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.taskbar; 17 18 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 19 import static com.android.launcher3.anim.Interpolators.ACCEL_2; 20 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; 21 import static com.android.launcher3.anim.Interpolators.DEACCEL; 22 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 23 24 import android.animation.Animator; 25 import android.animation.AnimatorListenerAdapter; 26 import android.animation.AnimatorSet; 27 import android.animation.Keyframe; 28 import android.animation.ObjectAnimator; 29 import android.animation.PropertyValuesHolder; 30 import android.animation.TimeInterpolator; 31 import android.content.res.Resources; 32 import android.text.TextUtils; 33 import android.view.View; 34 35 import com.android.launcher3.R; 36 import com.android.launcher3.icons.BitmapInfo; 37 import com.android.launcher3.model.data.ItemInfo; 38 import com.android.launcher3.uioverrides.PredictedAppIcon; 39 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.stream.Collectors; 43 44 /** Handles the Taskbar Education flow. */ 45 public class TaskbarEduController { 46 47 private static final long WAVE_ANIM_DELAY = 250; 48 private static final long WAVE_ANIM_STAGGER = 50; 49 private static final long WAVE_ANIM_EACH_ICON_DURATION = 633; 50 private static final long WAVE_ANIM_SLOT_MACHINE_DURATION = 1085; 51 // The fraction of each icon's animation at which we reach the top point of the wave. 52 private static final float WAVE_ANIM_FRACTION_TOP = 0.4f; 53 // The fraction of each icon's animation at which we reach the bottom, before overshooting. 54 private static final float WAVE_ANIM_FRACTION_BOTTOM = 0.9f; 55 private static final TimeInterpolator WAVE_ANIM_TO_TOP_INTERPOLATOR = FAST_OUT_SLOW_IN; 56 private static final TimeInterpolator WAVE_ANIM_TO_BOTTOM_INTERPOLATOR = ACCEL_2; 57 private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_INTERPOLATOR = DEACCEL; 58 private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR = ACCEL_DEACCEL; 59 private static final float WAVE_ANIM_ICON_SCALE = 1.2f; 60 // How many icons to cycle through in the slot machine (+ the original icon at each end). 61 private static final int WAVE_ANIM_SLOT_MACHINE_NUM_ICONS = 3; 62 63 private final TaskbarActivityContext mActivity; 64 private final float mWaveAnimTranslationY; 65 private final float mWaveAnimTranslationYReturnOvershoot; 66 67 // Initialized in init. 68 TaskbarControllers mControllers; 69 70 private TaskbarEduView mTaskbarEduView; 71 private Animator mAnim; 72 TaskbarEduController(TaskbarActivityContext activity)73 public TaskbarEduController(TaskbarActivityContext activity) { 74 mActivity = activity; 75 76 final Resources resources = activity.getResources(); 77 mWaveAnimTranslationY = resources.getDimension(R.dimen.taskbar_edu_wave_anim_trans_y); 78 mWaveAnimTranslationYReturnOvershoot = resources.getDimension( 79 R.dimen.taskbar_edu_wave_anim_trans_y_return_overshoot); 80 } 81 init(TaskbarControllers controllers)82 public void init(TaskbarControllers controllers) { 83 mControllers = controllers; 84 } 85 showEdu()86 void showEdu() { 87 mActivity.setTaskbarWindowFullscreen(true); 88 mActivity.getDragLayer().post(() -> { 89 mTaskbarEduView = (TaskbarEduView) mActivity.getLayoutInflater().inflate( 90 R.layout.taskbar_edu, mActivity.getDragLayer(), false); 91 mTaskbarEduView.init(new TaskbarEduCallbacks()); 92 mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null); 93 mTaskbarEduView.show(); 94 startAnim(createWaveAnim()); 95 }); 96 } 97 hideEdu()98 void hideEdu() { 99 if (mTaskbarEduView != null) { 100 mTaskbarEduView.close(true /* animate */); 101 } 102 } 103 104 /** 105 * Starts the given animation, ending the previous animation first if it's still playing. 106 */ startAnim(Animator anim)107 private void startAnim(Animator anim) { 108 if (mAnim != null) { 109 mAnim.end(); 110 } 111 mAnim = anim; 112 mAnim.addListener(new AnimatorListenerAdapter() { 113 @Override 114 public void onAnimationEnd(Animator animation) { 115 mAnim = null; 116 } 117 }); 118 mAnim.start(); 119 } 120 121 /** 122 * Creates a staggered "wave" animation where each icon translates and scales up in succession. 123 */ createWaveAnim()124 private Animator createWaveAnim() { 125 AnimatorSet waveAnim = new AnimatorSet(); 126 View[] icons = mControllers.taskbarViewController.getIconViews(); 127 for (int i = 0; i < icons.length; i++) { 128 View icon = icons[i]; 129 AnimatorSet iconAnim = new AnimatorSet(); 130 131 Keyframe[] scaleKeyframes = new Keyframe[] { 132 Keyframe.ofFloat(0, 1f), 133 Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, WAVE_ANIM_ICON_SCALE), 134 Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 1f), 135 Keyframe.ofFloat(1f, 1f) 136 }; 137 scaleKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR); 138 scaleKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR); 139 140 Keyframe[] translationYKeyframes = new Keyframe[] { 141 Keyframe.ofFloat(0, 0f), 142 Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, -mWaveAnimTranslationY), 143 Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 0f), 144 // Half of the remaining fraction overshoots, then the other half returns to 0. 145 Keyframe.ofFloat( 146 WAVE_ANIM_FRACTION_BOTTOM + (1 - WAVE_ANIM_FRACTION_BOTTOM) / 2f, 147 mWaveAnimTranslationYReturnOvershoot), 148 Keyframe.ofFloat(1f, 0f) 149 }; 150 translationYKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR); 151 translationYKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR); 152 translationYKeyframes[3].setInterpolator(WAVE_ANIM_OVERSHOOT_INTERPOLATOR); 153 translationYKeyframes[4].setInterpolator(WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR); 154 155 iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon, 156 PropertyValuesHolder.ofKeyframe(SCALE_PROPERTY, scaleKeyframes)) 157 .setDuration(WAVE_ANIM_EACH_ICON_DURATION)); 158 iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon, 159 PropertyValuesHolder.ofKeyframe(View.TRANSLATION_Y, translationYKeyframes)) 160 .setDuration(WAVE_ANIM_EACH_ICON_DURATION)); 161 162 if (icon instanceof PredictedAppIcon) { 163 // Play slot machine animation through random icons from AllAppsList. 164 PredictedAppIcon predictedAppIcon = (PredictedAppIcon) icon; 165 ItemInfo itemInfo = (ItemInfo) icon.getTag(); 166 List<BitmapInfo> iconsToAnimate = mControllers.uiController.getAppIconsForEdu() 167 .filter(appInfo -> !TextUtils.equals(appInfo.title, itemInfo.title)) 168 .map(appInfo -> appInfo.bitmap) 169 .filter(bitmap -> !bitmap.isNullOrLowRes()) 170 .collect(Collectors.toList()); 171 // Pick n icons at random. 172 Collections.shuffle(iconsToAnimate); 173 if (iconsToAnimate.size() > WAVE_ANIM_SLOT_MACHINE_NUM_ICONS) { 174 iconsToAnimate = iconsToAnimate.subList(0, WAVE_ANIM_SLOT_MACHINE_NUM_ICONS); 175 } 176 Animator slotMachineAnim = predictedAppIcon.createSlotMachineAnim(iconsToAnimate); 177 if (slotMachineAnim != null) { 178 iconAnim.play(slotMachineAnim.setDuration(WAVE_ANIM_SLOT_MACHINE_DURATION)); 179 } 180 } 181 182 iconAnim.setStartDelay(WAVE_ANIM_STAGGER * i); 183 waveAnim.play(iconAnim); 184 } 185 waveAnim.setStartDelay(WAVE_ANIM_DELAY); 186 return waveAnim; 187 } 188 189 /** 190 * Callbacks for {@link TaskbarEduView} to interact with its controller. 191 */ 192 class TaskbarEduCallbacks { onPageChanged(int currentPage, int pageCount)193 void onPageChanged(int currentPage, int pageCount) { 194 if (currentPage == 0) { 195 mTaskbarEduView.updateStartButton(R.string.taskbar_edu_close, 196 v -> mTaskbarEduView.close(true /* animate */)); 197 } else { 198 mTaskbarEduView.updateStartButton(R.string.taskbar_edu_previous, 199 v -> mTaskbarEduView.snapToPage(currentPage - 1)); 200 } 201 if (currentPage == pageCount - 1) { 202 mTaskbarEduView.updateEndButton(R.string.taskbar_edu_done, 203 v -> mTaskbarEduView.close(true /* animate */)); 204 } else { 205 mTaskbarEduView.updateEndButton(R.string.taskbar_edu_next, 206 v -> mTaskbarEduView.snapToPage(currentPage + 1)); 207 } 208 } 209 } 210 } 211