• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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