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