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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 20 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; 21 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; 22 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK; 23 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION; 24 import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED; 25 26 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; 27 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; 28 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; 29 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary; 30 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary; 31 32 import android.app.Activity; 33 import android.app.WindowConfiguration.WindowingMode; 34 import android.content.Intent; 35 import android.graphics.Rect; 36 import android.os.Bundle; 37 import android.os.IBinder; 38 import android.util.ArrayMap; 39 import android.window.TaskFragmentAnimationParams; 40 import android.window.TaskFragmentCreationParams; 41 import android.window.TaskFragmentInfo; 42 import android.window.TaskFragmentOperation; 43 import android.window.TaskFragmentOrganizer; 44 import android.window.TaskFragmentTransaction; 45 import android.window.WindowContainerTransaction; 46 47 import androidx.annotation.NonNull; 48 import androidx.annotation.Nullable; 49 50 import com.android.internal.annotations.VisibleForTesting; 51 52 import java.util.Map; 53 import java.util.concurrent.Executor; 54 55 /** 56 * Platform default Extensions implementation of {@link TaskFragmentOrganizer} to organize 57 * task fragments. 58 * 59 * All calls into methods of this class are expected to be on the UI thread. 60 */ 61 class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { 62 63 /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */ 64 @VisibleForTesting 65 final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); 66 67 @NonNull 68 private final TaskFragmentCallback mCallback; 69 70 @VisibleForTesting 71 @Nullable 72 TaskFragmentAnimationController mAnimationController; 73 74 /** 75 * Callback that notifies the controller about changes to task fragments. 76 */ 77 interface TaskFragmentCallback { onTransactionReady(@onNull TaskFragmentTransaction transaction)78 void onTransactionReady(@NonNull TaskFragmentTransaction transaction); 79 } 80 81 /** 82 * @param executor callbacks from WM Core are posted on this executor. It should be tied to the 83 * UI thread that all other calls into methods of this class are also on. 84 */ JetpackTaskFragmentOrganizer(@onNull Executor executor, @NonNull TaskFragmentCallback callback)85 JetpackTaskFragmentOrganizer(@NonNull Executor executor, 86 @NonNull TaskFragmentCallback callback) { 87 super(executor); 88 mCallback = callback; 89 } 90 91 @Override unregisterOrganizer()92 public void unregisterOrganizer() { 93 if (mAnimationController != null) { 94 mAnimationController.unregisterRemoteAnimations(); 95 mAnimationController = null; 96 } 97 super.unregisterOrganizer(); 98 } 99 100 /** 101 * Overrides the animation for transitions of embedded activities organized by this organizer. 102 */ overrideSplitAnimation()103 void overrideSplitAnimation() { 104 if (mAnimationController == null) { 105 mAnimationController = new TaskFragmentAnimationController(this); 106 } 107 mAnimationController.registerRemoteAnimations(); 108 } 109 110 /** 111 * Starts a new Activity and puts it into split with an existing Activity side-by-side. 112 * @param launchingFragmentToken token for the launching TaskFragment. If it exists, it will 113 * be resized based on {@param launchingFragmentBounds}. 114 * Otherwise, we will create a new TaskFragment with the given 115 * token for the {@param launchingActivity}. 116 * @param launchingRelBounds the initial relative bounds for the launching TaskFragment. 117 * @param launchingActivity the Activity to put on the left hand side of the split as the 118 * primary. 119 * @param secondaryFragmentToken token to create the secondary TaskFragment with. 120 * @param secondaryRelBounds the initial relative bounds for the secondary TaskFragment 121 * @param activityIntent Intent to start the secondary Activity with. 122 * @param activityOptions ActivityOptions to start the secondary Activity with. 123 * @param windowingMode the windowing mode to set for the TaskFragments. 124 * @param splitAttributes the {@link SplitAttributes} to represent the split. 125 */ startActivityToSide(@onNull WindowContainerTransaction wct, @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingRelBounds, @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, @NonNull Rect secondaryRelBounds, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule, @WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes)126 void startActivityToSide(@NonNull WindowContainerTransaction wct, 127 @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingRelBounds, 128 @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, 129 @NonNull Rect secondaryRelBounds, @NonNull Intent activityIntent, 130 @Nullable Bundle activityOptions, @NonNull SplitRule rule, 131 @WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes) { 132 final IBinder ownerToken = launchingActivity.getActivityToken(); 133 134 // Create or resize the launching TaskFragment. 135 if (mFragmentInfos.containsKey(launchingFragmentToken)) { 136 resizeTaskFragment(wct, launchingFragmentToken, launchingRelBounds); 137 updateWindowingMode(wct, launchingFragmentToken, windowingMode); 138 } else { 139 createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken, 140 launchingRelBounds, windowingMode, launchingActivity); 141 } 142 updateAnimationParams(wct, launchingFragmentToken, splitAttributes); 143 144 // Create a TaskFragment for the secondary activity. 145 final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder( 146 getOrganizerToken(), secondaryFragmentToken, ownerToken) 147 .setInitialRelativeBounds(secondaryRelBounds) 148 .setWindowingMode(windowingMode) 149 // Make sure to set the paired fragment token so that the new TaskFragment will be 150 // positioned right above the paired TaskFragment. 151 // This is needed in case we need to launch a placeholder Activity to split below a 152 // transparent always-expand Activity. 153 .setPairedPrimaryFragmentToken(launchingFragmentToken) 154 .build(); 155 createTaskFragment(wct, fragmentOptions); 156 updateAnimationParams(wct, secondaryFragmentToken, splitAttributes); 157 wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent, 158 activityOptions); 159 160 // Set adjacent to each other so that the containers below will be invisible. 161 setAdjacentTaskFragmentsWithRule(wct, launchingFragmentToken, secondaryFragmentToken, rule); 162 setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule, 163 false /* isStacked */); 164 } 165 166 /** 167 * Expands an existing TaskFragment to fill parent. 168 * @param wct WindowContainerTransaction in which the task fragment should be resized. 169 * @param container the {@link TaskFragmentContainer} to be expanded. 170 */ expandTaskFragment(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container)171 void expandTaskFragment(@NonNull WindowContainerTransaction wct, 172 @NonNull TaskFragmentContainer container) { 173 final IBinder fragmentToken = container.getTaskFragmentToken(); 174 resizeTaskFragment(wct, fragmentToken, new Rect()); 175 clearAdjacentTaskFragments(wct, fragmentToken); 176 updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED); 177 updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); 178 } 179 180 /** 181 * Expands an Activity to fill parent by moving it to a new TaskFragment. 182 * @param fragmentToken token to create new TaskFragment with. 183 * @param activity activity to move to the fill-parent TaskFragment. 184 */ expandActivity(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull Activity activity)185 void expandActivity(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, 186 @NonNull Activity activity) { 187 createTaskFragmentAndReparentActivity( 188 wct, fragmentToken, activity.getActivityToken(), new Rect(), 189 WINDOWING_MODE_UNDEFINED, activity); 190 updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); 191 } 192 193 /** 194 * @param ownerToken The token of the activity that creates this task fragment. It does not 195 * have to be a child of this task fragment, but must belong to the same task. 196 */ createTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect relBounds, @WindowingMode int windowingMode)197 void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, 198 @NonNull IBinder ownerToken, @NonNull Rect relBounds, 199 @WindowingMode int windowingMode) { 200 createTaskFragment(wct, fragmentToken, ownerToken, relBounds, windowingMode, 201 null /* pairedActivityToken */); 202 } 203 204 /** 205 * @param ownerToken The token of the activity that creates this task fragment. It does not 206 * have to be a child of this task fragment, but must belong to the same task. 207 * @param pairedActivityToken The token of the activity that will be reparented to this task 208 * fragment. When it is not {@code null}, the task fragment will be 209 * positioned right above it. 210 */ createTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect relBounds, @WindowingMode int windowingMode, @Nullable IBinder pairedActivityToken)211 void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, 212 @NonNull IBinder ownerToken, @NonNull Rect relBounds, @WindowingMode int windowingMode, 213 @Nullable IBinder pairedActivityToken) { 214 final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder( 215 getOrganizerToken(), fragmentToken, ownerToken) 216 .setInitialRelativeBounds(relBounds) 217 .setWindowingMode(windowingMode) 218 .setPairedActivityToken(pairedActivityToken) 219 .build(); 220 createTaskFragment(wct, fragmentOptions); 221 } 222 createTaskFragment(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentCreationParams fragmentOptions)223 void createTaskFragment(@NonNull WindowContainerTransaction wct, 224 @NonNull TaskFragmentCreationParams fragmentOptions) { 225 if (mFragmentInfos.containsKey(fragmentOptions.getFragmentToken())) { 226 throw new IllegalArgumentException( 227 "There is an existing TaskFragment with fragmentToken=" 228 + fragmentOptions.getFragmentToken()); 229 } 230 wct.createTaskFragment(fragmentOptions); 231 } 232 233 /** 234 * @param ownerToken The token of the activity that creates this task fragment. It does not 235 * have to be a child of this task fragment, but must belong to the same task. 236 */ createTaskFragmentAndReparentActivity(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect relBounds, @WindowingMode int windowingMode, @NonNull Activity activity)237 private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct, 238 @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect relBounds, 239 @WindowingMode int windowingMode, @NonNull Activity activity) { 240 final IBinder reparentActivityToken = activity.getActivityToken(); 241 createTaskFragment(wct, fragmentToken, ownerToken, relBounds, windowingMode, 242 reparentActivityToken); 243 wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken); 244 } 245 246 /** 247 * Sets the two given TaskFragments as adjacent to each other with respecting the given 248 * {@link SplitRule} for {@link WindowContainerTransaction.TaskFragmentAdjacentParams}. 249 */ setAdjacentTaskFragmentsWithRule(@onNull WindowContainerTransaction wct, @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule)250 void setAdjacentTaskFragmentsWithRule(@NonNull WindowContainerTransaction wct, 251 @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule) { 252 WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null; 253 final boolean finishSecondaryWithPrimary = 254 SplitContainer.shouldFinishSecondaryWithPrimary(splitRule); 255 final boolean finishPrimaryWithSecondary = 256 SplitContainer.shouldFinishPrimaryWithSecondary(splitRule); 257 if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) { 258 adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams(); 259 adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary); 260 adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary); 261 } 262 setAdjacentTaskFragments(wct, primary, secondary, adjacentParams); 263 } 264 setAdjacentTaskFragments(@onNull WindowContainerTransaction wct, @NonNull IBinder primary, @NonNull IBinder secondary, @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams)265 void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, 266 @NonNull IBinder primary, @NonNull IBinder secondary, 267 @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams) { 268 wct.setAdjacentTaskFragments(primary, secondary, adjacentParams); 269 } 270 clearAdjacentTaskFragments(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken)271 void clearAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, 272 @NonNull IBinder fragmentToken) { 273 // Clear primary will also clear secondary. 274 wct.clearAdjacentTaskFragments(fragmentToken); 275 } 276 setCompanionTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule, boolean isStacked)277 void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, 278 @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule, 279 boolean isStacked) { 280 final boolean finishPrimaryWithSecondary; 281 if (isStacked) { 282 finishPrimaryWithSecondary = shouldFinishAssociatedContainerWhenStacked( 283 getFinishPrimaryWithSecondaryBehavior(splitRule)); 284 } else { 285 finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule); 286 } 287 setCompanionTaskFragment(wct, primary, finishPrimaryWithSecondary ? secondary : null); 288 289 final boolean finishSecondaryWithPrimary; 290 if (isStacked) { 291 finishSecondaryWithPrimary = shouldFinishAssociatedContainerWhenStacked( 292 getFinishSecondaryWithPrimaryBehavior(splitRule)); 293 } else { 294 finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule); 295 } 296 setCompanionTaskFragment(wct, secondary, finishSecondaryWithPrimary ? primary : null); 297 } 298 setCompanionTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder primary, @Nullable IBinder secondary)299 void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary, 300 @Nullable IBinder secondary) { 301 wct.setCompanionTaskFragment(primary, secondary); 302 } 303 resizeTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @Nullable Rect relBounds)304 void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, 305 @Nullable Rect relBounds) { 306 if (!mFragmentInfos.containsKey(fragmentToken)) { 307 throw new IllegalArgumentException( 308 "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken); 309 } 310 if (relBounds == null) { 311 relBounds = new Rect(); 312 } 313 wct.setRelativeBounds(mFragmentInfos.get(fragmentToken).getToken(), relBounds); 314 } 315 updateWindowingMode(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @WindowingMode int windowingMode)316 void updateWindowingMode(@NonNull WindowContainerTransaction wct, 317 @NonNull IBinder fragmentToken, @WindowingMode int windowingMode) { 318 if (!mFragmentInfos.containsKey(fragmentToken)) { 319 throw new IllegalArgumentException( 320 "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken); 321 } 322 wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode); 323 } 324 325 /** 326 * Updates the {@link TaskFragmentAnimationParams} for the given TaskFragment based on 327 * {@link SplitAttributes}. 328 */ updateAnimationParams(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull SplitAttributes splitAttributes)329 void updateAnimationParams(@NonNull WindowContainerTransaction wct, 330 @NonNull IBinder fragmentToken, @NonNull SplitAttributes splitAttributes) { 331 updateAnimationParams(wct, fragmentToken, createAnimationParamsOrDefault(splitAttributes)); 332 } 333 updateAnimationParams(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams)334 void updateAnimationParams(@NonNull WindowContainerTransaction wct, 335 @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) { 336 final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( 337 OP_TYPE_SET_ANIMATION_PARAMS) 338 .setAnimationParams(animationParams) 339 .build(); 340 wct.addTaskFragmentOperation(fragmentToken, operation); 341 } 342 deleteTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken)343 void deleteTaskFragment(@NonNull WindowContainerTransaction wct, 344 @NonNull IBinder fragmentToken) { 345 wct.deleteTaskFragment(fragmentToken); 346 } 347 reorderTaskFragmentToFront(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken)348 void reorderTaskFragmentToFront(@NonNull WindowContainerTransaction wct, 349 @NonNull IBinder fragmentToken) { 350 final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( 351 OP_TYPE_REORDER_TO_FRONT).build(); 352 wct.addTaskFragmentOperation(fragmentToken, operation); 353 } 354 setTaskFragmentIsolatedNavigation(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, boolean isolatedNav)355 void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct, 356 @NonNull IBinder fragmentToken, boolean isolatedNav) { 357 final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( 358 OP_TYPE_SET_ISOLATED_NAVIGATION).setBooleanValue(isolatedNav).build(); 359 wct.addTaskFragmentOperation(fragmentToken, operation); 360 } 361 setTaskFragmentPinned(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, boolean pinned)362 void setTaskFragmentPinned(@NonNull WindowContainerTransaction wct, 363 @NonNull IBinder fragmentToken, boolean pinned) { 364 final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( 365 OP_TYPE_SET_PINNED).setBooleanValue(pinned).build(); 366 wct.addTaskFragmentOperation(fragmentToken, operation); 367 } 368 setTaskFragmentDimOnTask(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, boolean dimOnTask)369 void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct, 370 @NonNull IBinder fragmentToken, boolean dimOnTask) { 371 final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( 372 OP_TYPE_SET_DIM_ON_TASK).setBooleanValue(dimOnTask).build(); 373 wct.addTaskFragmentOperation(fragmentToken, operation); 374 } 375 updateTaskFragmentInfo(@onNull TaskFragmentInfo taskFragmentInfo)376 void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) { 377 mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo); 378 } 379 removeTaskFragmentInfo(@onNull TaskFragmentInfo taskFragmentInfo)380 void removeTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) { 381 mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); 382 } 383 384 @Override onTransactionReady(@onNull TaskFragmentTransaction transaction)385 public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { 386 mCallback.onTransactionReady(transaction); 387 } 388 createAnimationParamsOrDefault( @ullable SplitAttributes splitAttributes)389 private static TaskFragmentAnimationParams createAnimationParamsOrDefault( 390 @Nullable SplitAttributes splitAttributes) { 391 if (splitAttributes == null) { 392 return TaskFragmentAnimationParams.DEFAULT; 393 } 394 final AnimationBackground animationBackground = splitAttributes.getAnimationBackground(); 395 if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) { 396 return new TaskFragmentAnimationParams.Builder() 397 .setAnimationBackgroundColor(colorBackground.getColor()) 398 .build(); 399 } else { 400 return TaskFragmentAnimationParams.DEFAULT; 401 } 402 } 403 } 404