1 /* 2 * Copyright (C) 2017 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 com.android.server.wm; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 20 import static android.view.Display.INVALID_DISPLAY; 21 22 import static com.android.server.wm.ActivityStarter.Request; 23 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS; 24 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE; 25 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE; 26 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP; 27 28 import android.annotation.IntDef; 29 import android.annotation.Nullable; 30 import android.app.ActivityOptions; 31 import android.content.pm.ActivityInfo.WindowLayout; 32 import android.graphics.Rect; 33 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** 40 * {@link LaunchParamsController} calculates the {@link LaunchParams} by coordinating between 41 * registered {@link LaunchParamsModifier}s. 42 */ 43 class LaunchParamsController { 44 private final ActivityTaskManagerService mService; 45 private final LaunchParamsPersister mPersister; 46 private final List<LaunchParamsModifier> mModifiers = new ArrayList<>(); 47 48 // Temporary {@link LaunchParams} for internal calculations. This is kept separate from 49 // {@code mTmpCurrent} and {@code mTmpResult} to prevent clobbering values. 50 private final LaunchParams mTmpParams = new LaunchParams(); 51 52 private final LaunchParams mTmpCurrent = new LaunchParams(); 53 private final LaunchParams mTmpResult = new LaunchParams(); 54 LaunchParamsController(ActivityTaskManagerService service, LaunchParamsPersister persister)55 LaunchParamsController(ActivityTaskManagerService service, LaunchParamsPersister persister) { 56 mService = service; 57 mPersister = persister; 58 } 59 60 /** 61 * Creates a {@link LaunchParamsController} with default registered 62 * {@link LaunchParamsModifier}s. 63 */ registerDefaultModifiers(ActivityTaskSupervisor supervisor)64 void registerDefaultModifiers(ActivityTaskSupervisor supervisor) { 65 // {@link TaskLaunchParamsModifier} handles window layout preferences. 66 registerModifier(new TaskLaunchParamsModifier(supervisor)); 67 } 68 69 /** 70 * Returns the {@link LaunchParams} calculated by the registered modifiers 71 * @param task The {@link Task} currently being positioned. 72 * @param layout The specified {@link WindowLayout}. 73 * @param activity The {@link ActivityRecord} currently being positioned. 74 * @param source The {@link ActivityRecord} from which activity was started from. 75 * @param options The {@link ActivityOptions} specified for the activity. 76 * @param request The optional request from the activity starter. 77 * @param phase The {@link LaunchParamsModifier.Phase} that the resolution should finish. 78 * @param result The resulting params. 79 */ calculate(Task task, WindowLayout layout, ActivityRecord activity, ActivityRecord source, ActivityOptions options, @Nullable Request request, int phase, LaunchParams result)80 void calculate(Task task, WindowLayout layout, ActivityRecord activity, ActivityRecord source, 81 ActivityOptions options, @Nullable Request request, int phase, LaunchParams result) { 82 result.reset(); 83 84 if (task != null || activity != null) { 85 mPersister.getLaunchParams(task, activity, result); 86 } 87 88 // We start at the last registered {@link LaunchParamsModifier} as this represents 89 // The modifier closest to the product level. Moving back through the list moves closer to 90 // the platform logic. 91 for (int i = mModifiers.size() - 1; i >= 0; --i) { 92 mTmpCurrent.set(result); 93 mTmpResult.reset(); 94 final LaunchParamsModifier modifier = mModifiers.get(i); 95 96 switch(modifier.onCalculate(task, layout, activity, source, options, request, phase, 97 mTmpCurrent, mTmpResult)) { 98 case RESULT_SKIP: 99 // Do not apply any results when we are told to skip 100 continue; 101 case RESULT_DONE: 102 // Set result and return immediately. 103 result.set(mTmpResult); 104 return; 105 case RESULT_CONTINUE: 106 // Set result and continue 107 result.set(mTmpResult); 108 break; 109 } 110 } 111 112 if (activity != null && activity.requestedVrComponent != null) { 113 // Check if the Activity is a VR activity. If so, it should be launched in main display. 114 result.mPreferredTaskDisplayArea = mService.mRootWindowContainer 115 .getDefaultTaskDisplayArea(); 116 } else if (mService.mVr2dDisplayId != INVALID_DISPLAY) { 117 // Get the virtual display ID from ActivityTaskManagerService. If that's set we 118 // should always use that. 119 result.mPreferredTaskDisplayArea = mService.mRootWindowContainer 120 .getDisplayContent(mService.mVr2dDisplayId).getDefaultTaskDisplayArea(); 121 } 122 } 123 124 /** 125 * A convenience method for laying out a task. 126 * @return {@code true} if bounds were set on the task. {@code false} otherwise. 127 */ layoutTask(Task task, WindowLayout layout)128 boolean layoutTask(Task task, WindowLayout layout) { 129 return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/); 130 } 131 layoutTask(Task task, WindowLayout layout, ActivityRecord activity, ActivityRecord source, ActivityOptions options)132 boolean layoutTask(Task task, WindowLayout layout, ActivityRecord activity, 133 ActivityRecord source, ActivityOptions options) { 134 calculate(task, layout, activity, source, options, null /* request */, PHASE_BOUNDS, 135 mTmpParams); 136 137 // No changes, return. 138 if (mTmpParams.isEmpty()) { 139 return false; 140 } 141 142 mService.deferWindowLayout(); 143 144 try { 145 if (mTmpParams.mPreferredTaskDisplayArea != null 146 && task.getDisplayArea() != mTmpParams.mPreferredTaskDisplayArea) { 147 mService.mRootWindowContainer.moveRootTaskToTaskDisplayArea(task.getRootTaskId(), 148 mTmpParams.mPreferredTaskDisplayArea, true /* onTop */); 149 } 150 151 if (mTmpParams.hasWindowingMode() && task.isRootTask() 152 && mTmpParams.mWindowingMode != task.getWindowingMode()) { 153 final int activityType = activity != null 154 ? activity.getActivityType() : task.getActivityType(); 155 task.setWindowingMode(task.getDisplayArea().validateWindowingMode( 156 mTmpParams.mWindowingMode, activity, task, activityType)); 157 } 158 159 if (mTmpParams.mBounds.isEmpty()) { 160 return false; 161 } 162 163 if (task.getRootTask().inMultiWindowMode()) { 164 task.setBounds(mTmpParams.mBounds); 165 return true; 166 } 167 168 // Setting last non-fullscreen bounds to the bounds so next time the task enters 169 // freeform windowing mode it can be in this bounds. 170 task.setLastNonFullscreenBounds(mTmpParams.mBounds); 171 return false; 172 } finally { 173 mService.continueWindowLayout(); 174 } 175 } 176 177 /** 178 * Adds a modifier to participate in future bounds calculation. Note that the last registered 179 * {@link LaunchParamsModifier} will be the first to calculate the bounds. 180 */ registerModifier(LaunchParamsModifier modifier)181 void registerModifier(LaunchParamsModifier modifier) { 182 if (mModifiers.contains(modifier)) { 183 return; 184 } 185 186 mModifiers.add(modifier); 187 } 188 189 /** 190 * A container for holding launch related fields. 191 */ 192 static class LaunchParams { 193 /** The bounds within the parent container. */ 194 final Rect mBounds = new Rect(); 195 196 /** The display area the {@link Task} would prefer to be on. */ 197 @Nullable 198 TaskDisplayArea mPreferredTaskDisplayArea; 199 200 /** The windowing mode to be in. */ 201 int mWindowingMode; 202 203 /** Sets values back to default. {@link #isEmpty} will return {@code true} once called. */ reset()204 void reset() { 205 mBounds.setEmpty(); 206 mPreferredTaskDisplayArea = null; 207 mWindowingMode = WINDOWING_MODE_UNDEFINED; 208 } 209 210 /** Copies the values set on the passed in {@link LaunchParams}. */ set(LaunchParams params)211 void set(LaunchParams params) { 212 mBounds.set(params.mBounds); 213 mPreferredTaskDisplayArea = params.mPreferredTaskDisplayArea; 214 mWindowingMode = params.mWindowingMode; 215 } 216 217 /** Returns {@code true} if no values have been explicitly set. */ isEmpty()218 boolean isEmpty() { 219 return mBounds.isEmpty() && mPreferredTaskDisplayArea == null 220 && mWindowingMode == WINDOWING_MODE_UNDEFINED; 221 } 222 hasWindowingMode()223 boolean hasWindowingMode() { 224 return mWindowingMode != WINDOWING_MODE_UNDEFINED; 225 } 226 hasPreferredTaskDisplayArea()227 boolean hasPreferredTaskDisplayArea() { 228 return mPreferredTaskDisplayArea != null; 229 } 230 231 @Override equals(Object o)232 public boolean equals(Object o) { 233 if (this == o) return true; 234 if (o == null || getClass() != o.getClass()) return false; 235 236 LaunchParams that = (LaunchParams) o; 237 238 if (mPreferredTaskDisplayArea != that.mPreferredTaskDisplayArea) return false; 239 if (mWindowingMode != that.mWindowingMode) return false; 240 return mBounds != null ? mBounds.equals(that.mBounds) : that.mBounds == null; 241 } 242 243 @Override hashCode()244 public int hashCode() { 245 int result = mBounds != null ? mBounds.hashCode() : 0; 246 result = 31 * result + (mPreferredTaskDisplayArea != null 247 ? mPreferredTaskDisplayArea.hashCode() : 0); 248 result = 31 * result + mWindowingMode; 249 return result; 250 } 251 } 252 253 /** 254 * An interface implemented by those wanting to participate in bounds calculation. 255 */ 256 interface LaunchParamsModifier { 257 @Retention(RetentionPolicy.SOURCE) 258 @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE}) 259 @interface Result {} 260 261 /** Returned when the modifier does not want to influence the bounds calculation */ 262 int RESULT_SKIP = 0; 263 /** 264 * Returned when the modifier has changed the bounds and would like its results to be the 265 * final bounds applied. 266 */ 267 int RESULT_DONE = 1; 268 /** 269 * Returned when the modifier has changed the bounds but is okay with other modifiers 270 * influencing the bounds. 271 */ 272 int RESULT_CONTINUE = 2; 273 274 @Retention(RetentionPolicy.SOURCE) 275 @IntDef({PHASE_DISPLAY, PHASE_WINDOWING_MODE, PHASE_BOUNDS}) 276 @interface Phase {} 277 278 /** 279 * Stops once we are done with preferred display calculation. The result returned from this 280 * phase is only guaranteed to have a display area which display is the launch display. 281 */ 282 int PHASE_DISPLAY = 0; 283 284 /** 285 * Stops once we are done with windowing mode calculation. 286 */ 287 int PHASE_WINDOWING_MODE = 1; 288 289 /** 290 * Stops once we are done with display area calculation. 291 */ 292 int PHASE_DISPLAY_AREA = 2; 293 294 /** 295 * Stops once we are done with window bounds calculation. 296 */ 297 int PHASE_BOUNDS = 3; 298 299 /** 300 * Returns the launch params that the provided activity launch params should be overridden 301 * to. {@link LaunchParamsModifier} can use this for various purposes, including: 1) 302 * Providing default bounds if the launch bounds have not been provided. 2) Repositioning 303 * the task so it doesn't get placed over an existing task. 3) Resizing the task so that its 304 * dimensions match the activity's requested orientation. 305 * 306 * @param task Can be: 1) the target task in which the source activity wants to 307 * launch the target activity; 2) a newly created task that Android 308 * gives a chance to override its launching bounds; 3) {@code null} if 309 * this is called to override an activity's launching bounds. 310 * @param layout Desired layout when activity is first launched. 311 * @param activity Activity that is being started. This can be {@code null} on 312 * re-parenting an activity to a new task (e.g. for 313 * Picture-In-Picture). Tasks being created because an activity was 314 * launched should have this be non-null. 315 * @param source the Activity that launched a new task. Could be {@code null}. 316 * @param options {@link ActivityOptions} used to start the activity with. 317 * @param request Optional data to give more context on the launch 318 * @param phase the calculation phase, see {@link Phase} 319 * @param currentParams launching params after the process of last {@link 320 * LaunchParamsModifier}. 321 * @param outParams the result params to be set. 322 * @return see {@link LaunchParamsModifier.Result} 323 */ 324 @Result onCalculate(@ullable Task task, @Nullable WindowLayout layout, @Nullable ActivityRecord activity, @Nullable ActivityRecord source, @Nullable ActivityOptions options, @Nullable Request request, @Phase int phase, LaunchParams currentParams, LaunchParams outParams)325 int onCalculate(@Nullable Task task, @Nullable WindowLayout layout, 326 @Nullable ActivityRecord activity, @Nullable ActivityRecord source, 327 @Nullable ActivityOptions options, @Nullable Request request, 328 @Phase int phase, LaunchParams currentParams, LaunchParams outParams); 329 } 330 } 331