blob: 3027bd225216c1f8c324ab8dc7e6b4eb1121d179 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.stackdivider;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
import android.view.WindowManagerGlobal;
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Proxy to simplify calls into window manager/activity manager
*/
public class WindowManagerProxy {
private static final String TAG = "WindowManagerProxy";
private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
private static final WindowManagerProxy sInstance = new WindowManagerProxy();
@GuardedBy("mDockedRect")
private final Rect mDockedRect = new Rect();
private final Rect mTmpRect1 = new Rect();
@GuardedBy("mDockedRect")
private final Rect mTouchableRegion = new Rect();
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Runnable mSetTouchableRegionRunnable = new Runnable() {
@Override
public void run() {
try {
synchronized (mDockedRect) {
mTmpRect1.set(mTouchableRegion);
}
WindowManagerGlobal.getWindowManagerService().setDockedStackDividerTouchRegion(
mTmpRect1);
} catch (RemoteException e) {
Log.w(TAG, "Failed to set touchable region: " + e);
}
}
};
private WindowManagerProxy() {
}
public static WindowManagerProxy getInstance() {
return sInstance;
}
void dismissOrMaximizeDocked(
final SplitScreenTaskOrganizer tiles, final boolean dismissOrMaximize) {
mExecutor.execute(() -> applyDismissSplit(tiles, dismissOrMaximize));
}
public void setResizing(final boolean resizing) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
ActivityTaskManager.getService().setSplitScreenResizing(resizing);
} catch (RemoteException e) {
Log.w(TAG, "Error calling setDockedStackResizing: " + e);
}
}
});
}
/** Sets a touch region */
public void setTouchRegion(Rect region) {
synchronized (mDockedRect) {
mTouchableRegion.set(region);
}
mExecutor.execute(mSetTouchableRegionRunnable);
}
static void applyResizeSplits(int position, SplitDisplayLayout splitLayout) {
WindowContainerTransaction t = new WindowContainerTransaction();
splitLayout.resizeSplits(position, t);
WindowOrganizer.applyTransaction(t);
}
private static boolean getHomeAndRecentsTasks(List<WindowContainerToken> out,
WindowContainerToken parent) {
boolean resizable = false;
List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
? TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS)
: TaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS);
for (int i = 0, n = rootTasks.size(); i < n; ++i) {
final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
out.add(ti.token);
if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
resizable = ti.isResizable();
}
}
return resizable;
}
/**
* Assign a fixed override-bounds to home tasks that reflect their geometry while the primary
* split is minimized. This actually "sticks out" of the secondary split area, but when in
* minimized mode, the secondary split gets a 'negative' crop to expose it.
*/
static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, WindowContainerToken parent,
@NonNull WindowContainerTransaction wct) {
// Resize the home/recents stacks to the larger minimized-state size
final Rect homeBounds;
final ArrayList<WindowContainerToken> homeStacks = new ArrayList<>();
boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
if (isHomeResizable) {
homeBounds = layout.calcMinimizedHomeStackBounds();
} else {
homeBounds = new Rect(0, 0, layout.mDisplayLayout.width(),
layout.mDisplayLayout.height());
}
for (int i = homeStacks.size() - 1; i >= 0; --i) {
wct.setBounds(homeStacks.get(i), homeBounds);
}
layout.mTiles.mHomeBounds.set(homeBounds);
return isHomeResizable;
}
/**
* Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split.
* This assumes there is already something in the primary split since that is usually what
* triggers a call to this. In the same transaction, this overrides the home task bounds via
* {@link #applyHomeTasksMinimized}.
*
* @return whether the home stack is resizable
*/
static boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) {
// Set launchtile first so that any stack created after
// getAllStackInfos and before reparent (even if unlikely) are placed
// correctly.
TaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token);
List<ActivityManager.RunningTaskInfo> rootTasks =
TaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
WindowContainerTransaction wct = new WindowContainerTransaction();
if (rootTasks.isEmpty()) {
return false;
}
for (int i = rootTasks.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
// Only move resizeable task to split secondary. WM will just ignore this anyways...
if (!rootTask.isResizable()) continue;
// Only move fullscreen tasks to split secondary.
if (rootTask.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_FULLSCREEN) {
continue;
}
wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
}
// Move the secondary split-forward.
wct.reorder(tiles.mSecondary.token, true /* onTop */);
boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
WindowOrganizer.applyTransaction(wct);
return isHomeResizable;
}
private static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
final int atype = ti.configuration.windowConfiguration.getActivityType();
return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
}
/**
* Reparents all tile members back to their display and resets home task override bounds.
* @param dismissOrMaximize When {@code true} this resolves the split by closing the primary
* split (thus resulting in the top of the secondary split becoming
* fullscreen. {@code false} resolves the other way.
*/
static void applyDismissSplit(SplitScreenTaskOrganizer tiles, boolean dismissOrMaximize) {
// Set launch root first so that any task created after getChildContainers and
// before reparent (pretty unlikely) are put into fullscreen.
TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
// TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
// plus specific APIs to clean this up.
List<ActivityManager.RunningTaskInfo> primaryChildren =
TaskOrganizer.getChildTasks(tiles.mPrimary.token, null /* activityTypes */);
List<ActivityManager.RunningTaskInfo> secondaryChildren =
TaskOrganizer.getChildTasks(tiles.mSecondary.token, null /* activityTypes */);
// In some cases (eg. non-resizable is launched), system-server will leave split-screen.
// as a result, the above will not capture any tasks; yet, we need to clean-up the
// home task bounds.
List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS);
if (primaryChildren.isEmpty() && secondaryChildren.isEmpty()
&& freeHomeAndRecents.isEmpty()) {
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
if (dismissOrMaximize) {
// Dismissing, so move all primary split tasks first
for (int i = primaryChildren.size() - 1; i >= 0; --i) {
wct.reparent(primaryChildren.get(i).token, null /* parent */,
true /* onTop */);
}
// Don't need to worry about home tasks because they are already in the "proper"
// order within the secondary split.
for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
wct.reparent(ti.token, null /* parent */, true /* onTop */);
if (isHomeOrRecentTask(ti)) {
wct.setBounds(ti.token, null);
}
}
} else {
// Maximize, so move non-home secondary split first
for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
if (isHomeOrRecentTask(secondaryChildren.get(i))) {
continue;
}
wct.reparent(secondaryChildren.get(i).token, null /* parent */,
true /* onTop */);
}
// Find and place home tasks in-between. This simulates the fact that there was
// nothing behind the primary split's tasks.
for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
if (isHomeOrRecentTask(ti)) {
wct.reparent(ti.token, null /* parent */, true /* onTop */);
// reset bounds too
wct.setBounds(ti.token, null);
}
}
for (int i = primaryChildren.size() - 1; i >= 0; --i) {
wct.reparent(primaryChildren.get(i).token, null /* parent */,
true /* onTop */);
}
}
for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
wct.setBounds(freeHomeAndRecents.get(i).token, null);
}
// Reset focusable to true
wct.setFocusable(tiles.mPrimary.token, true /* focusable */);
WindowOrganizer.applyTransaction(wct);
}
}