blob: 4a2cad705c1734d79e1b30827fbac4a8359c40cf [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);
chaviwda7b3c22020-04-24 11:25:08 -0700116 }
117 }
118 }
119
120 @Override
121 public void onTaskVanished(RunningTaskInfo taskInfo) {
122 synchronized (this) {
123 final boolean isPrimaryTask = mPrimary != null
124 && taskInfo.token.equals(mPrimary.token);
125 final boolean isSecondaryTask = mSecondary != null
126 && taskInfo.token.equals(mSecondary.token);
127
128 if (mSplitScreenSupported && (isPrimaryTask || isSecondaryTask)) {
129 mSplitScreenSupported = false;
130
131 SurfaceControl.Transaction t = getTransaction();
132 t.remove(mPrimaryDim);
133 t.remove(mSecondaryDim);
134 t.remove(mPrimarySurface);
135 t.remove(mSecondarySurface);
136 t.apply();
137 releaseTransaction(t);
138
139 mDivider.onTaskVanished();
140 }
141 }
142 }
143
144 @Override
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000145 public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
146 if (taskInfo.displayId != DEFAULT_DISPLAY) {
147 return;
148 }
149 mDivider.getHandler().post(() -> handleTaskInfoChanged(taskInfo));
150 }
151
152 /**
153 * This is effectively a finite state machine which moves between the various split-screen
154 * presentations based on the contents of the split regions.
155 */
156 private void handleTaskInfoChanged(RunningTaskInfo info) {
Evan Roskya5858ed2020-04-21 18:47:37 -0700157 if (!mSplitScreenSupported) {
158 // This shouldn't happen; but apparently there is a chance that SysUI crashes without
159 // system server receiving binder-death (or maybe it receives binder-death too late?).
160 // In this situation, when sys-ui restarts, the split root-tasks will still exist so
161 // there is a small window of time during init() where WM might send messages here
162 // before init() fails. So, avoid a cycle of crashes by returning early.
163 Log.e(TAG, "Got handleTaskInfoChanged when not initialized: " + info);
164 return;
165 }
Evan Rosky89c285e2020-06-17 21:21:53 -0700166 final boolean secondaryImpliedMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
167 || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
168 && mDivider.isHomeStackResizable());
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000169 final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
170 final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
171 if (info.token.asBinder() == mPrimary.token.asBinder()) {
172 mPrimary = info;
173 } else if (info.token.asBinder() == mSecondary.token.asBinder()) {
174 mSecondary = info;
175 }
176 final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
177 final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
Evan Rosky89c285e2020-06-17 21:21:53 -0700178 final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
179 || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
180 && mDivider.isHomeStackResizable());
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000181 if (DEBUG) {
182 Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary);
183 }
Evan Roskyf64f5da2020-03-16 13:47:48 -0700184 if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty
Evan Rosky89c285e2020-06-17 21:21:53 -0700185 && secondaryImpliedMinimize == secondaryImpliesMinimize) {
Evan Roskyf64f5da2020-03-16 13:47:48 -0700186 // No relevant changes
187 return;
188 }
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000189 if (primaryIsEmpty || secondaryIsEmpty) {
190 // At-least one of the splits is empty which means we are currently transitioning
191 // into or out-of split-screen mode.
192 if (DEBUG) {
193 Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
194 + " " + mSecondary.topActivityType);
195 }
Evan Rosky90d5b6d2020-05-13 19:39:05 -0700196 if (mDivider.isDividerVisible()) {
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000197 // Was in split-mode, which means we are leaving split, so continue that.
198 // This happens when the stack in the primary-split is dismissed.
199 if (DEBUG) {
200 Log.d(TAG, " was in split, so this means leave it "
201 + mPrimary.topActivityType + " " + mSecondary.topActivityType);
202 }
Evan Rosky36138542020-05-01 18:02:11 -0700203 mDivider.startDismissSplit();
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000204 } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
205 // Wasn't in split-mode (both were empty), but now that the primary split is
206 // populated, we should fully enter split by moving everything else into secondary.
207 // This just tells window-manager to reparent things, the UI will respond
208 // when it gets new task info for the secondary split.
209 if (DEBUG) {
210 Log.d(TAG, " was not in split, but primary is populated, so enter it");
211 }
212 mDivider.startEnterSplit();
213 }
Evan Rosky89c285e2020-06-17 21:21:53 -0700214 } else if (secondaryImpliesMinimize) {
Evan Roskyaf9f27c2020-02-18 18:58:35 +0000215 // Both splits are populated but the secondary split has a home/recents stack on top,
216 // so enter minimized mode.
217 mDivider.ensureMinimizedSplit();
218 } else {
219 // Both splits are populated by normal activities, so make sure we aren't minimized.
220 mDivider.ensureNormalSplit();
221 }
222 }
223}