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