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