• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.window;
18 
19 import static android.view.WindowManager.TRANSIT_CHANGE;
20 import static android.view.WindowManager.TRANSIT_CLOSE;
21 import static android.view.WindowManager.TRANSIT_NONE;
22 import static android.view.WindowManager.TRANSIT_OPEN;
23 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
24 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
25 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
26 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
27 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
28 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
29 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
30 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
31 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
32 
33 import android.annotation.CallSuper;
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.annotation.TestApi;
37 import android.app.WindowConfiguration;
38 import android.content.res.Configuration;
39 import android.os.Bundle;
40 import android.os.IBinder;
41 import android.os.RemoteException;
42 import android.util.SparseArray;
43 import android.view.RemoteAnimationDefinition;
44 import android.view.WindowManager;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.concurrent.Executor;
49 
50 /**
51  * Interface for WindowManager to delegate control of {@code TaskFragment}.
52  * @hide
53  */
54 @TestApi
55 public class TaskFragmentOrganizer extends WindowOrganizer {
56 
57     /**
58      * Key to the {@link Throwable} in {@link TaskFragmentTransaction.Change#getErrorBundle()}.
59      * @hide
60      */
61     public static final String KEY_ERROR_CALLBACK_THROWABLE = "fragment_throwable";
62 
63     /**
64      * Key to the {@link TaskFragmentInfo} in
65      * {@link TaskFragmentTransaction.Change#getErrorBundle()}.
66      * @hide
67      */
68     public static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info";
69 
70     /**
71      * Key to the {@link WindowContainerTransaction.HierarchyOp} in
72      * {@link TaskFragmentTransaction.Change#getErrorBundle()}.
73      * @hide
74      */
75     public static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type";
76 
77     /**
78      * Creates a {@link Bundle} with an exception, operation type and TaskFragmentInfo (if any)
79      * that can be passed to {@link ITaskFragmentOrganizer#onTaskFragmentError}.
80      * @hide
81      */
putErrorInfoInBundle(@onNull Throwable exception, @Nullable TaskFragmentInfo info, int opType)82     public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception,
83             @Nullable TaskFragmentInfo info, int opType) {
84         final Bundle errorBundle = new Bundle();
85         errorBundle.putSerializable(KEY_ERROR_CALLBACK_THROWABLE, exception);
86         if (info != null) {
87             errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info);
88         }
89         errorBundle.putInt(KEY_ERROR_CALLBACK_OP_TYPE, opType);
90         return errorBundle;
91     }
92 
93     /**
94      * Callbacks from WM Core are posted on this executor.
95      */
96     private final Executor mExecutor;
97 
98     // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
99     /** Map from Task id to client tokens of TaskFragments in the Task. */
100     private final SparseArray<List<IBinder>> mTaskIdToFragmentTokens = new SparseArray<>();
101     /** Map from Task id to Task configuration. */
102     private final SparseArray<Configuration> mTaskIdToConfigurations = new SparseArray<>();
103 
TaskFragmentOrganizer(@onNull Executor executor)104     public TaskFragmentOrganizer(@NonNull Executor executor) {
105         mExecutor = executor;
106     }
107 
108     /**
109      * Gets the executor to run callbacks on.
110      */
111     @NonNull
getExecutor()112     public Executor getExecutor() {
113         return mExecutor;
114     }
115 
116     /**
117      * Registers a TaskFragmentOrganizer to manage TaskFragments.
118      */
119     @CallSuper
registerOrganizer()120     public void registerOrganizer() {
121         try {
122             getController().registerOrganizer(mInterface);
123         } catch (RemoteException e) {
124             throw e.rethrowFromSystemServer();
125         }
126     }
127 
128     /**
129      * Unregisters a previously registered TaskFragmentOrganizer.
130      */
131     @CallSuper
unregisterOrganizer()132     public void unregisterOrganizer() {
133         try {
134             getController().unregisterOrganizer(mInterface);
135         } catch (RemoteException e) {
136             throw e.rethrowFromSystemServer();
137         }
138     }
139 
140     /**
141      * Registers remote animations per transition type for the organizer. It will override the
142      * animations if the transition only contains windows that belong to the organized
143      * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
144      * @hide
145      */
146     @CallSuper
registerRemoteAnimations(@onNull RemoteAnimationDefinition definition)147     public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
148         try {
149             getController().registerRemoteAnimations(mInterface, definition);
150         } catch (RemoteException e) {
151             throw e.rethrowFromSystemServer();
152         }
153     }
154 
155     /**
156      * Unregisters remote animations per transition type for the organizer.
157      * @hide
158      */
159     @CallSuper
unregisterRemoteAnimations()160     public void unregisterRemoteAnimations() {
161         try {
162             getController().unregisterRemoteAnimations(mInterface);
163         } catch (RemoteException e) {
164             throw e.rethrowFromSystemServer();
165         }
166     }
167 
168     /**
169      * Notifies the server that the organizer has finished handling the given transaction. The
170      * server should apply the given {@link WindowContainerTransaction} for the necessary changes.
171      *
172      * @param transactionToken  {@link TaskFragmentTransaction#getTransactionToken()} from
173      *                          {@link #onTransactionReady(TaskFragmentTransaction)}
174      * @param wct               {@link WindowContainerTransaction} that the server should apply for
175      *                          update of the transaction.
176      * @param transitionType    {@link WindowManager.TransitionType} if it needs to start a
177      *                          transition.
178      * @param shouldApplyIndependently  If {@code true}, the {@code wct} will request a new
179      *                                  transition, which will be queued until the sync engine is
180      *                                  free if there is any other active sync. If {@code false},
181      *                                  the {@code wct} will be directly applied to the active sync.
182      * @see com.android.server.wm.WindowOrganizerController#enforceTaskFragmentOrganizerPermission
183      * for permission enforcement.
184      * @hide
185      */
onTransactionHandled(@onNull IBinder transactionToken, @NonNull WindowContainerTransaction wct, @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently)186     public void onTransactionHandled(@NonNull IBinder transactionToken,
187             @NonNull WindowContainerTransaction wct,
188             @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
189         wct.setTaskFragmentOrganizer(mInterface);
190         try {
191             getController().onTransactionHandled(transactionToken, wct, transitionType,
192                     shouldApplyIndependently);
193         } catch (RemoteException e) {
194             throw e.rethrowFromSystemServer();
195         }
196     }
197 
198     /**
199      * Routes to {@link ITaskFragmentOrganizerController#applyTransaction} instead of
200      * {@link IWindowOrganizerController#applyTransaction} for the different transition options.
201      *
202      * @see #applyTransaction(WindowContainerTransaction, int, boolean)
203      */
204     @Override
applyTransaction(@onNull WindowContainerTransaction wct)205     public void applyTransaction(@NonNull WindowContainerTransaction wct) {
206         // TODO(b/207070762) doing so to keep CTS compatibility. Remove in the next release.
207         applyTransaction(wct, getTransitionType(wct), false /* shouldApplyIndependently */);
208     }
209 
210     /**
211      * Requests the server to apply the given {@link WindowContainerTransaction}.
212      *
213      * @param wct   {@link WindowContainerTransaction} to apply.
214      * @param transitionType    {@link WindowManager.TransitionType} if it needs to start a
215      *                          transition.
216      * @param shouldApplyIndependently  If {@code true}, the {@code wct} will request a new
217      *                                  transition, which will be queued until the sync engine is
218      *                                  free if there is any other active sync. If {@code false},
219      *                                  the {@code wct} will be directly applied to the active sync.
220      * @see com.android.server.wm.WindowOrganizerController#enforceTaskFragmentOrganizerPermission
221      * for permission enforcement.
222      * @hide
223      */
applyTransaction(@onNull WindowContainerTransaction wct, @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently)224     public void applyTransaction(@NonNull WindowContainerTransaction wct,
225             @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
226         if (wct.isEmpty()) {
227             return;
228         }
229         wct.setTaskFragmentOrganizer(mInterface);
230         try {
231             getController().applyTransaction(wct, transitionType, shouldApplyIndependently);
232         } catch (RemoteException e) {
233             throw e.rethrowFromSystemServer();
234         }
235     }
236 
237     /**
238      * Gets the default {@link WindowManager.TransitionType} based on the requested
239      * {@link WindowContainerTransaction}.
240      * @hide
241      */
242     // TODO(b/207070762): let Extensions to set the transition type instead.
243     @WindowManager.TransitionType
getTransitionType(@onNull WindowContainerTransaction wct)244     public static int getTransitionType(@NonNull WindowContainerTransaction wct) {
245         if (wct.isEmpty()) {
246             return TRANSIT_NONE;
247         }
248         for (WindowContainerTransaction.Change change : wct.getChanges().values()) {
249             if ((change.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) {
250                 // Treat as TRANSIT_CHANGE when there is TaskFragment resizing.
251                 return TRANSIT_CHANGE;
252             }
253         }
254         boolean containsCreatingTaskFragment = false;
255         boolean containsDeleteTaskFragment = false;
256         final List<WindowContainerTransaction.HierarchyOp> ops = wct.getHierarchyOps();
257         for (int i = ops.size() - 1; i >= 0; i--) {
258             final int type = ops.get(i).getType();
259             if (type == HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT) {
260                 // Treat as TRANSIT_CHANGE when there is activity reparent.
261                 return TRANSIT_CHANGE;
262             }
263             if (type == HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT) {
264                 containsCreatingTaskFragment = true;
265             } else if (type == HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT) {
266                 containsDeleteTaskFragment = true;
267             }
268         }
269         if (containsCreatingTaskFragment) {
270             return TRANSIT_OPEN;
271         }
272         if (containsDeleteTaskFragment) {
273             return TRANSIT_CLOSE;
274         }
275 
276         // Use TRANSIT_CHANGE as default.
277         return TRANSIT_CHANGE;
278     }
279 
280     /**
281      * Called when a TaskFragment is created and organized by this organizer.
282      *
283      * @param taskFragmentInfo  Info of the TaskFragment that is created.
284      * @deprecated Use {@link #onTransactionReady(TaskFragmentTransaction)} instead.
285      */
286     @Deprecated
onTaskFragmentAppeared(@onNull TaskFragmentInfo taskFragmentInfo)287     public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {}
288 
289     /**
290      * Called when the status of an organized TaskFragment is changed.
291      *
292      * @param taskFragmentInfo  Info of the TaskFragment that is changed.
293      * @deprecated Use {@link #onTransactionReady(TaskFragmentTransaction)} instead.
294      */
295     @Deprecated
onTaskFragmentInfoChanged(@onNull TaskFragmentInfo taskFragmentInfo)296     public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
297 
298     /**
299      * Called when an organized TaskFragment is removed.
300      *
301      * @param taskFragmentInfo  Info of the TaskFragment that is removed.
302      * @deprecated Use {@link #onTransactionReady(TaskFragmentTransaction)} instead.
303      */
304     @Deprecated
onTaskFragmentVanished(@onNull TaskFragmentInfo taskFragmentInfo)305     public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {}
306 
307     /**
308      * Called when the parent leaf Task of organized TaskFragments is changed.
309      * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
310      * transaction.
311      *
312      * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
313      * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
314      * bounds.
315      *
316      * @param fragmentToken The parent Task this TaskFragment is changed.
317      * @param parentConfig  Config of the parent Task.
318      * @deprecated Use {@link #onTransactionReady(TaskFragmentTransaction)} instead.
319      */
320     @Deprecated
onTaskFragmentParentInfoChanged( @onNull IBinder fragmentToken, @NonNull Configuration parentConfig)321     public void onTaskFragmentParentInfoChanged(
322             @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {}
323 
324     /**
325      * Called when the {@link WindowContainerTransaction} created with
326      * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
327      *
328      * @param errorCallbackToken    token set in
329      *                             {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
330      * @param exception             exception from the server side.
331      * @deprecated Use {@link #onTransactionReady(TaskFragmentTransaction)} instead.
332      */
333     @Deprecated
onTaskFragmentError( @onNull IBinder errorCallbackToken, @NonNull Throwable exception)334     public void onTaskFragmentError(
335             @NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {}
336 
337     /**
338      * Called when the transaction is ready so that the organizer can update the TaskFragments based
339      * on the changes in transaction.
340      * @hide
341      */
onTransactionReady(@onNull TaskFragmentTransaction transaction)342     public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
343         // TODO(b/240519866): move to SplitController#onTransactionReady to make sure the whole
344         // transaction is handled in one sync block. Keep the implementation below to keep CTS
345         // compatibility. Remove in the next release.
346         final WindowContainerTransaction wct = new WindowContainerTransaction();
347         final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
348         for (TaskFragmentTransaction.Change change : changes) {
349             final int taskId = change.getTaskId();
350             switch (change.getType()) {
351                 case TYPE_TASK_FRAGMENT_APPEARED:
352                     if (!mTaskIdToFragmentTokens.contains(taskId)) {
353                         mTaskIdToFragmentTokens.put(taskId, new ArrayList<>());
354                     }
355                     mTaskIdToFragmentTokens.get(taskId).add(change.getTaskFragmentToken());
356                     onTaskFragmentParentInfoChanged(change.getTaskFragmentToken(),
357                             mTaskIdToConfigurations.get(taskId));
358                     onTaskFragmentAppeared(change.getTaskFragmentInfo());
359                     break;
360                 case TYPE_TASK_FRAGMENT_INFO_CHANGED:
361                     onTaskFragmentInfoChanged(change.getTaskFragmentInfo());
362                     break;
363                 case TYPE_TASK_FRAGMENT_VANISHED:
364                     if (mTaskIdToFragmentTokens.contains(taskId)) {
365                         final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
366                         tokens.remove(change.getTaskFragmentToken());
367                         if (tokens.isEmpty()) {
368                             mTaskIdToFragmentTokens.remove(taskId);
369                             mTaskIdToConfigurations.remove(taskId);
370                         }
371                     }
372                     onTaskFragmentVanished(change.getTaskFragmentInfo());
373                     break;
374                 case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
375                     final Configuration parentConfig = change.getTaskConfiguration();
376                     mTaskIdToConfigurations.put(taskId, parentConfig);
377                     final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
378                     if (tokens == null || tokens.isEmpty()) {
379                         break;
380                     }
381                     for (int i = tokens.size() - 1; i >= 0; i--) {
382                         onTaskFragmentParentInfoChanged(tokens.get(i), parentConfig);
383                     }
384                     break;
385                 case TYPE_TASK_FRAGMENT_ERROR:
386                     final Bundle errorBundle = change.getErrorBundle();
387                     onTaskFragmentError(
388                             change.getErrorCallbackToken(),
389                             errorBundle.getSerializable(KEY_ERROR_CALLBACK_THROWABLE,
390                                     java.lang.Throwable.class));
391                     break;
392                 case TYPE_ACTIVITY_REPARENTED_TO_TASK:
393                     // This is for CTS compat:
394                     // There is no TestApi for CTS to handle this type of change, but we don't want
395                     // it to throw exception as default. This has been updated in next release.
396                     break;
397                 default:
398                     throw new IllegalArgumentException(
399                             "Unknown TaskFragmentEvent=" + change.getType());
400             }
401         }
402 
403         // Notify the server, and the server should apply the WindowContainerTransaction.
404         onTransactionHandled(transaction.getTransactionToken(), wct, getTransitionType(wct),
405                 false /* shouldApplyIndependently */);
406     }
407 
408     private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
409         @Override
410         public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
411             mExecutor.execute(() -> TaskFragmentOrganizer.this.onTransactionReady(transaction));
412         }
413     };
414 
415     private final TaskFragmentOrganizerToken mToken = new TaskFragmentOrganizerToken(mInterface);
416 
417     @NonNull
getOrganizerToken()418     public TaskFragmentOrganizerToken getOrganizerToken() {
419         return mToken;
420     }
421 
getController()422     private ITaskFragmentOrganizerController getController() {
423         try {
424             return getWindowOrganizerController().getTaskFragmentOrganizerController();
425         } catch (RemoteException e) {
426             return null;
427         }
428     }
429 
430     /**
431      * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
432      * only occupies a portion of Task bounds.
433      * @hide
434      */
isActivityEmbedded(@onNull IBinder activityToken)435     public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
436         try {
437             return getController().isActivityEmbedded(activityToken);
438         } catch (RemoteException e) {
439             throw e.rethrowFromSystemServer();
440         }
441     }
442 }
443