1 /* 2 * Copyright (C) 2018 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.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 19 import static com.android.launcher3.anim.Interpolators.ACCEL_2; 20 import static com.android.launcher3.anim.Interpolators.INSTANT; 21 import static com.android.launcher3.anim.Interpolators.LINEAR; 22 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; 23 import static com.android.quickstep.BaseSwipeUpHandlerV2.RECENTS_ATTACH_DURATION; 24 import static com.android.quickstep.SysUINavigationMode.getMode; 25 import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape; 26 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; 27 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM; 28 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM; 29 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; 30 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; 31 32 import android.animation.Animator; 33 import android.annotation.TargetApi; 34 import android.content.Context; 35 import android.content.res.Resources; 36 import android.graphics.Rect; 37 import android.os.Build; 38 import android.view.MotionEvent; 39 import android.view.animation.Interpolator; 40 41 import androidx.annotation.Nullable; 42 import androidx.annotation.UiThread; 43 44 import com.android.launcher3.DeviceProfile; 45 import com.android.launcher3.R; 46 import com.android.launcher3.anim.AnimatorPlaybackController; 47 import com.android.launcher3.anim.PendingAnimation; 48 import com.android.launcher3.statehandlers.DepthController; 49 import com.android.launcher3.statemanager.BaseState; 50 import com.android.launcher3.statemanager.StatefulActivity; 51 import com.android.launcher3.touch.PagedOrientationHandler; 52 import com.android.launcher3.util.WindowBounds; 53 import com.android.quickstep.SysUINavigationMode.Mode; 54 import com.android.quickstep.util.ActivityInitListener; 55 import com.android.quickstep.util.ShelfPeekAnim; 56 import com.android.quickstep.util.SplitScreenBounds; 57 import com.android.quickstep.views.RecentsView; 58 import com.android.systemui.shared.recents.model.ThumbnailData; 59 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 60 61 import java.util.function.Consumer; 62 import java.util.function.Predicate; 63 64 /** 65 * Utility class which abstracts out the logical differences between Launcher and RecentsActivity. 66 */ 67 @TargetApi(Build.VERSION_CODES.P) 68 public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>, 69 ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>> { 70 71 public final boolean rotationSupportedByActivity; 72 73 private final STATE_TYPE mOverviewState, mBackgroundState; 74 BaseActivityInterface(boolean rotationSupportedByActivity, STATE_TYPE overviewState, STATE_TYPE backgroundState)75 protected BaseActivityInterface(boolean rotationSupportedByActivity, 76 STATE_TYPE overviewState, STATE_TYPE backgroundState) { 77 this.rotationSupportedByActivity = rotationSupportedByActivity; 78 mOverviewState = overviewState; 79 mBackgroundState = backgroundState; 80 } 81 onTransitionCancelled(boolean activityVisible)82 public void onTransitionCancelled(boolean activityVisible) { 83 ACTIVITY_TYPE activity = getCreatedActivity(); 84 if (activity == null) { 85 return; 86 } 87 STATE_TYPE startState = activity.getStateManager().getRestState(); 88 activity.getStateManager().goToState(startState, activityVisible); 89 } 90 getSwipeUpDestinationAndLength( DeviceProfile dp, Context context, Rect outRect, PagedOrientationHandler orientationHandler)91 public abstract int getSwipeUpDestinationAndLength( 92 DeviceProfile dp, Context context, Rect outRect, 93 PagedOrientationHandler orientationHandler); 94 onSwipeUpToRecentsComplete()95 public void onSwipeUpToRecentsComplete() { 96 // Re apply state in case we did something funky during the transition. 97 ACTIVITY_TYPE activity = getCreatedActivity(); 98 if (activity == null) { 99 return; 100 } 101 activity.getStateManager().reapplyState(); 102 } 103 onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState)104 public abstract void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState); 105 onAssistantVisibilityChanged(float visibility)106 public abstract void onAssistantVisibilityChanged(float visibility); 107 prepareRecentsUI(RecentsAnimationDeviceState deviceState, boolean activityVisible, Consumer<AnimatorPlaybackController> callback)108 public abstract AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState, 109 boolean activityVisible, Consumer<AnimatorPlaybackController> callback); 110 createActivityInitListener( Predicate<Boolean> onInitListener)111 public abstract ActivityInitListener createActivityInitListener( 112 Predicate<Boolean> onInitListener); 113 114 /** 115 * Sets a callback to be run when an activity launch happens while launcher is not yet resumed. 116 */ setOnDeferredActivityLaunchCallback(Runnable r)117 public void setOnDeferredActivityLaunchCallback(Runnable r) {} 118 119 @Nullable getCreatedActivity()120 public abstract ACTIVITY_TYPE getCreatedActivity(); 121 122 @Nullable getDepthController()123 public DepthController getDepthController() { 124 return null; 125 } 126 isResumed()127 public final boolean isResumed() { 128 ACTIVITY_TYPE activity = getCreatedActivity(); 129 return activity != null && activity.hasBeenResumed(); 130 } 131 isStarted()132 public final boolean isStarted() { 133 ACTIVITY_TYPE activity = getCreatedActivity(); 134 return activity != null && activity.isStarted(); 135 } 136 137 @UiThread 138 @Nullable getVisibleRecentsView()139 public abstract <T extends RecentsView> T getVisibleRecentsView(); 140 141 @UiThread switchToRecentsIfVisible(Runnable onCompleteCallback)142 public abstract boolean switchToRecentsIfVisible(Runnable onCompleteCallback); 143 getOverviewWindowBounds( Rect homeBounds, RemoteAnimationTargetCompat target)144 public abstract Rect getOverviewWindowBounds( 145 Rect homeBounds, RemoteAnimationTargetCompat target); 146 allowMinimizeSplitScreen()147 public abstract boolean allowMinimizeSplitScreen(); 148 deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev)149 public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { 150 return deviceState.isInDeferredGestureRegion(ev); 151 } 152 onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable)153 public abstract void onExitOverview(RecentsAnimationDeviceState deviceState, 154 Runnable exitRunnable); 155 156 /** 157 * Updates the prediction state to the overview state. 158 */ updateOverviewPredictionState()159 public void updateOverviewPredictionState() { 160 // By public overview predictions are not supported 161 } 162 163 /** 164 * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher} 165 */ getContainerType()166 public abstract int getContainerType(); 167 isInLiveTileMode()168 public abstract boolean isInLiveTileMode(); 169 onLaunchTaskFailed()170 public abstract void onLaunchTaskFailed(); 171 onLaunchTaskSuccess()172 public void onLaunchTaskSuccess() { 173 ACTIVITY_TYPE activity = getCreatedActivity(); 174 if (activity == null) { 175 return; 176 } 177 activity.getStateManager().moveToRestState(); 178 } 179 closeOverlay()180 public void closeOverlay() { } 181 switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, Runnable runnable)182 public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, Runnable runnable) { 183 ACTIVITY_TYPE activity = getCreatedActivity(); 184 if (activity == null) { 185 return; 186 } 187 RecentsView recentsView = activity.getOverviewPanel(); 188 if (recentsView == null) { 189 if (runnable != null) { 190 runnable.run(); 191 } 192 return; 193 } 194 recentsView.switchToScreenshot(thumbnailData, runnable); 195 } 196 197 /** 198 * Calculates the taskView size for the provided device configuration 199 */ calculateTaskSize(Context context, DeviceProfile dp, Rect outRect, PagedOrientationHandler orientedState)200 public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect, 201 PagedOrientationHandler orientedState) { 202 calculateTaskSize(context, dp, getExtraSpace(context, dp, orientedState), 203 outRect, orientedState); 204 } 205 getExtraSpace(Context context, DeviceProfile dp, PagedOrientationHandler orientedState)206 protected abstract float getExtraSpace(Context context, DeviceProfile dp, 207 PagedOrientationHandler orientedState); 208 calculateTaskSize( Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect, PagedOrientationHandler orientationHandler)209 private void calculateTaskSize( 210 Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect, 211 PagedOrientationHandler orientationHandler) { 212 Resources res = context.getResources(); 213 final boolean showLargeTaskSize = showOverviewActions(context) || 214 hideShelfInTwoButtonLandscape(context, orientationHandler); 215 216 final int paddingResId; 217 if (dp.isMultiWindowMode) { 218 paddingResId = R.dimen.multi_window_task_card_horz_space; 219 } else if (dp.isVerticalBarLayout()) { 220 paddingResId = R.dimen.landscape_task_card_horz_space; 221 } else if (showLargeTaskSize) { 222 paddingResId = R.dimen.portrait_task_card_horz_space_big_overview; 223 } else { 224 paddingResId = R.dimen.portrait_task_card_horz_space; 225 } 226 float paddingHorz = res.getDimension(paddingResId); 227 float paddingVert = showLargeTaskSize 228 ? 0 : res.getDimension(R.dimen.task_card_vert_space); 229 230 calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, 231 res.getDimension(R.dimen.task_thumbnail_top_margin), outRect); 232 } 233 calculateTaskSizeInternal(Context context, DeviceProfile dp, float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin, Rect outRect)234 private void calculateTaskSizeInternal(Context context, DeviceProfile dp, 235 float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin, 236 Rect outRect) { 237 float taskWidth, taskHeight; 238 Rect insets = dp.getInsets(); 239 if (dp.isMultiWindowMode) { 240 WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context); 241 taskWidth = bounds.availableSize.x; 242 taskHeight = bounds.availableSize.y; 243 } else { 244 taskWidth = dp.availableWidthPx; 245 taskHeight = dp.availableHeightPx; 246 } 247 248 // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless 249 // we override the insets ourselves. 250 int launcherVisibleWidth = dp.widthPx - insets.left - insets.right; 251 int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom; 252 253 float availableHeight = launcherVisibleHeight 254 - topIconMargin - extraVerticalSpace - paddingVert; 255 float availableWidth = launcherVisibleWidth - paddingHorz; 256 257 float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight); 258 float outWidth = scale * taskWidth; 259 float outHeight = scale * taskHeight; 260 261 // Center in the visible space 262 float x = insets.left + (launcherVisibleWidth - outWidth) / 2; 263 float y = insets.top + Math.max(topIconMargin, 264 (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2); 265 outRect.set(Math.round(x), Math.round(y), 266 Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight)); 267 } 268 269 /** 270 * Calculates the modal taskView size for the provided device configuration 271 */ calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect)272 public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) { 273 float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode 274 ? R.dimen.multi_window_task_card_horz_space 275 : dp.isVerticalBarLayout() 276 ? R.dimen.landscape_task_card_horz_space 277 : R.dimen.portrait_modal_task_card_horz_space); 278 float extraVerticalSpace = getOverviewActionsHeight(context); 279 float paddingVert = 0; 280 float topIconMargin = 0; 281 calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, 282 topIconMargin, outRect); 283 } 284 285 /** Gets the space that the overview actions will take, including margins. */ getOverviewActionsHeight(Context context)286 public final float getOverviewActionsHeight(Context context) { 287 Resources res = context.getResources(); 288 float actionsBottomMargin = 0; 289 if (getMode(context) == Mode.THREE_BUTTONS) { 290 actionsBottomMargin = res.getDimensionPixelSize( 291 R.dimen.overview_actions_bottom_margin_three_button); 292 } else { 293 actionsBottomMargin = res.getDimensionPixelSize( 294 R.dimen.overview_actions_bottom_margin_gesture); 295 } 296 float overviewActionsHeight = actionsBottomMargin 297 + res.getDimensionPixelSize(R.dimen.overview_actions_height); 298 return overviewActionsHeight; 299 } 300 301 public interface AnimationFactory { 302 createActivityInterface(long transitionLength)303 void createActivityInterface(long transitionLength); 304 onTransitionCancelled()305 default void onTransitionCancelled() { } 306 setShelfState(ShelfPeekAnim.ShelfAnimState animState, Interpolator interpolator, long duration)307 default void setShelfState(ShelfPeekAnim.ShelfAnimState animState, 308 Interpolator interpolator, long duration) { } 309 310 /** 311 * @param attached Whether to show RecentsView alongside the app window. If false, recents 312 * will be hidden by some property we can animate, e.g. alpha. 313 * @param animate Whether to animate recents to/from its new attached state. 314 */ setRecentsAttachedToAppWindow(boolean attached, boolean animate)315 default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { } 316 } 317 318 class DefaultAnimationFactory implements AnimationFactory { 319 320 protected final ACTIVITY_TYPE mActivity; 321 private final STATE_TYPE mStartState; 322 private final Consumer<AnimatorPlaybackController> mCallback; 323 324 private boolean mIsAttachedToWindow; 325 DefaultAnimationFactory(Consumer<AnimatorPlaybackController> callback)326 DefaultAnimationFactory(Consumer<AnimatorPlaybackController> callback) { 327 mCallback = callback; 328 329 mActivity = getCreatedActivity(); 330 mStartState = mActivity.getStateManager().getState(); 331 } 332 initUI()333 protected ACTIVITY_TYPE initUI() { 334 STATE_TYPE resetState = mStartState; 335 if (mStartState.shouldDisableRestore()) { 336 resetState = mActivity.getStateManager().getRestState(); 337 } 338 mActivity.getStateManager().setRestState(resetState); 339 mActivity.getStateManager().goToState(mBackgroundState, false); 340 return mActivity; 341 } 342 343 @Override createActivityInterface(long transitionLength)344 public void createActivityInterface(long transitionLength) { 345 PendingAnimation pa = new PendingAnimation(transitionLength * 2); 346 createBackgroundToOverviewAnim(mActivity, pa); 347 AnimatorPlaybackController controller = pa.createPlaybackController(); 348 mActivity.getStateManager().setCurrentUserControlledAnimation(controller); 349 350 // Since we are changing the start position of the UI, reapply the state, at the end 351 controller.setEndAction(() -> mActivity.getStateManager().goToState( 352 controller.getInterpolatedProgress() > 0.5 ? mOverviewState : mBackgroundState, 353 false)); 354 mCallback.accept(controller); 355 356 // Creating the activity controller animation sometimes reapplies the launcher state 357 // (because we set the animation as the current state animation), so we reapply the 358 // attached state here as well to ensure recents is shown/hidden appropriately. 359 if (SysUINavigationMode.getMode(mActivity) == Mode.NO_BUTTON) { 360 setRecentsAttachedToAppWindow(mIsAttachedToWindow, false); 361 } 362 } 363 364 @Override onTransitionCancelled()365 public void onTransitionCancelled() { 366 mActivity.getStateManager().goToState(mStartState, false /* animate */); 367 } 368 369 @Override setRecentsAttachedToAppWindow(boolean attached, boolean animate)370 public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { 371 if (mIsAttachedToWindow == attached && animate) { 372 return; 373 } 374 mIsAttachedToWindow = attached; 375 RecentsView recentsView = mActivity.getOverviewPanel(); 376 Animator fadeAnim = mActivity.getStateManager() 377 .createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0); 378 379 float fromTranslation = attached ? 1 : 0; 380 float toTranslation = attached ? 0 : 1; 381 mActivity.getStateManager() 382 .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM); 383 if (!recentsView.isShown() && animate) { 384 ADJACENT_PAGE_OFFSET.set(recentsView, fromTranslation); 385 } else { 386 fromTranslation = ADJACENT_PAGE_OFFSET.get(recentsView); 387 } 388 if (!animate) { 389 ADJACENT_PAGE_OFFSET.set(recentsView, toTranslation); 390 } else { 391 mActivity.getStateManager().createStateElementAnimation( 392 INDEX_RECENTS_TRANSLATE_X_ANIM, 393 fromTranslation, toTranslation).start(); 394 } 395 396 fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2); 397 fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start(); 398 } 399 createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa)400 protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) { 401 // Scale down recents from being full screen to being in overview. 402 RecentsView recentsView = activity.getOverviewPanel(); 403 pa.addFloat(recentsView, SCALE_PROPERTY, 404 recentsView.getMaxScaleForFullScreen(), 1, LINEAR); 405 pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR); 406 } 407 } 408 showOverviewActions(Context context)409 protected static boolean showOverviewActions(Context context) { 410 return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context); 411 } 412 } 413