1 /* 2 * Copyright (C) 2024 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.quickstep; 17 18 import static com.android.app.animation.Interpolators.INSTANT; 19 import static com.android.app.animation.Interpolators.LINEAR; 20 import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; 21 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK; 22 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS; 23 24 import android.animation.Animator; 25 import android.animation.ObjectAnimator; 26 import android.content.Context; 27 import android.content.res.Resources; 28 import android.graphics.Color; 29 import android.graphics.PointF; 30 import android.graphics.Rect; 31 import android.view.Gravity; 32 import android.view.MotionEvent; 33 import android.view.RemoteAnimationTarget; 34 import android.view.View; 35 36 import androidx.annotation.Nullable; 37 38 import com.android.launcher3.DeviceProfile; 39 import com.android.launcher3.Flags; 40 import com.android.launcher3.R; 41 import com.android.launcher3.statehandlers.DesktopVisibilityController; 42 import com.android.launcher3.statemanager.BaseState; 43 import com.android.launcher3.taskbar.TaskbarUIController; 44 import com.android.launcher3.util.DisplayController; 45 import com.android.launcher3.views.ScrimView; 46 import com.android.quickstep.orientation.RecentsPagedOrientationHandler; 47 import com.android.quickstep.util.ActivityInitListener; 48 import com.android.quickstep.util.AnimatorControllerWithResistance; 49 import com.android.quickstep.views.RecentsView; 50 import com.android.quickstep.views.RecentsViewContainer; 51 import com.android.systemui.shared.recents.model.ThumbnailData; 52 53 import java.util.HashMap; 54 import java.util.function.Consumer; 55 import java.util.function.Predicate; 56 57 public abstract class BaseContainerInterface<STATE_TYPE extends BaseState<STATE_TYPE>, 58 CONTAINER_TYPE extends RecentsViewContainer> { 59 60 public boolean rotationSupportedByActivity = false; 61 62 @Nullable getCreatedContainer()63 public abstract CONTAINER_TYPE getCreatedContainer(); 64 isInLiveTileMode()65 public abstract boolean isInLiveTileMode(); 66 onAssistantVisibilityChanged(float assistantVisibility)67 public abstract void onAssistantVisibilityChanged(float assistantVisibility); 68 allowMinimizeSplitScreen()69 public abstract boolean allowMinimizeSplitScreen(); 70 isResumed()71 public abstract boolean isResumed(); 72 isStarted()73 public abstract boolean isStarted(); deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev)74 public abstract boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, 75 MotionEvent ev); 76 77 /** @return whether to allow going to All Apps from Overview. */ allowAllAppsFromOverview()78 public abstract boolean allowAllAppsFromOverview(); 79 80 /** 81 * Returns the color of the scrim behind overview when at rest in this state. 82 * Return {@link Color#TRANSPARENT} for no scrim. 83 */ getOverviewScrimColorForState(CONTAINER_TYPE container, STATE_TYPE state)84 protected abstract int getOverviewScrimColorForState(CONTAINER_TYPE container, 85 STATE_TYPE state); 86 getSwipeUpDestinationAndLength( DeviceProfile dp, Context context, Rect outRect, RecentsPagedOrientationHandler orientationHandler)87 public abstract int getSwipeUpDestinationAndLength( 88 DeviceProfile dp, Context context, Rect outRect, 89 RecentsPagedOrientationHandler orientationHandler); 90 91 @Nullable getTaskbarController()92 public abstract TaskbarUIController getTaskbarController(); 93 prepareRecentsUI( RecentsAnimationDeviceState deviceState, boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback)94 public abstract BaseActivityInterface.AnimationFactory prepareRecentsUI( 95 RecentsAnimationDeviceState deviceState, boolean activityVisible, 96 Consumer<AnimatorControllerWithResistance> callback); 97 createActivityInitListener( Predicate<Boolean> onInitListener)98 public abstract ActivityInitListener createActivityInitListener( 99 Predicate<Boolean> onInitListener); 100 /** 101 * Returns the expected STATE_TYPE from the provided GestureEndTarget. 102 */ stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget)103 public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget); 104 switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas, Runnable runnable)105 public abstract void switchRunningTaskViewToScreenshot(HashMap<Integer, 106 ThumbnailData> thumbnailDatas, Runnable runnable); 107 closeOverlay()108 public abstract void closeOverlay(); 109 getOverviewWindowBounds( Rect homeBounds, RemoteAnimationTarget target)110 public abstract Rect getOverviewWindowBounds( 111 Rect homeBounds, RemoteAnimationTarget target); 112 onLaunchTaskFailed()113 public abstract void onLaunchTaskFailed(); 114 onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable)115 public abstract void onExitOverview(RotationTouchHelper deviceState, 116 Runnable exitRunnable); 117 118 /** Called when the animation to home has fully settled. */ onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState)119 public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {} 120 121 /** 122 * Sets a callback to be run when an activity launch happens while launcher is not yet resumed. 123 */ setOnDeferredActivityLaunchCallback(Runnable r)124 public void setOnDeferredActivityLaunchCallback(Runnable r) {} 125 /** 126 * @return Whether the gesture in progress should be cancelled. 127 */ shouldCancelCurrentGesture()128 public boolean shouldCancelCurrentGesture() { 129 return false; 130 } 131 132 @Nullable getDesktopVisibilityController()133 public DesktopVisibilityController getDesktopVisibilityController() { 134 return null; 135 } 136 137 /** 138 * Called when the gesture ends and the animation starts towards the given target. Used to add 139 * an optional additional animation with the same duration. 140 */ getParallelAnimationToLauncher( GestureState.GestureEndTarget endTarget, long duration, RecentsAnimationCallbacks callbacks)141 public @Nullable Animator getParallelAnimationToLauncher( 142 GestureState.GestureEndTarget endTarget, long duration, 143 RecentsAnimationCallbacks callbacks) { 144 if (endTarget == RECENTS) { 145 CONTAINER_TYPE container = getCreatedContainer(); 146 if (container == null) { 147 return null; 148 } 149 RecentsView recentsView = container.getOverviewPanel(); 150 STATE_TYPE state = stateFromGestureEndTarget(endTarget); 151 ScrimView scrimView = container.getScrimView(); 152 ObjectAnimator anim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR, 153 getOverviewScrimColorForState(container, state)); 154 anim.setDuration(duration); 155 anim.setInterpolator(recentsView == null || !recentsView.isKeyboardTaskFocusPending() 156 ? LINEAR : INSTANT); 157 return anim; 158 } 159 return null; 160 } 161 162 /** 163 * Called when the animation to the target has finished, but right before updating the state. 164 * @return A View that needs to draw before ending the recents animation to LAST_TASK. 165 * (This is a hack to ensure Taskbar draws its background first to avoid flickering.) 166 */ onSettledOnEndTarget(GestureState.GestureEndTarget endTarget)167 public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) { 168 TaskbarUIController taskbarUIController = getTaskbarController(); 169 if (taskbarUIController != null) { 170 taskbarUIController.setSystemGestureInProgress(false); 171 return taskbarUIController.getRootView(); 172 } 173 return null; 174 } 175 176 /** 177 * Called when the current gesture transition is cancelled. 178 * @param activityVisible Whether the user can see the changes we make here, so try to animate. 179 * @param endTarget If the gesture ended before we got cancelled, where we were headed. 180 */ onTransitionCancelled(boolean activityVisible, @Nullable GestureState.GestureEndTarget endTarget)181 public void onTransitionCancelled(boolean activityVisible, 182 @Nullable GestureState.GestureEndTarget endTarget) { 183 RecentsViewContainer container = getCreatedContainer(); 184 if (container == null) { 185 return; 186 } 187 RecentsView recentsView = container.getOverviewPanel(); 188 BaseState startState = recentsView.getStateManager().getRestState(); 189 if (endTarget != null) { 190 // We were on our way to this state when we got canceled, end there instead. 191 startState = stateFromGestureEndTarget(endTarget); 192 DesktopVisibilityController controller = getDesktopVisibilityController(); 193 if (controller != null && controller.areDesktopTasksVisible() 194 && endTarget == LAST_TASK) { 195 // When we are cancelling the transition and going back to last task, move to 196 // rest state instead when desktop tasks are visible. 197 // If a fullscreen task is visible, launcher goes to normal state when the 198 // activity is stopped. This does not happen when desktop tasks are visible 199 // on top of launcher. Force the launcher state to rest state here. 200 startState = recentsView.getStateManager().getRestState(); 201 // Do not animate the transition 202 activityVisible = false; 203 } 204 } 205 recentsView.getStateManager().goToState(startState, activityVisible); 206 } 207 calculateTaskSize(Context context, DeviceProfile dp, Rect outRect, RecentsPagedOrientationHandler orientationHandler)208 public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect, 209 RecentsPagedOrientationHandler orientationHandler) { 210 if (dp.isTablet) { 211 if (Flags.enableGridOnlyOverview()) { 212 calculateGridTaskSize(context, dp, outRect, orientationHandler); 213 } else { 214 calculateFocusTaskSize(context, dp, outRect); 215 } 216 } else { 217 Resources res = context.getResources(); 218 float maxScale = res.getFloat(R.dimen.overview_max_scale); 219 int taskMargin = dp.overviewTaskMarginPx; 220 // In fake orientation, OverviewActions is hidden and we only leave a margin there. 221 int overviewActionsClaimedSpace = orientationHandler.isLayoutNaturalToLauncher() 222 ? dp.getOverviewActionsClaimedSpace() : dp.overviewActionsTopMarginPx; 223 calculateTaskSizeInternal( 224 context, 225 dp, 226 dp.overviewTaskThumbnailTopMarginPx, 227 overviewActionsClaimedSpace, 228 res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin, 229 maxScale, 230 Gravity.CENTER, 231 outRect, 232 orientationHandler); 233 } 234 } 235 236 /** 237 * Calculates the taskView size for carousel during app to overview animation on tablets. 238 */ calculateCarouselTaskSize(Context context, DeviceProfile dp, Rect outRect, RecentsPagedOrientationHandler orientationHandler)239 public final void calculateCarouselTaskSize(Context context, DeviceProfile dp, Rect outRect, 240 RecentsPagedOrientationHandler orientationHandler) { 241 if (dp.isTablet && dp.isGestureMode) { 242 Resources res = context.getResources(); 243 float minScale = res.getFloat(R.dimen.overview_carousel_min_scale); 244 Rect gridRect = new Rect(); 245 calculateGridSize(dp, context, gridRect); 246 calculateTaskSizeInternal(context, dp, gridRect, minScale, Gravity.CENTER | Gravity.TOP, 247 outRect); 248 } else { 249 calculateTaskSize(context, dp, outRect, orientationHandler); 250 } 251 } 252 calculateFocusTaskSize(Context context, DeviceProfile dp, Rect outRect)253 private void calculateFocusTaskSize(Context context, DeviceProfile dp, Rect outRect) { 254 Resources res = context.getResources(); 255 float maxScale = res.getFloat(R.dimen.overview_max_scale); 256 Rect gridRect = new Rect(); 257 calculateGridSize(dp, context, gridRect); 258 calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect); 259 } 260 calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity, Rect outRect, RecentsPagedOrientationHandler orientationHandler)261 private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove, 262 int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity, 263 Rect outRect, RecentsPagedOrientationHandler orientationHandler) { 264 Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx); 265 266 Rect insets; 267 if (orientationHandler.isLayoutNaturalToLauncher()) { 268 insets = dp.getInsets(); 269 } else { 270 Rect portraitInsets = dp.getInsets(); 271 DisplayController displayController = DisplayController.INSTANCE.get(context); 272 Rect deviceRotationInsets = displayController.getInfo().getCurrentBounds().get( 273 orientationHandler.getRotation()).insets; 274 // Obtain the landscape/seascape insets, and rotate it to portrait perspective. 275 orientationHandler.rotateInsets(deviceRotationInsets, outRect); 276 // Then combine with portrait's insets to leave space for status bar/nav bar in 277 // either orientations. 278 outRect.set( 279 Math.max(outRect.left, portraitInsets.left), 280 Math.max(outRect.top, portraitInsets.top), 281 Math.max(outRect.right, portraitInsets.right), 282 Math.max(outRect.bottom, portraitInsets.bottom) 283 ); 284 insets = outRect; 285 } 286 potentialTaskRect.inset(insets); 287 288 outRect.set( 289 minimumHorizontalPadding, 290 claimedSpaceAbove, 291 minimumHorizontalPadding, 292 claimedSpaceBelow); 293 // Rotate the paddings to portrait perspective, 294 orientationHandler.rotateInsets(outRect, outRect); 295 potentialTaskRect.inset(outRect); 296 297 calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect); 298 } 299 calculateTaskSizeInternal(Context context, DeviceProfile dp, Rect potentialTaskRect, float targetScale, int gravity, Rect outRect)300 private void calculateTaskSizeInternal(Context context, DeviceProfile dp, 301 Rect potentialTaskRect, float targetScale, int gravity, Rect outRect) { 302 PointF taskDimension = getTaskDimension(context, dp); 303 304 float scale = Math.min( 305 potentialTaskRect.width() / taskDimension.x, 306 potentialTaskRect.height() / taskDimension.y); 307 scale = Math.min(scale, targetScale); 308 int outWidth = Math.round(scale * taskDimension.x); 309 int outHeight = Math.round(scale * taskDimension.y); 310 311 Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect); 312 } 313 getTaskDimension(Context context, DeviceProfile dp)314 private static PointF getTaskDimension(Context context, DeviceProfile dp) { 315 PointF dimension = new PointF(); 316 getTaskDimension(context, dp, dimension); 317 return dimension; 318 } 319 320 /** 321 * Gets the dimension of the task in the current system state. 322 */ getTaskDimension(Context context, DeviceProfile dp, PointF out)323 public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) { 324 out.x = dp.widthPx; 325 out.y = dp.heightPx; 326 if (dp.isTablet && !DisplayController.isTransientTaskbar(context)) { 327 out.y -= dp.taskbarHeight; 328 } 329 } 330 331 /** 332 * Calculates the overview grid size for the provided device configuration. 333 */ calculateGridSize(DeviceProfile dp, Context context, Rect outRect)334 public final void calculateGridSize(DeviceProfile dp, Context context, Rect outRect) { 335 Rect insets = dp.getInsets(); 336 int topMargin = dp.overviewTaskThumbnailTopMarginPx; 337 int bottomMargin = dp.getOverviewActionsClaimedSpace(); 338 if (dp.isTaskbarPresent && Flags.enableGridOnlyOverview()) { 339 topMargin += context.getResources().getDimensionPixelSize( 340 R.dimen.overview_top_margin_grid_only); 341 bottomMargin += context.getResources().getDimensionPixelSize( 342 R.dimen.overview_bottom_margin_grid_only); 343 } 344 int sideMargin = dp.overviewGridSideMargin; 345 346 outRect.set(0, 0, dp.widthPx, dp.heightPx); 347 outRect.inset(Math.max(insets.left, sideMargin), insets.top + topMargin, 348 Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin)); 349 } 350 351 /** 352 * Calculates the overview grid non-focused task size for the provided device configuration. 353 */ calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect, RecentsPagedOrientationHandler orientationHandler)354 public final void calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect, 355 RecentsPagedOrientationHandler orientationHandler) { 356 Resources res = context.getResources(); 357 Rect potentialTaskRect = new Rect(); 358 if (Flags.enableGridOnlyOverview()) { 359 calculateGridSize(dp, context, potentialTaskRect); 360 } else { 361 calculateFocusTaskSize(context, dp, potentialTaskRect); 362 } 363 364 float rowHeight = (potentialTaskRect.height() + dp.overviewTaskThumbnailTopMarginPx 365 - dp.overviewRowSpacing) / 2f; 366 367 PointF taskDimension = getTaskDimension(context, dp); 368 float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y; 369 int outWidth = Math.round(scale * taskDimension.x); 370 int outHeight = Math.round(scale * taskDimension.y); 371 372 int gravity = Gravity.TOP; 373 gravity |= orientationHandler.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT; 374 Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect); 375 } 376 377 /** 378 * Calculates the modal taskView size for the provided device configuration 379 */ calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect, RecentsPagedOrientationHandler orientationHandler)380 public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect, 381 RecentsPagedOrientationHandler orientationHandler) { 382 calculateTaskSize(context, dp, outRect, orientationHandler); 383 boolean isGridOnlyOverview = dp.isTablet && Flags.enableGridOnlyOverview(); 384 int claimedSpaceBelow = isGridOnlyOverview 385 ? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight 386 : (dp.heightPx - outRect.bottom - dp.getInsets().bottom); 387 int minimumHorizontalPadding = 0; 388 if (!isGridOnlyOverview) { 389 float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale); 390 minimumHorizontalPadding = 391 Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2); 392 } 393 calculateTaskSizeInternal( 394 context, 395 dp, 396 dp.overviewTaskMarginPx, 397 claimedSpaceBelow, 398 minimumHorizontalPadding, 399 1f /*maxScale*/, 400 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 401 outRect, 402 orientationHandler); 403 } 404 } 405