• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.wm.shell.activityembedding;
18 
19 import static android.app.ActivityOptions.ANIM_CUSTOM;
20 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
21 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
22 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
23 
24 import static com.android.wm.shell.transition.DefaultTransitionHandler.isSupportedOverrideAnimation;
25 import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
26 
27 import static java.util.Objects.requireNonNull;
28 
29 import android.content.Context;
30 import android.graphics.Rect;
31 import android.os.IBinder;
32 import android.util.ArrayMap;
33 import android.view.SurfaceControl;
34 import android.window.TransitionInfo;
35 import android.window.TransitionInfo.AnimationOptions;
36 import android.window.TransitionRequestInfo;
37 import android.window.WindowContainerTransaction;
38 
39 import androidx.annotation.NonNull;
40 import androidx.annotation.Nullable;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.wm.shell.shared.TransitionUtil;
44 import com.android.wm.shell.sysui.ShellInit;
45 import com.android.wm.shell.transition.Transitions;
46 
47 import java.util.List;
48 
49 /**
50  * Responsible for handling ActivityEmbedding related transitions.
51  */
52 public class ActivityEmbeddingController implements Transitions.TransitionHandler {
53 
54     private final Context mContext;
55     @VisibleForTesting
56     final Transitions mTransitions;
57     @VisibleForTesting
58     final ActivityEmbeddingAnimationRunner mAnimationRunner;
59 
60     /**
61      * Keeps track of the currently-running transition callback associated with each transition
62      * token.
63      */
64     private final ArrayMap<IBinder, Transitions.TransitionFinishCallback> mTransitionCallbacks =
65             new ArrayMap<>();
66 
ActivityEmbeddingController(@onNull Context context, @NonNull ShellInit shellInit, @NonNull Transitions transitions)67     private ActivityEmbeddingController(@NonNull Context context, @NonNull ShellInit shellInit,
68             @NonNull Transitions transitions) {
69         mContext = requireNonNull(context);
70         mTransitions = requireNonNull(transitions);
71         mAnimationRunner = new ActivityEmbeddingAnimationRunner(context, this);
72 
73         shellInit.addInitCallback(this::onInit, this);
74     }
75 
76     /**
77      * Creates {@link ActivityEmbeddingController}, returns {@code null} if the feature is not
78      * supported.
79      */
80     @Nullable
create(@onNull Context context, @NonNull ShellInit shellInit, @NonNull Transitions transitions)81     public static ActivityEmbeddingController create(@NonNull Context context,
82             @NonNull ShellInit shellInit, @NonNull Transitions transitions) {
83         return Transitions.ENABLE_SHELL_TRANSITIONS
84                 ? new ActivityEmbeddingController(context, shellInit, transitions)
85                 : null;
86     }
87 
88     /** Registers to handle transitions. */
onInit()89     public void onInit() {
90         mTransitions.addHandler(this);
91     }
92 
93     /** Whether ActivityEmbeddingController should animate this transition. */
shouldAnimate(@onNull TransitionInfo info)94     public boolean shouldAnimate(@NonNull TransitionInfo info) {
95         if (info.getType() == TRANSIT_TASK_FRAGMENT_DRAG_RESIZE) {
96             // The TRANSIT_TASK_FRAGMENT_DRAG_RESIZE type happens when the user drags the
97             // interactive divider to resize the split containers. The content is veiled, so we will
98             // handle the transition with a jump cut.
99             return true;
100         }
101         boolean containsEmbeddingChange = false;
102         for (TransitionInfo.Change change : info.getChanges()) {
103             if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
104                     FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
105                 // Whether the Task contains any ActivityEmbedding split before or after the
106                 // transition.
107                 containsEmbeddingChange = true;
108             }
109         }
110         if (!containsEmbeddingChange) {
111             // Let the system to play the default animation if there is no ActivityEmbedding split
112             // window. This allows to play the app customized animation when there is no embedding,
113             // such as the device is in a folded state.
114             return false;
115         }
116 
117         if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) {
118             return false;
119         }
120 
121         return shouldAnimateAnimationOptions(info);
122     }
123 
shouldAnimateAnimationOptions(@onNull TransitionInfo info)124     private boolean shouldAnimateAnimationOptions(@NonNull TransitionInfo info) {
125         for (TransitionInfo.Change change : info.getChanges()) {
126             if (!shouldAnimateAnimationOptions(change.getAnimationOptions())) {
127                 // If any of override animation is not supported, don't animate the transition.
128                 return false;
129             }
130         }
131         return true;
132     }
133 
shouldAnimateAnimationOptions(@ullable AnimationOptions options)134     private boolean shouldAnimateAnimationOptions(@Nullable AnimationOptions options) {
135         if (options == null) {
136             return true;
137         }
138         // Scene-transition should be handled by app side.
139         if (options.getType() == ANIM_SCENE_TRANSITION) {
140             return false;
141         }
142         // The case of ActivityOptions#makeCustomAnimation, Activity#overridePendingTransition,
143         // and Activity#overrideActivityTransition are supported.
144         if (options.getType() == ANIM_CUSTOM) {
145             return true;
146         }
147         // Use default transition handler to animate other override animation.
148         return !isSupportedOverrideAnimation(options);
149     }
150 
151     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)152     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
153             @NonNull SurfaceControl.Transaction startTransaction,
154             @NonNull SurfaceControl.Transaction finishTransaction,
155             @NonNull Transitions.TransitionFinishCallback finishCallback) {
156 
157         if (!shouldAnimate(info)) return false;
158 
159         // Start ActivityEmbedding animation.
160         mTransitionCallbacks.put(transition, finishCallback);
161         mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
162         return true;
163     }
164 
165     @Override
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)166     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
167             @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
168             @NonNull IBinder mergeTarget,
169             @NonNull Transitions.TransitionFinishCallback finishCallback) {
170         mAnimationRunner.cancelAnimationFromMerge();
171     }
172 
173     /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */
containsNonEmbeddedChange(@onNull TransitionInfo info)174     private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) {
175         for (TransitionInfo.Change change : info.getChanges()) {
176             if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
177                 return true;
178             }
179         }
180         return false;
181     }
182 
handleNonEmbeddedChanges(List<TransitionInfo.Change> changes)183     private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
184         final Rect nonClosingEmbeddedArea = new Rect();
185         for (int i = changes.size() - 1; i >= 0; i--) {
186             final TransitionInfo.Change change = changes.get(i);
187             if (!TransitionUtil.isClosingType(change.getMode())) {
188                 if (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
189                     nonClosingEmbeddedArea.union(change.getEndAbsBounds());
190                     continue;
191                 }
192                 // Not able to handle non-embedded container if it is not closing.
193                 return false;
194             }
195         }
196         for (int i = changes.size() - 1; i >= 0; i--) {
197             final TransitionInfo.Change change = changes.get(i);
198             if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
199                     && !nonClosingEmbeddedArea.contains(change.getEndAbsBounds())) {
200                 // Unknown to animate containers outside the area of embedded activities.
201                 return false;
202             }
203         }
204         // Drop the non-embedded closing change because it is occluded by embedded activities.
205         for (int i = changes.size() - 1; i >= 0; i--) {
206             final TransitionInfo.Change change = changes.get(i);
207             if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
208                 changes.remove(i);
209             }
210         }
211         return true;
212     }
213 
214     @Nullable
215     @Override
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)216     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
217             @NonNull TransitionRequestInfo request) {
218         return null;
219     }
220 
221     @Override
setAnimScaleSetting(float scale)222     public void setAnimScaleSetting(float scale) {
223         mAnimationRunner.setAnimScaleSetting(scale);
224     }
225 
226     /** Called when the animation is finished. */
onAnimationFinished(@onNull IBinder transition)227     void onAnimationFinished(@NonNull IBinder transition) {
228         final Transitions.TransitionFinishCallback callback =
229                 mTransitionCallbacks.remove(transition);
230         if (callback == null) {
231             throw new IllegalStateException("No finish callback found");
232         }
233         callback.onTransitionFinished(null /* wct */);
234     }
235 }
236