1 /* 2 * Copyright (C) 2019 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 android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 19 import static android.content.Intent.EXTRA_COMPONENT_NAME; 20 import static android.content.Intent.EXTRA_USER; 21 22 import static com.android.app.animation.Interpolators.ACCELERATE; 23 import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT; 24 import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION; 25 import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE; 26 import static com.android.launcher3.GestureNavContract.EXTRA_ON_FINISH_CALLBACK; 27 import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK; 28 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; 29 import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely; 30 31 import android.animation.ObjectAnimator; 32 import android.annotation.TargetApi; 33 import android.app.ActivityManager.RunningTaskInfo; 34 import android.app.ActivityOptions; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.graphics.Matrix; 38 import android.graphics.Rect; 39 import android.graphics.RectF; 40 import android.os.Build; 41 import android.os.Bundle; 42 import android.os.Handler; 43 import android.os.IBinder; 44 import android.os.Looper; 45 import android.os.Message; 46 import android.os.Messenger; 47 import android.os.ParcelUuid; 48 import android.os.RemoteException; 49 import android.os.UserHandle; 50 import android.util.Log; 51 import android.view.RemoteAnimationTarget; 52 import android.view.Surface; 53 import android.view.SurfaceControl; 54 import android.view.SurfaceControl.Transaction; 55 56 import androidx.annotation.NonNull; 57 import androidx.annotation.Nullable; 58 59 import com.android.launcher3.DeviceProfile; 60 import com.android.launcher3.Utilities; 61 import com.android.launcher3.anim.AnimatedFloat; 62 import com.android.launcher3.anim.AnimatorPlaybackController; 63 import com.android.launcher3.anim.PendingAnimation; 64 import com.android.launcher3.anim.SpringAnimationBuilder; 65 import com.android.launcher3.states.StateAnimationConfig; 66 import com.android.launcher3.util.DisplayController; 67 import com.android.quickstep.fallback.FallbackRecentsView; 68 import com.android.quickstep.fallback.RecentsState; 69 import com.android.quickstep.util.RectFSpringAnim; 70 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties; 71 import com.android.quickstep.util.TransformParams; 72 import com.android.quickstep.util.TransformParams.BuilderProxy; 73 import com.android.systemui.shared.recents.model.Task.TaskKey; 74 import com.android.systemui.shared.system.InputConsumerController; 75 76 import java.lang.ref.WeakReference; 77 import java.util.ArrayList; 78 import java.util.UUID; 79 import java.util.function.Consumer; 80 81 /** 82 * Handles the navigation gestures when a 3rd party launcher is the default home activity. 83 */ 84 @TargetApi(Build.VERSION_CODES.R) 85 public class FallbackSwipeHandler extends 86 AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView, RecentsState> { 87 88 private static final String TAG = "FallbackSwipeHandler"; 89 90 /** 91 * Message used for receiving gesture nav contract information. We use a static messenger to 92 * avoid leaking too make binders in case the receiving launcher does not handle the contract 93 * properly. 94 */ 95 private static StaticMessageReceiver sMessageReceiver = null; 96 97 private FallbackHomeAnimationFactory mActiveAnimationFactory; 98 private final boolean mRunningOverHome; 99 100 private final Matrix mTmpMatrix = new Matrix(); 101 private float mMaxLauncherScale = 1; 102 103 private boolean mAppCanEnterPip; 104 FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, boolean continuingLastGesture, InputConsumerController inputConsumer)105 public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState, 106 TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, 107 boolean continuingLastGesture, InputConsumerController inputConsumer) { 108 super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs, 109 continuingLastGesture, inputConsumer); 110 111 mRunningOverHome = mGestureState.getRunningTask() != null 112 && mGestureState.getRunningTask().isHomeTask(); 113 if (mRunningOverHome) { 114 runActionOnRemoteHandles(remoteTargetHandle -> 115 remoteTargetHandle.getTransformParams().setHomeBuilderProxy( 116 FallbackSwipeHandler.this::updateHomeActivityTransformDuringSwipeUp)); 117 } 118 } 119 120 @Override initTransitionEndpoints(DeviceProfile dp)121 protected void initTransitionEndpoints(DeviceProfile dp) { 122 super.initTransitionEndpoints(dp); 123 if (mRunningOverHome) { 124 // Full screen scale should be independent of remote target handle 125 mMaxLauncherScale = 1 / mRemoteTargetHandles[0].getTaskViewSimulator() 126 .getFullScreenScale(); 127 } 128 } 129 updateHomeActivityTransformDuringSwipeUp(SurfaceProperties builder, RemoteAnimationTarget app, TransformParams params)130 private void updateHomeActivityTransformDuringSwipeUp(SurfaceProperties builder, 131 RemoteAnimationTarget app, TransformParams params) { 132 setHomeScaleAndAlpha(builder, app, mCurrentShift.value, 133 Utilities.boundToRange(1 - mCurrentShift.value, 0, 1)); 134 } 135 setHomeScaleAndAlpha(SurfaceProperties builder, RemoteAnimationTarget app, float verticalShift, float alpha)136 private void setHomeScaleAndAlpha(SurfaceProperties builder, 137 RemoteAnimationTarget app, float verticalShift, float alpha) { 138 float scale = Utilities.mapRange(verticalShift, 1, mMaxLauncherScale); 139 mTmpMatrix.setScale(scale, scale, 140 app.localBounds.exactCenterX(), app.localBounds.exactCenterY()); 141 builder.setMatrix(mTmpMatrix).setAlpha(alpha); 142 builder.setShow(); 143 } 144 145 @Override createHomeAnimationFactory(ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent, boolean appCanEnterPip, RemoteAnimationTarget runningTaskTarget)146 protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies, 147 long duration, boolean isTargetTranslucent, boolean appCanEnterPip, 148 RemoteAnimationTarget runningTaskTarget) { 149 mAppCanEnterPip = appCanEnterPip; 150 if (appCanEnterPip) { 151 return new FallbackPipToHomeAnimationFactory(); 152 } 153 mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration); 154 startHomeIntent(mActiveAnimationFactory, runningTaskTarget); 155 return mActiveAnimationFactory; 156 } 157 startHomeIntent( @ullable FallbackHomeAnimationFactory gestureContractAnimationFactory, @Nullable RemoteAnimationTarget runningTaskTarget)158 private void startHomeIntent( 159 @Nullable FallbackHomeAnimationFactory gestureContractAnimationFactory, 160 @Nullable RemoteAnimationTarget runningTaskTarget) { 161 ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0); 162 Intent intent = new Intent(mGestureState.getHomeIntent()); 163 if (gestureContractAnimationFactory != null && runningTaskTarget != null) { 164 gestureContractAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo); 165 } 166 startHomeIntentSafely(mContext, intent, options.toBundle()); 167 } 168 169 @Override handleTaskAppeared(RemoteAnimationTarget[] appearedTaskTarget)170 protected boolean handleTaskAppeared(RemoteAnimationTarget[] appearedTaskTarget) { 171 if (mActiveAnimationFactory != null 172 && mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) { 173 mActiveAnimationFactory = null; 174 return false; 175 } 176 177 return super.handleTaskAppeared(appearedTaskTarget); 178 } 179 180 @Override finishRecentsControllerToHome(Runnable callback)181 protected void finishRecentsControllerToHome(Runnable callback) { 182 final Runnable recentsCallback; 183 if (mAppCanEnterPip) { 184 // Make sure Launcher is resumed after auto-enter-pip transition to actually trigger 185 // the PiP task appearing. 186 recentsCallback = () -> { 187 callback.run(); 188 startHomeIntent( 189 null /* gestureContractAnimationFactory */, null /* runningTaskTarget */); 190 }; 191 } else { 192 recentsCallback = callback; 193 } 194 mRecentsView.cleanupRemoteTargets(); 195 mRecentsAnimationController.finish( 196 mAppCanEnterPip /* toRecents */, recentsCallback, true /* sendUserLeaveHint */); 197 } 198 199 @Override switchToScreenshot()200 protected void switchToScreenshot() { 201 if (mRunningOverHome) { 202 // When the current task is home, then we don't need to capture anything 203 mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); 204 } else { 205 super.switchToScreenshot(); 206 } 207 } 208 209 @Override notifyGestureAnimationStartToRecents()210 protected void notifyGestureAnimationStartToRecents() { 211 if (mRunningOverHome) { 212 if (DisplayController.getNavigationMode(mContext).hasGestures) { 213 mRecentsView.onGestureAnimationStartOnHome( 214 mGestureState.getRunningTask().getPlaceholderTasks(), 215 mDeviceState.getRotationTouchHelper()); 216 } 217 } else { 218 super.notifyGestureAnimationStartToRecents(); 219 } 220 } 221 222 private class FallbackPipToHomeAnimationFactory extends HomeAnimationFactory { 223 @NonNull 224 @Override createActivityAnimationToHome()225 public AnimatorPlaybackController createActivityAnimationToHome() { 226 // copied from {@link LauncherSwipeHandlerV2.LauncherHomeAnimationFactory} 227 long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx); 228 return mActivity.getStateManager().createAnimationToNewWorkspace( 229 RecentsState.HOME, accuracy, StateAnimationConfig.SKIP_ALL_ANIMATIONS); 230 } 231 } 232 233 private class FallbackHomeAnimationFactory extends HomeAnimationFactory 234 implements Consumer<Message> { 235 private final Rect mTempRect = new Rect(); 236 private final TransformParams mHomeAlphaParams = new TransformParams(); 237 private final AnimatedFloat mHomeAlpha; 238 239 private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat(); 240 private final AnimatedFloat mRecentsAlpha = new AnimatedFloat(); 241 242 private final RectF mTargetRect = new RectF(); 243 private SurfaceControl mSurfaceControl; 244 245 private boolean mAnimationFinished; 246 private Message mOnFinishCallback; 247 248 private final long mDuration; 249 250 private RectFSpringAnim mSpringAnim; FallbackHomeAnimationFactory(long duration)251 FallbackHomeAnimationFactory(long duration) { 252 mDuration = duration; 253 254 if (mRunningOverHome) { 255 mHomeAlpha = new AnimatedFloat(); 256 mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1); 257 mVerticalShiftForScale.value = mCurrentShift.value; 258 runActionOnRemoteHandles(remoteTargetHandle -> 259 remoteTargetHandle.getTransformParams().setHomeBuilderProxy( 260 FallbackHomeAnimationFactory.this 261 ::updateHomeActivityTransformDuringHomeAnim)); 262 } else { 263 mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha); 264 mHomeAlpha.value = 0; 265 mHomeAlphaParams.setHomeBuilderProxy( 266 this::updateHomeActivityTransformDuringHomeAnim); 267 } 268 269 mRecentsAlpha.value = 1; 270 runActionOnRemoteHandles(remoteTargetHandle -> 271 remoteTargetHandle.getTransformParams().setBaseBuilderProxy( 272 FallbackHomeAnimationFactory.this 273 ::updateRecentsActivityTransformDuringHomeAnim)); 274 } 275 276 @NonNull 277 @Override getWindowTargetRect()278 public RectF getWindowTargetRect() { 279 if (mTargetRect.isEmpty()) { 280 mTargetRect.set(super.getWindowTargetRect()); 281 } 282 return mTargetRect; 283 } 284 updateRecentsActivityTransformDuringHomeAnim(SurfaceProperties builder, RemoteAnimationTarget app, TransformParams params)285 private void updateRecentsActivityTransformDuringHomeAnim(SurfaceProperties builder, 286 RemoteAnimationTarget app, TransformParams params) { 287 builder.setAlpha(mRecentsAlpha.value); 288 } 289 updateHomeActivityTransformDuringHomeAnim(SurfaceProperties builder, RemoteAnimationTarget app, TransformParams params)290 private void updateHomeActivityTransformDuringHomeAnim(SurfaceProperties builder, 291 RemoteAnimationTarget app, TransformParams params) { 292 setHomeScaleAndAlpha(builder, app, mVerticalShiftForScale.value, mHomeAlpha.value); 293 } 294 295 @NonNull 296 @Override createActivityAnimationToHome()297 public AnimatorPlaybackController createActivityAnimationToHome() { 298 PendingAnimation pa = new PendingAnimation(mDuration); 299 pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCELERATE); 300 return pa.createPlaybackController(); 301 } 302 updateHomeAlpha()303 private void updateHomeAlpha() { 304 if (mHomeAlphaParams.getTargetSet() != null) { 305 mHomeAlphaParams.applySurfaceParams( 306 mHomeAlphaParams.createSurfaceParams(BuilderProxy.NO_OP)); 307 } 308 } 309 handleHomeTaskAppeared(RemoteAnimationTarget[] appearedTaskTargets)310 public boolean handleHomeTaskAppeared(RemoteAnimationTarget[] appearedTaskTargets) { 311 RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0]; 312 if (appearedTaskTarget.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME) { 313 RemoteAnimationTargets targets = new RemoteAnimationTargets( 314 new RemoteAnimationTarget[] {appearedTaskTarget}, 315 new RemoteAnimationTarget[0], new RemoteAnimationTarget[0], 316 appearedTaskTarget.mode); 317 mHomeAlphaParams.setTargetSet(targets); 318 updateHomeAlpha(); 319 return true; 320 } 321 return false; 322 } 323 324 @Override playAtomicAnimation(float velocity)325 public void playAtomicAnimation(float velocity) { 326 ObjectAnimator alphaAnim = mHomeAlpha.animateToValue(mHomeAlpha.value, 1); 327 alphaAnim.setDuration(mDuration).setInterpolator(ACCELERATE); 328 alphaAnim.start(); 329 330 if (mRunningOverHome) { 331 // Spring back launcher scale 332 new SpringAnimationBuilder(mContext) 333 .setStartValue(mVerticalShiftForScale.value) 334 .setEndValue(0) 335 .setStartVelocity(-velocity / mTransitionDragLength) 336 .setMinimumVisibleChange(1f / mDp.heightPx) 337 .setDampingRatio(0.6f) 338 .setStiffness(800) 339 .build(mVerticalShiftForScale, AnimatedFloat.VALUE) 340 .start(); 341 } 342 } 343 344 @Override setAnimation(RectFSpringAnim anim)345 public void setAnimation(RectFSpringAnim anim) { 346 mSpringAnim = anim; 347 mSpringAnim.addAnimatorListener(forEndCallback(this::onRectAnimationEnd)); 348 } 349 onRectAnimationEnd()350 private void onRectAnimationEnd() { 351 mAnimationFinished = true; 352 maybeSendEndMessage(); 353 } 354 maybeSendEndMessage()355 private void maybeSendEndMessage() { 356 if (mAnimationFinished && mOnFinishCallback != null) { 357 try { 358 mOnFinishCallback.replyTo.send(mOnFinishCallback); 359 } catch (RemoteException e) { 360 Log.e(TAG, "Error sending icon position", e); 361 } 362 } 363 } 364 365 @Override accept(Message msg)366 public void accept(Message msg) { 367 try { 368 Bundle data = msg.getData(); 369 RectF position = data.getParcelable(EXTRA_ICON_POSITION); 370 if (!position.isEmpty()) { 371 mSurfaceControl = data.getParcelable(EXTRA_ICON_SURFACE); 372 mTargetRect.set(position); 373 if (mSpringAnim != null) { 374 mSpringAnim.onTargetPositionChanged(); 375 } 376 mOnFinishCallback = data.getParcelable(EXTRA_ON_FINISH_CALLBACK); 377 } 378 maybeSendEndMessage(); 379 } catch (Exception e) { 380 // Ignore 381 } 382 } 383 384 @Override update(RectF currentRect, float progress, float radius)385 public void update(RectF currentRect, float progress, float radius) { 386 if (mSurfaceControl != null) { 387 currentRect.roundOut(mTempRect); 388 Transaction t = new Transaction(); 389 try { 390 t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0); 391 t.apply(); 392 } catch (RuntimeException e) { 393 // Ignore 394 } 395 } 396 } 397 addGestureContract(Intent intent, RunningTaskInfo runningTaskInfo)398 private void addGestureContract(Intent intent, RunningTaskInfo runningTaskInfo) { 399 if (mRunningOverHome || runningTaskInfo == null) { 400 return; 401 } 402 403 TaskKey key = new TaskKey(runningTaskInfo); 404 if (key.getComponent() != null) { 405 if (sMessageReceiver == null) { 406 sMessageReceiver = new StaticMessageReceiver(); 407 } 408 409 Bundle gestureNavContract = new Bundle(); 410 gestureNavContract.putParcelable(EXTRA_COMPONENT_NAME, key.getComponent()); 411 gestureNavContract.putParcelable(EXTRA_USER, UserHandle.of(key.userId)); 412 gestureNavContract.putParcelable( 413 EXTRA_REMOTE_CALLBACK, sMessageReceiver.newCallback(this)); 414 intent.putExtra(EXTRA_GESTURE_CONTRACT, gestureNavContract); 415 } 416 } 417 } 418 419 private static class StaticMessageReceiver implements Handler.Callback { 420 421 private final Messenger mMessenger = 422 new Messenger(new Handler(Looper.getMainLooper(), this)); 423 424 private ParcelUuid mCurrentUID = new ParcelUuid(UUID.randomUUID()); 425 private WeakReference<Consumer<Message>> mCurrentCallback = new WeakReference<>(null); 426 newCallback(Consumer<Message> callback)427 public Message newCallback(Consumer<Message> callback) { 428 mCurrentUID = new ParcelUuid(UUID.randomUUID()); 429 mCurrentCallback = new WeakReference<>(callback); 430 431 Message msg = Message.obtain(); 432 msg.replyTo = mMessenger; 433 msg.obj = mCurrentUID; 434 return msg; 435 } 436 437 @Override handleMessage(@onNull Message message)438 public boolean handleMessage(@NonNull Message message) { 439 if (mCurrentUID.equals(message.obj)) { 440 Consumer<Message> consumer = mCurrentCallback.get(); 441 if (consumer != null) { 442 consumer.accept(message); 443 return true; 444 } 445 } 446 return false; 447 } 448 } 449 } 450