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.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION; 19 import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE; 20 import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES; 21 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED; 22 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; 23 24 import android.animation.Animator; 25 import android.animation.AnimatorSet; 26 import android.os.RemoteException; 27 import android.util.Log; 28 import android.view.TaskTransitionSpec; 29 import android.view.View; 30 import android.view.WindowManagerGlobal; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 35 import com.android.launcher3.DeviceProfile; 36 import com.android.launcher3.LauncherState; 37 import com.android.launcher3.QuickstepTransitionManager; 38 import com.android.launcher3.R; 39 import com.android.launcher3.Utilities; 40 import com.android.launcher3.anim.AnimatedFloat; 41 import com.android.launcher3.logging.InstanceId; 42 import com.android.launcher3.logging.InstanceIdSequence; 43 import com.android.launcher3.model.data.ItemInfo; 44 import com.android.launcher3.uioverrides.QuickstepLauncher; 45 import com.android.launcher3.util.DisplayController; 46 import com.android.launcher3.util.MultiPropertyFactory; 47 import com.android.launcher3.util.OnboardingPrefs; 48 import com.android.quickstep.RecentsAnimationCallbacks; 49 import com.android.quickstep.util.GroupTask; 50 import com.android.quickstep.views.RecentsView; 51 52 import java.io.PrintWriter; 53 import java.util.Arrays; 54 55 /** 56 * A data source which integrates with a Launcher instance 57 */ 58 public class LauncherTaskbarUIController extends TaskbarUIController { 59 60 private static final String TAG = "TaskbarUIController"; 61 62 public static final int MINUS_ONE_PAGE_PROGRESS_INDEX = 0; 63 public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1; 64 public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2; 65 public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3; 66 67 public static final int DISPLAY_PROGRESS_COUNT = 4; 68 69 private final AnimatedFloat mTaskbarInAppDisplayProgress = new AnimatedFloat( 70 this::onInAppDisplayProgressChanged); 71 private final MultiPropertyFactory<AnimatedFloat> mTaskbarInAppDisplayProgressMultiProp = 72 new MultiPropertyFactory<>(mTaskbarInAppDisplayProgress, 73 AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max); 74 75 private final QuickstepLauncher mLauncher; 76 77 private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = 78 dp -> { 79 onStashedInAppChanged(dp); 80 if (mControllers != null && mControllers.taskbarViewController != null) { 81 mControllers.taskbarViewController.onRotationChanged(dp); 82 } 83 }; 84 85 // Initialized in init. 86 private final TaskbarLauncherStateController 87 mTaskbarLauncherStateController = new TaskbarLauncherStateController(); 88 LauncherTaskbarUIController(QuickstepLauncher launcher)89 public LauncherTaskbarUIController(QuickstepLauncher launcher) { 90 mLauncher = launcher; 91 } 92 93 @Override init(TaskbarControllers taskbarControllers)94 protected void init(TaskbarControllers taskbarControllers) { 95 super.init(taskbarControllers); 96 97 mTaskbarLauncherStateController.init(mControllers, mLauncher, 98 mControllers.getSharedState().sysuiStateFlags); 99 100 mLauncher.setTaskbarUIController(this); 101 102 onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */); 103 104 onStashedInAppChanged(mLauncher.getDeviceProfile()); 105 mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 106 107 // Restore the in-app display progress from before Taskbar was recreated. 108 float[] prevProgresses = mControllers.getSharedState().inAppDisplayProgressMultiPropValues; 109 // Make a copy of the previous progress to set since updating the multiprop will update 110 // the property which also calls onInAppDisplayProgressChanged() which writes the current 111 // values into the shared state 112 prevProgresses = Arrays.copyOf(prevProgresses, prevProgresses.length); 113 for (int i = 0; i < prevProgresses.length; i++) { 114 mTaskbarInAppDisplayProgressMultiProp.get(i).setValue(prevProgresses[i]); 115 } 116 } 117 118 @Override onDestroy()119 protected void onDestroy() { 120 super.onDestroy(); 121 onLauncherResumedOrPaused(false); 122 mTaskbarLauncherStateController.onDestroy(); 123 124 mLauncher.setTaskbarUIController(null); 125 mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 126 updateTaskTransitionSpec(true); 127 } 128 onInAppDisplayProgressChanged()129 private void onInAppDisplayProgressChanged() { 130 if (mControllers != null) { 131 // Update our shared state so we can restore it if taskbar gets recreated. 132 for (int i = 0; i < DISPLAY_PROGRESS_COUNT; i++) { 133 mControllers.getSharedState().inAppDisplayProgressMultiPropValues[i] = 134 mTaskbarInAppDisplayProgressMultiProp.get(i).getValue(); 135 } 136 // Ensure nav buttons react to our latest state if necessary. 137 mControllers.navbarButtonsViewController.updateNavButtonTranslationY(); 138 } 139 } 140 141 @Override isTaskbarTouchable()142 protected boolean isTaskbarTouchable() { 143 return !(mTaskbarLauncherStateController.isAnimatingToLauncher() 144 && mTaskbarLauncherStateController.isTaskbarAlignedWithHotseat()); 145 } 146 setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)147 public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) { 148 mTaskbarLauncherStateController.setShouldDelayLauncherStateAnim( 149 shouldDelayLauncherStateAnim); 150 } 151 152 /** 153 * Adds the Launcher resume animator to the given animator set. 154 * 155 * This should be used to run a Launcher resume animation whose progress matches a 156 * swipe progress. 157 * 158 * @param placeholderDuration a placeholder duration to be used to ensure all full-length 159 * sub-animations are properly coordinated. This duration should not 160 * actually be used since this animation tracks a swipe progress. 161 */ addLauncherResumeAnimation(AnimatorSet animation, int placeholderDuration)162 protected void addLauncherResumeAnimation(AnimatorSet animation, int placeholderDuration) { 163 animation.play(onLauncherResumedOrPaused( 164 /* isResumed= */ true, 165 /* fromInit= */ false, 166 /* startAnimation= */ false, 167 placeholderDuration)); 168 } 169 170 /** 171 * Should be called from onResume() and onPause(), and animates the Taskbar accordingly. 172 */ onLauncherResumedOrPaused(boolean isResumed)173 public void onLauncherResumedOrPaused(boolean isResumed) { 174 onLauncherResumedOrPaused(isResumed, false /* fromInit */); 175 } 176 onLauncherResumedOrPaused(boolean isResumed, boolean fromInit)177 private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) { 178 onLauncherResumedOrPaused( 179 isResumed, 180 fromInit, 181 /* startAnimation= */ true, 182 DisplayController.isTransientTaskbar(mLauncher) 183 ? TRANSIENT_TASKBAR_TRANSITION_DURATION 184 : (!isResumed 185 ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION 186 : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION)); 187 } 188 189 @Nullable onLauncherResumedOrPaused( boolean isResumed, boolean fromInit, boolean startAnimation, int duration)190 private Animator onLauncherResumedOrPaused( 191 boolean isResumed, boolean fromInit, boolean startAnimation, int duration) { 192 // Launcher is resumed during the swipe-to-overview gesture under shell-transitions, so 193 // avoid updating taskbar state in that situation (when it's non-interactive -- or 194 // "background") to avoid premature animations. 195 if (ENABLE_SHELL_TRANSITIONS && isResumed 196 && mLauncher.getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE) 197 && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) { 198 return null; 199 } 200 201 mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed); 202 return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation); 203 } 204 refreshResumedState()205 public void refreshResumedState() { 206 onLauncherResumedOrPaused(mLauncher.hasBeenResumed()); 207 } 208 209 /** 210 * Create Taskbar animation when going from an app to Launcher as part of recents transition. 211 * @param toState If known, the state we will end up in when reaching Launcher. 212 * @param callbacks callbacks to track the recents animation lifecycle. The state change is 213 * automatically reset once the recents animation finishes 214 */ createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)215 public Animator createAnimToLauncher(@NonNull LauncherState toState, 216 @NonNull RecentsAnimationCallbacks callbacks, long duration) { 217 return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration); 218 } 219 isDraggingItem()220 public boolean isDraggingItem() { 221 return mControllers.taskbarDragController.isDragging(); 222 } 223 224 @Override onStashedInAppChanged()225 protected void onStashedInAppChanged() { 226 onStashedInAppChanged(mLauncher.getDeviceProfile()); 227 } 228 onStashedInAppChanged(DeviceProfile deviceProfile)229 private void onStashedInAppChanged(DeviceProfile deviceProfile) { 230 boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp(); 231 deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps; 232 updateTaskTransitionSpec(taskbarStashedInApps); 233 } 234 updateTaskTransitionSpec(boolean taskbarIsHidden)235 private void updateTaskTransitionSpec(boolean taskbarIsHidden) { 236 try { 237 if (taskbarIsHidden) { 238 // Clear custom task transition settings when the taskbar is stashed 239 WindowManagerGlobal.getWindowManagerService().clearTaskTransitionSpec(); 240 } else { 241 // Adjust task transition spec to account for taskbar being visible 242 WindowManagerGlobal.getWindowManagerService().setTaskTransitionSpec( 243 new TaskTransitionSpec( 244 mLauncher.getColor(R.color.taskbar_background))); 245 } 246 } catch (RemoteException e) { 247 // This shouldn't happen but if it does task animations won't look good until the 248 // taskbar stashing state is changed. 249 Log.e(TAG, "Failed to update task transition spec to account for new taskbar state", 250 e); 251 } 252 } 253 254 /** 255 * Starts a Taskbar EDU flow, if the user should see one upon launching an application. 256 */ showEduOnAppLaunch()257 public void showEduOnAppLaunch() { 258 if (!shouldShowEduOnAppLaunch()) { 259 return; 260 } 261 262 // Persistent features EDU tooltip. 263 if (!DisplayController.isTransientTaskbar(mLauncher)) { 264 mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu(); 265 return; 266 } 267 268 // Transient swipe EDU tooltip. 269 mControllers.taskbarEduTooltipController.maybeShowSwipeEdu(); 270 } 271 272 /** 273 * Returns {@code true} if a Taskbar education should be shown on application launch. 274 */ shouldShowEduOnAppLaunch()275 public boolean shouldShowEduOnAppLaunch() { 276 if (Utilities.isRunningInTestHarness()) { 277 return false; 278 } 279 280 // Persistent features EDU tooltip. 281 if (!DisplayController.isTransientTaskbar(mLauncher)) { 282 return !mLauncher.getOnboardingPrefs().hasReachedMaxCount( 283 OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP); 284 } 285 286 // Transient swipe EDU tooltip. 287 return mControllers.taskbarEduTooltipController.getTooltipStep() < TOOLTIP_STEP_FEATURES; 288 } 289 290 @Override onTaskbarIconLaunched(ItemInfo item)291 public void onTaskbarIconLaunched(ItemInfo item) { 292 super.onTaskbarIconLaunched(item); 293 InstanceId instanceId = new InstanceIdSequence().newInstanceId(); 294 mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item, 295 instanceId); 296 } 297 298 /** 299 * Animates Taskbar elements during a transition to a Launcher state that should use in-app 300 * layouts. 301 * 302 * @param progress [0, 1] 303 * 0 => use home layout 304 * 1 => use in-app layout 305 */ onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex)306 public void onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex) { 307 mTaskbarInAppDisplayProgressMultiProp.get(progressIndex).setValue(progress); 308 if (mControllers == null) { 309 // This method can be called before init() is called. 310 return; 311 } 312 if (mControllers.uiController.isIconAlignedWithHotseat() 313 && !mTaskbarLauncherStateController.isAnimatingToLauncher()) { 314 // Only animate the nav buttons while home and not animating home, otherwise let 315 // the TaskbarViewController handle it. 316 mControllers.navbarButtonsViewController 317 .getTaskbarNavButtonTranslationYForInAppDisplay() 318 .updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY() 319 * mTaskbarInAppDisplayProgress.value); 320 mControllers.navbarButtonsViewController 321 .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress); 322 } 323 } 324 325 /** Returns true iff any in-app display progress > 0. */ shouldUseInAppLayout()326 public boolean shouldUseInAppLayout() { 327 return mTaskbarInAppDisplayProgress.value > 0; 328 } 329 330 @Override onExpandPip()331 public void onExpandPip() { 332 super.onExpandPip(); 333 mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, false); 334 mTaskbarLauncherStateController.applyState(); 335 } 336 337 @Override updateStateForSysuiFlags(int sysuiFlags)338 public void updateStateForSysuiFlags(int sysuiFlags) { 339 mTaskbarLauncherStateController.updateStateForSysuiFlags(sysuiFlags); 340 } 341 342 @Override isIconAlignedWithHotseat()343 public boolean isIconAlignedWithHotseat() { 344 return mTaskbarLauncherStateController.isIconAlignedWithHotseat(); 345 } 346 347 @Override isHotseatIconOnTopWhenAligned()348 public boolean isHotseatIconOnTopWhenAligned() { 349 return mTaskbarLauncherStateController.isInHotseatOnTopStates() 350 && mTaskbarInAppDisplayProgressMultiProp.get(MINUS_ONE_PAGE_PROGRESS_INDEX) 351 .getValue() == 0; 352 } 353 354 @Override isInOverview()355 protected boolean isInOverview() { 356 return mTaskbarLauncherStateController.isInOverview(); 357 } 358 359 @Override getRecentsView()360 public RecentsView getRecentsView() { 361 return mLauncher.getOverviewPanel(); 362 } 363 364 @Override launchSplitTasks(@onNull View taskView, @NonNull GroupTask groupTask)365 public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) { 366 mLauncher.launchSplitTasks(taskView, groupTask); 367 } 368 369 @Override onIconLayoutBoundsChanged()370 protected void onIconLayoutBoundsChanged() { 371 mTaskbarLauncherStateController.resetIconAlignment(); 372 } 373 374 @Override dumpLogs(String prefix, PrintWriter pw)375 public void dumpLogs(String prefix, PrintWriter pw) { 376 super.dumpLogs(prefix, pw); 377 378 pw.println(String.format("%s\tTaskbar in-app display progress: %.2f", prefix, 379 mTaskbarInAppDisplayProgress.value)); 380 mTaskbarInAppDisplayProgressMultiProp.dump( 381 prefix + "\t\t", 382 pw, 383 "mTaskbarInAppDisplayProgressMultiProp", 384 "MINUS_ONE_PAGE_PROGRESS_INDEX", 385 "ALL_APPS_PAGE_PROGRESS_INDEX", 386 "WIDGETS_PAGE_PROGRESS_INDEX", 387 "SYSUI_SURFACE_PROGRESS_INDEX"); 388 389 mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw); 390 } 391 } 392