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_VISIBLE; 22 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; 23 import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity; 24 25 import android.animation.Animator; 26 import android.animation.AnimatorSet; 27 import android.window.RemoteTransition; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 import com.android.launcher3.DeviceProfile; 33 import com.android.launcher3.Flags; 34 import com.android.launcher3.LauncherState; 35 import com.android.launcher3.QuickstepTransitionManager; 36 import com.android.launcher3.Utilities; 37 import com.android.launcher3.anim.AnimatedFloat; 38 import com.android.launcher3.logging.InstanceId; 39 import com.android.launcher3.logging.InstanceIdSequence; 40 import com.android.launcher3.model.data.ItemInfo; 41 import com.android.launcher3.statehandlers.DesktopVisibilityController; 42 import com.android.launcher3.taskbar.bubbles.BubbleBarController; 43 import com.android.launcher3.uioverrides.QuickstepLauncher; 44 import com.android.launcher3.util.DisplayController; 45 import com.android.launcher3.util.MultiPropertyFactory; 46 import com.android.launcher3.util.OnboardingPrefs; 47 import com.android.quickstep.HomeVisibilityState; 48 import com.android.quickstep.LauncherActivityInterface; 49 import com.android.quickstep.RecentsAnimationCallbacks; 50 import com.android.quickstep.SystemUiProxy; 51 import com.android.quickstep.util.GroupTask; 52 import com.android.quickstep.util.TISBindHelper; 53 import com.android.quickstep.views.RecentsView; 54 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; 55 56 import java.io.PrintWriter; 57 import java.util.Arrays; 58 59 /** 60 * A data source which integrates with a Launcher instance 61 */ 62 public class LauncherTaskbarUIController extends TaskbarUIController { 63 64 private static final String TAG = "TaskbarUIController"; 65 66 public static final int MINUS_ONE_PAGE_PROGRESS_INDEX = 0; 67 public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1; 68 public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2; 69 public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3; 70 71 public static final int DISPLAY_PROGRESS_COUNT = 4; 72 73 private final AnimatedFloat mTaskbarInAppDisplayProgress = new AnimatedFloat( 74 this::onInAppDisplayProgressChanged); 75 private final MultiPropertyFactory<AnimatedFloat> mTaskbarInAppDisplayProgressMultiProp = 76 new MultiPropertyFactory<>(mTaskbarInAppDisplayProgress, 77 AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max); 78 79 private final QuickstepLauncher mLauncher; 80 private final HomeVisibilityState mHomeState; 81 82 private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = 83 dp -> { 84 onStashedInAppChanged(dp); 85 if (mControllers != null && mControllers.taskbarViewController != null) { 86 mControllers.taskbarViewController.onRotationChanged(dp); 87 } 88 }; 89 private final HomeVisibilityState.VisibilityChangeListener mVisibilityChangeListener = 90 this::onLauncherVisibilityChanged; 91 92 // Initialized in init. 93 private final TaskbarLauncherStateController 94 mTaskbarLauncherStateController = new TaskbarLauncherStateController(); 95 LauncherTaskbarUIController(QuickstepLauncher launcher)96 public LauncherTaskbarUIController(QuickstepLauncher launcher) { 97 mLauncher = launcher; 98 mHomeState = SystemUiProxy.INSTANCE.get(mLauncher).getHomeVisibilityState(); 99 } 100 101 @Override init(TaskbarControllers taskbarControllers)102 protected void init(TaskbarControllers taskbarControllers) { 103 super.init(taskbarControllers); 104 105 mTaskbarLauncherStateController.init(mControllers, mLauncher, 106 mControllers.getSharedState().sysuiStateFlags); 107 108 mLauncher.setTaskbarUIController(this); 109 mHomeState.addListener(mVisibilityChangeListener); 110 onLauncherVisibilityChanged( 111 Flags.useActivityOverlay() 112 ? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed(), 113 true /* fromInit */); 114 115 onStashedInAppChanged(mLauncher.getDeviceProfile()); 116 mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 117 118 // Restore the in-app display progress from before Taskbar was recreated. 119 float[] prevProgresses = mControllers.getSharedState().inAppDisplayProgressMultiPropValues; 120 // Make a copy of the previous progress to set since updating the multiprop will update 121 // the property which also calls onInAppDisplayProgressChanged() which writes the current 122 // values into the shared state 123 prevProgresses = Arrays.copyOf(prevProgresses, prevProgresses.length); 124 for (int i = 0; i < prevProgresses.length; i++) { 125 mTaskbarInAppDisplayProgressMultiProp.get(i).setValue(prevProgresses[i]); 126 } 127 } 128 129 @Override onDestroy()130 protected void onDestroy() { 131 super.onDestroy(); 132 onLauncherVisibilityChanged(false); 133 mTaskbarLauncherStateController.onDestroy(); 134 135 mLauncher.setTaskbarUIController(null); 136 mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 137 mHomeState.removeListener(mVisibilityChangeListener); 138 } 139 onInAppDisplayProgressChanged()140 private void onInAppDisplayProgressChanged() { 141 if (mControllers != null) { 142 // Update our shared state so we can restore it if taskbar gets recreated. 143 for (int i = 0; i < DISPLAY_PROGRESS_COUNT; i++) { 144 mControllers.getSharedState().inAppDisplayProgressMultiPropValues[i] = 145 mTaskbarInAppDisplayProgressMultiProp.get(i).getValue(); 146 } 147 // Ensure nav buttons react to our latest state if necessary. 148 mControllers.navbarButtonsViewController.updateNavButtonTranslationY(); 149 } 150 } 151 152 @Override isTaskbarTouchable()153 protected boolean isTaskbarTouchable() { 154 return !(mTaskbarLauncherStateController.isAnimatingToLauncher() 155 && mTaskbarLauncherStateController.isTaskbarAlignedWithHotseat()); 156 } 157 setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)158 public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) { 159 mTaskbarLauncherStateController.setShouldDelayLauncherStateAnim( 160 shouldDelayLauncherStateAnim); 161 } 162 163 /** 164 * Adds the Launcher resume animator to the given animator set. 165 * 166 * This should be used to run a Launcher resume animation whose progress matches a 167 * swipe progress. 168 * 169 * @param placeholderDuration a placeholder duration to be used to ensure all full-length 170 * sub-animations are properly coordinated. This duration should not 171 * actually be used since this animation tracks a swipe progress. 172 */ addLauncherVisibilityChangedAnimation(AnimatorSet animation, int placeholderDuration)173 protected void addLauncherVisibilityChangedAnimation(AnimatorSet animation, 174 int placeholderDuration) { 175 animation.play(onLauncherVisibilityChanged( 176 /* isResumed= */ true, 177 /* fromInit= */ false, 178 /* startAnimation= */ false, 179 placeholderDuration)); 180 } 181 182 /** 183 * Should be called from onResume() and onPause(), and animates the Taskbar accordingly. 184 */ 185 @Override onLauncherVisibilityChanged(boolean isVisible)186 public void onLauncherVisibilityChanged(boolean isVisible) { 187 onLauncherVisibilityChanged(isVisible, false /* fromInit */); 188 } 189 onLauncherVisibilityChanged(boolean isVisible, boolean fromInit)190 private void onLauncherVisibilityChanged(boolean isVisible, boolean fromInit) { 191 onLauncherVisibilityChanged( 192 isVisible, 193 fromInit, 194 /* startAnimation= */ true, 195 DisplayController.isTransientTaskbar(mLauncher) 196 ? TRANSIENT_TASKBAR_TRANSITION_DURATION 197 : (!isVisible 198 ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION 199 : QuickstepTransitionManager.getTaskbarToHomeDuration())); 200 } 201 202 @Nullable onLauncherVisibilityChanged( boolean isVisible, boolean fromInit, boolean startAnimation, int duration)203 private Animator onLauncherVisibilityChanged( 204 boolean isVisible, boolean fromInit, boolean startAnimation, int duration) { 205 // Launcher is resumed during the swipe-to-overview gesture under shell-transitions, so 206 // avoid updating taskbar state in that situation (when it's non-interactive -- or 207 // "background") to avoid premature animations. 208 if (ENABLE_SHELL_TRANSITIONS && isVisible 209 && mLauncher.getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE) 210 && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) { 211 return null; 212 } 213 214 DesktopVisibilityController desktopController = 215 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController(); 216 if (!enableDesktopWindowingWallpaperActivity() 217 && desktopController != null 218 && desktopController.areDesktopTasksVisible()) { 219 // TODO: b/333533253 - Remove after flag rollout 220 isVisible = false; 221 } 222 223 mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, isVisible); 224 if (fromInit) { 225 duration = 0; 226 } 227 return mTaskbarLauncherStateController.applyState(duration, startAnimation); 228 } 229 230 @Override onStateTransitionCompletedAfterSwipeToHome(LauncherState state)231 public void onStateTransitionCompletedAfterSwipeToHome(LauncherState state) { 232 mTaskbarLauncherStateController.onStateTransitionCompletedAfterSwipeToHome(state); 233 } 234 235 @Override refreshResumedState()236 public void refreshResumedState() { 237 onLauncherVisibilityChanged(Flags.useActivityOverlay() 238 ? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed()); 239 } 240 241 @Override adjustHotseatForBubbleBar(boolean isBubbleBarVisible)242 public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) { 243 if (mLauncher.getHotseat() != null) { 244 mLauncher.getHotseat().adjustForBubbleBar(isBubbleBarVisible); 245 } 246 } 247 248 /** 249 * Create Taskbar animation when going from an app to Launcher as part of recents transition. 250 * @param toState If known, the state we will end up in when reaching Launcher. 251 * @param callbacks callbacks to track the recents animation lifecycle. The state change is 252 * automatically reset once the recents animation finishes 253 */ createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)254 public Animator createAnimToLauncher(@NonNull LauncherState toState, 255 @NonNull RecentsAnimationCallbacks callbacks, long duration) { 256 return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration); 257 } 258 updateTaskbarLauncherStateGoingHome()259 public void updateTaskbarLauncherStateGoingHome() { 260 mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, true); 261 mTaskbarLauncherStateController.applyState(); 262 } 263 isDraggingItem()264 public boolean isDraggingItem() { 265 return mControllers.taskbarDragController.isDragging(); 266 } 267 268 @Override onStashedInAppChanged()269 protected void onStashedInAppChanged() { 270 onStashedInAppChanged(mLauncher.getDeviceProfile()); 271 } 272 onStashedInAppChanged(DeviceProfile deviceProfile)273 private void onStashedInAppChanged(DeviceProfile deviceProfile) { 274 boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp(); 275 deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps; 276 } 277 278 /** 279 * Starts a Taskbar EDU flow, if the user should see one upon launching an application. 280 */ showEduOnAppLaunch()281 public void showEduOnAppLaunch() { 282 if (!shouldShowEduOnAppLaunch()) { 283 // Called in case the edu finishes and search edu is still pending 284 mControllers.taskbarEduTooltipController.maybeShowSearchEdu(); 285 return; 286 } 287 288 // Persistent features EDU tooltip. 289 if (!DisplayController.isTransientTaskbar(mLauncher)) { 290 mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu(); 291 return; 292 } 293 294 // Transient swipe EDU tooltip. 295 mControllers.taskbarEduTooltipController.maybeShowSwipeEdu(); 296 } 297 298 /** Will make the next onRecentsAnimationFinished() animation a no-op. */ setSkipNextRecentsAnimEnd()299 public void setSkipNextRecentsAnimEnd() { 300 mTaskbarLauncherStateController.setSkipNextRecentsAnimEnd(); 301 } 302 303 /** 304 * Returns {@code true} if a Taskbar education should be shown on application launch. 305 */ shouldShowEduOnAppLaunch()306 public boolean shouldShowEduOnAppLaunch() { 307 if (Utilities.isRunningInTestHarness()) { 308 return false; 309 } 310 311 // Persistent features EDU tooltip. 312 if (!DisplayController.isTransientTaskbar(mLauncher)) { 313 return !OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.hasReachedMax(mLauncher); 314 } 315 316 // Transient swipe EDU tooltip. 317 return mControllers.taskbarEduTooltipController.getTooltipStep() < TOOLTIP_STEP_FEATURES; 318 } 319 320 @Override onTaskbarIconLaunched(ItemInfo item)321 public void onTaskbarIconLaunched(ItemInfo item) { 322 super.onTaskbarIconLaunched(item); 323 InstanceId instanceId = new InstanceIdSequence().newInstanceId(); 324 mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item, 325 instanceId); 326 } 327 328 /** 329 * Animates Taskbar elements during a transition to a Launcher state that should use in-app 330 * layouts. 331 * 332 * @param progress [0, 1] 333 * 0 => use home layout 334 * 1 => use in-app layout 335 */ onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex)336 public void onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex) { 337 mTaskbarInAppDisplayProgressMultiProp.get(progressIndex).setValue(progress); 338 if (mControllers == null) { 339 // This method can be called before init() is called. 340 return; 341 } 342 if (mControllers.uiController.isIconAlignedWithHotseat() 343 && !mTaskbarLauncherStateController.isAnimatingToLauncher()) { 344 // Only animate the nav buttons while home and not animating home, otherwise let 345 // the TaskbarViewController handle it. 346 mControllers.navbarButtonsViewController 347 .getTaskbarNavButtonTranslationYForInAppDisplay() 348 .updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY() 349 * mTaskbarInAppDisplayProgress.value); 350 mControllers.navbarButtonsViewController 351 .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress); 352 } 353 } 354 355 /** Returns true iff any in-app display progress > 0. */ shouldUseInAppLayout()356 public boolean shouldUseInAppLayout() { 357 return mTaskbarInAppDisplayProgress.value > 0; 358 } 359 isBubbleBarEnabled()360 public boolean isBubbleBarEnabled() { 361 return BubbleBarController.isBubbleBarEnabled(); 362 } 363 364 /** Whether the bubble bar has any bubbles. */ hasBubbles()365 public boolean hasBubbles() { 366 if (mControllers == null) { 367 return false; 368 } 369 if (mControllers.bubbleControllers.isEmpty()) { 370 return false; 371 } 372 return mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles(); 373 } 374 375 @Override onExpandPip()376 public void onExpandPip() { 377 super.onExpandPip(); 378 mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, false); 379 mTaskbarLauncherStateController.applyState(); 380 } 381 382 @Override updateStateForSysuiFlags(@ystemUiStateFlags long sysuiFlags)383 public void updateStateForSysuiFlags(@SystemUiStateFlags long sysuiFlags) { 384 mTaskbarLauncherStateController.updateStateForSysuiFlags(sysuiFlags); 385 } 386 387 @Override isIconAlignedWithHotseat()388 public boolean isIconAlignedWithHotseat() { 389 return mTaskbarLauncherStateController.isIconAlignedWithHotseat(); 390 } 391 392 @Override isHotseatIconOnTopWhenAligned()393 public boolean isHotseatIconOnTopWhenAligned() { 394 return mTaskbarLauncherStateController.isInHotseatOnTopStates() 395 && mTaskbarInAppDisplayProgressMultiProp.get(MINUS_ONE_PAGE_PROGRESS_INDEX) 396 .getValue() == 0; 397 } 398 399 @Override isInOverviewUi()400 protected boolean isInOverviewUi() { 401 return mTaskbarLauncherStateController.isInOverviewUi(); 402 } 403 404 @Override canToggleHomeAllApps()405 protected boolean canToggleHomeAllApps() { 406 return mLauncher.isResumed() 407 && !mTaskbarLauncherStateController.isInOverviewUi() 408 && !mLauncher.areDesktopTasksVisible(); 409 } 410 411 @Override getRecentsView()412 public RecentsView getRecentsView() { 413 return mLauncher.getOverviewPanel(); 414 } 415 416 @Override launchSplitTasks( @onNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition)417 public void launchSplitTasks( 418 @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { 419 mLauncher.launchSplitTasks(groupTask, remoteTransition); 420 } 421 422 @Override onIconLayoutBoundsChanged()423 protected void onIconLayoutBoundsChanged() { 424 mTaskbarLauncherStateController.resetIconAlignment(); 425 } 426 427 @Nullable 428 @Override getTISBindHelper()429 protected TISBindHelper getTISBindHelper() { 430 return mLauncher.getTISBindHelper(); 431 } 432 433 @Override dumpLogs(String prefix, PrintWriter pw)434 public void dumpLogs(String prefix, PrintWriter pw) { 435 super.dumpLogs(prefix, pw); 436 437 pw.println(String.format("%s\tTaskbar in-app display progress: %.2f", prefix, 438 mTaskbarInAppDisplayProgress.value)); 439 mTaskbarInAppDisplayProgressMultiProp.dump( 440 prefix + "\t\t", 441 pw, 442 "mTaskbarInAppDisplayProgressMultiProp", 443 "MINUS_ONE_PAGE_PROGRESS_INDEX", 444 "ALL_APPS_PAGE_PROGRESS_INDEX", 445 "WIDGETS_PAGE_PROGRESS_INDEX", 446 "SYSUI_SURFACE_PROGRESS_INDEX"); 447 448 mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw); 449 } 450 451 @Override getTaskbarUIControllerName()452 protected String getTaskbarUIControllerName() { 453 return "LauncherTaskbarUIController"; 454 } 455 } 456