• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.systemui.stackdivider;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
23 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
24 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
25 import static android.view.Display.DEFAULT_DISPLAY;
26 
27 import android.annotation.NonNull;
28 import android.app.ActivityManager;
29 import android.app.ActivityTaskManager;
30 import android.graphics.Rect;
31 import android.os.Handler;
32 import android.os.RemoteException;
33 import android.util.Log;
34 import android.view.Display;
35 import android.view.SurfaceControl;
36 import android.view.WindowManagerGlobal;
37 import android.window.TaskOrganizer;
38 import android.window.WindowContainerToken;
39 import android.window.WindowContainerTransaction;
40 
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.systemui.TransactionPool;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.concurrent.ExecutorService;
47 import java.util.concurrent.Executors;
48 
49 /**
50  * Proxy to simplify calls into window manager/activity manager
51  */
52 public class WindowManagerProxy {
53 
54     private static final String TAG = "WindowManagerProxy";
55     private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
56 
57     @GuardedBy("mDockedRect")
58     private final Rect mDockedRect = new Rect();
59 
60     private final Rect mTmpRect1 = new Rect();
61 
62     @GuardedBy("mDockedRect")
63     private final Rect mTouchableRegion = new Rect();
64 
65     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
66 
67     private final SyncTransactionQueue mSyncTransactionQueue;
68 
69     private final Runnable mSetTouchableRegionRunnable = new Runnable() {
70         @Override
71         public void run() {
72             try {
73                 synchronized (mDockedRect) {
74                     mTmpRect1.set(mTouchableRegion);
75                 }
76                 WindowManagerGlobal.getWindowManagerService().setDockedStackDividerTouchRegion(
77                         mTmpRect1);
78             } catch (RemoteException e) {
79                 Log.w(TAG, "Failed to set touchable region: " + e);
80             }
81         }
82     };
83 
WindowManagerProxy(TransactionPool transactionPool, Handler handler)84     WindowManagerProxy(TransactionPool transactionPool, Handler handler) {
85         mSyncTransactionQueue = new SyncTransactionQueue(transactionPool, handler);
86     }
87 
dismissOrMaximizeDocked(final SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout, final boolean dismissOrMaximize)88     void dismissOrMaximizeDocked(final SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout,
89             final boolean dismissOrMaximize) {
90         mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize));
91     }
92 
setResizing(final boolean resizing)93     public void setResizing(final boolean resizing) {
94         mExecutor.execute(new Runnable() {
95             @Override
96             public void run() {
97                 try {
98                     ActivityTaskManager.getService().setSplitScreenResizing(resizing);
99                 } catch (RemoteException e) {
100                     Log.w(TAG, "Error calling setDockedStackResizing: " + e);
101                 }
102             }
103         });
104     }
105 
106     /** Sets a touch region */
setTouchRegion(Rect region)107     public void setTouchRegion(Rect region) {
108         synchronized (mDockedRect) {
109             mTouchableRegion.set(region);
110         }
111         mExecutor.execute(mSetTouchableRegionRunnable);
112     }
113 
applyResizeSplits(int position, SplitDisplayLayout splitLayout)114     void applyResizeSplits(int position, SplitDisplayLayout splitLayout) {
115         WindowContainerTransaction t = new WindowContainerTransaction();
116         splitLayout.resizeSplits(position, t);
117         applySyncTransaction(t);
118     }
119 
getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out, WindowContainerToken parent)120     private static boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
121             WindowContainerToken parent) {
122         boolean resizable = false;
123         List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
124                 ? TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS)
125                 : TaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS);
126         for (int i = 0, n = rootTasks.size(); i < n; ++i) {
127             final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
128             out.add(ti);
129             if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
130                 resizable = ti.isResizeable;
131             }
132         }
133         return resizable;
134     }
135 
136     /**
137      * Assign a fixed override-bounds to home tasks that reflect their geometry while the primary
138      * split is minimized. This actually "sticks out" of the secondary split area, but when in
139      * minimized mode, the secondary split gets a 'negative' crop to expose it.
140      */
applyHomeTasksMinimized(SplitDisplayLayout layout, WindowContainerToken parent, @NonNull WindowContainerTransaction wct)141     static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, WindowContainerToken parent,
142             @NonNull WindowContainerTransaction wct) {
143         // Resize the home/recents stacks to the larger minimized-state size
144         final Rect homeBounds;
145         final ArrayList<ActivityManager.RunningTaskInfo> homeStacks = new ArrayList<>();
146         boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
147         if (isHomeResizable) {
148             homeBounds = layout.calcResizableMinimizedHomeStackBounds();
149         } else {
150             // home is not resizable, so lock it to its inherent orientation size.
151             homeBounds = new Rect(0, 0, 0, 0);
152             for (int i = homeStacks.size() - 1; i >= 0; --i) {
153                 if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_HOME) {
154                     final int orient = homeStacks.get(i).configuration.orientation;
155                     final boolean displayLandscape = layout.mDisplayLayout.isLandscape();
156                     final boolean isLandscape = orient == ORIENTATION_LANDSCAPE
157                             || (orient == ORIENTATION_UNDEFINED && displayLandscape);
158                     homeBounds.right = isLandscape == displayLandscape
159                             ? layout.mDisplayLayout.width() : layout.mDisplayLayout.height();
160                     homeBounds.bottom = isLandscape == displayLandscape
161                             ? layout.mDisplayLayout.height() : layout.mDisplayLayout.width();
162                     break;
163                 }
164             }
165         }
166         for (int i = homeStacks.size() - 1; i >= 0; --i) {
167             // For non-resizable homes, the minimized size is actually the fullscreen-size. As a
168             // result, we don't minimize for recents since it only shows half-size screenshots.
169             if (!isHomeResizable) {
170                 if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_RECENTS) {
171                     continue;
172                 }
173                 wct.setWindowingMode(homeStacks.get(i).token, WINDOWING_MODE_FULLSCREEN);
174             }
175             wct.setBounds(homeStacks.get(i).token, homeBounds);
176         }
177         layout.mTiles.mHomeBounds.set(homeBounds);
178         return isHomeResizable;
179     }
180 
181     /**
182      * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split.
183      * This assumes there is already something in the primary split since that is usually what
184      * triggers a call to this. In the same transaction, this overrides the home task bounds via
185      * {@link #applyHomeTasksMinimized}.
186      *
187      * @return whether the home stack is resizable
188      */
applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout)189     boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) {
190         // Set launchtile first so that any stack created after
191         // getAllStackInfos and before reparent (even if unlikely) are placed
192         // correctly.
193         TaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token);
194         List<ActivityManager.RunningTaskInfo> rootTasks =
195                 TaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
196         WindowContainerTransaction wct = new WindowContainerTransaction();
197         if (rootTasks.isEmpty()) {
198             return false;
199         }
200         ActivityManager.RunningTaskInfo topHomeTask = null;
201         for (int i = rootTasks.size() - 1; i >= 0; --i) {
202             final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
203             // Only move resizeable task to split secondary. However, we have an exception
204             // for non-resizable home because we will minimize to show it.
205             if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME) {
206                 continue;
207             }
208             // Only move fullscreen tasks to split secondary.
209             if (rootTask.configuration.windowConfiguration.getWindowingMode()
210                     != WINDOWING_MODE_FULLSCREEN) {
211                 continue;
212             }
213             // Since this iterates from bottom to top, update topHomeTask for every fullscreen task
214             // so it will be left with the status of the top one.
215             topHomeTask = isHomeOrRecentTask(rootTask) ? rootTask : null;
216             wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
217         }
218         // Move the secondary split-forward.
219         wct.reorder(tiles.mSecondary.token, true /* onTop */);
220         boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
221         if (topHomeTask != null) {
222             // Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST
223             // is enabled, this temporarily syncs the home surface position with offset until
224             // sync transaction finishes.
225             wct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds);
226         }
227         applySyncTransaction(wct);
228         return isHomeResizable;
229     }
230 
isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti)231     static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
232         final int atype = ti.configuration.windowConfiguration.getActivityType();
233         return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
234     }
235 
236     /**
237      * Reparents all tile members back to their display and resets home task override bounds.
238      * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary
239      *                          split (thus resulting in the top of the secondary split becoming
240      *                          fullscreen. {@code false} resolves the other way.
241      */
applyDismissSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout, boolean dismissOrMaximize)242     void applyDismissSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout,
243             boolean dismissOrMaximize) {
244         // Set launch root first so that any task created after getChildContainers and
245         // before reparent (pretty unlikely) are put into fullscreen.
246         TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
247         // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
248         //                 plus specific APIs to clean this up.
249         List<ActivityManager.RunningTaskInfo> primaryChildren =
250                 TaskOrganizer.getChildTasks(tiles.mPrimary.token, null /* activityTypes */);
251         List<ActivityManager.RunningTaskInfo> secondaryChildren =
252                 TaskOrganizer.getChildTasks(tiles.mSecondary.token, null /* activityTypes */);
253         // In some cases (eg. non-resizable is launched), system-server will leave split-screen.
254         // as a result, the above will not capture any tasks; yet, we need to clean-up the
255         // home task bounds.
256         List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
257                 TaskOrganizer.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS);
258         // Filter out the root split tasks
259         freeHomeAndRecents.removeIf(p -> p.token.equals(tiles.mSecondary.token)
260                 || p.token.equals(tiles.mPrimary.token));
261 
262         if (primaryChildren.isEmpty() && secondaryChildren.isEmpty()
263                 && freeHomeAndRecents.isEmpty()) {
264             return;
265         }
266         WindowContainerTransaction wct = new WindowContainerTransaction();
267         if (dismissOrMaximize) {
268             // Dismissing, so move all primary split tasks first
269             for (int i = primaryChildren.size() - 1; i >= 0; --i) {
270                 wct.reparent(primaryChildren.get(i).token, null /* parent */,
271                         true /* onTop */);
272             }
273             boolean homeOnTop = false;
274             // Don't need to worry about home tasks because they are already in the "proper"
275             // order within the secondary split.
276             for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
277                 final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
278                 wct.reparent(ti.token, null /* parent */, true /* onTop */);
279                 if (isHomeOrRecentTask(ti)) {
280                     wct.setBounds(ti.token, null);
281                     wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
282                     if (i == 0) {
283                         homeOnTop = true;
284                     }
285                 }
286             }
287             if (homeOnTop) {
288                 // Translate/update-crop of secondary out-of-band with sync transaction -- instead
289                 // play this in sync with new home-app frame because until BALST is enabled this
290                 // shows up on screen before the syncTransaction returns.
291                 // We only have access to the secondary root surface, though, so in order to
292                 // position things properly, we have to take into account the existing negative
293                 // offset/crop of the minimized-home task.
294                 final boolean landscape = layout.mDisplayLayout.isLandscape();
295                 final int posX = landscape ? layout.mSecondary.left - tiles.mHomeBounds.left
296                         : layout.mSecondary.left;
297                 final int posY = landscape ? layout.mSecondary.top
298                         : layout.mSecondary.top - tiles.mHomeBounds.top;
299                 final SurfaceControl.Transaction sft = new SurfaceControl.Transaction();
300                 sft.setPosition(tiles.mSecondarySurface, posX, posY);
301                 final Rect crop = new Rect(0, 0, layout.mDisplayLayout.width(),
302                         layout.mDisplayLayout.height());
303                 crop.offset(-posX, -posY);
304                 sft.setWindowCrop(tiles.mSecondarySurface, crop);
305                 wct.setBoundsChangeTransaction(tiles.mSecondary.token, sft);
306             }
307         } else {
308             // Maximize, so move non-home secondary split first
309             for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
310                 if (isHomeOrRecentTask(secondaryChildren.get(i))) {
311                     continue;
312                 }
313                 wct.reparent(secondaryChildren.get(i).token, null /* parent */,
314                         true /* onTop */);
315             }
316             // Find and place home tasks in-between. This simulates the fact that there was
317             // nothing behind the primary split's tasks.
318             for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
319                 final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
320                 if (isHomeOrRecentTask(ti)) {
321                     wct.reparent(ti.token, null /* parent */, true /* onTop */);
322                     // reset bounds and mode too
323                     wct.setBounds(ti.token, null);
324                     wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
325                 }
326             }
327             for (int i = primaryChildren.size() - 1; i >= 0; --i) {
328                 wct.reparent(primaryChildren.get(i).token, null /* parent */,
329                         true /* onTop */);
330             }
331         }
332         for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
333             wct.setBounds(freeHomeAndRecents.get(i).token, null);
334             wct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED);
335         }
336         // Reset focusable to true
337         wct.setFocusable(tiles.mPrimary.token, true /* focusable */);
338         applySyncTransaction(wct);
339     }
340 
341     /**
342      * Utility to apply a sync transaction serially with other sync transactions.
343      *
344      * @see SyncTransactionQueue#queue
345      */
applySyncTransaction(WindowContainerTransaction wct)346     void applySyncTransaction(WindowContainerTransaction wct) {
347         mSyncTransactionQueue.queue(wct);
348     }
349 
350     /**
351      * @see SyncTransactionQueue#queueIfWaiting
352      */
queueSyncTransactionIfWaiting(WindowContainerTransaction wct)353     boolean queueSyncTransactionIfWaiting(WindowContainerTransaction wct) {
354         return mSyncTransactionQueue.queueIfWaiting(wct);
355     }
356 
357     /**
358      * @see SyncTransactionQueue#runInSync
359      */
runInSync(SyncTransactionQueue.TransactionRunnable runnable)360     void runInSync(SyncTransactionQueue.TransactionRunnable runnable) {
361         mSyncTransactionQueue.runInSync(runnable);
362     }
363 }
364