blob: 91bb8d0079489fc65a79913de3749d6f5492b440 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.wm.ActivityStarter.Request;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.content.pm.ActivityInfo.WindowLayout;
import android.graphics.Rect;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* {@link LaunchParamsController} calculates the {@link LaunchParams} by coordinating between
* registered {@link LaunchParamsModifier}s.
*/
class LaunchParamsController {
private final ActivityTaskManagerService mService;
private final LaunchParamsPersister mPersister;
private final List<LaunchParamsModifier> mModifiers = new ArrayList<>();
// Temporary {@link LaunchParams} for internal calculations. This is kept separate from
// {@code mTmpCurrent} and {@code mTmpResult} to prevent clobbering values.
private final LaunchParams mTmpParams = new LaunchParams();
private final LaunchParams mTmpCurrent = new LaunchParams();
private final LaunchParams mTmpResult = new LaunchParams();
LaunchParamsController(ActivityTaskManagerService service, LaunchParamsPersister persister) {
mService = service;
mPersister = persister;
}
/**
* Creates a {@link LaunchParamsController} with default registered
* {@link LaunchParamsModifier}s.
*/
void registerDefaultModifiers(ActivityTaskSupervisor supervisor) {
// {@link TaskLaunchParamsModifier} handles window layout preferences.
registerModifier(new TaskLaunchParamsModifier(supervisor));
if (DesktopModeLaunchParamsModifier.isDesktopModeSupported()) {
// {@link DesktopModeLaunchParamsModifier} handles default task size changes
registerModifier(new DesktopModeLaunchParamsModifier());
}
}
/**
* Returns the {@link LaunchParams} calculated by the registered modifiers
* @param task The {@link Task} currently being positioned.
* @param layout The specified {@link WindowLayout}.
* @param activity The {@link ActivityRecord} currently being positioned.
* @param source The {@link ActivityRecord} from which activity was started from.
* @param options The {@link ActivityOptions} specified for the activity.
* @param request The optional request from the activity starter.
* @param phase The {@link LaunchParamsModifier.Phase} that the resolution should finish.
* @param result The resulting params.
*/
void calculate(Task task, WindowLayout layout, ActivityRecord activity, ActivityRecord source,
ActivityOptions options, @Nullable Request request, int phase, LaunchParams result) {
result.reset();
if (task != null || activity != null) {
mPersister.getLaunchParams(task, activity, result);
}
// We start at the last registered {@link LaunchParamsModifier} as this represents
// The modifier closest to the product level. Moving back through the list moves closer to
// the platform logic.
for (int i = mModifiers.size() - 1; i >= 0; --i) {
mTmpCurrent.set(result);
mTmpResult.reset();
final LaunchParamsModifier modifier = mModifiers.get(i);
switch(modifier.onCalculate(task, layout, activity, source, options, request, phase,
mTmpCurrent, mTmpResult)) {
case RESULT_SKIP:
// Do not apply any results when we are told to skip
continue;
case RESULT_DONE:
// Set result and return immediately.
result.set(mTmpResult);
return;
case RESULT_CONTINUE:
// Set result and continue
result.set(mTmpResult);
break;
}
}
if (activity != null && activity.requestedVrComponent != null) {
// Check if the Activity is a VR activity. If so, it should be launched in main display.
result.mPreferredTaskDisplayArea = mService.mRootWindowContainer
.getDefaultTaskDisplayArea();
} else if (mService.mVr2dDisplayId != INVALID_DISPLAY) {
// Get the virtual display ID from ActivityTaskManagerService. If that's set we
// should always use that.
result.mPreferredTaskDisplayArea = mService.mRootWindowContainer
.getDisplayContent(mService.mVr2dDisplayId).getDefaultTaskDisplayArea();
}
}
/**
* A convenience method for laying out a task.
* @return {@code true} if bounds were set on the task. {@code false} otherwise.
*/
boolean layoutTask(Task task, WindowLayout layout) {
return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/);
}
boolean layoutTask(Task task, WindowLayout layout, ActivityRecord activity,
ActivityRecord source, ActivityOptions options) {
calculate(task, layout, activity, source, options, null /* request */, PHASE_BOUNDS,
mTmpParams);
// No changes, return.
if (mTmpParams.isEmpty()) {
return false;
}
mService.deferWindowLayout();
try {
if (mTmpParams.mBounds.isEmpty()) {
return false;
}
if (task.getRootTask().inMultiWindowMode()) {
task.setBounds(mTmpParams.mBounds);
return true;
}
// Setting last non-fullscreen bounds to the bounds so next time the task enters
// freeform windowing mode it can be in this bounds.
task.setLastNonFullscreenBounds(mTmpParams.mBounds);
return false;
} finally {
mService.continueWindowLayout();
}
}
/**
* Adds a modifier to participate in future bounds calculation. Note that the last registered
* {@link LaunchParamsModifier} will be the first to calculate the bounds.
*/
void registerModifier(LaunchParamsModifier modifier) {
if (mModifiers.contains(modifier)) {
return;
}
mModifiers.add(modifier);
}
/**
* A container for holding launch related fields.
*/
static class LaunchParams {
/** The bounds within the parent container. */
final Rect mBounds = new Rect();
/** The display area the {@link Task} would prefer to be on. */
@Nullable
TaskDisplayArea mPreferredTaskDisplayArea;
/** The windowing mode to be in. */
int mWindowingMode;
/** Sets values back to default. {@link #isEmpty} will return {@code true} once called. */
void reset() {
mBounds.setEmpty();
mPreferredTaskDisplayArea = null;
mWindowingMode = WINDOWING_MODE_UNDEFINED;
}
/** Copies the values set on the passed in {@link LaunchParams}. */
void set(LaunchParams params) {
mBounds.set(params.mBounds);
mPreferredTaskDisplayArea = params.mPreferredTaskDisplayArea;
mWindowingMode = params.mWindowingMode;
}
/** Returns {@code true} if no values have been explicitly set. */
boolean isEmpty() {
return mBounds.isEmpty() && mPreferredTaskDisplayArea == null
&& mWindowingMode == WINDOWING_MODE_UNDEFINED;
}
boolean hasWindowingMode() {
return mWindowingMode != WINDOWING_MODE_UNDEFINED;
}
boolean hasPreferredTaskDisplayArea() {
return mPreferredTaskDisplayArea != null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LaunchParams that = (LaunchParams) o;
if (mPreferredTaskDisplayArea != that.mPreferredTaskDisplayArea) return false;
if (mWindowingMode != that.mWindowingMode) return false;
return mBounds != null ? mBounds.equals(that.mBounds) : that.mBounds == null;
}
@Override
public int hashCode() {
int result = mBounds != null ? mBounds.hashCode() : 0;
result = 31 * result + (mPreferredTaskDisplayArea != null
? mPreferredTaskDisplayArea.hashCode() : 0);
result = 31 * result + mWindowingMode;
return result;
}
}
/**
* An interface implemented by those wanting to participate in bounds calculation.
*/
interface LaunchParamsModifier {
@Retention(RetentionPolicy.SOURCE)
@IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE})
@interface Result {}
/** Returned when the modifier does not want to influence the bounds calculation */
int RESULT_SKIP = 0;
/**
* Returned when the modifier has changed the bounds and would like its results to be the
* final bounds applied.
*/
int RESULT_DONE = 1;
/**
* Returned when the modifier has changed the bounds but is okay with other modifiers
* influencing the bounds.
*/
int RESULT_CONTINUE = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef({PHASE_DISPLAY, PHASE_WINDOWING_MODE, PHASE_BOUNDS})
@interface Phase {}
/**
* Stops once we are done with preferred display calculation. The result returned from this
* phase is only guaranteed to have a display area which display is the launch display.
*/
int PHASE_DISPLAY = 0;
/**
* Stops once we are done with windowing mode calculation.
*/
int PHASE_WINDOWING_MODE = 1;
/**
* Stops once we are done with display area calculation.
*/
int PHASE_DISPLAY_AREA = 2;
/**
* Stops once we are done with window bounds calculation.
*/
int PHASE_BOUNDS = 3;
/**
* Returns the launch params that the provided activity launch params should be overridden
* to. {@link LaunchParamsModifier} can use this for various purposes, including: 1)
* Providing default bounds if the launch bounds have not been provided. 2) Repositioning
* the task so it doesn't get placed over an existing task. 3) Resizing the task so that its
* dimensions match the activity's requested orientation.
*
* @param task Can be: 1) the target task in which the source activity wants to
* launch the target activity; 2) a newly created task that Android
* gives a chance to override its launching bounds; 3) {@code null} if
* this is called to override an activity's launching bounds.
* @param layout Desired layout when activity is first launched.
* @param activity Activity that is being started. This can be {@code null} on
* re-parenting an activity to a new task (e.g. for
* Picture-In-Picture). Tasks being created because an activity was
* launched should have this be non-null.
* @param source the Activity that launched a new task. Could be {@code null}.
* @param options {@link ActivityOptions} used to start the activity with.
* @param request Optional data to give more context on the launch
* @param phase the calculation phase, see {@link Phase}
* @param currentParams launching params after the process of last {@link
* LaunchParamsModifier}.
* @param outParams the result params to be set.
* @return see {@link LaunchParamsModifier.Result}
*/
@Result
int onCalculate(@Nullable Task task, @Nullable WindowLayout layout,
@Nullable ActivityRecord activity, @Nullable ActivityRecord source,
@Nullable ActivityOptions options, @Nullable Request request,
@Phase int phase, LaunchParams currentParams, LaunchParams outParams);
}
}