1 /*
2  * Copyright (C) 2021 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 
17 package androidx.window.extensions.embedding;
18 
19 import static android.os.Process.THREAD_PRIORITY_DISPLAY;
20 import static android.view.RemoteAnimationTarget.MODE_CHANGING;
21 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
22 import static android.view.RemoteAnimationTarget.MODE_OPENING;
23 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
24 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
25 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
26 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
27 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
28 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
29 
30 import android.animation.Animator;
31 import android.animation.ValueAnimator;
32 import android.graphics.Rect;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.RemoteException;
36 import android.util.Log;
37 import android.view.IRemoteAnimationFinishedCallback;
38 import android.view.IRemoteAnimationRunner;
39 import android.view.RemoteAnimationTarget;
40 import android.view.SurfaceControl;
41 import android.view.WindowManager;
42 import android.view.animation.Animation;
43 
44 import androidx.annotation.NonNull;
45 import androidx.annotation.Nullable;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.function.BiFunction;
50 
51 /** To run the TaskFragment animations. */
52 class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
53 
54     private static final String TAG = "TaskFragAnimationRunner";
55     private final Handler mHandler;
56     private final TaskFragmentAnimationSpec mAnimationSpec;
57 
TaskFragmentAnimationRunner()58     TaskFragmentAnimationRunner() {
59         HandlerThread animationThread = new HandlerThread(
60                 "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY);
61         animationThread.start();
62         mHandler = animationThread.getThreadHandler();
63         mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
64     }
65 
66     @Nullable
67     private Animator mAnimator;
68 
69     @Override
onAnimationStart(@indowManager.TransitionOldType int transit, @NonNull RemoteAnimationTarget[] apps, @NonNull RemoteAnimationTarget[] wallpapers, @NonNull RemoteAnimationTarget[] nonApps, @NonNull IRemoteAnimationFinishedCallback finishedCallback)70     public void onAnimationStart(@WindowManager.TransitionOldType int transit,
71             @NonNull RemoteAnimationTarget[] apps,
72             @NonNull RemoteAnimationTarget[] wallpapers,
73             @NonNull RemoteAnimationTarget[] nonApps,
74             @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
75         if (wallpapers.length != 0 || nonApps.length != 0) {
76             throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
77                     + "wallpaper or non-app windows.");
78         }
79         if (TaskFragmentAnimationController.DEBUG) {
80             Log.v(TAG, "onAnimationStart transit=" + transit);
81         }
82         mHandler.post(() -> startAnimation(transit, apps, finishedCallback));
83     }
84 
85     @Override
onAnimationCancelled()86     public void onAnimationCancelled() {
87         mHandler.post(this::cancelAnimation);
88     }
89 
90     /** Creates and starts animation. */
startAnimation(@indowManager.TransitionOldType int transit, @NonNull RemoteAnimationTarget[] targets, @NonNull IRemoteAnimationFinishedCallback finishedCallback)91     private void startAnimation(@WindowManager.TransitionOldType int transit,
92             @NonNull RemoteAnimationTarget[] targets,
93             @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
94         if (mAnimator != null) {
95             Log.w(TAG, "start new animation when the previous one is not finished yet.");
96             mAnimator.cancel();
97         }
98         mAnimator = createAnimator(transit, targets, finishedCallback);
99         mAnimator.start();
100     }
101 
102     /** Cancels animation. */
cancelAnimation()103     private void cancelAnimation() {
104         if (mAnimator == null) {
105             return;
106         }
107         mAnimator.cancel();
108         mAnimator = null;
109     }
110 
111     /** Creates the animator given the transition type and windows. */
112     @NonNull
createAnimator(@indowManager.TransitionOldType int transit, @NonNull RemoteAnimationTarget[] targets, @NonNull IRemoteAnimationFinishedCallback finishedCallback)113     private Animator createAnimator(@WindowManager.TransitionOldType int transit,
114             @NonNull RemoteAnimationTarget[] targets,
115             @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
116         final List<TaskFragmentAnimationAdapter> adapters =
117                 createAnimationAdapters(transit, targets);
118         long duration = 0;
119         for (TaskFragmentAnimationAdapter adapter : adapters) {
120             duration = Math.max(duration, adapter.getDurationHint());
121         }
122         final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
123         animator.setDuration(duration);
124         animator.addUpdateListener((anim) -> {
125             // Update all adapters in the same transaction.
126             final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
127             for (TaskFragmentAnimationAdapter adapter : adapters) {
128                 adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
129             }
130             t.apply();
131         });
132         animator.addListener(new Animator.AnimatorListener() {
133             @Override
134             public void onAnimationStart(Animator animation) {}
135 
136             @Override
137             public void onAnimationEnd(Animator animation) {
138                 final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
139                 for (TaskFragmentAnimationAdapter adapter : adapters) {
140                     adapter.onAnimationEnd(t);
141                 }
142                 t.apply();
143 
144                 try {
145                     finishedCallback.onAnimationFinished();
146                 } catch (RemoteException e) {
147                     e.rethrowFromSystemServer();
148                 }
149                 mAnimator = null;
150             }
151 
152             @Override
153             public void onAnimationCancel(Animator animation) {}
154 
155             @Override
156             public void onAnimationRepeat(Animator animation) {}
157         });
158         return animator;
159     }
160 
161     /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
162     @NonNull
createAnimationAdapters( @indowManager.TransitionOldType int transit, @NonNull RemoteAnimationTarget[] targets)163     private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
164             @WindowManager.TransitionOldType int transit,
165             @NonNull RemoteAnimationTarget[] targets) {
166         switch (transit) {
167             case TRANSIT_OLD_ACTIVITY_OPEN:
168             case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
169                 return createOpenAnimationAdapters(targets);
170             case TRANSIT_OLD_ACTIVITY_CLOSE:
171             case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
172                 return createCloseAnimationAdapters(targets);
173             case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
174                 return createChangeAnimationAdapters(targets);
175             default:
176                 throw new IllegalArgumentException("Unhandled transit type=" + transit);
177         }
178     }
179 
180     @NonNull
createOpenAnimationAdapters( @onNull RemoteAnimationTarget[] targets)181     private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
182             @NonNull RemoteAnimationTarget[] targets) {
183         return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
184                 mAnimationSpec::loadOpenAnimation);
185     }
186 
187     @NonNull
createCloseAnimationAdapters( @onNull RemoteAnimationTarget[] targets)188     private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
189             @NonNull RemoteAnimationTarget[] targets) {
190         return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
191                 mAnimationSpec::loadCloseAnimation);
192     }
193 
194     /**
195      * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
196      * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
197      */
198     @NonNull
createOpenCloseAnimationAdapters( @onNull RemoteAnimationTarget[] targets, boolean isOpening, @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider)199     private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
200             @NonNull RemoteAnimationTarget[] targets, boolean isOpening,
201             @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
202         // We need to know if the target window is only a partial of the whole animation screen.
203         // If so, we will need to adjust it to make the whole animation screen looks like one.
204         final List<RemoteAnimationTarget> openingTargets = new ArrayList<>();
205         final List<RemoteAnimationTarget> closingTargets = new ArrayList<>();
206         final Rect openingWholeScreenBounds = new Rect();
207         final Rect closingWholeScreenBounds = new Rect();
208         for (RemoteAnimationTarget target : targets) {
209             if (target.mode != MODE_CLOSING) {
210                 openingTargets.add(target);
211                 openingWholeScreenBounds.union(target.screenSpaceBounds);
212             } else {
213                 closingTargets.add(target);
214                 closingWholeScreenBounds.union(target.screenSpaceBounds);
215                 // Union the start bounds since this may be the ClosingChanging animation.
216                 closingWholeScreenBounds.union(target.startBounds);
217             }
218         }
219 
220         // For OPEN transition, open windows should be above close windows.
221         // For CLOSE transition, open windows should be below close windows.
222         int offsetLayer = TYPE_LAYER_OFFSET;
223         final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
224         for (RemoteAnimationTarget target : openingTargets) {
225             final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
226                     animationProvider, openingWholeScreenBounds);
227             if (isOpening) {
228                 adapter.overrideLayer(offsetLayer++);
229             }
230             adapters.add(adapter);
231         }
232         for (RemoteAnimationTarget target : closingTargets) {
233             final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
234                     animationProvider, closingWholeScreenBounds);
235             if (!isOpening) {
236                 adapter.overrideLayer(offsetLayer++);
237             }
238             adapters.add(adapter);
239         }
240         return adapters;
241     }
242 
243     @NonNull
createOpenCloseAnimationAdapter( @onNull RemoteAnimationTarget target, @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider, @NonNull Rect wholeAnimationBounds)244     private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
245             @NonNull RemoteAnimationTarget target,
246             @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
247             @NonNull Rect wholeAnimationBounds) {
248         final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
249         return new TaskFragmentAnimationAdapter(animation, target, target.leash,
250                 wholeAnimationBounds);
251     }
252 
253     @NonNull
createChangeAnimationAdapters( @onNull RemoteAnimationTarget[] targets)254     private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
255             @NonNull RemoteAnimationTarget[] targets) {
256         if (shouldUseJumpCutForChangeAnimation(targets)) {
257             return new ArrayList<>();
258         }
259 
260         final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
261         for (RemoteAnimationTarget target : targets) {
262             if (target.mode == MODE_CHANGING) {
263                 // This is the target with bounds change.
264                 final Animation[] animations =
265                         mAnimationSpec.createChangeBoundsChangeAnimations(target);
266                 // Adapter for the starting snapshot leash.
267                 adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter(
268                         animations[0], target));
269                 // Adapter for the ending bounds changed leash.
270                 adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter(
271                         animations[1], target));
272                 continue;
273             }
274 
275             // These are the other targets that don't have bounds change in the same transition.
276             final Animation animation;
277             if (target.hasAnimatingParent) {
278                 // No-op if it will be covered by the changing parent window.
279                 animation = TaskFragmentAnimationSpec.createNoopAnimation(target);
280             } else if (target.mode == MODE_CLOSING) {
281                 animation = mAnimationSpec.createChangeBoundsCloseAnimation(target);
282             } else {
283                 animation = mAnimationSpec.createChangeBoundsOpenAnimation(target);
284             }
285             adapters.add(new TaskFragmentAnimationAdapter(animation, target));
286         }
287         return adapters;
288     }
289 
290     /**
291      * Whether we should use jump cut for the change transition.
292      * This normally happens when opening a new secondary with the existing primary using a
293      * different split layout. This can be complicated, like from horizontal to vertical split with
294      * new split pairs.
295      * Uses a jump cut animation to simplify.
296      */
shouldUseJumpCutForChangeAnimation(@onNull RemoteAnimationTarget[] targets)297     private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
298         boolean hasOpeningWindow = false;
299         boolean hasClosingWindow = false;
300         for (RemoteAnimationTarget target : targets) {
301             if (target.hasAnimatingParent) {
302                 continue;
303             }
304             hasOpeningWindow |= target.mode == MODE_OPENING;
305             hasClosingWindow |= target.mode == MODE_CLOSING;
306         }
307         return hasOpeningWindow && hasClosingWindow;
308     }
309 }
310