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