Make positioning in letterbox mode configurable.
Changes: - Allow to specify in config_letterboxPortraitGravity and config_letterboxLandscapeGravity desired alignment for letterboxed apps. - Add ADB commands to allow overriding these config values for testing - Move positioning logic for letterbox mode from WM Core to WM Shell. Test: atest LetterboxTaskListenerTest LetterboxConfigControllerTest Test: go/wm-smoke Test: manual with adb shell dumpsys activity service SystemUIService WMShell (get)set-letterbox-portrait-gravity / (get)set-letterbox-landscape-gravity Fix: 170216257, 170215849 Change-Id: I8e6392faab05bec1bf61b9691f3bd9b8b09b9621
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"messages": {
|
||||
"-1993693214": {
|
||||
"message": "Letterbox Task Changed: #%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/letterbox\/LetterboxTaskListener.java"
|
||||
},
|
||||
"-1683614271": {
|
||||
"message": "Existing task: id=%d component=%s",
|
||||
"level": "VERBOSE",
|
||||
@@ -67,12 +73,6 @@
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
|
||||
},
|
||||
"-848099324": {
|
||||
"message": "Letterbox Task Appeared: #%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/LetterboxTaskListener.java"
|
||||
},
|
||||
"-842742255": {
|
||||
"message": "%s onTaskAppeared unknown taskId=%d winMode=%d",
|
||||
"level": "VERBOSE",
|
||||
@@ -97,6 +97,12 @@
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
|
||||
},
|
||||
"-342975160": {
|
||||
"message": "Letterbox Task Vanished: #%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/letterbox\/LetterboxTaskListener.java"
|
||||
},
|
||||
"-234284913": {
|
||||
"message": "unpair taskId=%d pair=%s",
|
||||
"level": "VERBOSE",
|
||||
@@ -175,24 +181,12 @@
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
|
||||
},
|
||||
"1104702476": {
|
||||
"message": "Letterbox Task Changed: #%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/LetterboxTaskListener.java"
|
||||
},
|
||||
"1184615936": {
|
||||
"message": "Set drop target window visibility: displayId=%d visibility=%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_DRAG_AND_DROP",
|
||||
"at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
|
||||
},
|
||||
"1218010718": {
|
||||
"message": "Letterbox Task Vanished: #%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/LetterboxTaskListener.java"
|
||||
},
|
||||
"1481772149": {
|
||||
"message": "Current target: %s",
|
||||
"level": "VERBOSE",
|
||||
@@ -205,6 +199,12 @@
|
||||
"group": "WM_SHELL_DRAG_AND_DROP",
|
||||
"at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
|
||||
},
|
||||
"1885882094": {
|
||||
"message": "Letterbox Task Appeared: #%d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_SHELL_TASK_ORG",
|
||||
"at": "com\/android\/wm\/shell\/letterbox\/LetterboxTaskListener.java"
|
||||
},
|
||||
"1891981945": {
|
||||
"message": "release entry.taskId=%s listener=%s size=%d",
|
||||
"level": "VERBOSE",
|
||||
|
||||
@@ -36,4 +36,16 @@
|
||||
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
|
||||
when the PIP menu is shown in center. -->
|
||||
<string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>
|
||||
|
||||
<!-- Gravity of letterboxed apps in portrait screen orientation.
|
||||
Can be Gravity.TOP, Gravity.CENTER or Gravity.BOTTOM.
|
||||
Any other value will result in runtime exception for a letterboxed activity.
|
||||
Default is Gravity.TOP. -->
|
||||
<integer name="config_letterboxPortraitGravity">0x00000030</integer>
|
||||
|
||||
<!-- Gravity of letterboxed apps in landscape screen orientation.
|
||||
Can be Gravity.LEFT, Gravity.CENTER or Gravity.RIGHT.
|
||||
Any other value will result in runtime exception for a letterboxed activity.
|
||||
Default is Gravity.CENTER. -->
|
||||
<integer name="config_letterboxLandscapeGravity">0x00000011</integer>
|
||||
</resources>
|
||||
|
||||
@@ -32,14 +32,17 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
/**
|
||||
* Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
|
||||
*/
|
||||
public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
private static final String TAG = "FullscreenTaskListener";
|
||||
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
|
||||
private final ArraySet<Integer> mTasks = new ArraySet<>();
|
||||
|
||||
FullscreenTaskListener(SyncTransactionQueue syncQueue) {
|
||||
public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
|
||||
mSyncQueue = syncQueue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.protolog.ShellProtoLogGroup;
|
||||
|
||||
/**
|
||||
* Organizes a task in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN} when
|
||||
* it's presented in the letterbox mode either because orientations of a top activity and a device
|
||||
* don't match or because a top activity is in a size compat mode.
|
||||
*/
|
||||
final class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
private static final String TAG = "LetterboxTaskListener";
|
||||
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
|
||||
private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>();
|
||||
|
||||
LetterboxTaskListener(SyncTransactionQueue syncQueue) {
|
||||
mSyncQueue = syncQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
||||
synchronized (mLeashByTaskId) {
|
||||
if (mLeashByTaskId.get(taskInfo.taskId) != null) {
|
||||
throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
|
||||
}
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Appeared: #%d",
|
||||
taskInfo.taskId);
|
||||
mLeashByTaskId.put(taskInfo.taskId, leash);
|
||||
final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds();
|
||||
final Rect activtyBounds = taskInfo.letterboxActivityBounds;
|
||||
final Point taskPositionInParent = taskInfo.positionInParent;
|
||||
mSyncQueue.runInSync(t -> {
|
||||
setPositionAndWindowCrop(
|
||||
t, leash, activtyBounds, taskBounds, taskPositionInParent);
|
||||
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
|
||||
t.setAlpha(leash, 1f);
|
||||
t.setMatrix(leash, 1, 0, 0, 1);
|
||||
t.show(leash);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
synchronized (mLeashByTaskId) {
|
||||
if (mLeashByTaskId.get(taskInfo.taskId) == null) {
|
||||
Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
|
||||
return;
|
||||
}
|
||||
mLeashByTaskId.remove(taskInfo.taskId);
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Vanished: #%d",
|
||||
taskInfo.taskId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
synchronized (mLeashByTaskId) {
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Changed: #%d",
|
||||
taskInfo.taskId);
|
||||
final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
|
||||
final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds();
|
||||
final Rect activtyBounds = taskInfo.letterboxActivityBounds;
|
||||
final Point taskPositionInParent = taskInfo.positionInParent;
|
||||
mSyncQueue.runInSync(t -> {
|
||||
setPositionAndWindowCrop(
|
||||
t, leash, activtyBounds, taskBounds, taskPositionInParent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void setPositionAndWindowCrop(
|
||||
SurfaceControl.Transaction transaction,
|
||||
SurfaceControl leash,
|
||||
final Rect activityBounds,
|
||||
final Rect taskBounds,
|
||||
final Point taskPositionInParent) {
|
||||
Rect activtyInTaskCoordinates = new Rect(activityBounds);
|
||||
activtyInTaskCoordinates.offset(-taskBounds.left, -taskBounds.top);
|
||||
transaction.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
|
||||
transaction.setWindowCrop(leash, activtyInTaskCoordinates);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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;
|
||||
|
||||
import android.view.Gravity;
|
||||
|
||||
import com.android.wm.shell.apppairs.AppPairs;
|
||||
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
|
||||
import com.android.wm.shell.letterbox.LetterboxConfigController;
|
||||
import com.android.wm.shell.onehanded.OneHanded;
|
||||
import com.android.wm.shell.pip.Pip;
|
||||
import com.android.wm.shell.splitscreen.SplitScreen;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* An entry point into the shell for dumping shell internal state and running adb commands.
|
||||
*
|
||||
* Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
|
||||
*/
|
||||
public final class ShellCommandHandler {
|
||||
|
||||
private final Optional<SplitScreen> mSplitScreenOptional;
|
||||
private final Optional<Pip> mPipOptional;
|
||||
private final Optional<OneHanded> mOneHandedOptional;
|
||||
private final Optional<HideDisplayCutout> mHideDisplayCutout;
|
||||
private final ShellTaskOrganizer mShellTaskOrganizer;
|
||||
private final Optional<AppPairs> mAppPairsOptional;
|
||||
private final LetterboxConfigController mLetterboxConfigController;
|
||||
|
||||
public ShellCommandHandler(
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
Optional<SplitScreen> splitScreenOptional,
|
||||
Optional<Pip> pipOptional,
|
||||
Optional<OneHanded> oneHandedOptional,
|
||||
Optional<HideDisplayCutout> hideDisplayCutout,
|
||||
Optional<AppPairs> appPairsOptional,
|
||||
LetterboxConfigController letterboxConfigController) {
|
||||
mShellTaskOrganizer = shellTaskOrganizer;
|
||||
mSplitScreenOptional = splitScreenOptional;
|
||||
mPipOptional = pipOptional;
|
||||
mOneHandedOptional = oneHandedOptional;
|
||||
mHideDisplayCutout = hideDisplayCutout;
|
||||
mAppPairsOptional = appPairsOptional;
|
||||
mLetterboxConfigController = letterboxConfigController;
|
||||
}
|
||||
|
||||
/** Dumps WM Shell internal state. */
|
||||
public void dump(PrintWriter pw) {
|
||||
mShellTaskOrganizer.dump(pw, "");
|
||||
pw.println();
|
||||
pw.println();
|
||||
mPipOptional.ifPresent(pip -> pip.dump(pw));
|
||||
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, ""));
|
||||
}
|
||||
|
||||
|
||||
/** Returns {@code true} if command was found and executed. */
|
||||
public boolean handleCommand(String[] args, PrintWriter pw) {
|
||||
if (args.length < 2) {
|
||||
// Argument at position 0 is "WMShell".
|
||||
return false;
|
||||
}
|
||||
switch (args[1]) {
|
||||
case "set-letterbox-portrait-gravity":
|
||||
return runSetLetterboxPortraitGravity(args, pw);
|
||||
case "get-letterbox-portrait-gravity":
|
||||
return runGetLetterboxPortraitGravity(pw);
|
||||
case "set-letterbox-landscape-gravity":
|
||||
return runSetLetterboxLandscapeGravity(args, pw);
|
||||
case "get-letterbox-landscape-gravity":
|
||||
return runGetLetterboxLandscapeGravity(pw);
|
||||
case "pair":
|
||||
return runPair(args, pw);
|
||||
case "unpair":
|
||||
return runUnpair(args, pw);
|
||||
case "help":
|
||||
return runHelp(pw);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean runSetLetterboxPortraitGravity(String[] args, PrintWriter pw) {
|
||||
if (args.length < 3) {
|
||||
// First two arguments are "WMShell" and command name.
|
||||
pw.println("Error: reset, TOP, CENTER or BOTTOM should be provided as an argument");
|
||||
return true;
|
||||
}
|
||||
switch (args[2]) {
|
||||
case "reset":
|
||||
mLetterboxConfigController.resetPortraitGravity();
|
||||
break;
|
||||
case "TOP":
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.TOP);
|
||||
break;
|
||||
case "CENTER":
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.CENTER);
|
||||
break;
|
||||
case "BOTTOM":
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM);
|
||||
break;
|
||||
default:
|
||||
pw.println("Error: expected reset, TOP, CENTER or BOTTOM but got " + args[2]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runGetLetterboxPortraitGravity(PrintWriter pw) {
|
||||
final int gravity = mLetterboxConfigController.getPortraitGravity();
|
||||
switch (gravity) {
|
||||
case Gravity.TOP:
|
||||
pw.println("TOP");
|
||||
break;
|
||||
case Gravity.CENTER:
|
||||
pw.println("CENTER");
|
||||
break;
|
||||
case Gravity.BOTTOM:
|
||||
pw.println("BOTTOM");
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unexpected gravity: " + gravity);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runSetLetterboxLandscapeGravity(String[] args, PrintWriter pw) {
|
||||
if (args.length < 3) {
|
||||
// First two arguments are "WMShell" and command name.
|
||||
pw.println("Error: reset, LEFT, CENTER or RIGHT should be provided as an argument");
|
||||
return false;
|
||||
}
|
||||
switch (args[2]) {
|
||||
case "reset":
|
||||
mLetterboxConfigController.resetLandscapeGravity();
|
||||
break;
|
||||
case "LEFT":
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.LEFT);
|
||||
break;
|
||||
case "CENTER":
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER);
|
||||
break;
|
||||
case "RIGHT":
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.RIGHT);
|
||||
break;
|
||||
default:
|
||||
pw.println(
|
||||
"Error: expected reset, LEFT, CENTER or RIGHT but got " + args[2]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runGetLetterboxLandscapeGravity(PrintWriter pw) {
|
||||
final int gravity = mLetterboxConfigController.getLandscapeGravity();
|
||||
switch (gravity) {
|
||||
case Gravity.LEFT:
|
||||
pw.println("LEFT");
|
||||
break;
|
||||
case Gravity.CENTER:
|
||||
pw.println("CENTER");
|
||||
break;
|
||||
case Gravity.RIGHT:
|
||||
pw.println("RIGHT");
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unexpected gravity: " + gravity);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runPair(String[] args, PrintWriter pw) {
|
||||
if (args.length < 4) {
|
||||
// First two arguments are "WMShell" and command name.
|
||||
pw.println("Error: two task ids should be provided as arguments");
|
||||
return false;
|
||||
}
|
||||
final int taskId1 = new Integer(args[2]);
|
||||
final int taskId2 = new Integer(args[3]);
|
||||
mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runUnpair(String[] args, PrintWriter pw) {
|
||||
if (args.length < 3) {
|
||||
// First two arguments are "WMShell" and command name.
|
||||
pw.println("Error: task id should be provided as an argument");
|
||||
return false;
|
||||
}
|
||||
final int taskId = new Integer(args[2]);
|
||||
mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runHelp(PrintWriter pw) {
|
||||
pw.println("Window Manager Shell commands:");
|
||||
pw.println(" help");
|
||||
pw.println(" Print this help text.");
|
||||
pw.println(" <no arguments provided>");
|
||||
pw.println(" Dump Window Manager Shell internal state");
|
||||
pw.println(" set-letterbox-portrait-gravity [reset|TOP|CENTER|BOTTOM]");
|
||||
pw.println(" get-letterbox-portrait-gravity");
|
||||
pw.println(" Set, reset or print letterbox gravity for portrait screen mode.");
|
||||
pw.println(" set-letterbox-landscape-gravity [reset|LEFT|CENTER|RIGHT]");
|
||||
pw.println(" get-letterbox-landscape-gravity");
|
||||
pw.println(" Set, reset or print letterbox gravity for landscape screen mode.");
|
||||
pw.println(" pair <taskId1> <taskId2>");
|
||||
pw.println(" unpair <taskId>");
|
||||
pw.println(" Pairs/unpairs tasks with given ids.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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;
|
||||
|
||||
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;
|
||||
import com.android.wm.shell.splitscreen.SplitScreen;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* An entry point into the shell for dumping shell internal state.
|
||||
*/
|
||||
public class ShellDump {
|
||||
|
||||
private final Optional<SplitScreen> mSplitScreenOptional;
|
||||
private final Optional<Pip> mPipOptional;
|
||||
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<AppPairs> appPairsOptional) {
|
||||
mShellTaskOrganizer = shellTaskOrganizer;
|
||||
mSplitScreenOptional = splitScreenOptional;
|
||||
mPipOptional = pipOptional;
|
||||
mOneHandedOptional = oneHandedOptional;
|
||||
mHideDisplayCutout = hideDisplayCutout;
|
||||
mAppPairsOptional = appPairsOptional;
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw) {
|
||||
mShellTaskOrganizer.dump(pw, "");
|
||||
pw.println();
|
||||
pw.println();
|
||||
mPipOptional.ifPresent(pip -> pip.dump(pw));
|
||||
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,9 +16,13 @@
|
||||
|
||||
package com.android.wm.shell;
|
||||
|
||||
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
|
||||
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_LETTERBOX;
|
||||
|
||||
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.letterbox.LetterboxTaskListener;
|
||||
import com.android.wm.shell.splitscreen.SplitScreen;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -33,24 +37,36 @@ public class ShellInit {
|
||||
private final ShellTaskOrganizer mShellTaskOrganizer;
|
||||
private final Optional<SplitScreen> mSplitScreenOptional;
|
||||
private final Optional<AppPairs> mAppPairsOptional;
|
||||
private final LetterboxTaskListener mLetterboxTaskListener;
|
||||
private final FullscreenTaskListener mFullscreenTaskListener;
|
||||
|
||||
public ShellInit(DisplayImeController displayImeController,
|
||||
DragAndDropController dragAndDropController,
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
Optional<SplitScreen> splitScreenOptional,
|
||||
Optional<AppPairs> appPairsOptional) {
|
||||
Optional<AppPairs> appPairsOptional,
|
||||
LetterboxTaskListener letterboxTaskListener,
|
||||
FullscreenTaskListener fullscreenTaskListener) {
|
||||
mDisplayImeController = displayImeController;
|
||||
mDragAndDropController = dragAndDropController;
|
||||
mShellTaskOrganizer = shellTaskOrganizer;
|
||||
mSplitScreenOptional = splitScreenOptional;
|
||||
mAppPairsOptional = appPairsOptional;
|
||||
mLetterboxTaskListener = letterboxTaskListener;
|
||||
mFullscreenTaskListener = fullscreenTaskListener;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
// Start listening for display changes
|
||||
mDisplayImeController.startMonitorDisplays();
|
||||
|
||||
mShellTaskOrganizer.addListenerForType(
|
||||
mLetterboxTaskListener, TASK_LISTENER_TYPE_LETTERBOX);
|
||||
mShellTaskOrganizer.addListenerForType(
|
||||
mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
|
||||
// Register the shell organizer
|
||||
mShellTaskOrganizer.registerOrganizer();
|
||||
|
||||
mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
|
||||
// Bind the splitscreen impl to the drag drop controller
|
||||
mDragAndDropController.setSplitScreenController(mSplitScreenOptional);
|
||||
|
||||
@@ -118,8 +118,6 @@ public class ShellTaskOrganizer extends TaskOrganizer {
|
||||
SyncTransactionQueue syncQueue, TransactionPool transactionPool,
|
||||
ShellExecutor mainExecutor, ShellExecutor animExecutor, Context context) {
|
||||
super(taskOrganizerController, mainExecutor);
|
||||
addListenerForType(new FullscreenTaskListener(syncQueue), TASK_LISTENER_TYPE_FULLSCREEN);
|
||||
addListenerForType(new LetterboxTaskListener(syncQueue), TASK_LISTENER_TYPE_LETTERBOX);
|
||||
mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
|
||||
if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
|
||||
// TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.letterbox;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Gravity;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
|
||||
/**
|
||||
* Controls access to and overrides of resource config values used by {@link
|
||||
* LetterboxTaskOrganizer}.
|
||||
*/
|
||||
public final class LetterboxConfigController {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
/** {@link Gravity} of letterboxed apps in portrait screen orientation. */
|
||||
private int mLetterboxPortraitGravity;
|
||||
|
||||
/** {@link Gravity} of letterboxed apps in landscape screen orientation. */
|
||||
private int mLetterboxLandscapeGravity;
|
||||
|
||||
public LetterboxConfigController(Context context) {
|
||||
mContext = context;
|
||||
mLetterboxPortraitGravity =
|
||||
mContext.getResources().getInteger(R.integer.config_letterboxPortraitGravity);
|
||||
mLetterboxLandscapeGravity =
|
||||
mContext.getResources().getInteger(R.integer.config_letterboxLandscapeGravity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides {@link Gravity} of letterboxed apps in portrait screen orientation.
|
||||
*
|
||||
* @throws IllegalArgumentException if gravity isn't equal to {@link Gravity#TOP}, {@link
|
||||
* Gravity#CENTER} or {@link Gravity#BOTTOM}.
|
||||
*/
|
||||
public void setPortraitGravity(int gravity) {
|
||||
if (gravity != Gravity.TOP && gravity != Gravity.CENTER && gravity != Gravity.BOTTOM) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected Gravity#TOP, Gravity#CENTER or Gravity#BOTTOM but got"
|
||||
+ gravity);
|
||||
}
|
||||
mLetterboxPortraitGravity = gravity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets {@link Gravity} of letterboxed apps in portrait screen orientation to {@link
|
||||
* R.integer.config_letterboxPortraitGravity}.
|
||||
*/
|
||||
public void resetPortraitGravity() {
|
||||
mLetterboxPortraitGravity =
|
||||
mContext.getResources().getInteger(R.integer.config_letterboxPortraitGravity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets {@link Gravity} of letterboxed apps in portrait screen orientation.
|
||||
*/
|
||||
public int getPortraitGravity() {
|
||||
return mLetterboxPortraitGravity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides {@link Gravity} of letterboxed apps in landscape screen orientation.
|
||||
*
|
||||
* @throws IllegalArgumentException if gravity isn't equal to {@link Gravity#RIGHT}, {@link
|
||||
* Gravity#CENTER} or {@link Gravity#LEFT}.
|
||||
*/
|
||||
public void setLandscapeGravity(int gravity) {
|
||||
if (gravity != Gravity.LEFT && gravity != Gravity.CENTER && gravity != Gravity.RIGHT) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected Gravity#LEFT, Gravity#CENTER or Gravity#RIGHT but got"
|
||||
+ gravity);
|
||||
}
|
||||
mLetterboxLandscapeGravity = gravity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets {@link Gravity} of letterboxed apps in landscape screen orientation to {@link
|
||||
* R.integer.config_letterboxLandscapeGravity}.
|
||||
*/
|
||||
public void resetLandscapeGravity() {
|
||||
mLetterboxLandscapeGravity =
|
||||
mContext.getResources().getInteger(R.integer.config_letterboxLandscapeGravity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets {@link Gravity} of letterboxed apps in landscape screen orientation.
|
||||
*/
|
||||
public int getLandscapeGravity() {
|
||||
return mLetterboxLandscapeGravity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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.letterbox;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Gravity;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.Transitions;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.protolog.ShellProtoLogGroup;
|
||||
|
||||
/**
|
||||
* Organizes a task in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN} when
|
||||
* it's presented in the letterbox mode either because orientations of a top activity and a device
|
||||
* don't match or because a top activity is in a size compat mode.
|
||||
*/
|
||||
public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
private static final String TAG = "LetterboxTaskListener";
|
||||
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
private final LetterboxConfigController mLetterboxConfigController;
|
||||
private final WindowManager mWindowManager;
|
||||
private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>();
|
||||
|
||||
public LetterboxTaskListener(
|
||||
SyncTransactionQueue syncQueue,
|
||||
LetterboxConfigController letterboxConfigController,
|
||||
WindowManager windowManager) {
|
||||
mSyncQueue = syncQueue;
|
||||
mLetterboxConfigController = letterboxConfigController;
|
||||
mWindowManager = windowManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
||||
if (mLeashByTaskId.get(taskInfo.taskId) != null) {
|
||||
throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
|
||||
}
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Appeared: #%d",
|
||||
taskInfo.taskId);
|
||||
mLeashByTaskId.put(taskInfo.taskId, leash);
|
||||
Point positionInParent = new Point();
|
||||
Rect crop = new Rect();
|
||||
resolveTaskPositionAndCrop(taskInfo, positionInParent, crop);
|
||||
mSyncQueue.runInSync(t -> {
|
||||
setPositionAndWindowCrop(t, leash, positionInParent, crop);
|
||||
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
|
||||
t.setAlpha(leash, 1f);
|
||||
t.setMatrix(leash, 1, 0, 0, 1);
|
||||
t.show(leash);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
if (mLeashByTaskId.get(taskInfo.taskId) == null) {
|
||||
Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
|
||||
return;
|
||||
}
|
||||
mLeashByTaskId.remove(taskInfo.taskId);
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Vanished: #%d",
|
||||
taskInfo.taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Changed: #%d",
|
||||
taskInfo.taskId);
|
||||
final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
|
||||
Point positionInParent = new Point();
|
||||
Rect crop = new Rect();
|
||||
resolveTaskPositionAndCrop(taskInfo, positionInParent, crop);
|
||||
mSyncQueue.runInSync(t -> setPositionAndWindowCrop(t, leash, positionInParent, crop));
|
||||
}
|
||||
|
||||
private static void setPositionAndWindowCrop(
|
||||
SurfaceControl.Transaction transaction,
|
||||
SurfaceControl leash,
|
||||
final Point positionInParent,
|
||||
final Rect crop) {
|
||||
transaction.setPosition(leash, positionInParent.x, positionInParent.y);
|
||||
transaction.setWindowCrop(leash, crop);
|
||||
}
|
||||
|
||||
private void resolveTaskPositionAndCrop(
|
||||
ActivityManager.RunningTaskInfo taskInfo,
|
||||
Point positionInParent,
|
||||
Rect crop) {
|
||||
// In screen coordinates
|
||||
Rect parentBounds = new Rect(taskInfo.parentBounds);
|
||||
// Intersect parent and max bounds. This is required for situations when parent bounds
|
||||
// go beyond display bounds, for example, in One-handed mode.
|
||||
final Rect maxBounds = taskInfo.getConfiguration().windowConfiguration.getMaxBounds();
|
||||
if (!parentBounds.intersect(maxBounds)) {
|
||||
Slog.w(TAG, "Task parent and max bounds don't intersect: #" + taskInfo.taskId);
|
||||
}
|
||||
|
||||
// In screen coordinates
|
||||
final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds();
|
||||
final Rect activityBounds = taskInfo.letterboxActivityBounds;
|
||||
|
||||
Insets insets = getInsets();
|
||||
|
||||
Rect taskBoundsWithInsets = new Rect(taskBounds);
|
||||
applyInsets(taskBoundsWithInsets, insets, taskInfo.parentBounds);
|
||||
|
||||
Rect activityBoundsWithInsets = new Rect(activityBounds);
|
||||
applyInsets(activityBoundsWithInsets, insets, taskInfo.parentBounds);
|
||||
|
||||
Rect parentBoundsWithInsets = new Rect(parentBounds);
|
||||
applyInsets(parentBoundsWithInsets, insets, parentBounds);
|
||||
|
||||
// Crop need to be in the task coordinates.
|
||||
crop.set(activityBoundsWithInsets);
|
||||
crop.offset(-taskBounds.left, -taskBounds.top);
|
||||
|
||||
// Account for insets since coordinates calculations below are done with them.
|
||||
positionInParent.x = parentBoundsWithInsets.left - parentBounds.left
|
||||
- (taskBoundsWithInsets.left - taskBounds.left);
|
||||
positionInParent.y = parentBoundsWithInsets.top - parentBounds.top
|
||||
- (taskBoundsWithInsets.top - taskBounds.top);
|
||||
|
||||
// Calculating a position of task bounds (without insets) in parent coordinates (without
|
||||
// insets) to align activity bounds (without insets) as requested in config. Activity
|
||||
// accounts for insets that overlap with its bounds (this overlap can be partial) so
|
||||
// ignoring overlap with insets when computing the position. Also, cropping unwanted insets
|
||||
// while keeping the top one if the activity is aligned at the top of the window to show
|
||||
// status bar decor view.
|
||||
if (parentBounds.height() >= parentBounds.width()) {
|
||||
final int gravity = mLetterboxConfigController.getPortraitGravity();
|
||||
// Center activity horizontally.
|
||||
positionInParent.x +=
|
||||
(parentBoundsWithInsets.width() - activityBoundsWithInsets.width()) / 2
|
||||
+ taskBoundsWithInsets.left - activityBoundsWithInsets.left;
|
||||
switch (gravity) {
|
||||
case Gravity.TOP:
|
||||
positionInParent.y += taskBoundsWithInsets.top - activityBoundsWithInsets.top;
|
||||
// Showing status bar decor view.
|
||||
crop.top -= activityBoundsWithInsets.top - activityBounds.top;
|
||||
break;
|
||||
case Gravity.CENTER:
|
||||
positionInParent.y +=
|
||||
taskBoundsWithInsets.top - activityBoundsWithInsets.top
|
||||
+ (parentBoundsWithInsets.height()
|
||||
- activityBoundsWithInsets.height()) / 2;
|
||||
break;
|
||||
case Gravity.BOTTOM:
|
||||
positionInParent.y +=
|
||||
parentBoundsWithInsets.height() - activityBoundsWithInsets.bottom
|
||||
+ taskBoundsWithInsets.top;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError(
|
||||
"Unexpected portrait gravity " + gravity
|
||||
+ " for task: #" + taskInfo.taskId);
|
||||
}
|
||||
} else {
|
||||
final int gravity = mLetterboxConfigController.getLandscapeGravity();
|
||||
// Align activity to the top.
|
||||
positionInParent.y += taskBoundsWithInsets.top - activityBoundsWithInsets.top;
|
||||
// Showing status bar decor view.
|
||||
crop.top -= activityBoundsWithInsets.top - activityBounds.top;
|
||||
switch (gravity) {
|
||||
case Gravity.LEFT:
|
||||
positionInParent.x += taskBoundsWithInsets.left - activityBoundsWithInsets.left;
|
||||
break;
|
||||
case Gravity.CENTER:
|
||||
positionInParent.x +=
|
||||
(parentBoundsWithInsets.width() - activityBoundsWithInsets.width()) / 2
|
||||
+ taskBoundsWithInsets.left - activityBoundsWithInsets.left;
|
||||
break;
|
||||
case Gravity.RIGHT:
|
||||
positionInParent.x +=
|
||||
parentBoundsWithInsets.width()
|
||||
- activityBoundsWithInsets.right + taskBoundsWithInsets.left;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError(
|
||||
"Unexpected landscape gravity " + gravity
|
||||
+ " for task: #" + taskInfo.taskId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Insets getInsets() {
|
||||
return mWindowManager
|
||||
.getMaximumWindowMetrics()
|
||||
.getWindowInsets()
|
||||
.getInsets(
|
||||
WindowInsets.Type.navigationBars()
|
||||
| WindowInsets.Type.statusBars()
|
||||
| WindowInsets.Type.displayCutout());
|
||||
}
|
||||
|
||||
private void applyInsets(Rect innerBounds, Insets insets, Rect outerBounds) {
|
||||
Rect outerBoundsWithInsets = new Rect(outerBounds);
|
||||
outerBoundsWithInsets.inset(insets);
|
||||
innerBounds.intersect(outerBoundsWithInsets);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/**
|
||||
* Tests for {@link LetterboxTaskListener}.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class LetterboxTaskListenerTest {
|
||||
|
||||
private static final Rect ACTIVITY_BOUNDS = new Rect(300, 200, 700, 400);
|
||||
private static final Rect TASK_BOUNDS = new Rect(200, 100, 800, 500);
|
||||
private static final Rect TASK_BOUNDS_2 = new Rect(300, 200, 800, 500);
|
||||
private static final Point TASK_POSITION_IN_PARENT = new Point(100, 50);
|
||||
private static final Point TASK_POSITION_IN_PARENT_2 = new Point(200, 100);
|
||||
|
||||
private static final Rect EXPECTED_WINDOW_CROP = new Rect(100, 100, 500, 300);
|
||||
private static final Rect EXPECTED_WINDOW_CROP_2 = new Rect(0, 0, 400, 200);
|
||||
|
||||
private static final RunningTaskInfo TASK_INFO = createTaskInfo(
|
||||
/* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS, TASK_POSITION_IN_PARENT);
|
||||
|
||||
private static final RunningTaskInfo TASK_INFO_2 = createTaskInfo(
|
||||
/* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS_2, TASK_POSITION_IN_PARENT_2);
|
||||
|
||||
@Mock private SurfaceControl mLeash;
|
||||
@Mock private SurfaceControl.Transaction mTransaction;
|
||||
private LetterboxTaskListener mLetterboxTaskListener;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mLetterboxTaskListener = new LetterboxTaskListener(
|
||||
new SyncTransactionQueue(
|
||||
new TransactionPool() {
|
||||
@Override
|
||||
public SurfaceControl.Transaction acquire() {
|
||||
return mTransaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release(SurfaceControl.Transaction t) {
|
||||
}
|
||||
},
|
||||
new Handler(Looper.getMainLooper())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskAppearedAndonTaskInfoChanged_setCorrectPositionAndCrop() {
|
||||
mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash);
|
||||
|
||||
verify(mTransaction).setPosition(
|
||||
eq(mLeash),
|
||||
eq((float) TASK_POSITION_IN_PARENT.x),
|
||||
eq((float) TASK_POSITION_IN_PARENT.y));
|
||||
// Should return activty coordinates offset by task coordinates
|
||||
verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP));
|
||||
|
||||
mLetterboxTaskListener.onTaskInfoChanged(TASK_INFO_2);
|
||||
|
||||
verify(mTransaction).setPosition(
|
||||
eq(mLeash),
|
||||
eq((float) TASK_POSITION_IN_PARENT_2.x),
|
||||
eq((float) TASK_POSITION_IN_PARENT_2.y));
|
||||
// Should return activty coordinates offset by task coordinates
|
||||
verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP_2));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() {
|
||||
mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash);
|
||||
mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash);
|
||||
}
|
||||
|
||||
private static RunningTaskInfo createTaskInfo(
|
||||
int taskId,
|
||||
final Rect activityBounds,
|
||||
final Rect taskBounds,
|
||||
final Point taskPositionInParent) {
|
||||
RunningTaskInfo taskInfo = new RunningTaskInfo();
|
||||
taskInfo.taskId = taskId;
|
||||
taskInfo.configuration.windowConfiguration.setBounds(taskBounds);
|
||||
taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds);
|
||||
taskInfo.positionInParent = new Point(taskPositionInParent);
|
||||
return taskInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.letterbox;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.view.Gravity;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.ShellTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Tests for {@link LetterboxConfigController}.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class LetterboxConfigControllerTest extends ShellTestCase {
|
||||
|
||||
private LetterboxConfigController mLetterboxConfigController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mLetterboxConfigController = new LetterboxConfigController(getContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPortraitGravity_noOverrides_returnConfigValue() {
|
||||
assertEquals(
|
||||
mLetterboxConfigController.getPortraitGravity(),
|
||||
getContext().getResources().getInteger(R.integer.config_letterboxPortraitGravity));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLandscapeGravity_noOverrides_returnConfigValue() {
|
||||
assertEquals(
|
||||
mLetterboxConfigController.getLandscapeGravity(),
|
||||
getContext().getResources().getInteger(R.integer.config_letterboxLandscapeGravity));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetPortraitGravity_validValue_savesValue() {
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM);
|
||||
assertEquals(mLetterboxConfigController.getPortraitGravity(), Gravity.BOTTOM);
|
||||
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.CENTER);
|
||||
assertEquals(mLetterboxConfigController.getPortraitGravity(), Gravity.CENTER);
|
||||
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.TOP);
|
||||
assertEquals(mLetterboxConfigController.getPortraitGravity(), Gravity.TOP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetLandscapeGravity_validValue_savesValue() {
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.LEFT);
|
||||
assertEquals(mLetterboxConfigController.getLandscapeGravity(), Gravity.LEFT);
|
||||
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER);
|
||||
assertEquals(mLetterboxConfigController.getLandscapeGravity(), Gravity.CENTER);
|
||||
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.RIGHT);
|
||||
assertEquals(mLetterboxConfigController.getLandscapeGravity(), Gravity.RIGHT);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testSetPortraitGravity_invalidValue_throwsException() {
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.RIGHT);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testSetLandscapeGravity_invalidValue_throwsException() {
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.TOP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetPortraitGravity() {
|
||||
int defaultGravity =
|
||||
getContext().getResources().getInteger(R.integer.config_letterboxPortraitGravity);
|
||||
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM);
|
||||
mLetterboxConfigController.resetPortraitGravity();
|
||||
assertEquals(mLetterboxConfigController.getPortraitGravity(), defaultGravity);
|
||||
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.CENTER);
|
||||
mLetterboxConfigController.resetPortraitGravity();
|
||||
assertEquals(mLetterboxConfigController.getPortraitGravity(), defaultGravity);
|
||||
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.TOP);
|
||||
mLetterboxConfigController.resetPortraitGravity();
|
||||
assertEquals(mLetterboxConfigController.getPortraitGravity(), defaultGravity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetLandscapeGravity() {
|
||||
int defaultGravity =
|
||||
getContext().getResources().getInteger(R.integer.config_letterboxLandscapeGravity);
|
||||
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.RIGHT);
|
||||
mLetterboxConfigController.resetLandscapeGravity();
|
||||
assertEquals(mLetterboxConfigController.getLandscapeGravity(), defaultGravity);
|
||||
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER);
|
||||
mLetterboxConfigController.resetLandscapeGravity();
|
||||
assertEquals(mLetterboxConfigController.getLandscapeGravity(), defaultGravity);
|
||||
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.LEFT);
|
||||
mLetterboxConfigController.resetLandscapeGravity();
|
||||
assertEquals(mLetterboxConfigController.getLandscapeGravity(), defaultGravity);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* 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.letterbox;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.Gravity;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.ShellTestCase;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/**
|
||||
* Tests for {@link LetterboxTaskListener}.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class LetterboxTaskListenerTest extends ShellTestCase {
|
||||
|
||||
@Mock private SurfaceControl mLeash;
|
||||
@Mock private SurfaceControl.Transaction mTransaction;
|
||||
@Mock private WindowManager mWindowManager;
|
||||
@Mock private WindowMetrics mWindowMetrics;
|
||||
@Mock private WindowInsets mWindowInsets;
|
||||
private LetterboxTaskListener mLetterboxTaskListener;
|
||||
private LetterboxConfigController mLetterboxConfigController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mLetterboxConfigController = new LetterboxConfigController(getContext());
|
||||
mLetterboxTaskListener = new LetterboxTaskListener(
|
||||
new SyncTransactionQueue(
|
||||
new TransactionPool() {
|
||||
@Override
|
||||
public SurfaceControl.Transaction acquire() {
|
||||
return mTransaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release(SurfaceControl.Transaction t) {
|
||||
}
|
||||
},
|
||||
new Handler(Looper.getMainLooper())),
|
||||
mLetterboxConfigController,
|
||||
mWindowManager);
|
||||
|
||||
when(mWindowManager.getMaximumWindowMetrics()).thenReturn(mWindowMetrics);
|
||||
when(mWindowMetrics.getWindowInsets()).thenReturn(mWindowInsets);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskInfoChanged_updatesPositionAndCrop() {
|
||||
setWindowBoundsAndInsets(
|
||||
/* windowBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
|
||||
Insets.NONE);
|
||||
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER);
|
||||
mLetterboxTaskListener.onTaskAppeared(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 0, 200, 100),
|
||||
/* activityBounds */ new Rect(75, 0, 125, 75),
|
||||
/* taskBounds */ new Rect(50, 0, 125, 100)),
|
||||
mLeash);
|
||||
|
||||
// Task doesn't need to repositioned
|
||||
verifySetPosition(50, 0);
|
||||
// Should return activity coordinates offset by task coordinates
|
||||
verifySetWindowCrop(new Rect(25, 0, 75, 75));
|
||||
|
||||
mLetterboxTaskListener.onTaskInfoChanged(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 0, 200, 100),
|
||||
// Activity is offset by 25 to the left
|
||||
/* activityBounds */ new Rect(50, 0, 100, 75),
|
||||
/* taskBounds */ new Rect(50, 0, 125, 100)));
|
||||
|
||||
// Task needs to be repositioned by 25 to the left
|
||||
verifySetPosition(75, 0);
|
||||
// Should return activity coordinates offset by task coordinates
|
||||
verifySetWindowCrop(new Rect(0, 0, 50, 75));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskInfoAppeared_landscapeWithLeftGravity() {
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.LEFT);
|
||||
setWindowBoundsAndInsets(
|
||||
/* windowBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
|
||||
Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 10));
|
||||
|
||||
mLetterboxTaskListener.onTaskAppeared(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 0, 200, 100),
|
||||
/* activityBounds */ new Rect(150, 0, 200, 75),
|
||||
/* taskBounds */ new Rect(125, 0, 200, 100)),
|
||||
mLeash);
|
||||
|
||||
verifySetPosition(-15, 0);
|
||||
// Should return activity coordinates offset by task coordinates minus unwanted right inset
|
||||
verifySetWindowCrop(new Rect(25, 0, 65, 75));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskInfoAppeared_landscapeWithCenterGravity() {
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER);
|
||||
setWindowBoundsAndInsets(
|
||||
/* windowBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
|
||||
Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 10));
|
||||
|
||||
mLetterboxTaskListener.onTaskAppeared(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 0, 200, 100),
|
||||
/* activityBounds */ new Rect(150, 0, 200, 75),
|
||||
/* taskBounds */ new Rect(125, 0, 200, 100)),
|
||||
mLeash);
|
||||
|
||||
verifySetPosition(55, 0);
|
||||
// Should return activity coordinates offset by task coordinates minus unwanted right inset
|
||||
verifySetWindowCrop(new Rect(25, 0, 65, 75));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskInfoAppeared_landscapeWithRightGravity() {
|
||||
mLetterboxConfigController.setLandscapeGravity(Gravity.RIGHT);
|
||||
setWindowBoundsAndInsets(
|
||||
/* windowBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
|
||||
Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 10));
|
||||
|
||||
mLetterboxTaskListener.onTaskAppeared(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 0, 200, 100),
|
||||
/* activityBounds */ new Rect(50, 0, 100, 75),
|
||||
/* taskBounds */ new Rect(25, 0, 100, 100)),
|
||||
mLeash);
|
||||
|
||||
verifySetPosition(115, 0);
|
||||
// Should return activity coordinates offset by task coordinates
|
||||
verifySetWindowCrop(new Rect(25, 0, 75, 75));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskInfoAppeared_portraitWithTopGravity() {
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.TOP);
|
||||
setWindowBoundsAndInsets(
|
||||
/* windowBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
|
||||
Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 20));
|
||||
|
||||
mLetterboxTaskListener.onTaskAppeared(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 0, 100, 150),
|
||||
/* activityBounds */ new Rect(0, 75, 50, 125),
|
||||
/* taskBounds */ new Rect(0, 50, 100, 125)),
|
||||
mLeash);
|
||||
|
||||
verifySetPosition(20, -15);
|
||||
// Should return activity coordinates offset by task coordinates minus unwanted left inset
|
||||
verifySetWindowCrop(new Rect(10, 25, 50, 75));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskInfoAppeared_portraitWithCenterGravity() {
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.CENTER);
|
||||
setWindowBoundsAndInsets(
|
||||
/* windowBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
|
||||
Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 20));
|
||||
|
||||
mLetterboxTaskListener.onTaskAppeared(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 0, 100, 150),
|
||||
/* activityBounds */ new Rect(0, 75, 50, 125),
|
||||
/* taskBounds */ new Rect(0, 50, 100, 125)),
|
||||
mLeash);
|
||||
|
||||
verifySetPosition(20, 20);
|
||||
// Should return activity coordinates offset by task coordinates minus unwanted left inset
|
||||
verifySetWindowCrop(new Rect(10, 25, 50, 75));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskInfoAppeared_portraitWithBottomGravity() {
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM);
|
||||
setWindowBoundsAndInsets(
|
||||
/* windowBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
|
||||
Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 20));
|
||||
|
||||
mLetterboxTaskListener.onTaskAppeared(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 0, 100, 150),
|
||||
/* activityBounds */ new Rect(0, 75, 50, 125),
|
||||
/* taskBounds */ new Rect(0, 50, 100, 125)),
|
||||
mLeash);
|
||||
|
||||
verifySetPosition(20, 55);
|
||||
// Should return activity coordinates offset by task coordinates minus unwanted left inset
|
||||
verifySetWindowCrop(new Rect(10, 25, 50, 75));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskInfoAppeared_partlyOverlapsWithAllInsets() {
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.TOP);
|
||||
setWindowBoundsAndInsets(
|
||||
/* windowBounds= */ new Rect(0, 0, 200, 125), // equal to parent bounds
|
||||
Insets.of(/* left= */ 25, /* top= */ 25, /* right= */ 35, /* bottom= */ 15));
|
||||
|
||||
mLetterboxTaskListener.onTaskAppeared(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 0, 200, 125),
|
||||
/* activityBounds */ new Rect(15, 0, 175, 120),
|
||||
/* taskBounds */ new Rect(0, 0, 100, 125)), // equal to parent bounds
|
||||
mLeash);
|
||||
|
||||
// Activity fully covers parent bounds with insets so doesn't need to be moved.
|
||||
verifySetPosition(0, 0);
|
||||
// Should return activity coordinates offset by task coordinates minus all insets
|
||||
// except top one (keep status bar decor visible).
|
||||
verifySetWindowCrop(new Rect(25, 0, 165, 110));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnTaskInfoAppeared_parentShiftedLikeInOneHandedMode() {
|
||||
mLetterboxConfigController.setPortraitGravity(Gravity.TOP);
|
||||
setWindowBoundsAndInsets(
|
||||
/* windowBounds= */ new Rect(0, 0, 100, 150),
|
||||
Insets.of(/* left= */ 0, /* top= */ 10, /* right= */ 0, /* bottom= */ 0));
|
||||
|
||||
mLetterboxTaskListener.onTaskAppeared(
|
||||
createTaskInfo(
|
||||
/* taskId */ 1,
|
||||
/* parentBounds */ new Rect(0, 75, 100, 225),
|
||||
/* activityBounds */ new Rect(25, 75, 75, 125),
|
||||
/* taskBounds */ new Rect(0, 75, 100, 125)),
|
||||
mLeash);
|
||||
|
||||
verifySetPosition(0, 0);
|
||||
verifySetWindowCrop(new Rect(25, 0, 75, 50));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() {
|
||||
setWindowBoundsAndInsets(new Rect(), Insets.NONE);
|
||||
RunningTaskInfo taskInfo =
|
||||
createTaskInfo(/* taskId */ 1, new Rect(), new Rect(), new Rect());
|
||||
mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash);
|
||||
mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash);
|
||||
}
|
||||
|
||||
private void setWindowBoundsAndInsets(Rect windowBounds, Insets insets) {
|
||||
when(mWindowMetrics.getBounds()).thenReturn(windowBounds);
|
||||
when(mWindowInsets.getInsets(anyInt())).thenReturn(insets);
|
||||
}
|
||||
|
||||
private void verifySetPosition(int x, int y) {
|
||||
verify(mTransaction).setPosition(eq(mLeash), eq((float) x), eq((float) y));
|
||||
}
|
||||
|
||||
private void verifySetWindowCrop(final Rect crop) {
|
||||
// Should return activty coordinates offset by task coordinates
|
||||
verify(mTransaction).setWindowCrop(eq(mLeash), eq(crop));
|
||||
}
|
||||
|
||||
private static RunningTaskInfo createTaskInfo(
|
||||
int taskId,
|
||||
final Rect parentBounds,
|
||||
final Rect activityBounds,
|
||||
final Rect taskBounds) {
|
||||
RunningTaskInfo taskInfo = new RunningTaskInfo();
|
||||
taskInfo.taskId = taskId;
|
||||
taskInfo.parentBounds = parentBounds;
|
||||
taskInfo.configuration.windowConfiguration.setBounds(taskBounds);
|
||||
taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds);
|
||||
|
||||
return taskInfo;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user