blob: 6751e8d6223d3d10f5f9530f68034b697786c485 [file] [log] [blame]
Evan Roskyaf9f27c2020-02-18 18:58:35 +00001/*
2 * Copyright (C) 2020 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.systemui.stackdivider;
18
19import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
22import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
23import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
24import static android.view.Display.DEFAULT_DISPLAY;
25
26import android.app.ActivityManager.RunningTaskInfo;
Evan Roskyaf9f27c2020-02-18 18:58:35 +000027import android.app.WindowConfiguration;
Louis Changa009c762020-02-26 11:21:31 +080028import android.graphics.Rect;
Evan Roskyaf9f27c2020-02-18 18:58:35 +000029import android.os.RemoteException;
30import android.util.Log;
31import android.view.Display;
Evan Roskyaf9f27c2020-02-18 18:58:35 +000032import android.view.SurfaceControl;
33import android.view.SurfaceSession;
Wale Ogunwaleadf116e2020-03-27 16:36:01 -070034import android.window.TaskOrganizer;
Louis Changa009c762020-02-26 11:21:31 +080035
Wale Ogunwaleadf116e2020-03-27 16:36:01 -070036class SplitScreenTaskOrganizer extends TaskOrganizer {
Evan Roskya5858ed2020-04-21 18:47:37 -070037 private static final String TAG = "SplitScreenTaskOrg";
Evan Roskyaf9f27c2020-02-18 18:58:35 +000038 private static final boolean DEBUG = Divider.DEBUG;
39
40 RunningTaskInfo mPrimary;
41 RunningTaskInfo mSecondary;
42 SurfaceControl mPrimarySurface;
43 SurfaceControl mSecondarySurface;
44 SurfaceControl mPrimaryDim;
45 SurfaceControl mSecondaryDim;
Louis Changa009c762020-02-26 11:21:31 +080046 Rect mHomeBounds = new Rect();
Evan Roskyaf9f27c2020-02-18 18:58:35 +000047 final Divider mDivider;
Evan Roskyb8540a02020-03-25 16:30:24 -070048 private boolean mSplitScreenSupported = false;
Evan Roskyaf9f27c2020-02-18 18:58:35 +000049
chaviwda7b3c22020-04-24 11:25:08 -070050 final SurfaceSession mSurfaceSession = new SurfaceSession();
51
Evan Roskyaf9f27c2020-02-18 18:58:35 +000052 SplitScreenTaskOrganizer(Divider divider) {
53 mDivider = divider;
54 }
55
chaviwda7b3c22020-04-24 11:25:08 -070056 void init() throws RemoteException {
Wale Ogunwaleadf116e2020-03-27 16:36:01 -070057 registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
58 registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
chaviwda7b3c22020-04-24 11:25:08 -070059 synchronized (this) {
60 try {
61 mPrimary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
62 WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
63 mSecondary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
64 WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
65 } catch (Exception e) {
66 // teardown to prevent callbacks
67 unregisterOrganizer();
68 throw e;
69 }
Evan Roskyb8540a02020-03-25 16:30:24 -070070 }
Evan Roskyaf9f27c2020-02-18 18:58:35 +000071 }
72
Evan Roskyb8540a02020-03-25 16:30:24 -070073 boolean isSplitScreenSupported() {
74 return mSplitScreenSupported;
75 }
76
Evan Roskyaf9f27c2020-02-18 18:58:35 +000077 SurfaceControl.Transaction getTransaction() {
78 return mDivider.mTransactionPool.acquire();
79 }
80
81 void releaseTransaction(SurfaceControl.Transaction t) {
82 mDivider.mTransactionPool.release(t);
83 }
84
85 @Override
chaviw7de50002020-04-27 12:33:30 -070086 public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
chaviwda7b3c22020-04-24 11:25:08 -070087 synchronized (this) {
88 if (mPrimary == null || mSecondary == null) {
89 Log.w(TAG, "Received onTaskAppeared before creating root tasks " + taskInfo);
90 return;
91 }
92
93 if (taskInfo.token.equals(mPrimary.token)) {
chaviw7de50002020-04-27 12:33:30 -070094 mPrimarySurface = leash;
chaviwda7b3c22020-04-24 11:25:08 -070095 } else if (taskInfo.token.equals(mSecondary.token)) {
chaviw7de50002020-04-27 12:33:30 -070096 mSecondarySurface = leash;
chaviwda7b3c22020-04-24 11:25:08 -070097 }
98
99 if (!mSplitScreenSupported && mPrimarySurface != null && mSecondarySurface != null) {
100 mSplitScreenSupported = true;
101
102 // Initialize dim surfaces:
103 mPrimaryDim = new SurfaceControl.Builder(mSurfaceSession)
104 .setParent(mPrimarySurface).setColorLayer()
105 .setName("Primary Divider Dim").build();
106 mSecondaryDim = new SurfaceControl.Builder(mSurfaceSession)
107 .setParent(mSecondarySurface).setColorLayer()
108 .setName("Secondary Divider Dim").build();
109 SurfaceControl.Transaction t = getTransaction();
110 t.setLayer(mPrimaryDim, Integer.MAX_VALUE);
111 t.setColor(mPrimaryDim, new float[]{0f, 0f, 0f});
112 t.setLayer(mSecondaryDim, Integer.MAX_VALUE);
113 t.setColor(mSecondaryDim, new float[]{0f, 0f, 0f});
114 t.apply();
115 releaseTransaction(t);
Tony Huang3dd2b932020-06-15 05:40:40 +0000116
117 mDivider.onTasksReady();
chaviwda7b3c22020-04-24 11:25:08 -0700118 }
119 }
120 }
121
122 @Override
123 public void onTaskVanished(RunningTaskInfo taskInfo) {
124 synchronized (this) {
125 final boolean isPrimaryTask = mPrimary != null
126 && taskInfo.token.equals(mPrimary.token);
127 final boolean isSecondaryTask = mSecondary != null
128 && taskInfo.token.equals(mSecondary.token);
129
130 if (mSplitScreenSupported && (isPrimaryTask || isSecondaryTask)) {
131 mSplitScreenSupported = false;
132
133 SurfaceControl.Transaction t = getTransaction();
134 t.remove(mPrimaryDim);
135 t.remove(mSecondaryDim);
136 t.remove(mPrimarySurface);
137 t.remove(mSecondarySurface);
138 t.apply();
139 releaseTransaction(t);
140
141 mDivider.onTaskVanished();
142 }
143 }
144 }
145
146 @Override
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000147 public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
148 if (taskInfo.displayId != DEFAULT_DISPLAY) {
149 return;
150 }
151 mDivider.getHandler().post(() -> handleTaskInfoChanged(taskInfo));
152 }
153
154 /**
155 * This is effectively a finite state machine which moves between the various split-screen
156 * presentations based on the contents of the split regions.
157 */
158 private void handleTaskInfoChanged(RunningTaskInfo info) {
Evan Roskya5858ed2020-04-21 18:47:37 -0700159 if (!mSplitScreenSupported) {
160 // This shouldn't happen; but apparently there is a chance that SysUI crashes without
161 // system server receiving binder-death (or maybe it receives binder-death too late?).
162 // In this situation, when sys-ui restarts, the split root-tasks will still exist so
163 // there is a small window of time during init() where WM might send messages here
164 // before init() fails. So, avoid a cycle of crashes by returning early.
165 Log.e(TAG, "Got handleTaskInfoChanged when not initialized: " + info);
166 return;
167 }
Evan Rosky89c285e2020-06-17 21:21:53 -0700168 final boolean secondaryImpliedMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
169 || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
170 && mDivider.isHomeStackResizable());
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000171 final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
172 final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
173 if (info.token.asBinder() == mPrimary.token.asBinder()) {
174 mPrimary = info;
175 } else if (info.token.asBinder() == mSecondary.token.asBinder()) {
176 mSecondary = info;
177 }
178 final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
179 final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
Evan Rosky89c285e2020-06-17 21:21:53 -0700180 final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
181 || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
182 && mDivider.isHomeStackResizable());
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000183 if (DEBUG) {
184 Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary);
185 }
Evan Roskyf64f5da2020-03-16 13:47:48 -0700186 if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty
Evan Rosky89c285e2020-06-17 21:21:53 -0700187 && secondaryImpliedMinimize == secondaryImpliesMinimize) {
Evan Roskyf64f5da2020-03-16 13:47:48 -0700188 // No relevant changes
189 return;
190 }
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000191 if (primaryIsEmpty || secondaryIsEmpty) {
192 // At-least one of the splits is empty which means we are currently transitioning
193 // into or out-of split-screen mode.
194 if (DEBUG) {
195 Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
196 + " " + mSecondary.topActivityType);
197 }
Evan Rosky90d5b6d2020-05-13 19:39:05 -0700198 if (mDivider.isDividerVisible()) {
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000199 // Was in split-mode, which means we are leaving split, so continue that.
200 // This happens when the stack in the primary-split is dismissed.
201 if (DEBUG) {
202 Log.d(TAG, " was in split, so this means leave it "
203 + mPrimary.topActivityType + " " + mSecondary.topActivityType);
204 }
Evan Rosky36138542020-05-01 18:02:11 -0700205 mDivider.startDismissSplit();
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000206 } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
207 // Wasn't in split-mode (both were empty), but now that the primary split is
208 // populated, we should fully enter split by moving everything else into secondary.
209 // This just tells window-manager to reparent things, the UI will respond
210 // when it gets new task info for the secondary split.
211 if (DEBUG) {
212 Log.d(TAG, " was not in split, but primary is populated, so enter it");
213 }
214 mDivider.startEnterSplit();
215 }
Evan Rosky89c285e2020-06-17 21:21:53 -0700216 } else if (secondaryImpliesMinimize) {
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000217 // Both splits are populated but the secondary split has a home/recents stack on top,
218 // so enter minimized mode.
219 mDivider.ensureMinimizedSplit();
220 } else {
221 // Both splits are populated by normal activities, so make sure we aren't minimized.
222 mDivider.ensureNormalSplit();
223 }
224 }
225}