blob: ee93fc8db06e543fd7bc8abfd6a428ad1957faa5 [file] [log] [blame]
Wale Ogunwale9dcf9462017-09-19 15:13:01 -07001/*
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
17package com.android.server.am;
18
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -070019import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
Wale Ogunwale04a05ac2017-09-17 21:35:02 -070020import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
22import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
Wale Ogunwaleab5de372017-10-18 06:46:31 -070023import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
Wale Ogunwale04a05ac2017-09-17 21:35:02 -070024import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
25import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
26import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
27import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
28import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
29import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
30import static android.view.Display.DEFAULT_DISPLAY;
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070031import static android.view.Display.FLAG_PRIVATE;
32import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
33import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
34import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
35import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
36import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
Yi Jin6c6e9ca2018-03-20 16:53:35 -070037import static com.android.server.am.ActivityDisplayProto.CONFIGURATION_CONTAINER;
38import static com.android.server.am.ActivityDisplayProto.STACKS;
39import static com.android.server.am.ActivityDisplayProto.ID;
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070040
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -070041import android.annotation.Nullable;
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -070042import android.app.ActivityOptions;
Wale Ogunwale6767eae2018-05-03 15:52:51 -070043import android.app.ActivityTaskManagerInternal;
Wale Ogunwale04a05ac2017-09-17 21:35:02 -070044import android.app.WindowConfiguration;
Bryce Leef3c6a472017-11-14 14:53:06 -080045import android.graphics.Point;
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070046import android.util.IntArray;
47import android.util.Slog;
48import android.util.proto.ProtoOutputStream;
49import android.view.Display;
Wale Ogunwale04a05ac2017-09-17 21:35:02 -070050import com.android.internal.annotations.VisibleForTesting;
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070051import com.android.server.wm.ConfigurationContainer;
Winson Chung59a47ded2018-01-25 17:46:06 +000052import com.android.server.wm.DisplayWindowController;
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070053
Winson Chung59a47ded2018-01-25 17:46:06 +000054import com.android.server.wm.WindowContainerListener;
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -070055import java.io.PrintWriter;
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070056import java.util.ArrayList;
57
58/**
59 * Exactly one of these classes per Display in the system. Capable of holding zero or more
60 * attached {@link ActivityStack}s.
61 */
Winson Chung59a47ded2018-01-25 17:46:06 +000062class ActivityDisplay extends ConfigurationContainer<ActivityStack>
63 implements WindowContainerListener {
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070064 private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM;
65 private static final String TAG_STACK = TAG + POSTFIX_STACK;
66
67 static final int POSITION_TOP = Integer.MAX_VALUE;
68 static final int POSITION_BOTTOM = Integer.MIN_VALUE;
69
Bryce Leed9cce2c2017-12-04 16:16:27 -080070
71 /**
72 * Counter for next free stack ID to use for dynamic activity stacks. Unique across displays.
73 */
74 private static int sNextFreeStackId = 0;
75
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070076 private ActivityStackSupervisor mSupervisor;
77 /** Actual Display this object tracks. */
78 int mDisplayId;
79 Display mDisplay;
80
Winson Chung0f7ec962018-05-03 18:03:15 -070081 /**
82 * All of the stacks on this display. Order matters, topmost stack is in front of all other
83 * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls
84 * changing the list should also call {@link #onStackOrderChanged()}.
85 */
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -070086 private final ArrayList<ActivityStack> mStacks = new ArrayList<>();
Winson Chung0f7ec962018-05-03 18:03:15 -070087 private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>();
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070088
89 /** Array of all UIDs that are present on the display. */
90 private IntArray mDisplayAccessUIDs = new IntArray();
91
92 /** All tokens used to put activities on this stack to sleep (including mOffToken) */
Wale Ogunwale6767eae2018-05-03 15:52:51 -070093 final ArrayList<ActivityTaskManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070094 /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
Wale Ogunwale6767eae2018-05-03 15:52:51 -070095 ActivityTaskManagerInternal.SleepToken mOffToken;
Wale Ogunwale9dcf9462017-09-19 15:13:01 -070096
97 private boolean mSleeping;
98
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -070099 // Cached reference to some special stacks we tend to get a lot so we don't need to loop
100 // through the list to find them.
101 private ActivityStack mHomeStack = null;
102 private ActivityStack mRecentsStack = null;
103 private ActivityStack mPinnedStack = null;
104 private ActivityStack mSplitScreenPrimaryStack = null;
105
Bryce Leef3c6a472017-11-14 14:53:06 -0800106 // Used in updating the display size
107 private Point mTmpDisplaySize = new Point();
108
Winson Chung59a47ded2018-01-25 17:46:06 +0000109 private DisplayWindowController mWindowContainerController;
110
Wale Ogunwale45477b52018-03-06 12:24:19 -0800111 @VisibleForTesting
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700112 ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
Wale Ogunwale45477b52018-03-06 12:24:19 -0800113 this(supervisor, supervisor.mDisplayManager.getDisplay(displayId));
114 }
115
116 ActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700117 mSupervisor = supervisor;
Wale Ogunwale45477b52018-03-06 12:24:19 -0800118 mDisplayId = display.getDisplayId();
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700119 mDisplay = display;
Winson Chung59a47ded2018-01-25 17:46:06 +0000120 mWindowContainerController = createWindowContainerController();
Bryce Leef3c6a472017-11-14 14:53:06 -0800121 updateBounds();
122 }
123
Winson Chung59a47ded2018-01-25 17:46:06 +0000124 protected DisplayWindowController createWindowContainerController() {
Wale Ogunwale45477b52018-03-06 12:24:19 -0800125 return new DisplayWindowController(mDisplay, this);
Winson Chung59a47ded2018-01-25 17:46:06 +0000126 }
127
Bryce Leef3c6a472017-11-14 14:53:06 -0800128 void updateBounds() {
129 mDisplay.getSize(mTmpDisplaySize);
130 setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700131 }
132
133 void addChild(ActivityStack stack, int position) {
134 if (position == POSITION_BOTTOM) {
135 position = 0;
136 } else if (position == POSITION_TOP) {
137 position = mStacks.size();
138 }
139 if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack
140 + " to displayId=" + mDisplayId + " position=" + position);
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700141 addStackReferenceIfNeeded(stack);
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700142 positionChildAt(stack, position);
Wale Ogunwalec9e57de2018-05-08 14:28:07 -0700143 mSupervisor.mService.mAm.updateSleepIfNeededLocked();
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700144 }
145
146 void removeChild(ActivityStack stack) {
147 if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack
148 + " from displayId=" + mDisplayId);
149 mStacks.remove(stack);
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700150 removeStackReferenceIfNeeded(stack);
Wale Ogunwalec9e57de2018-05-08 14:28:07 -0700151 mSupervisor.mService.mAm.updateSleepIfNeededLocked();
Winson Chung0f7ec962018-05-03 18:03:15 -0700152 onStackOrderChanged();
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700153 }
154
155 void positionChildAtTop(ActivityStack stack) {
156 positionChildAt(stack, mStacks.size());
157 }
158
159 void positionChildAtBottom(ActivityStack stack) {
160 positionChildAt(stack, 0);
161 }
162
163 private void positionChildAt(ActivityStack stack, int position) {
Winson Chung123e07a2018-02-27 11:47:16 -0800164 // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust
165 // the position internally, also update the logic here
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700166 mStacks.remove(stack);
Winson Chung59a47ded2018-01-25 17:46:06 +0000167 final int insertPosition = getTopInsertPosition(stack, position);
168 mStacks.add(insertPosition, stack);
169 mWindowContainerController.positionChildAt(stack.getWindowContainerController(),
170 insertPosition);
Winson Chung0f7ec962018-05-03 18:03:15 -0700171 onStackOrderChanged();
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700172 }
173
174 private int getTopInsertPosition(ActivityStack stack, int candidatePosition) {
175 int position = mStacks.size();
Kazuki Takise148d00a2018-05-31 15:32:19 +0900176 if (stack.inPinnedWindowingMode()) {
177 // Stack in pinned windowing mode is z-ordered on-top of all other stacks so okay to
178 // just return the candidate position.
179 return Math.min(position, candidatePosition);
180 }
181 while (position > 0) {
182 final ActivityStack targetStack = mStacks.get(position - 1);
183 if (!targetStack.isAlwaysOnTop()) {
184 // We reached a stack that isn't always-on-top.
185 break;
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700186 }
Kazuki Takise148d00a2018-05-31 15:32:19 +0900187 if (stack.isAlwaysOnTop() && !targetStack.inPinnedWindowingMode()) {
188 // Always on-top non-pinned windowing mode stacks can go anywhere below pinned stack.
189 break;
190 }
191 position--;
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700192 }
193 return Math.min(position, candidatePosition);
194 }
195
196 <T extends ActivityStack> T getStack(int stackId) {
197 for (int i = mStacks.size() - 1; i >= 0; --i) {
198 final ActivityStack stack = mStacks.get(i);
199 if (stack.mStackId == stackId) {
200 return (T) stack;
201 }
202 }
203 return null;
204 }
205
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700206 /**
207 * @return the topmost stack on the display that is compatible with the input windowing mode and
208 * activity type. {@code null} means no compatible stack on the display.
209 * @see ConfigurationContainer#isCompatible(int, int)
210 */
211 <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700212 if (activityType == ACTIVITY_TYPE_HOME) {
213 return (T) mHomeStack;
214 } else if (activityType == ACTIVITY_TYPE_RECENTS) {
215 return (T) mRecentsStack;
216 }
217 if (windowingMode == WINDOWING_MODE_PINNED) {
218 return (T) mPinnedStack;
219 } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
220 return (T) mSplitScreenPrimaryStack;
221 }
Wale Ogunwaleab5de372017-10-18 06:46:31 -0700222
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700223 for (int i = mStacks.size() - 1; i >= 0; --i) {
224 final ActivityStack stack = mStacks.get(i);
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700225 if (stack.isCompatible(windowingMode, activityType)) {
226 return (T) stack;
227 }
228 }
229 return null;
230 }
231
Wale Ogunwaleab5de372017-10-18 06:46:31 -0700232 private boolean alwaysCreateStack(int windowingMode, int activityType) {
233 // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
234 // modes so that we can manage visual ordering and return types correctly.
235 return activityType == ACTIVITY_TYPE_STANDARD
236 && (windowingMode == WINDOWING_MODE_FULLSCREEN
237 || windowingMode == WINDOWING_MODE_FREEFORM
238 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
239 }
240
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700241 /**
Wale Ogunwaleab5de372017-10-18 06:46:31 -0700242 * Returns an existing stack compatible with the windowing mode and activity type or creates one
243 * if a compatible stack doesn't exist.
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700244 * @see #getStack(int, int)
245 * @see #createStack(int, int, boolean)
246 */
247 <T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType,
248 boolean onTop) {
Wale Ogunwaleab5de372017-10-18 06:46:31 -0700249 if (!alwaysCreateStack(windowingMode, activityType)) {
250 T stack = getStack(windowingMode, activityType);
251 if (stack != null) {
252 return stack;
253 }
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700254 }
255 return createStack(windowingMode, activityType, onTop);
256 }
257
Wale Ogunwale68278562017-09-23 17:13:55 -0700258 /**
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700259 * Returns an existing stack compatible with the input params or creates one
260 * if a compatible stack doesn't exist.
261 * @see #getOrCreateStack(int, int, boolean)
262 */
263 <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r,
264 @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, int activityType,
265 boolean onTop) {
266 final int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
267 return getOrCreateStack(windowingMode, activityType, onTop);
268 }
269
Bryce Leed9cce2c2017-12-04 16:16:27 -0800270 private int getNextStackId() {
271 return sNextFreeStackId++;
272 }
273
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700274 /**
Wale Ogunwale68278562017-09-23 17:13:55 -0700275 * Creates a stack matching the input windowing mode and activity type on this display.
276 * @param windowingMode The windowing mode the stack should be created in. If
277 * {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
278 * be created in {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}.
279 * @param activityType The activityType the stack should be created in. If
280 * {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
281 * be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
282 * @param onTop If true the stack will be created at the top of the display, else at the bottom.
283 * @return The newly created stack.
284 */
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700285 <T extends ActivityStack> T createStack(int windowingMode, int activityType, boolean onTop) {
286
Wale Ogunwale68278562017-09-23 17:13:55 -0700287 if (activityType == ACTIVITY_TYPE_UNDEFINED) {
288 // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
289 // anything else should be passing it in anyways...
290 activityType = ACTIVITY_TYPE_STANDARD;
291 }
292
293 if (activityType != ACTIVITY_TYPE_STANDARD) {
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700294 // For now there can be only one stack of a particular non-standard activity type on a
295 // display. So, get that ignoring whatever windowing mode it is currently in.
296 T stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
297 if (stack != null) {
298 throw new IllegalArgumentException("Stack=" + stack + " of activityType="
299 + activityType + " already on display=" + this + ". Can't have multiple.");
300 }
301 }
302
Wale Ogunwalec9e57de2018-05-08 14:28:07 -0700303 final ActivityManagerService service = mSupervisor.mService.mAm;
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700304 if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700305 service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
306 service.mSupportsPictureInPicture, activityType)) {
307 throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
308 + windowingMode);
309 }
310
311 if (windowingMode == WINDOWING_MODE_UNDEFINED) {
312 // TODO: Should be okay to have stacks with with undefined windowing mode long term, but
313 // have to set them to something for now due to logic that depending on them.
Wale Ogunwale44f036f2017-09-29 05:09:09 -0700314 windowingMode = getWindowingMode(); // Put in current display's windowing mode
315 if (windowingMode == WINDOWING_MODE_UNDEFINED) {
316 // Else fullscreen for now...
317 windowingMode = WINDOWING_MODE_FULLSCREEN;
318 }
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700319 }
320
Bryce Leed9cce2c2017-12-04 16:16:27 -0800321 final int stackId = getNextStackId();
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700322 return createStackUnchecked(windowingMode, activityType, stackId, onTop);
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700323 }
324
325 @VisibleForTesting
326 <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
327 int stackId, boolean onTop) {
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700328 if (windowingMode == WINDOWING_MODE_PINNED) {
329 return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700330 }
Wale Ogunwale30e441d2017-11-09 08:28:45 -0800331 return (T) new ActivityStack(
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700332 this, stackId, mSupervisor, windowingMode, activityType, onTop);
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700333 }
334
Wale Ogunwale68278562017-09-23 17:13:55 -0700335 /**
336 * Removes stacks in the input windowing modes from the system if they are of activity type
337 * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
338 */
339 void removeStacksInWindowingModes(int... windowingModes) {
340 if (windowingModes == null || windowingModes.length == 0) {
341 return;
342 }
343
344 for (int j = windowingModes.length - 1 ; j >= 0; --j) {
345 final int windowingMode = windowingModes[j];
346 for (int i = mStacks.size() - 1; i >= 0; --i) {
347 final ActivityStack stack = mStacks.get(i);
348 if (!stack.isActivityTypeStandardOrUndefined()) {
349 continue;
350 }
351 if (stack.getWindowingMode() != windowingMode) {
352 continue;
353 }
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700354 mSupervisor.removeStack(stack);
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700355 }
356 }
357 }
358
Wale Ogunwale68278562017-09-23 17:13:55 -0700359 void removeStacksWithActivityTypes(int... activityTypes) {
360 if (activityTypes == null || activityTypes.length == 0) {
361 return;
362 }
363
364 for (int j = activityTypes.length - 1 ; j >= 0; --j) {
365 final int activityType = activityTypes[j];
366 for (int i = mStacks.size() - 1; i >= 0; --i) {
367 final ActivityStack stack = mStacks.get(i);
368 if (stack.getActivityType() == activityType) {
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700369 mSupervisor.removeStack(stack);
Wale Ogunwale68278562017-09-23 17:13:55 -0700370 }
371 }
372 }
373 }
374
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700375 void onStackWindowingModeChanged(ActivityStack stack) {
376 removeStackReferenceIfNeeded(stack);
377 addStackReferenceIfNeeded(stack);
378 }
379
380 private void addStackReferenceIfNeeded(ActivityStack stack) {
381 final int activityType = stack.getActivityType();
382 final int windowingMode = stack.getWindowingMode();
383
384 if (activityType == ACTIVITY_TYPE_HOME) {
385 if (mHomeStack != null && mHomeStack != stack) {
386 throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
387 + mHomeStack + " already exist on display=" + this + " stack=" + stack);
388 }
389 mHomeStack = stack;
390 } else if (activityType == ACTIVITY_TYPE_RECENTS) {
391 if (mRecentsStack != null && mRecentsStack != stack) {
392 throw new IllegalArgumentException("addStackReferenceIfNeeded: recents stack="
393 + mRecentsStack + " already exist on display=" + this + " stack=" + stack);
394 }
395 mRecentsStack = stack;
396 }
397 if (windowingMode == WINDOWING_MODE_PINNED) {
398 if (mPinnedStack != null && mPinnedStack != stack) {
399 throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
400 + mPinnedStack + " already exist on display=" + this
401 + " stack=" + stack);
402 }
403 mPinnedStack = stack;
404 } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
405 if (mSplitScreenPrimaryStack != null && mSplitScreenPrimaryStack != stack) {
406 throw new IllegalArgumentException("addStackReferenceIfNeeded:"
407 + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
408 + " already exist on display=" + this + " stack=" + stack);
409 }
410 mSplitScreenPrimaryStack = stack;
Wale Ogunwale30e441d2017-11-09 08:28:45 -0800411 onSplitScreenModeActivated();
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700412 }
413 }
414
415 private void removeStackReferenceIfNeeded(ActivityStack stack) {
416 if (stack == mHomeStack) {
417 mHomeStack = null;
418 } else if (stack == mRecentsStack) {
419 mRecentsStack = null;
420 } else if (stack == mPinnedStack) {
421 mPinnedStack = null;
422 } else if (stack == mSplitScreenPrimaryStack) {
423 mSplitScreenPrimaryStack = null;
Wale Ogunwale30e441d2017-11-09 08:28:45 -0800424 // Inform the reset of the system that split-screen mode was dismissed so things like
425 // resizing all the other stacks can take place.
426 onSplitScreenModeDismissed();
427 }
428 }
429
430 private void onSplitScreenModeDismissed() {
431 mSupervisor.mWindowManager.deferSurfaceLayout();
432 try {
433 // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
434 for (int i = mStacks.size() - 1; i >= 0; --i) {
435 final ActivityStack otherStack = mStacks.get(i);
436 if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
437 continue;
438 }
Andrii Kulian9da138a2018-04-24 17:12:44 -0700439 otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN, false /* animate */,
440 false /* showRecents */, false /* enteringSplitScreenMode */,
441 true /* deferEnsuringVisibility */);
Wale Ogunwale30e441d2017-11-09 08:28:45 -0800442 }
443 } finally {
Wale Ogunwale1dbc5c82017-12-08 08:12:20 -0800444 final ActivityStack topFullscreenStack =
Wale Ogunwalef3257852018-01-24 08:52:28 -0800445 getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
Wale Ogunwale1dbc5c82017-12-08 08:12:20 -0800446 if (topFullscreenStack != null && mHomeStack != null && !isTopStack(mHomeStack)) {
Wale Ogunwaleeb76b762017-11-17 10:08:04 -0800447 // Whenever split-screen is dismissed we want the home stack directly behind the
Wale Ogunwale1dbc5c82017-12-08 08:12:20 -0800448 // current top fullscreen stack so it shows up when the top stack is finished.
Wale Ogunwaleeb76b762017-11-17 10:08:04 -0800449 // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
450 // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
451 // once we have that.
452 mHomeStack.moveToFront("onSplitScreenModeDismissed");
Wale Ogunwale1dbc5c82017-12-08 08:12:20 -0800453 topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
Wale Ogunwaleeb76b762017-11-17 10:08:04 -0800454 }
Wale Ogunwale30e441d2017-11-09 08:28:45 -0800455 mSupervisor.mWindowManager.continueSurfaceLayout();
456 }
457 }
458
459 private void onSplitScreenModeActivated() {
460 mSupervisor.mWindowManager.deferSurfaceLayout();
461 try {
462 // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
463 for (int i = mStacks.size() - 1; i >= 0; --i) {
464 final ActivityStack otherStack = mStacks.get(i);
465 if (otherStack == mSplitScreenPrimaryStack
466 || !otherStack.affectedBySplitScreenResize()) {
467 continue;
468 }
Wale Ogunwaledf262f52017-12-07 18:17:12 -0800469 otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
470 false /* animate */, false /* showRecents */,
Andrii Kulian9da138a2018-04-24 17:12:44 -0700471 true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */);
Wale Ogunwale30e441d2017-11-09 08:28:45 -0800472 }
473 } finally {
474 mSupervisor.mWindowManager.continueSurfaceLayout();
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700475 }
476 }
477
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700478 /**
479 * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
480 * @param windowingMode The windowing mode we are checking support for.
481 * @param supportsMultiWindow If we should consider support for multi-window mode in general.
482 * @param supportsSplitScreen If we should consider support for split-screen multi-window.
483 * @param supportsFreeform If we should consider support for freeform multi-window.
484 * @param supportsPip If we should consider support for picture-in-picture mutli-window.
485 * @param activityType The activity type under consideration.
486 * @return true if the windowing mode is supported.
487 */
488 private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
489 boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
490 int activityType) {
491
492 if (windowingMode == WINDOWING_MODE_UNDEFINED
493 || windowingMode == WINDOWING_MODE_FULLSCREEN) {
494 return true;
495 }
496 if (!supportsMultiWindow) {
497 return false;
498 }
499
500 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
501 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
Wale Ogunwale2b07da82017-11-08 14:52:40 -0800502 return supportsSplitScreen
503 && WindowConfiguration.supportSplitScreenWindowingMode(activityType);
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700504 }
505
506 if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
507 return false;
508 }
509
510 if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
511 return false;
512 }
513 return true;
514 }
515
516 int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
517 @Nullable TaskRecord task, int activityType) {
518
519 // First preference if the windowing mode in the activity options if set.
520 int windowingMode = (options != null)
521 ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
522
523 // If windowing mode is unset, then next preference is the candidate task, then the
524 // activity record.
525 if (windowingMode == WINDOWING_MODE_UNDEFINED) {
526 if (task != null) {
527 windowingMode = task.getWindowingMode();
528 }
529 if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
530 windowingMode = r.getWindowingMode();
531 }
532 if (windowingMode == WINDOWING_MODE_UNDEFINED) {
533 // Use the display's windowing mode.
534 windowingMode = getWindowingMode();
535 }
536 }
537
538 // Make sure the windowing mode we are trying to use makes sense for what is supported.
Wale Ogunwalec9e57de2018-05-08 14:28:07 -0700539 final ActivityManagerService service = mSupervisor.mService.mAm;
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700540 boolean supportsMultiWindow = service.mSupportsMultiWindow;
541 boolean supportsSplitScreen = service.mSupportsSplitScreenMultiWindow;
542 boolean supportsFreeform = service.mSupportsFreeformWindowManagement;
543 boolean supportsPip = service.mSupportsPictureInPicture;
544 if (supportsMultiWindow) {
545 if (task != null) {
546 supportsMultiWindow = task.isResizeable();
547 supportsSplitScreen = task.supportsSplitScreenWindowingMode();
548 // TODO: Do we need to check for freeform and Pip support here?
549 } else if (r != null) {
550 supportsMultiWindow = r.isResizeable();
551 supportsSplitScreen = r.supportsSplitScreenWindowingMode();
552 supportsFreeform = r.supportsFreeform();
553 supportsPip = r.supportsPictureInPicture();
554 }
555 }
556
557 final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
558 if (!inSplitScreenMode
559 && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
560 // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
561 // trying to launch in split-screen secondary.
562 windowingMode = WINDOWING_MODE_FULLSCREEN;
563 } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
564 && supportsSplitScreen) {
565 windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
566 }
567
568 if (windowingMode != WINDOWING_MODE_UNDEFINED
569 && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
570 supportsFreeform, supportsPip, activityType)) {
571 return windowingMode;
572 }
Wale Ogunwale30e441d2017-11-09 08:28:45 -0800573 // Try to use the display's windowing mode otherwise fallback to fullscreen.
574 windowingMode = getWindowingMode();
575 return windowingMode != WINDOWING_MODE_UNDEFINED
576 ? windowingMode : WINDOWING_MODE_FULLSCREEN;
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700577 }
578
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700579 /**
580 * Get the topmost stack on the display. It may be different from focused stack, because
581 * focus may be on another display.
582 */
583 ActivityStack getTopStack() {
584 return mStacks.isEmpty() ? null : mStacks.get(mStacks.size() - 1);
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700585 }
586
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700587 boolean isTopStack(ActivityStack stack) {
588 return stack == getTopStack();
589 }
590
chaviw2c500982018-01-04 17:05:05 -0800591 boolean isTopNotPinnedStack(ActivityStack stack) {
Wale Ogunwale2cca8622017-12-11 08:40:13 -0800592 for (int i = mStacks.size() - 1; i >= 0; --i) {
593 final ActivityStack current = mStacks.get(i);
chaviw2c500982018-01-04 17:05:05 -0800594 if (!current.inPinnedWindowingMode()) {
Wale Ogunwale2cca8622017-12-11 08:40:13 -0800595 return current == stack;
596 }
597 }
598 return false;
599 }
600
Wale Ogunwalef3257852018-01-24 08:52:28 -0800601 ActivityStack getTopStackInWindowingMode(int windowingMode) {
602 for (int i = mStacks.size() - 1; i >= 0; --i) {
603 final ActivityStack current = mStacks.get(i);
604 if (windowingMode == current.getWindowingMode()) {
605 return current;
606 }
607 }
608 return null;
609 }
610
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700611 int getIndexOf(ActivityStack stack) {
612 return mStacks.indexOf(stack);
613 }
614
615 void onLockTaskPackagesUpdated() {
616 for (int i = mStacks.size() - 1; i >= 0; --i) {
617 mStacks.get(i).onLockTaskPackagesUpdated();
618 }
619 }
620
Wale Ogunwale7e1f5f52017-10-18 15:19:59 -0700621 /** We are in the process of exiting split-screen mode. */
622 void onExitingSplitScreenMode() {
623 // Remove reference to the primary-split-screen stack so it no longer has any effect on the
624 // display. For example, we want to be able to create fullscreen stack for standard activity
625 // types when exiting split-screen mode.
626 mSplitScreenPrimaryStack = null;
627 }
628
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700629 ActivityStack getSplitScreenPrimaryStack() {
630 return mSplitScreenPrimaryStack;
631 }
632
633 boolean hasSplitScreenPrimaryStack() {
634 return mSplitScreenPrimaryStack != null;
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700635 }
636
637 PinnedActivityStack getPinnedStack() {
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700638 return (PinnedActivityStack) mPinnedStack;
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700639 }
640
641 boolean hasPinnedStack() {
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700642 return mPinnedStack != null;
Wale Ogunwale04a05ac2017-09-17 21:35:02 -0700643 }
644
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700645 @Override
646 public String toString() {
647 return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
648 }
649
650 @Override
651 protected int getChildCount() {
652 return mStacks.size();
653 }
654
655 @Override
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700656 protected ActivityStack getChildAt(int index) {
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700657 return mStacks.get(index);
658 }
659
660 @Override
661 protected ConfigurationContainer getParent() {
662 return mSupervisor;
663 }
664
665 boolean isPrivate() {
666 return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
667 }
668
669 boolean isUidPresent(int uid) {
670 for (ActivityStack stack : mStacks) {
671 if (stack.isUidPresent(uid)) {
672 return true;
673 }
674 }
675 return false;
676 }
677
Bryce Leef19cbe22018-02-02 15:09:21 -0800678 void remove() {
679 final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove();
680 while (getChildCount() > 0) {
681 final ActivityStack stack = getChildAt(0);
682 if (destroyContentOnRemoval) {
Andrii Kulian823af6e2018-02-28 18:51:36 -0800683 // Override the stack configuration to make it equal to the current applied one, so
684 // that we don't accidentally report configuration change to activities that are
685 // going to be finished.
686 stack.onOverrideConfigurationChanged(stack.getConfiguration());
Bryce Leef19cbe22018-02-02 15:09:21 -0800687 mSupervisor.moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY,
688 false /* onTop */);
689 stack.finishAllActivitiesLocked(true /* immediately */);
690 } else {
691 // Moving all tasks to fullscreen stack, because it's guaranteed to be
692 // a valid launch stack for all activities. This way the task history from
693 // external display will be preserved on primary after move.
694 mSupervisor.moveTasksToFullscreenStackLocked(stack, true /* onTop */);
695 }
696 }
697
698 mWindowContainerController.removeContainer();
699 mWindowContainerController = null;
700 }
701
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700702 /** Update and get all UIDs that are present on the display and have access to it. */
703 IntArray getPresentUIDs() {
704 mDisplayAccessUIDs.clear();
705 for (ActivityStack stack : mStacks) {
706 stack.getPresentUIDs(mDisplayAccessUIDs);
707 }
708 return mDisplayAccessUIDs;
709 }
710
Bryce Leef19cbe22018-02-02 15:09:21 -0800711 private boolean shouldDestroyContentOnRemove() {
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700712 return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
713 }
714
715 boolean shouldSleep() {
716 return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
Wale Ogunwalec9e57de2018-05-08 14:28:07 -0700717 && (mSupervisor.mService.mAm.mRunningVoice == null);
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700718 }
719
Winson Chunge2d72172018-01-25 17:46:20 +0000720 /**
Winson Chung3e2980e2018-03-29 17:28:57 -0700721 * @return the stack currently above the {@param stack}. Can be null if the {@param stack} is
722 * already top-most.
Winson Chunge2d72172018-01-25 17:46:20 +0000723 */
Winson Chung3e2980e2018-03-29 17:28:57 -0700724 ActivityStack getStackAbove(ActivityStack stack) {
725 final int stackIndex = mStacks.indexOf(stack) + 1;
Winson Chunge2d72172018-01-25 17:46:20 +0000726 return (stackIndex < mStacks.size()) ? mStacks.get(stackIndex) : null;
727 }
728
729 /**
Winson Chung3e2980e2018-03-29 17:28:57 -0700730 * Adjusts the {@param stack} behind the last visible stack in the display if necessary.
731 * Generally used in conjunction with {@link #moveStackBehindStack}.
Winson Chunge2d72172018-01-25 17:46:20 +0000732 */
Winson Chung3e2980e2018-03-29 17:28:57 -0700733 void moveStackBehindBottomMostVisibleStack(ActivityStack stack) {
734 if (stack.shouldBeVisible(null)) {
735 // Skip if the stack is already visible
Winson Chunge2d72172018-01-25 17:46:20 +0000736 return;
737 }
738
Winson Chung3e2980e2018-03-29 17:28:57 -0700739 // Move the stack to the bottom to not affect the following visibility checks
740 positionChildAtBottom(stack);
Winson Chunge2d72172018-01-25 17:46:20 +0000741
Winson Chung3e2980e2018-03-29 17:28:57 -0700742 // Find the next position where the stack should be placed
Winson Chunge2d72172018-01-25 17:46:20 +0000743 final int numStacks = mStacks.size();
744 for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
Winson Chung3e2980e2018-03-29 17:28:57 -0700745 final ActivityStack s = mStacks.get(stackNdx);
746 if (s == stack) {
Winson Chunge2d72172018-01-25 17:46:20 +0000747 continue;
748 }
Winson Chung3e2980e2018-03-29 17:28:57 -0700749 final int winMode = s.getWindowingMode();
Winson Chunge2d72172018-01-25 17:46:20 +0000750 final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN ||
751 winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
Winson Chung3e2980e2018-03-29 17:28:57 -0700752 if (s.shouldBeVisible(null) && isValidWindowingMode) {
753 // Move the provided stack to behind this stack
754 positionChildAt(stack, Math.max(0, stackNdx - 1));
Winson Chunge2d72172018-01-25 17:46:20 +0000755 break;
756 }
757 }
758 }
759
760 /**
Winson Chung3e2980e2018-03-29 17:28:57 -0700761 * Moves the {@param stack} behind the given {@param behindStack} if possible. If
762 * {@param behindStack} is not currently in the display, then then the stack is moved to the
763 * back. Generally used in conjunction with {@link #moveStackBehindBottomMostVisibleStack}.
Winson Chunge2d72172018-01-25 17:46:20 +0000764 */
Winson Chung3e2980e2018-03-29 17:28:57 -0700765 void moveStackBehindStack(ActivityStack stack, ActivityStack behindStack) {
766 if (behindStack == null || behindStack == stack) {
Winson Chunge2d72172018-01-25 17:46:20 +0000767 return;
768 }
769
Winson Chung123e07a2018-02-27 11:47:16 -0800770 // Note that positionChildAt will first remove the given stack before inserting into the
771 // list, so we need to adjust the insertion index to account for the removed index
772 // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
773 // position internally
Winson Chung3e2980e2018-03-29 17:28:57 -0700774 final int stackIndex = mStacks.indexOf(stack);
Winson Chung123e07a2018-02-27 11:47:16 -0800775 final int behindStackIndex = mStacks.indexOf(behindStack);
Winson Chung3e2980e2018-03-29 17:28:57 -0700776 final int insertIndex = stackIndex <= behindStackIndex
Winson Chung123e07a2018-02-27 11:47:16 -0800777 ? behindStackIndex - 1 : behindStackIndex;
Winson Chung3e2980e2018-03-29 17:28:57 -0700778 positionChildAt(stack, Math.max(0, insertIndex));
Winson Chunge2d72172018-01-25 17:46:20 +0000779 }
780
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700781 boolean isSleeping() {
782 return mSleeping;
783 }
784
785 void setIsSleeping(boolean asleep) {
786 mSleeping = asleep;
787 }
788
Winson Chung0f7ec962018-05-03 18:03:15 -0700789 /**
790 * Adds a listener to be notified whenever the stack order in the display changes. Currently
791 * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
792 * current animation when the system state changes.
793 */
794 void registerStackOrderChangedListener(OnStackOrderChangedListener listener) {
795 if (!mStackOrderChangedCallbacks.contains(listener)) {
796 mStackOrderChangedCallbacks.add(listener);
797 }
798 }
799
800 /**
801 * Removes a previously registered stack order change listener.
802 */
803 void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) {
804 mStackOrderChangedCallbacks.remove(listener);
805 }
806
807 private void onStackOrderChanged() {
808 for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
809 mStackOrderChangedCallbacks.get(i).onStackOrderChanged();
810 }
811 }
812
Chavi Weingarten3a748552018-05-14 17:32:42 +0000813 /**
814 * See {@link DisplayWindowController#deferUpdateImeTarget()}
815 */
816 public void deferUpdateImeTarget() {
817 mWindowContainerController.deferUpdateImeTarget();
818 }
819
820 /**
821 * See {@link DisplayWindowController#deferUpdateImeTarget()}
822 */
823 public void continueUpdateImeTarget() {
824 mWindowContainerController.continueUpdateImeTarget();
825 }
826
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700827 public void dump(PrintWriter pw, String prefix) {
Wale Ogunwale30e441d2017-11-09 08:28:45 -0800828 pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size());
829 final String myPrefix = prefix + " ";
830 if (mHomeStack != null) {
831 pw.println(myPrefix + "mHomeStack=" + mHomeStack);
832 }
833 if (mRecentsStack != null) {
834 pw.println(myPrefix + "mRecentsStack=" + mRecentsStack);
835 }
836 if (mPinnedStack != null) {
837 pw.println(myPrefix + "mPinnedStack=" + mPinnedStack);
838 }
839 if (mSplitScreenPrimaryStack != null) {
840 pw.println(myPrefix + "mSplitScreenPrimaryStack=" + mSplitScreenPrimaryStack);
841 }
Wale Ogunwalea0f5b5e2017-10-11 09:37:23 -0700842 }
843
Bryce Lee77a7dd62018-01-22 15:47:09 -0800844 public void dumpStacks(PrintWriter pw) {
845 for (int i = mStacks.size() - 1; i >= 0; --i) {
846 pw.print(mStacks.get(i).mStackId);
847 if (i > 0) {
848 pw.print(",");
849 }
850 }
851 }
852
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700853 public void writeToProto(ProtoOutputStream proto, long fieldId) {
854 final long token = proto.start(fieldId);
Adrian Roos4921ccf2017-09-28 16:54:06 +0200855 super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700856 proto.write(ID, mDisplayId);
857 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
858 final ActivityStack stack = mStacks.get(stackNdx);
859 stack.writeToProto(proto, STACKS);
860 }
861 proto.end(token);
862 }
Winson Chung0f7ec962018-05-03 18:03:15 -0700863
864 /**
865 * Callback for when the order of the stacks in the display changes.
866 */
867 interface OnStackOrderChangedListener {
868 void onStackOrderChanged();
869 }
Wale Ogunwale9dcf9462017-09-19 15:13:01 -0700870}