1 /* 2 * Copyright (C) 2022 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.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 23 import static android.app.WindowConfiguration.inMultiWindowMode; 24 25 import android.app.Activity; 26 import android.app.ActivityClient; 27 import android.app.WindowConfiguration; 28 import android.app.WindowConfiguration.WindowingMode; 29 import android.content.res.Configuration; 30 import android.graphics.Rect; 31 import android.os.IBinder; 32 import android.util.ArraySet; 33 import android.util.Log; 34 import android.window.TaskFragmentInfo; 35 import android.window.TaskFragmentParentInfo; 36 import android.window.WindowContainerTransaction; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.Set; 44 45 /** Represents TaskFragments and split pairs below a Task. */ 46 class TaskContainer { 47 private static final String TAG = TaskContainer.class.getSimpleName(); 48 49 /** The unique task id. */ 50 private final int mTaskId; 51 52 /** Active TaskFragments in this Task. */ 53 @NonNull 54 final List<TaskFragmentContainer> mContainers = new ArrayList<>(); 55 56 /** Active split pairs in this Task. */ 57 @NonNull 58 final List<SplitContainer> mSplitContainers = new ArrayList<>(); 59 60 @NonNull 61 private final Configuration mConfiguration; 62 63 private int mDisplayId; 64 65 private boolean mIsVisible; 66 67 /** 68 * TaskFragments that the organizer has requested to be closed. They should be removed when 69 * the organizer receives 70 * {@link SplitController#onTaskFragmentVanished(WindowContainerTransaction, TaskFragmentInfo)} 71 * event for them. 72 */ 73 final Set<IBinder> mFinishedContainer = new ArraySet<>(); 74 75 /** 76 * The {@link TaskContainer} constructor 77 * 78 * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with 79 * {@code activityInTask}. 80 * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to 81 * initialize the {@link TaskContainer} properties. 82 * 83 */ TaskContainer(int taskId, @NonNull Activity activityInTask)84 TaskContainer(int taskId, @NonNull Activity activityInTask) { 85 if (taskId == INVALID_TASK_ID) { 86 throw new IllegalArgumentException("Invalid Task id"); 87 } 88 mTaskId = taskId; 89 final TaskProperties taskProperties = TaskProperties 90 .getTaskPropertiesFromActivity(activityInTask); 91 mConfiguration = taskProperties.getConfiguration(); 92 mDisplayId = taskProperties.getDisplayId(); 93 // Note that it is always called when there's a new Activity is started, which implies 94 // the host task is visible. 95 mIsVisible = true; 96 } 97 getTaskId()98 int getTaskId() { 99 return mTaskId; 100 } 101 getDisplayId()102 int getDisplayId() { 103 return mDisplayId; 104 } 105 isVisible()106 boolean isVisible() { 107 return mIsVisible; 108 } 109 110 @NonNull getConfiguration()111 Configuration getConfiguration() { 112 // Make a copy in case the config is updated unexpectedly. 113 return new Configuration(mConfiguration); 114 } 115 116 @NonNull getTaskProperties()117 TaskProperties getTaskProperties() { 118 return new TaskProperties(mDisplayId, mConfiguration); 119 } 120 updateTaskFragmentParentInfo(@onNull TaskFragmentParentInfo info)121 void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { 122 mConfiguration.setTo(info.getConfiguration()); 123 mDisplayId = info.getDisplayId(); 124 mIsVisible = info.isVisible(); 125 } 126 127 /** 128 * Returns the windowing mode for the TaskFragments below this Task, which should be split with 129 * other TaskFragments. 130 * 131 * @param taskFragmentBounds Requested bounds for the TaskFragment. It will be empty when 132 * the pair of TaskFragments are stacked due to the limited space. 133 */ 134 @WindowingMode getWindowingModeForSplitTaskFragment(@ullable Rect taskFragmentBounds)135 int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) { 136 // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it 137 // will be set to UNDEFINED which will then inherit the Task windowing mode. 138 if (taskFragmentBounds == null || taskFragmentBounds.isEmpty() || isInPictureInPicture()) { 139 return WINDOWING_MODE_UNDEFINED; 140 } 141 // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen. 142 // However, when the Task is in other multi windowing mode, such as Freeform, we need to 143 // have the activity windowing mode to match the Task, otherwise things like 144 // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the 145 // Task windowing mode if the Task is in multi window. 146 // TODO we won't need this anymore after we migrate Freeform caption to WM Shell. 147 return isInMultiWindow() ? getWindowingMode() : WINDOWING_MODE_MULTI_WINDOW; 148 } 149 isInPictureInPicture()150 boolean isInPictureInPicture() { 151 return getWindowingMode() == WINDOWING_MODE_PINNED; 152 } 153 isInMultiWindow()154 boolean isInMultiWindow() { 155 return WindowConfiguration.inMultiWindowMode(getWindowingMode()); 156 } 157 158 @WindowingMode getWindowingMode()159 private int getWindowingMode() { 160 return getConfiguration().windowConfiguration.getWindowingMode(); 161 } 162 163 /** Whether there is any {@link TaskFragmentContainer} below this Task. */ isEmpty()164 boolean isEmpty() { 165 return mContainers.isEmpty() && mFinishedContainer.isEmpty(); 166 } 167 168 /** Called when the activity is destroyed. */ onActivityDestroyed(@onNull IBinder activityToken)169 void onActivityDestroyed(@NonNull IBinder activityToken) { 170 for (TaskFragmentContainer container : mContainers) { 171 container.onActivityDestroyed(activityToken); 172 } 173 } 174 175 /** Removes the pending appeared activity from all TaskFragments in this Task. */ cleanupPendingAppearedActivity(@onNull IBinder activityToken)176 void cleanupPendingAppearedActivity(@NonNull IBinder activityToken) { 177 for (TaskFragmentContainer container : mContainers) { 178 container.removePendingAppearedActivity(activityToken); 179 } 180 } 181 182 @Nullable getTopTaskFragmentContainer()183 TaskFragmentContainer getTopTaskFragmentContainer() { 184 if (mContainers.isEmpty()) { 185 return null; 186 } 187 return mContainers.get(mContainers.size() - 1); 188 } 189 190 @Nullable getTopNonFinishingActivity()191 Activity getTopNonFinishingActivity() { 192 for (int i = mContainers.size() - 1; i >= 0; i--) { 193 final Activity activity = mContainers.get(i).getTopNonFinishingActivity(); 194 if (activity != null) { 195 return activity; 196 } 197 } 198 return null; 199 } 200 indexOf(@onNull TaskFragmentContainer child)201 int indexOf(@NonNull TaskFragmentContainer child) { 202 return mContainers.indexOf(child); 203 } 204 205 /** Whether the Task is in an intermediate state waiting for the server update.*/ isInIntermediateState()206 boolean isInIntermediateState() { 207 for (TaskFragmentContainer container : mContainers) { 208 if (container.isInIntermediateState()) { 209 // We are in an intermediate state to wait for server update on this TaskFragment. 210 return true; 211 } 212 } 213 return false; 214 } 215 216 /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */ getSplitStates(@onNull List<SplitInfo> outSplitStates)217 void getSplitStates(@NonNull List<SplitInfo> outSplitStates) { 218 for (SplitContainer container : mSplitContainers) { 219 outSplitStates.add(container.toSplitInfo()); 220 } 221 } 222 223 /** 224 * A wrapper class which contains the display ID and {@link Configuration} of a 225 * {@link TaskContainer} 226 */ 227 static final class TaskProperties { 228 private final int mDisplayId; 229 @NonNull 230 private final Configuration mConfiguration; 231 TaskProperties(int displayId, @NonNull Configuration configuration)232 TaskProperties(int displayId, @NonNull Configuration configuration) { 233 mDisplayId = displayId; 234 mConfiguration = configuration; 235 } 236 getDisplayId()237 int getDisplayId() { 238 return mDisplayId; 239 } 240 241 @NonNull getConfiguration()242 Configuration getConfiguration() { 243 return mConfiguration; 244 } 245 246 /** 247 * Obtains the {@link TaskProperties} for the task that the provided {@link Activity} is 248 * associated with. 249 * <p> 250 * Note that for most case, caller should use 251 * {@link SplitPresenter#getTaskProperties(Activity)} instead. This method is used before 252 * the {@code activity} goes into split. 253 * </p><p> 254 * If the {@link Activity} is in fullscreen, override 255 * {@link WindowConfiguration#getBounds()} with {@link WindowConfiguration#getMaxBounds()} 256 * in case the {@link Activity} is letterboxed. Otherwise, get the Task 257 * {@link Configuration} from the server side or use {@link Activity}'s 258 * {@link Configuration} as a fallback if the Task {@link Configuration} cannot be obtained. 259 */ 260 @NonNull getTaskPropertiesFromActivity(@onNull Activity activity)261 static TaskProperties getTaskPropertiesFromActivity(@NonNull Activity activity) { 262 final int displayId = activity.getDisplayId(); 263 // Use a copy of configuration because activity's configuration may be updated later, 264 // or we may get unexpected TaskContainer's configuration if Activity's configuration is 265 // updated. An example is Activity is going to be in split. 266 final Configuration activityConfig = new Configuration( 267 activity.getResources().getConfiguration()); 268 final WindowConfiguration windowConfiguration = activityConfig.windowConfiguration; 269 final int windowingMode = windowConfiguration.getWindowingMode(); 270 if (!inMultiWindowMode(windowingMode)) { 271 // Use the max bounds in fullscreen in case the Activity is letterboxed. 272 windowConfiguration.setBounds(windowConfiguration.getMaxBounds()); 273 return new TaskProperties(displayId, activityConfig); 274 } 275 final Configuration taskConfig = ActivityClient.getInstance() 276 .getTaskConfiguration(activity.getActivityToken()); 277 if (taskConfig == null) { 278 Log.w(TAG, "Could not obtain task configuration for activity:" + activity); 279 // Still report activity config if task config cannot be obtained from the server 280 // side. 281 return new TaskProperties(displayId, activityConfig); 282 } 283 return new TaskProperties(displayId, taskConfig); 284 } 285 } 286 } 287