App-pairs baseline commit
- AppPairs: Main interface used to pair and unpair tasks.
- AppPairsController: Main controller for app-pairs and implements
AppPairs interface.
- AppPair: Manages a singal instance of an app-pair consisting of a
root task and 2 other tasks that are paired together.
- AppPairPool: Helper object pool for AppPair class so we don't have to
wait for a root task creation from system_server when we need an
app-pair.
- adb shell commands for pairing and unpairing tasks
Test: adb shell commands for pair/unpair
adb shell dumpsys activity service SystemUIService WMShell \
pair taskId1 taskId2
adb shell dumpsys activity service SystemUIService WMShell \
unpair taskId
Test: AppPairTests, AppPairsPoolTests, AppPairControllerTests
Bug: 172704672
Change-Id: Ie0a70c1aa764ad83b763931501234c1960251ca4
This commit is contained in:
committed by
Jerry Chang
parent
15a494804c
commit
fe8c73b18d
@@ -79,6 +79,12 @@
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
|
||||
},
|
||||
"-742394458": {
|
||||
"message": "pair task1=%d task2=%d in AppPair=%s",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/apppairs\/AppPair.java"
|
||||
},
|
||||
"-710770147": {
|
||||
"message": "Add target: %s",
|
||||
"level": "VERBOSE",
|
||||
@@ -91,6 +97,12 @@
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
|
||||
},
|
||||
"-234284913": {
|
||||
"message": "unpair taskId=%d pair=%s",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
|
||||
},
|
||||
"-191422040": {
|
||||
"message": "Transition animations finished, notifying core %s",
|
||||
"level": "VERBOSE",
|
||||
@@ -139,12 +151,30 @@
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
|
||||
},
|
||||
"900599280": {
|
||||
"message": "Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b",
|
||||
"level": "ERROR",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/apppairs\/AppPair.java"
|
||||
},
|
||||
"950299522": {
|
||||
"message": "taskId %d isn't isn't in an app-pair.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
|
||||
},
|
||||
"980952660": {
|
||||
"message": "Task root back pressed taskId=%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
|
||||
},
|
||||
"1079041527": {
|
||||
"message": "incrementPool size=%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
|
||||
},
|
||||
"1104702476": {
|
||||
"message": "Letterbox Task Changed: #%d",
|
||||
"level": "VERBOSE",
|
||||
@@ -175,12 +205,24 @@
|
||||
"group": "WM_SHELL_DRAG_AND_DROP",
|
||||
"at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
|
||||
},
|
||||
"1891981945": {
|
||||
"message": "release entry.taskId=%s listener=%s size=%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
|
||||
},
|
||||
"1990759023": {
|
||||
"message": "addListenerForType types=%s listener=%s",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
|
||||
},
|
||||
"2006473416": {
|
||||
"message": "acquire entry.taskId=%s listener=%s size=%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
|
||||
},
|
||||
"2057038970": {
|
||||
"message": "Display changed: %d",
|
||||
"level": "VERBOSE",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.wm.shell;
|
||||
|
||||
import com.android.wm.shell.apppairs.AppPairs;
|
||||
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
|
||||
import com.android.wm.shell.onehanded.OneHanded;
|
||||
import com.android.wm.shell.pip.Pip;
|
||||
@@ -34,17 +35,20 @@ public class ShellDump {
|
||||
private final Optional<OneHanded> mOneHandedOptional;
|
||||
private final Optional<HideDisplayCutout> mHideDisplayCutout;
|
||||
private final ShellTaskOrganizer mShellTaskOrganizer;
|
||||
private final Optional<AppPairs> mAppPairsOptional;
|
||||
|
||||
public ShellDump(ShellTaskOrganizer shellTaskOrganizer,
|
||||
Optional<SplitScreen> splitScreenOptional,
|
||||
Optional<Pip> pipOptional,
|
||||
Optional<OneHanded> oneHandedOptional,
|
||||
Optional<HideDisplayCutout> hideDisplayCutout) {
|
||||
Optional<HideDisplayCutout> hideDisplayCutout,
|
||||
Optional<AppPairs> appPairsOptional) {
|
||||
mShellTaskOrganizer = shellTaskOrganizer;
|
||||
mSplitScreenOptional = splitScreenOptional;
|
||||
mPipOptional = pipOptional;
|
||||
mOneHandedOptional = oneHandedOptional;
|
||||
mHideDisplayCutout = hideDisplayCutout;
|
||||
mAppPairsOptional = appPairsOptional;
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw) {
|
||||
@@ -55,5 +59,8 @@ public class ShellDump {
|
||||
mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
|
||||
mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
|
||||
mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
|
||||
pw.println();
|
||||
pw.println();
|
||||
mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, ""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.wm.shell;
|
||||
|
||||
import com.android.wm.shell.apppairs.AppPairs;
|
||||
import com.android.wm.shell.common.DisplayImeController;
|
||||
import com.android.wm.shell.draganddrop.DragAndDropController;
|
||||
import com.android.wm.shell.splitscreen.SplitScreen;
|
||||
@@ -31,15 +32,18 @@ public class ShellInit {
|
||||
private final DragAndDropController mDragAndDropController;
|
||||
private final ShellTaskOrganizer mShellTaskOrganizer;
|
||||
private final Optional<SplitScreen> mSplitScreenOptional;
|
||||
private final Optional<AppPairs> mAppPairsOptional;
|
||||
|
||||
public ShellInit(DisplayImeController displayImeController,
|
||||
DragAndDropController dragAndDropController,
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
Optional<SplitScreen> splitScreenOptional) {
|
||||
Optional<SplitScreen> splitScreenOptional,
|
||||
Optional<AppPairs> appPairsOptional) {
|
||||
mDisplayImeController = displayImeController;
|
||||
mDragAndDropController = dragAndDropController;
|
||||
mShellTaskOrganizer = shellTaskOrganizer;
|
||||
mSplitScreenOptional = splitScreenOptional;
|
||||
mAppPairsOptional = appPairsOptional;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
@@ -47,6 +51,7 @@ public class ShellInit {
|
||||
mDisplayImeController.startMonitorDisplays();
|
||||
// Register the shell organizer
|
||||
mShellTaskOrganizer.registerOrganizer();
|
||||
mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
|
||||
// Bind the splitscreen impl to the drag drop controller
|
||||
mDragAndDropController.setSplitScreenController(mSplitScreenOptional);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.app.WindowConfiguration.WindowingMode;
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
@@ -39,6 +38,7 @@ import android.window.TaskAppearedInfo;
|
||||
import android.window.TaskOrganizer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
@@ -309,6 +309,15 @@ public class ShellTaskOrganizer extends TaskOrganizer {
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets running task by taskId. Returns {@code null} if no such task observed. */
|
||||
@Nullable
|
||||
public RunningTaskInfo getRunningTaskInfo(int taskId) {
|
||||
synchronized (mLock) {
|
||||
final TaskAppearedInfo info = mTasks.get(taskId);
|
||||
return info != null ? info.getTaskInfo() : null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
|
||||
TaskListener oldListener, TaskListener newListener) {
|
||||
if (oldListener == newListener) return false;
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
|
||||
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.graphics.Rect;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* An app-pairs consisting of {@link #mRootTaskInfo} that acts as the hierarchy parent of
|
||||
* {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair.
|
||||
* Also includes all UI for managing the pair like the divider.
|
||||
*/
|
||||
// TODO: Add divider
|
||||
// TODO: Handle display rotation
|
||||
class AppPair implements ShellTaskOrganizer.TaskListener {
|
||||
private static final String TAG = AppPair.class.getSimpleName();
|
||||
|
||||
private ActivityManager.RunningTaskInfo mRootTaskInfo;
|
||||
private SurfaceControl mRootTaskLeash;
|
||||
private ActivityManager.RunningTaskInfo mTaskInfo1;
|
||||
private SurfaceControl mTaskLeash1;
|
||||
private ActivityManager.RunningTaskInfo mTaskInfo2;
|
||||
private SurfaceControl mTaskLeash2;
|
||||
|
||||
private final AppPairsController mController;
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
|
||||
AppPair(AppPairsController controller) {
|
||||
mController = controller;
|
||||
mSyncQueue = controller.getSyncTransactionQueue();
|
||||
}
|
||||
|
||||
int getRootTaskId() {
|
||||
return mRootTaskInfo != null ? mRootTaskInfo.taskId : INVALID_TASK_ID;
|
||||
}
|
||||
|
||||
private int getTaskId1() {
|
||||
return mTaskInfo1 != null ? mTaskInfo1.taskId : INVALID_TASK_ID;
|
||||
}
|
||||
|
||||
private int getTaskId2() {
|
||||
return mTaskInfo2 != null ? mTaskInfo2.taskId : INVALID_TASK_ID;
|
||||
}
|
||||
|
||||
boolean contains(int taskId) {
|
||||
return taskId == getRootTaskId() || taskId == getTaskId1() || taskId == getTaskId2();
|
||||
}
|
||||
|
||||
boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
|
||||
ProtoLog.v(WM_SHELL_TASK_ORG, "pair task1=%d task2=%d in AppPair=%s",
|
||||
task1.taskId, task2.taskId, this);
|
||||
|
||||
if (!task1.isResizeable || !task2.isResizeable) {
|
||||
ProtoLog.e(WM_SHELL_TASK_ORG,
|
||||
"Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b",
|
||||
task1.isResizeable, task2.isResizeable);
|
||||
return false;
|
||||
}
|
||||
|
||||
mTaskInfo1 = task1;
|
||||
mTaskInfo2 = task2;
|
||||
|
||||
// TODO: properly calculate bounds for pairs.
|
||||
final Rect rootBounds = mRootTaskInfo.configuration.windowConfiguration.getBounds();
|
||||
final Rect bounds1 = new Rect(
|
||||
rootBounds.left, rootBounds.top, rootBounds.right / 2, rootBounds.bottom / 2);
|
||||
final Rect bounds2 = new Rect(
|
||||
bounds1.right, bounds1.bottom, rootBounds.right, rootBounds.bottom);
|
||||
final WindowContainerToken token1 = task1.token;
|
||||
final WindowContainerToken token2 = task2.token;
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
|
||||
wct.setHidden(mRootTaskInfo.token, false)
|
||||
.reparent(token1, mRootTaskInfo.token, true /* onTop */)
|
||||
.reparent(token2, mRootTaskInfo.token, true /* onTop */)
|
||||
.setWindowingMode(token1, WINDOWING_MODE_MULTI_WINDOW)
|
||||
.setWindowingMode(token2, WINDOWING_MODE_MULTI_WINDOW)
|
||||
.setBounds(token1, bounds1)
|
||||
.setBounds(token2, bounds2)
|
||||
// Moving the root task to top after the child tasks were repareted , or the root
|
||||
// task cannot be visible and focused.
|
||||
.reorder(mRootTaskInfo.token, true);
|
||||
mController.getTaskOrganizer().applyTransaction(wct);
|
||||
return true;
|
||||
}
|
||||
|
||||
void unpair() {
|
||||
final WindowContainerToken token1 = mTaskInfo1.token;
|
||||
final WindowContainerToken token2 = mTaskInfo2.token;
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
|
||||
// Reparent out of this container and reset windowing mode.
|
||||
wct.setHidden(mRootTaskInfo.token, true)
|
||||
.reorder(mRootTaskInfo.token, false)
|
||||
.reparent(token1, null, false /* onTop */)
|
||||
.reparent(token2, null, false /* onTop */)
|
||||
.setWindowingMode(token1, WINDOWING_MODE_UNDEFINED)
|
||||
.setWindowingMode(token2, WINDOWING_MODE_UNDEFINED);
|
||||
mController.getTaskOrganizer().applyTransaction(wct);
|
||||
|
||||
mTaskInfo1 = null;
|
||||
mTaskInfo2 = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
||||
if (mRootTaskInfo == null || taskInfo.taskId == mRootTaskInfo.taskId) {
|
||||
mRootTaskInfo = taskInfo;
|
||||
mRootTaskLeash = leash;
|
||||
} else if (taskInfo.taskId == getTaskId1()) {
|
||||
mTaskInfo1 = taskInfo;
|
||||
mTaskLeash1 = leash;
|
||||
} else if (taskInfo.taskId == getTaskId2()) {
|
||||
mTaskInfo2 = taskInfo;
|
||||
mTaskLeash2 = leash;
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown task=" + taskInfo.taskId);
|
||||
}
|
||||
|
||||
if (mTaskLeash1 == null || mTaskLeash2 == null) return;
|
||||
|
||||
// TODO: Is there more we need to do here?
|
||||
mSyncQueue.runInSync(t -> t
|
||||
.setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
|
||||
mTaskInfo1.positionInParent.y)
|
||||
.setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
|
||||
mTaskInfo2.positionInParent.y)
|
||||
.show(mRootTaskLeash)
|
||||
.show(mTaskLeash1)
|
||||
.show(mTaskLeash2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
if (taskInfo.taskId == getRootTaskId()) {
|
||||
mRootTaskInfo = taskInfo;
|
||||
} else if (taskInfo.taskId == getTaskId1()) {
|
||||
mTaskInfo1 = taskInfo;
|
||||
} else if (taskInfo.taskId == getTaskId2()) {
|
||||
mTaskInfo2 = taskInfo;
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown task=" + taskInfo.taskId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
if (taskInfo.taskId == getRootTaskId()) {
|
||||
// We don't want to release this object back to the pool since the root task went away.
|
||||
mController.unpair(mRootTaskInfo.taskId, false /* releaseToPool */);
|
||||
} else if (taskInfo.taskId == getTaskId1() || taskInfo.taskId == getTaskId2()) {
|
||||
mController.unpair(mRootTaskInfo.taskId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(@NonNull PrintWriter pw, String prefix) {
|
||||
final String innerPrefix = prefix + " ";
|
||||
final String childPrefix = innerPrefix + " ";
|
||||
pw.println(prefix + this);
|
||||
pw.println(innerPrefix + "Root taskId=" + getRootTaskId()
|
||||
+ " winMode=" + mRootTaskInfo.getWindowingMode());
|
||||
if (mTaskInfo1 != null) {
|
||||
pw.println(innerPrefix + "1 taskId=" + mTaskInfo1.taskId
|
||||
+ " winMode=" + mTaskInfo1.getWindowingMode());
|
||||
}
|
||||
if (mTaskInfo2 != null) {
|
||||
pw.println(innerPrefix + "2 taskId=" + mTaskInfo2.taskId
|
||||
+ " winMode=" + mTaskInfo2.getWindowingMode());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return TAG + "#" + getRootTaskId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Interface to engage app pairs feature.
|
||||
*/
|
||||
public interface AppPairs {
|
||||
/** Pairs indicated tasks. */
|
||||
boolean pair(int task1, int task2);
|
||||
/** Pairs indicated tasks. */
|
||||
boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2);
|
||||
/** Unpairs any app-pair containing this task id. */
|
||||
void unpair(int taskId);
|
||||
/** Dumps current status of app pairs. */
|
||||
void dump(@NonNull PrintWriter pw, String prefix);
|
||||
/** Called when the shell organizer has been registered. */
|
||||
void onOrganizerRegistered();
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Class manages app-pairs multitasking mode and implements the main interface {@link AppPairs}.
|
||||
*/
|
||||
public class AppPairsController implements AppPairs {
|
||||
private static final String TAG = AppPairsController.class.getSimpleName();
|
||||
|
||||
private final ShellTaskOrganizer mTaskOrganizer;
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
|
||||
private AppPairsPool mPairsPool;
|
||||
// Active app-pairs mapped by root task id key.
|
||||
private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
|
||||
|
||||
public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) {
|
||||
mTaskOrganizer = organizer;
|
||||
mSyncQueue = syncQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrganizerRegistered() {
|
||||
if (mPairsPool == null) {
|
||||
setPairsPool(new AppPairsPool(this));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setPairsPool(AppPairsPool pool) {
|
||||
mPairsPool = pool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pair(int taskId1, int taskId2) {
|
||||
final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
|
||||
final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
|
||||
if (task1 == null || task2 == null) {
|
||||
return false;
|
||||
}
|
||||
return pair(task1, task2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pair(ActivityManager.RunningTaskInfo task1,
|
||||
ActivityManager.RunningTaskInfo task2) {
|
||||
return pairInner(task1, task2) != null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
AppPair pairInner(
|
||||
@NonNull ActivityManager.RunningTaskInfo task1,
|
||||
@NonNull ActivityManager.RunningTaskInfo task2) {
|
||||
final AppPair pair = mPairsPool.acquire();
|
||||
if (!pair.pair(task1, task2)) {
|
||||
mPairsPool.release(pair);
|
||||
return null;
|
||||
}
|
||||
|
||||
mActiveAppPairs.put(pair.getRootTaskId(), pair);
|
||||
return pair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unpair(int taskId) {
|
||||
unpair(taskId, true /* releaseToPool */);
|
||||
}
|
||||
|
||||
void unpair(int taskId, boolean releaseToPool) {
|
||||
AppPair pair = mActiveAppPairs.get(taskId);
|
||||
if (pair == null) {
|
||||
for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
|
||||
final AppPair candidate = mActiveAppPairs.valueAt(i);
|
||||
if (candidate.contains(taskId)) {
|
||||
pair = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pair == null) {
|
||||
ProtoLog.v(WM_SHELL_TASK_ORG, "taskId %d isn't isn't in an app-pair.", taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
ProtoLog.v(WM_SHELL_TASK_ORG, "unpair taskId=%d pair=%s", taskId, pair);
|
||||
mActiveAppPairs.remove(pair.getRootTaskId());
|
||||
pair.unpair();
|
||||
if (releaseToPool) {
|
||||
mPairsPool.release(pair);
|
||||
}
|
||||
}
|
||||
|
||||
ShellTaskOrganizer getTaskOrganizer() {
|
||||
return mTaskOrganizer;
|
||||
}
|
||||
|
||||
SyncTransactionQueue getSyncTransactionQueue() {
|
||||
return mSyncQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(@NonNull PrintWriter pw, String prefix) {
|
||||
final String innerPrefix = prefix + " ";
|
||||
final String childPrefix = innerPrefix + " ";
|
||||
pw.println(prefix + this);
|
||||
|
||||
for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
|
||||
mActiveAppPairs.valueAt(i).dump(pw, childPrefix);
|
||||
}
|
||||
|
||||
if (mPairsPool != null) {
|
||||
mPairsPool.dump(pw, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return TAG + "#" + mActiveAppPairs.size();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class that manager pool of {@link AppPair} objects. Helps reduce the need to call system_server
|
||||
* to create a root task for the app-pair when needed since we always have one ready to go.
|
||||
*/
|
||||
class AppPairsPool {
|
||||
private static final String TAG = AppPairsPool.class.getSimpleName();
|
||||
|
||||
@VisibleForTesting
|
||||
final AppPairsController mController;
|
||||
// The pool
|
||||
private final ArrayList<AppPair> mPool = new ArrayList();
|
||||
|
||||
AppPairsPool(AppPairsController controller) {
|
||||
mController = controller;
|
||||
incrementPool();
|
||||
}
|
||||
|
||||
AppPair acquire() {
|
||||
final AppPair entry = mPool.remove(mPool.size() - 1);
|
||||
ProtoLog.v(WM_SHELL_TASK_ORG, "acquire entry.taskId=%s listener=%s size=%d",
|
||||
entry.getRootTaskId(), entry, mPool.size());
|
||||
if (mPool.size() == 0) {
|
||||
incrementPool();
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
void release(AppPair entry) {
|
||||
mPool.add(entry);
|
||||
ProtoLog.v(WM_SHELL_TASK_ORG, "release entry.taskId=%s listener=%s size=%d",
|
||||
entry.getRootTaskId(), entry, mPool.size());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void incrementPool() {
|
||||
ProtoLog.v(WM_SHELL_TASK_ORG, "incrementPool size=%d", mPool.size());
|
||||
final AppPair entry = new AppPair(mController);
|
||||
// TODO: multi-display...
|
||||
mController.getTaskOrganizer().createRootTask(
|
||||
DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN, entry);
|
||||
mPool.add(entry);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int poolSize() {
|
||||
return mPool.size();
|
||||
}
|
||||
|
||||
public void dump(@NonNull PrintWriter pw, String prefix) {
|
||||
final String innerPrefix = prefix + " ";
|
||||
final String childPrefix = innerPrefix + " ";
|
||||
pw.println(prefix + this);
|
||||
for (int i = mPool.size() - 1; i >= 0; --i) {
|
||||
mPool.get(i).dump(pw, childPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return TAG + "#" + mPool.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** Tests for {@link AppPair} */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AppPairTests {
|
||||
|
||||
private AppPairsController mController;
|
||||
@Mock private SyncTransactionQueue mSyncQueue;
|
||||
@Mock private ShellTaskOrganizer mTaskOrganizer;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mController = new TestAppPairsController(mTaskOrganizer, mSyncQueue);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {}
|
||||
|
||||
@Test
|
||||
public void testContains() {
|
||||
final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
|
||||
final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
|
||||
|
||||
final AppPair pair = mController.pairInner(task1, task2);
|
||||
assertThat(pair.contains(task1.taskId)).isTrue();
|
||||
assertThat(pair.contains(task2.taskId)).isTrue();
|
||||
|
||||
pair.unpair();
|
||||
assertThat(pair.contains(task1.taskId)).isFalse();
|
||||
assertThat(pair.contains(task2.taskId)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVanishUnpairs() {
|
||||
final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
|
||||
final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
|
||||
|
||||
final AppPair pair = mController.pairInner(task1, task2);
|
||||
assertThat(pair.contains(task1.taskId)).isTrue();
|
||||
assertThat(pair.contains(task2.taskId)).isTrue();
|
||||
|
||||
pair.onTaskVanished(task1);
|
||||
assertThat(pair.contains(task1.taskId)).isFalse();
|
||||
assertThat(pair.contains(task2.taskId)).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** Tests for {@link AppPairsController} */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AppPairsControllerTests {
|
||||
private TestAppPairsController mController;
|
||||
private TestAppPairsPool mPool;
|
||||
@Mock private SyncTransactionQueue mSyncQueue;
|
||||
@Mock private ShellTaskOrganizer mTaskOrganizer;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mController = new TestAppPairsController(mTaskOrganizer, mSyncQueue);
|
||||
mPool = mController.getPool();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {}
|
||||
|
||||
@Test
|
||||
public void testPairUnpair() {
|
||||
final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
|
||||
final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
|
||||
|
||||
final AppPair pair = mController.pairInner(task1, task2);
|
||||
assertThat(pair.contains(task1.taskId)).isTrue();
|
||||
assertThat(pair.contains(task2.taskId)).isTrue();
|
||||
assertThat(mPool.poolSize()).isGreaterThan(0);
|
||||
|
||||
mController.unpair(task2.taskId);
|
||||
assertThat(pair.contains(task1.taskId)).isFalse();
|
||||
assertThat(pair.contains(task2.taskId)).isFalse();
|
||||
assertThat(mPool.poolSize()).isGreaterThan(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnpair_DontReleaseToPool() {
|
||||
final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
|
||||
final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
|
||||
|
||||
final AppPair pair = mController.pairInner(task1, task2);
|
||||
assertThat(pair.contains(task1.taskId)).isTrue();
|
||||
assertThat(pair.contains(task2.taskId)).isTrue();
|
||||
|
||||
mController.unpair(task2.taskId, false /* releaseToPool */);
|
||||
assertThat(pair.contains(task1.taskId)).isFalse();
|
||||
assertThat(pair.contains(task2.taskId)).isFalse();
|
||||
assertThat(mPool.poolSize()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** Tests for {@link AppPairsPool} */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AppPairsPoolTests {
|
||||
private TestAppPairsController mController;
|
||||
private TestAppPairsPool mPool;
|
||||
@Mock private SyncTransactionQueue mSyncQueue;
|
||||
@Mock private ShellTaskOrganizer mTaskOrganizer;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mController = new TestAppPairsController(mTaskOrganizer, mSyncQueue);
|
||||
mPool = mController.getPool();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {}
|
||||
|
||||
@Test
|
||||
public void testInitialState() {
|
||||
// Pool should always start off with at least 1 entry.
|
||||
assertThat(mPool.poolSize()).isGreaterThan(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcquireRelease() {
|
||||
assertThat(mPool.poolSize()).isGreaterThan(0);
|
||||
final AppPair appPair = mPool.acquire();
|
||||
assertThat(mPool.poolSize()).isGreaterThan(0);
|
||||
mPool.release(appPair);
|
||||
assertThat(mPool.poolSize()).isGreaterThan(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
|
||||
public class TestAppPairsController extends AppPairsController {
|
||||
TestAppPairsPool mPool;
|
||||
|
||||
public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) {
|
||||
super(organizer, syncQueue);
|
||||
mPool = new TestAppPairsPool(this);
|
||||
setPairsPool(mPool);
|
||||
}
|
||||
|
||||
TestAppPairsPool getPool() {
|
||||
return mPool;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
|
||||
public class TestAppPairsPool extends AppPairsPool{
|
||||
TestAppPairsPool(AppPairsController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
void incrementPool() {
|
||||
final AppPair entry = new AppPair(mController);
|
||||
final ActivityManager.RunningTaskInfo info =
|
||||
new TestRunningTaskInfoBuilder().build();
|
||||
entry.onTaskAppeared(info, null /* leash */);
|
||||
release(entry);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.wm.shell.apppairs;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.graphics.Rect;
|
||||
import android.window.IWindowContainerToken;
|
||||
import android.window.WindowContainerToken;
|
||||
|
||||
public class TestRunningTaskInfoBuilder {
|
||||
static int sNextTaskId = 500;
|
||||
private Rect mBounds = new Rect(0, 0, 100, 100);
|
||||
private WindowContainerToken mToken =
|
||||
new WindowContainerToken(new IWindowContainerToken.Default());
|
||||
|
||||
TestRunningTaskInfoBuilder setBounds(Rect bounds) {
|
||||
mBounds.set(bounds);
|
||||
return this;
|
||||
}
|
||||
|
||||
ActivityManager.RunningTaskInfo build() {
|
||||
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
|
||||
info.taskId = sNextTaskId++;
|
||||
info.configuration.windowConfiguration.setBounds(mBounds);
|
||||
info.token = mToken;
|
||||
info.isResizeable = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,8 @@ public class SystemUIFactory {
|
||||
.setOneHanded(mWMComponent.getOneHanded())
|
||||
.setBubbles(mWMComponent.getBubbles())
|
||||
.setHideDisplayCutout(mWMComponent.getHideDisplayCutout())
|
||||
.setShellDump(mWMComponent.getShellDump());
|
||||
.setShellDump(mWMComponent.getShellDump())
|
||||
.setAppPairs(mWMComponent.getAppPairs());
|
||||
} else {
|
||||
// TODO: Call on prepareSysUIComponentBuilder but not with real components.
|
||||
builder = builder.setPip(Optional.ofNullable(null))
|
||||
@@ -118,7 +119,8 @@ public class SystemUIFactory {
|
||||
.setOneHanded(Optional.ofNullable(null))
|
||||
.setBubbles(Optional.ofNullable(null))
|
||||
.setHideDisplayCutout(Optional.ofNullable(null))
|
||||
.setShellDump(Optional.ofNullable(null));
|
||||
.setShellDump(Optional.ofNullable(null))
|
||||
.setAppPairs(Optional.ofNullable(null));
|
||||
}
|
||||
mSysUIComponent = builder.build();
|
||||
if (initializeComponents) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.android.systemui.keyguard.KeyguardSliceProvider;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.util.InjectionInflationController;
|
||||
import com.android.wm.shell.ShellDump;
|
||||
import com.android.wm.shell.apppairs.AppPairs;
|
||||
import com.android.wm.shell.bubbles.Bubbles;
|
||||
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
|
||||
import com.android.wm.shell.onehanded.OneHanded;
|
||||
@@ -60,6 +61,9 @@ public interface SysUIComponent {
|
||||
@BindsInstance
|
||||
Builder setSplitScreen(Optional<SplitScreen> s);
|
||||
|
||||
@BindsInstance
|
||||
Builder setAppPairs(Optional<AppPairs> s);
|
||||
|
||||
@BindsInstance
|
||||
Builder setOneHanded(Optional<OneHanded> o);
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.dagger;
|
||||
import com.android.systemui.wmshell.WMShellModule;
|
||||
import com.android.wm.shell.ShellDump;
|
||||
import com.android.wm.shell.ShellInit;
|
||||
import com.android.wm.shell.apppairs.AppPairs;
|
||||
import com.android.wm.shell.bubbles.Bubbles;
|
||||
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
|
||||
import com.android.wm.shell.onehanded.OneHanded;
|
||||
@@ -70,6 +71,9 @@ public interface WMComponent {
|
||||
@WMSingleton
|
||||
Optional<SplitScreen> getSplitScreen();
|
||||
|
||||
@WMSingleton
|
||||
Optional<AppPairs> getAppPairs();
|
||||
|
||||
@WMSingleton
|
||||
Optional<Bubbles> getBubbles();
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ import com.android.systemui.statusbar.policy.UserInfoController;
|
||||
import com.android.systemui.tracing.ProtoTracer;
|
||||
import com.android.systemui.tracing.nano.SystemUiTraceProto;
|
||||
import com.android.wm.shell.ShellDump;
|
||||
import com.android.wm.shell.apppairs.AppPairs;
|
||||
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
|
||||
import com.android.wm.shell.nano.WmShellTraceProto;
|
||||
import com.android.wm.shell.onehanded.OneHanded;
|
||||
@@ -98,6 +99,7 @@ public final class WMShell extends SystemUI
|
||||
private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
|
||||
private final ProtoTracer mProtoTracer;
|
||||
private final Optional<ShellDump> mShellDump;
|
||||
private final Optional<AppPairs> mAppPairsOptional;
|
||||
|
||||
private boolean mIsSysUiStateValid;
|
||||
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
|
||||
@@ -116,7 +118,8 @@ public final class WMShell extends SystemUI
|
||||
Optional<OneHanded> oneHandedOptional,
|
||||
Optional<HideDisplayCutout> hideDisplayCutoutOptional,
|
||||
ProtoTracer protoTracer,
|
||||
Optional<ShellDump> shellDump) {
|
||||
Optional<ShellDump> shellDump,
|
||||
Optional<AppPairs> appPairsOptional) {
|
||||
super(context);
|
||||
mCommandQueue = commandQueue;
|
||||
mConfigurationController = configurationController;
|
||||
@@ -131,6 +134,7 @@ public final class WMShell extends SystemUI
|
||||
mProtoTracer = protoTracer;
|
||||
mProtoTracer.add(this);
|
||||
mShellDump = shellDump;
|
||||
mAppPairsOptional = appPairsOptional;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -335,6 +339,21 @@ public final class WMShell extends SystemUI
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case "pair": {
|
||||
String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
|
||||
final int taskId1 = new Integer(groups[0]);
|
||||
final int taskId2 = new Integer(groups[1]);
|
||||
mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2));
|
||||
return true;
|
||||
}
|
||||
|
||||
case "unpair": {
|
||||
String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
|
||||
final int taskId = new Integer(groups[0]);
|
||||
mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.android.wm.shell.ShellDump;
|
||||
import com.android.wm.shell.ShellInit;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.WindowManagerShellWrapper;
|
||||
import com.android.wm.shell.apppairs.AppPairs;
|
||||
import com.android.wm.shell.bubbles.BubbleController;
|
||||
import com.android.wm.shell.bubbles.Bubbles;
|
||||
import com.android.wm.shell.common.AnimationThread;
|
||||
@@ -75,11 +76,13 @@ public abstract class WMShellBaseModule {
|
||||
static ShellInit provideShellInit(DisplayImeController displayImeController,
|
||||
DragAndDropController dragAndDropController,
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
Optional<SplitScreen> splitScreenOptional) {
|
||||
Optional<SplitScreen> splitScreenOptional,
|
||||
Optional<AppPairs> appPairsOptional) {
|
||||
return new ShellInit(displayImeController,
|
||||
dragAndDropController,
|
||||
shellTaskOrganizer,
|
||||
splitScreenOptional);
|
||||
splitScreenOptional,
|
||||
appPairsOptional);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,9 +95,10 @@ public abstract class WMShellBaseModule {
|
||||
Optional<SplitScreen> splitScreenOptional,
|
||||
Optional<Pip> pipOptional,
|
||||
Optional<OneHanded> oneHandedOptional,
|
||||
Optional<HideDisplayCutout> hideDisplayCutout) {
|
||||
Optional<HideDisplayCutout> hideDisplayCutout,
|
||||
Optional<AppPairs> appPairsOptional) {
|
||||
return Optional.of(new ShellDump(shellTaskOrganizer, splitScreenOptional, pipOptional,
|
||||
oneHandedOptional, hideDisplayCutout));
|
||||
oneHandedOptional, hideDisplayCutout, appPairsOptional));
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@@ -187,6 +191,9 @@ public abstract class WMShellBaseModule {
|
||||
@BindsOptionalOf
|
||||
abstract SplitScreen optionalSplitScreen();
|
||||
|
||||
@BindsOptionalOf
|
||||
abstract AppPairs optionalAppPairs();
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static Optional<Bubbles> provideBubbles(Context context,
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.android.systemui.dagger.WMSingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.WindowManagerShellWrapper;
|
||||
import com.android.wm.shell.apppairs.AppPairs;
|
||||
import com.android.wm.shell.apppairs.AppPairsController;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.DisplayImeController;
|
||||
import com.android.wm.shell.common.FloatingContentCoordinator;
|
||||
@@ -79,6 +81,13 @@ public class WMShellModule {
|
||||
taskStackListener);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static AppPairs provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
|
||||
SyncTransactionQueue syncQueue) {
|
||||
return new AppPairsController(shellTaskOrganizer, syncQueue);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static Optional<Pip> providePip(Context context, DisplayController displayController,
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.tracing.ProtoTracer;
|
||||
import com.android.wm.shell.ShellDump;
|
||||
import com.android.wm.shell.apppairs.AppPairs;
|
||||
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
|
||||
import com.android.wm.shell.onehanded.OneHanded;
|
||||
import com.android.wm.shell.onehanded.OneHandedGestureHandler;
|
||||
@@ -68,6 +69,7 @@ public class WMShellTest extends SysuiTestCase {
|
||||
@Mock HideDisplayCutout mHideDisplayCutout;
|
||||
@Mock ProtoTracer mProtoTracer;
|
||||
@Mock ShellDump mShellDump;
|
||||
@Mock AppPairs mAppPairs;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -77,7 +79,7 @@ public class WMShellTest extends SysuiTestCase {
|
||||
mKeyguardUpdateMonitor, mNavigationModeController,
|
||||
mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen),
|
||||
Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), mProtoTracer,
|
||||
Optional.of(mShellDump));
|
||||
Optional.of(mShellDump), Optional.of(mAppPairs));
|
||||
|
||||
when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
|
||||
}
|
||||
|
||||
@@ -5888,7 +5888,21 @@ class Task extends WindowContainer<WindowContainer> {
|
||||
try {
|
||||
// Protect against recursion.
|
||||
mInResumeTopActivity = true;
|
||||
result = resumeTopActivityInnerLocked(prev, options);
|
||||
|
||||
// TODO(b/172885410): Allow the top activities of all visible leaf tasks to be resumed
|
||||
if (mCreatedByOrganizer && !isLeafTask()
|
||||
&& getConfiguration().windowConfiguration.getWindowingMode()
|
||||
== WINDOWING_MODE_FULLSCREEN) {
|
||||
for (int i = mChildren.size() - 1; i >= 0; i--) {
|
||||
final Task child = (Task) getChildAt(i);
|
||||
if (!child.shouldBeVisible(null /* starting */)) {
|
||||
break;
|
||||
}
|
||||
result |= child.resumeTopActivityUncheckedLocked(prev, options);
|
||||
}
|
||||
} else {
|
||||
result = resumeTopActivityInnerLocked(prev, options);
|
||||
}
|
||||
|
||||
// When resuming the top activity, it may be necessary to pause the top activity (for
|
||||
// example, returning to the lock screen. We suppress the normal pause logic in
|
||||
|
||||
@@ -434,8 +434,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
|
||||
|
||||
if (hop.isReparent()) {
|
||||
final boolean isNonOrganizedRootableTask =
|
||||
(task.isRootTask() && !task.mCreatedByOrganizer)
|
||||
|| task.getParent().asTask().mCreatedByOrganizer;
|
||||
task.isRootTask() || task.getParent().asTask().mCreatedByOrganizer;
|
||||
if (isNonOrganizedRootableTask) {
|
||||
WindowContainer newParent = hop.getNewParent() == null
|
||||
? dc.getDefaultTaskDisplayArea()
|
||||
|
||||
Reference in New Issue
Block a user