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_MULTI_WINDOW; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 21 22 import android.app.Activity; 23 import android.app.WindowConfiguration.WindowingMode; 24 import android.content.Intent; 25 import android.content.res.Configuration; 26 import android.graphics.Rect; 27 import android.os.Bundle; 28 import android.os.IBinder; 29 import android.util.ArrayMap; 30 import android.window.TaskFragmentCreationParams; 31 import android.window.TaskFragmentInfo; 32 import android.window.TaskFragmentOrganizer; 33 import android.window.WindowContainerTransaction; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 38 import java.util.Map; 39 import java.util.concurrent.Executor; 40 41 /** 42 * Platform default Extensions implementation of {@link TaskFragmentOrganizer} to organize 43 * task fragments. 44 * 45 * All calls into methods of this class are expected to be on the UI thread. 46 */ 47 class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { 48 49 /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */ 50 private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); 51 52 /** 53 * Mapping from the client assigned unique token to the TaskFragment parent 54 * {@link Configuration}. 55 */ 56 final Map<IBinder, Configuration> mFragmentParentConfigs = new ArrayMap<>(); 57 58 private final TaskFragmentCallback mCallback; 59 private TaskFragmentAnimationController mAnimationController; 60 61 /** 62 * Callback that notifies the controller about changes to task fragments. 63 */ 64 interface TaskFragmentCallback { onTaskFragmentAppeared(@onNull TaskFragmentInfo taskFragmentInfo)65 void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo); onTaskFragmentInfoChanged(@onNull TaskFragmentInfo taskFragmentInfo)66 void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo); onTaskFragmentVanished(@onNull TaskFragmentInfo taskFragmentInfo)67 void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo); onTaskFragmentParentInfoChanged(@onNull IBinder fragmentToken, @NonNull Configuration parentConfig)68 void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, 69 @NonNull Configuration parentConfig); 70 } 71 72 /** 73 * @param executor callbacks from WM Core are posted on this executor. It should be tied to the 74 * UI thread that all other calls into methods of this class are also on. 75 */ JetpackTaskFragmentOrganizer(@onNull Executor executor, TaskFragmentCallback callback)76 JetpackTaskFragmentOrganizer(@NonNull Executor executor, TaskFragmentCallback callback) { 77 super(executor); 78 mCallback = callback; 79 } 80 81 @Override unregisterOrganizer()82 public void unregisterOrganizer() { 83 stopOverrideSplitAnimation(); 84 mAnimationController = null; 85 super.unregisterOrganizer(); 86 } 87 startOverrideSplitAnimation()88 void startOverrideSplitAnimation() { 89 if (mAnimationController == null) { 90 mAnimationController = new TaskFragmentAnimationController(this); 91 } 92 mAnimationController.registerRemoteAnimations(); 93 } 94 stopOverrideSplitAnimation()95 void stopOverrideSplitAnimation() { 96 if (mAnimationController != null) { 97 mAnimationController.unregisterRemoteAnimations(); 98 } 99 } 100 101 /** 102 * Starts a new Activity and puts it into split with an existing Activity side-by-side. 103 * @param launchingFragmentToken token for the launching TaskFragment. If it exists, it will 104 * be resized based on {@param launchingFragmentBounds}. 105 * Otherwise, we will create a new TaskFragment with the given 106 * token for the {@param launchingActivity}. 107 * @param launchingFragmentBounds the initial bounds for the launching TaskFragment. 108 * @param launchingActivity the Activity to put on the left hand side of the split as the 109 * primary. 110 * @param secondaryFragmentToken token to create the secondary TaskFragment with. 111 * @param secondaryFragmentBounds the initial bounds for the secondary TaskFragment 112 * @param activityIntent Intent to start the secondary Activity with. 113 * @param activityOptions ActivityOptions to start the secondary Activity with. 114 */ 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)115 void startActivityToSide(@NonNull WindowContainerTransaction wct, 116 @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds, 117 @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, 118 @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent, 119 @Nullable Bundle activityOptions, @NonNull SplitRule rule) { 120 final IBinder ownerToken = launchingActivity.getActivityToken(); 121 122 // Create or resize the launching TaskFragment. 123 if (mFragmentInfos.containsKey(launchingFragmentToken)) { 124 resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds); 125 } else { 126 createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken, 127 launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity); 128 } 129 130 // Create a TaskFragment for the secondary activity. 131 createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken, 132 secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent, 133 activityOptions); 134 135 // Set adjacent to each other so that the containers below will be invisible. 136 setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule); 137 } 138 139 /** 140 * Expands an existing TaskFragment to fill parent. 141 * @param wct WindowContainerTransaction in which the task fragment should be resized. 142 * @param fragmentToken token of an existing TaskFragment. 143 */ expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken)144 void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { 145 resizeTaskFragment(wct, fragmentToken, new Rect()); 146 setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */); 147 } 148 149 /** 150 * Expands an existing TaskFragment to fill parent. 151 * @param fragmentToken token of an existing TaskFragment. 152 */ expandTaskFragment(IBinder fragmentToken)153 void expandTaskFragment(IBinder fragmentToken) { 154 WindowContainerTransaction wct = new WindowContainerTransaction(); 155 expandTaskFragment(wct, fragmentToken); 156 applyTransaction(wct); 157 } 158 159 /** 160 * Expands an Activity to fill parent by moving it to a new TaskFragment. 161 * @param fragmentToken token to create new TaskFragment with. 162 * @param activity activity to move to the fill-parent TaskFragment. 163 */ expandActivity(IBinder fragmentToken, Activity activity)164 void expandActivity(IBinder fragmentToken, Activity activity) { 165 final WindowContainerTransaction wct = new WindowContainerTransaction(); 166 createTaskFragmentAndReparentActivity( 167 wct, fragmentToken, activity.getActivityToken(), new Rect(), 168 WINDOWING_MODE_UNDEFINED, activity); 169 applyTransaction(wct); 170 } 171 172 /** 173 * @param ownerToken The token of the activity that creates this task fragment. It does not 174 * have to be a child of this task fragment, but must belong to the same task. 175 */ createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode)176 void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken, 177 IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) { 178 final TaskFragmentCreationParams fragmentOptions = 179 createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode); 180 wct.createTaskFragment(fragmentOptions); 181 } 182 183 /** 184 * @param ownerToken The token of the activity that creates this task fragment. It does not 185 * have to be a child of this task fragment, but must belong to the same task. 186 */ createTaskFragmentAndReparentActivity( WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity)187 private void createTaskFragmentAndReparentActivity( 188 WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, 189 @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) { 190 createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode); 191 wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken()); 192 } 193 194 /** 195 * @param ownerToken The token of the activity that creates this task fragment. It does not 196 * have to be a child of this task fragment, but must belong to the same task. 197 */ createTaskFragmentAndStartActivity( WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent, @Nullable Bundle activityOptions)198 private void createTaskFragmentAndStartActivity( 199 WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, 200 @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent, 201 @Nullable Bundle activityOptions) { 202 createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode); 203 wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions); 204 } 205 setAdjacentTaskFragments(@onNull WindowContainerTransaction wct, @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule)206 void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, 207 @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) { 208 WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null; 209 final boolean finishSecondaryWithPrimary = 210 splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule); 211 final boolean finishPrimaryWithSecondary = 212 splitRule != null && SplitContainer.shouldFinishPrimaryWithSecondary(splitRule); 213 if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) { 214 adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams(); 215 adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary); 216 adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary); 217 } 218 wct.setAdjacentTaskFragments(primary, secondary, adjacentParams); 219 } 220 createFragmentOptions(IBinder fragmentToken, IBinder ownerToken, Rect bounds, @WindowingMode int windowingMode)221 TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken, 222 Rect bounds, @WindowingMode int windowingMode) { 223 if (mFragmentInfos.containsKey(fragmentToken)) { 224 throw new IllegalArgumentException( 225 "There is an existing TaskFragment with fragmentToken=" + fragmentToken); 226 } 227 228 return new TaskFragmentCreationParams.Builder( 229 getOrganizerToken(), 230 fragmentToken, 231 ownerToken) 232 .setInitialBounds(bounds) 233 .setWindowingMode(windowingMode) 234 .build(); 235 } 236 resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken, @Nullable Rect bounds)237 void resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken, 238 @Nullable Rect bounds) { 239 if (!mFragmentInfos.containsKey(fragmentToken)) { 240 throw new IllegalArgumentException( 241 "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken); 242 } 243 if (bounds == null) { 244 bounds = new Rect(); 245 } 246 wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds); 247 } 248 deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken)249 void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { 250 if (!mFragmentInfos.containsKey(fragmentToken)) { 251 throw new IllegalArgumentException( 252 "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken); 253 } 254 wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken()); 255 } 256 257 @Override onTaskFragmentAppeared(@onNull TaskFragmentInfo taskFragmentInfo)258 public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { 259 final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); 260 mFragmentInfos.put(fragmentToken, taskFragmentInfo); 261 262 if (mCallback != null) { 263 mCallback.onTaskFragmentAppeared(taskFragmentInfo); 264 } 265 } 266 267 @Override onTaskFragmentInfoChanged(@onNull TaskFragmentInfo taskFragmentInfo)268 public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) { 269 final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); 270 mFragmentInfos.put(fragmentToken, taskFragmentInfo); 271 272 if (mCallback != null) { 273 mCallback.onTaskFragmentInfoChanged(taskFragmentInfo); 274 } 275 } 276 277 @Override onTaskFragmentVanished(@onNull TaskFragmentInfo taskFragmentInfo)278 public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { 279 mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); 280 mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken()); 281 282 if (mCallback != null) { 283 mCallback.onTaskFragmentVanished(taskFragmentInfo); 284 } 285 } 286 287 @Override onTaskFragmentParentInfoChanged( @onNull IBinder fragmentToken, @NonNull Configuration parentConfig)288 public void onTaskFragmentParentInfoChanged( 289 @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) { 290 mFragmentParentConfigs.put(fragmentToken, parentConfig); 291 292 if (mCallback != null) { 293 mCallback.onTaskFragmentParentInfoChanged(fragmentToken, parentConfig); 294 } 295 } 296 } 297