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