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.am;
18 
19 import android.annotation.IntDef;
20 import android.app.ActivityOptions;
21 import android.content.pm.ActivityInfo.WindowLayout;
22 import android.graphics.Rect;
23 
24 import java.lang.annotation.Retention;
25 import java.lang.annotation.RetentionPolicy;
26 import java.util.ArrayList;
27 import java.util.List;
28 
29 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
30 import static android.view.Display.INVALID_DISPLAY;
31 
32 import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
33 import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
34 import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
35 
36 /**
37  * {@link LaunchParamsController} calculates the {@link LaunchParams} by coordinating between
38  * registered {@link LaunchParamsModifier}s.
39  */
40 class LaunchParamsController {
41     private final ActivityManagerService mService;
42     private final List<LaunchParamsModifier> mModifiers = new ArrayList<>();
43 
44     // Temporary {@link LaunchParams} for internal calculations. This is kept separate from
45     // {@code mTmpCurrent} and {@code mTmpResult} to prevent clobbering values.
46     private final LaunchParams mTmpParams = new LaunchParams();
47 
48     private final LaunchParams mTmpCurrent = new LaunchParams();
49     private final LaunchParams mTmpResult = new LaunchParams();
50 
LaunchParamsController(ActivityManagerService service)51     LaunchParamsController(ActivityManagerService service) {
52        mService = service;
53     }
54 
55     /**
56      * Creates a {@link LaunchParamsController} with default registered
57      * {@link LaunchParamsModifier}s.
58      */
registerDefaultModifiers(ActivityStackSupervisor supervisor)59     void registerDefaultModifiers(ActivityStackSupervisor supervisor) {
60         // {@link TaskLaunchParamsModifier} handles window layout preferences.
61         registerModifier(new TaskLaunchParamsModifier());
62 
63         // {@link ActivityLaunchParamsModifier} is the most specific modifier and thus should be
64         // registered last (applied first) out of the defaults.
65         registerModifier(new ActivityLaunchParamsModifier(supervisor));
66     }
67 
68     /**
69      * Returns the {@link LaunchParams} calculated by the registered modifiers
70      * @param task      The {@link TaskRecord} currently being positioned.
71      * @param layout    The specified {@link WindowLayout}.
72      * @param activity  The {@link ActivityRecord} currently being positioned.
73      * @param source    The {@link ActivityRecord} from which activity was started from.
74      * @param options   The {@link ActivityOptions} specified for the activity.
75      * @param result    The resulting params.
76      */
calculate(TaskRecord task, WindowLayout layout, ActivityRecord activity, ActivityRecord source, ActivityOptions options, LaunchParams result)77     void calculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
78                    ActivityRecord source, ActivityOptions options, LaunchParams result) {
79         result.reset();
80 
81         // We start at the last registered {@link LaunchParamsModifier} as this represents
82         // The modifier closest to the product level. Moving back through the list moves closer to
83         // the platform logic.
84         for (int i = mModifiers.size() - 1; i >= 0; --i) {
85             mTmpCurrent.set(result);
86             mTmpResult.reset();
87             final LaunchParamsModifier modifier = mModifiers.get(i);
88 
89             switch(modifier.onCalculate(task, layout, activity, source, options, mTmpCurrent,
90                     mTmpResult)) {
91                 case RESULT_SKIP:
92                     // Do not apply any results when we are told to skip
93                     continue;
94                 case RESULT_DONE:
95                     // Set result and return immediately.
96                     result.set(mTmpResult);
97                     return;
98                 case RESULT_CONTINUE:
99                     // Set result and continue
100                     result.set(mTmpResult);
101                     break;
102             }
103         }
104     }
105 
106     /**
107      * A convenience method for laying out a task.
108      * @return {@code true} if bounds were set on the task. {@code false} otherwise.
109      */
layoutTask(TaskRecord task, WindowLayout layout)110     boolean layoutTask(TaskRecord task, WindowLayout layout) {
111         return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/);
112     }
113 
layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity, ActivityRecord source, ActivityOptions options)114     boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity,
115             ActivityRecord source, ActivityOptions options) {
116         calculate(task, layout, activity, source, options, mTmpParams);
117 
118         // No changes, return.
119         if (mTmpParams.isEmpty()) {
120             return false;
121         }
122 
123         mService.mWindowManager.deferSurfaceLayout();
124 
125         try {
126             if (mTmpParams.hasPreferredDisplay()
127                     && mTmpParams.mPreferredDisplayId != task.getStack().getDisplay().mDisplayId) {
128                 mService.moveStackToDisplay(task.getStackId(), mTmpParams.mPreferredDisplayId);
129             }
130 
131             if (mTmpParams.hasWindowingMode()
132                     && mTmpParams.mWindowingMode != task.getStack().getWindowingMode()) {
133                 task.getStack().setWindowingMode(mTmpParams.mWindowingMode);
134             }
135 
136             if (!mTmpParams.mBounds.isEmpty()) {
137                 task.updateOverrideConfiguration(mTmpParams.mBounds);
138                 return true;
139             } else {
140                 return false;
141             }
142         } finally {
143             mService.mWindowManager.continueSurfaceLayout();
144         }
145     }
146 
147     /**
148      * Adds a modifier to participate in future bounds calculation. Note that the last registered
149      * {@link LaunchParamsModifier} will be the first to calculate the bounds.
150      */
registerModifier(LaunchParamsModifier modifier)151     void registerModifier(LaunchParamsModifier modifier) {
152         if (mModifiers.contains(modifier)) {
153             return;
154         }
155 
156         mModifiers.add(modifier);
157     }
158 
159     /**
160      * A container for holding launch related fields.
161      */
162     static class LaunchParams {
163         /** The bounds within the parent container. */
164         final Rect mBounds = new Rect();
165 
166         /** The id of the display the {@link TaskRecord} would prefer to be on. */
167         int mPreferredDisplayId;
168 
169         /** The windowing mode to be in. */
170         int mWindowingMode;
171 
172         /** Sets values back to default. {@link #isEmpty} will return {@code true} once called. */
reset()173         void reset() {
174             mBounds.setEmpty();
175             mPreferredDisplayId = INVALID_DISPLAY;
176             mWindowingMode = WINDOWING_MODE_UNDEFINED;
177         }
178 
179         /** Copies the values set on the passed in {@link LaunchParams}. */
set(LaunchParams params)180         void set(LaunchParams params) {
181             mBounds.set(params.mBounds);
182             mPreferredDisplayId = params.mPreferredDisplayId;
183             mWindowingMode = params.mWindowingMode;
184         }
185 
186         /** Returns {@code true} if no values have been explicitly set. */
isEmpty()187         boolean isEmpty() {
188             return mBounds.isEmpty() && mPreferredDisplayId == INVALID_DISPLAY
189                     && mWindowingMode == WINDOWING_MODE_UNDEFINED;
190         }
191 
hasWindowingMode()192         boolean hasWindowingMode() {
193             return mWindowingMode != WINDOWING_MODE_UNDEFINED;
194         }
195 
hasPreferredDisplay()196         boolean hasPreferredDisplay() {
197             return mPreferredDisplayId != INVALID_DISPLAY;
198         }
199 
200         @Override
equals(Object o)201         public boolean equals(Object o) {
202             if (this == o) return true;
203             if (o == null || getClass() != o.getClass()) return false;
204 
205             LaunchParams that = (LaunchParams) o;
206 
207             if (mPreferredDisplayId != that.mPreferredDisplayId) return false;
208             if (mWindowingMode != that.mWindowingMode) return false;
209             return mBounds != null ? mBounds.equals(that.mBounds) : that.mBounds == null;
210         }
211 
212         @Override
hashCode()213         public int hashCode() {
214             int result = mBounds != null ? mBounds.hashCode() : 0;
215             result = 31 * result + mPreferredDisplayId;
216             result = 31 * result + mWindowingMode;
217             return result;
218         }
219     }
220 
221     /**
222      * An interface implemented by those wanting to participate in bounds calculation.
223      */
224     interface LaunchParamsModifier {
225         @Retention(RetentionPolicy.SOURCE)
226         @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE})
227         @interface Result {}
228 
229         // Returned when the modifier does not want to influence the bounds calculation
230         int RESULT_SKIP = 0;
231         // Returned when the modifier has changed the bounds and would like its results to be the
232         // final bounds applied.
233         int RESULT_DONE = 1;
234         // Returned when the modifier has changed the bounds but is okay with other modifiers
235         // influencing the bounds.
236         int RESULT_CONTINUE = 2;
237 
238         /**
239          * Called when asked to calculate {@link LaunchParams}.
240          * @param task            The {@link TaskRecord} currently being positioned.
241          * @param layout          The specified {@link WindowLayout}.
242          * @param activity        The {@link ActivityRecord} currently being positioned.
243          * @param source          The {@link ActivityRecord} activity was started from.
244          * @param options         The {@link ActivityOptions} specified for the activity.
245          * @param currentParams   The current {@link LaunchParams}. This can differ from the initial
246          *                        params as it represents the modified params up to this point.
247          * @param outParams       The resulting {@link LaunchParams} after all calculations.
248          * @return                A {@link Result} representing the result of the
249          *                        {@link LaunchParams} calculation.
250          */
251         @Result
onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity, ActivityRecord source, ActivityOptions options, LaunchParams currentParams, LaunchParams outParams)252         int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
253                 ActivityRecord source, ActivityOptions options, LaunchParams currentParams,
254                 LaunchParams outParams);
255     }
256 }
257