Merge "Give the tests the ability to emulate other devices screens" into tm-dev am: 84051cd8bf
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/17413625 Change-Id: Iad9381b40f1080c0f77e1d3b5bd36760a4c13ca4 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -242,7 +242,9 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
|
||||
change |= CHANGE_SUPPORTED_BOUNDS;
|
||||
|
||||
Point currentS = newInfo.currentSize;
|
||||
Point expectedS = oldInfo.mPerDisplayBounds.get(newInfo.displayId).first.size;
|
||||
Pair<CachedDisplayInfo, WindowBounds[]> cachedBounds =
|
||||
oldInfo.mPerDisplayBounds.get(newInfo.displayId);
|
||||
Point expectedS = cachedBounds == null ? null : cachedBounds.first.size;
|
||||
if (newInfo.supportedBounds.size() != oldInfo.supportedBounds.size()) {
|
||||
Log.e("b/198965093",
|
||||
"Inconsistent number of displays"
|
||||
@@ -250,10 +252,12 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
|
||||
+ "\noldInfo.supportedBounds: " + oldInfo.supportedBounds
|
||||
+ "\nnewInfo.supportedBounds: " + newInfo.supportedBounds);
|
||||
}
|
||||
if ((Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
|
||||
if (expectedS != null
|
||||
&& (Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
|
||||
|| Math.max(currentS.x, currentS.y) != Math.max(expectedS.x, expectedS.y))
|
||||
&& display.getState() == Display.STATE_OFF) {
|
||||
Log.e("b/198965093", "Display size changed while display is off, ignoring change");
|
||||
Log.e("b/198965093",
|
||||
"Display size changed while display is off, ignoring change");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
|
||||
import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
|
||||
import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
|
||||
import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
|
||||
import static com.android.launcher3.ResourceUtils.getDimenByName;
|
||||
import static com.android.launcher3.Utilities.dpiFromPx;
|
||||
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
|
||||
import static com.android.launcher3.util.RotationUtils.deltaRotation;
|
||||
@@ -157,16 +156,16 @@ public class WindowManagerProxy implements ResourceBasedOverride {
|
||||
int bottomNav = isTablet
|
||||
? 0
|
||||
: (config.screenHeightDp > config.screenWidthDp
|
||||
? getDimenByName(NAVBAR_HEIGHT, systemRes, 0)
|
||||
? getDimenByName(NAVBAR_HEIGHT, systemRes)
|
||||
: (isGesture
|
||||
? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0)
|
||||
? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes)
|
||||
: 0));
|
||||
Insets newNavInsets = Insets.of(navInsets.left, navInsets.top, navInsets.right, bottomNav);
|
||||
insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
|
||||
insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
|
||||
|
||||
Insets statusBarInsets = oldInsets.getInsets(WindowInsets.Type.statusBars());
|
||||
int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
|
||||
int statusBarHeight = getDimenByName("status_bar_height", systemRes);
|
||||
Insets newStatusBarInsets = Insets.of(
|
||||
statusBarInsets.left,
|
||||
Math.max(statusBarInsets.top, statusBarHeight),
|
||||
@@ -222,23 +221,23 @@ public class WindowManagerProxy implements ResourceBasedOverride {
|
||||
boolean isTabletOrGesture = isTablet
|
||||
|| (Utilities.ATLEAST_R && isGestureNav(context));
|
||||
|
||||
int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
|
||||
int statusBarHeight = getDimenByName("status_bar_height", systemRes);
|
||||
|
||||
int navBarHeightPortrait, navBarHeightLandscape, navbarWidthLandscape;
|
||||
|
||||
navBarHeightPortrait = isTablet
|
||||
? (mTaskbarDrawnInProcess
|
||||
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
|
||||
: getDimenByName(NAVBAR_HEIGHT, systemRes, 0);
|
||||
: getDimenByName(NAVBAR_HEIGHT, systemRes);
|
||||
|
||||
navBarHeightLandscape = isTablet
|
||||
? (mTaskbarDrawnInProcess
|
||||
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
|
||||
: (isTabletOrGesture
|
||||
? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0) : 0);
|
||||
? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes) : 0);
|
||||
navbarWidthLandscape = isTabletOrGesture
|
||||
? 0
|
||||
: getDimenByName(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, systemRes, 0);
|
||||
: getDimenByName(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, systemRes);
|
||||
|
||||
WindowBounds[] result = new WindowBounds[4];
|
||||
Point tempSize = new Point();
|
||||
@@ -274,6 +273,13 @@ public class WindowManagerProxy implements ResourceBasedOverride {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around the utility method for easier emulation
|
||||
*/
|
||||
protected int getDimenByName(String resName, Resources res) {
|
||||
return ResourceUtils.getDimenByName(resName, res, 0);
|
||||
}
|
||||
|
||||
protected boolean isGestureNav(Context context) {
|
||||
return ResourceUtils.getIntegerByName("config_navBarInteractionMode",
|
||||
context.getResources(), INVALID_RESOURCE_HANDLE) == 2;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"pixel6pro": {
|
||||
"width": 1440,
|
||||
"height": 3120,
|
||||
"density": 560,
|
||||
"name": "pixel6pro",
|
||||
"cutout": "0, 130, 0, 0",
|
||||
"grids": [
|
||||
"normal",
|
||||
"reasonable",
|
||||
"practical",
|
||||
"big",
|
||||
"crazy_big"
|
||||
],
|
||||
"resourceOverrides": {
|
||||
"status_bar_height": 98,
|
||||
"navigation_bar_height_landscape": 56,
|
||||
"navigation_bar_height": 56,
|
||||
"navigation_bar_width": 56
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"data needs updating": 0
|
||||
},
|
||||
"pixel5": {
|
||||
"width": 1080,
|
||||
"height": 2340,
|
||||
"density": 440,
|
||||
"name": "pixel5",
|
||||
"cutout": "0, 136, 0, 0",
|
||||
"grids": [
|
||||
"normal",
|
||||
"reasonable",
|
||||
"practical",
|
||||
"big",
|
||||
"crazy_big"
|
||||
],
|
||||
"resourceOverrides": {
|
||||
"status_bar_height": 66,
|
||||
"navigation_bar_height_landscape": 44,
|
||||
"navigation_bar_height": 44,
|
||||
"navigation_bar_width": 44
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.launcher3.deviceemulator;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.UserHandle;
|
||||
import android.view.Display;
|
||||
import android.view.IWindowManager;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
|
||||
import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
|
||||
import com.android.launcher3.tapl.LauncherInstrumentation;
|
||||
import com.android.launcher3.util.window.WindowManagerProxy;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
|
||||
public class DisplayEmulator {
|
||||
Context mContext;
|
||||
LauncherInstrumentation mLauncher;
|
||||
DisplayEmulator(Context context, LauncherInstrumentation launcher) {
|
||||
mContext = context;
|
||||
mLauncher = launcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* By changing the WindowManagerProxy we can override the window insets information
|
||||
**/
|
||||
private IWindowManager changeWindowManagerInstance(DeviceEmulationData deviceData) {
|
||||
WindowManagerProxy.INSTANCE.initializeForTesting(
|
||||
new TestWindowManagerProxy(mContext, deviceData));
|
||||
return WindowManagerGlobal.getWindowManagerService();
|
||||
}
|
||||
|
||||
public <T> T emulate(DeviceEmulationData device, String grid, Callable<T> runInEmulation)
|
||||
throws Exception {
|
||||
WindowManagerProxy original = WindowManagerProxy.INSTANCE.get(mContext);
|
||||
// Set up emulation
|
||||
final int userId = UserHandle.myUserId();
|
||||
WindowManagerProxy.INSTANCE.initializeForTesting(
|
||||
new TestWindowManagerProxy(mContext, device));
|
||||
IWindowManager wm = changeWindowManagerInstance(device);
|
||||
// Change density twice to force display controller to reset its state
|
||||
wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density / 2, userId);
|
||||
wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density, userId);
|
||||
wm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, device.width, device.height);
|
||||
wm.setForcedDisplayScalingMode(Display.DEFAULT_DISPLAY, 1);
|
||||
|
||||
// Set up grid
|
||||
setGrid(grid);
|
||||
try {
|
||||
return runInEmulation.call();
|
||||
} finally {
|
||||
// Clear emulation
|
||||
WindowManagerProxy.INSTANCE.initializeForTesting(original);
|
||||
UiDevice.getInstance(getInstrumentation()).executeShellCommand("cmd window reset");
|
||||
}
|
||||
}
|
||||
|
||||
private void setGrid(String gridType) {
|
||||
// When the grid changes, the desktop arrangement get stored in SQL and we need to wait to
|
||||
// make sure there is no SQL operations running and get SQL_BUSY error, that's why we need
|
||||
// to call mLauncher.waitForLauncherInitialized();
|
||||
mLauncher.waitForLauncherInitialized();
|
||||
String testProviderAuthority = mContext.getPackageName() + ".grid_control";
|
||||
Uri gridUri = new Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(testProviderAuthority)
|
||||
.appendPath("default_grid")
|
||||
.build();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("name", gridType);
|
||||
mContext.getContentResolver().update(gridUri, values, null, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.launcher3.deviceemulator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.view.Display;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
|
||||
import com.android.launcher3.util.RotationUtils;
|
||||
import com.android.launcher3.util.WindowBounds;
|
||||
import com.android.launcher3.util.window.CachedDisplayInfo;
|
||||
import com.android.launcher3.util.window.WindowManagerProxy;
|
||||
|
||||
public class TestWindowManagerProxy extends WindowManagerProxy {
|
||||
|
||||
private final DeviceEmulationData mDevice;
|
||||
|
||||
public TestWindowManagerProxy(Context context, DeviceEmulationData device) {
|
||||
super(true);
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternalDisplay(Display display) {
|
||||
return display.getDisplayId() == Display.DEFAULT_DISPLAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDimenByName(String resName, Resources res) {
|
||||
Integer mock = mDevice.resourceOverrides.get(resName);
|
||||
return mock != null ? mock : super.getDimenByName(resName, res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedDisplayInfo getDisplayInfo(Context context, Display display) {
|
||||
int rotation = display.getRotation();
|
||||
Point size = new Point(mDevice.width, mDevice.height);
|
||||
RotationUtils.rotateSize(size, rotation);
|
||||
Rect cutout = new Rect(mDevice.cutout);
|
||||
RotationUtils.rotateRect(cutout, rotation);
|
||||
return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowBounds getRealBounds(Context windowContext, Display display,
|
||||
CachedDisplayInfo info) {
|
||||
return estimateInternalDisplayBounds(windowContext)
|
||||
.get(getDisplayId(display)).second[display.getRotation()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
|
||||
Rect outInsets) {
|
||||
outInsets.set(getRealBounds(context, context.getDisplay(),
|
||||
getDisplayInfo(context, context.getDisplay())).insets);
|
||||
return oldInsets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.launcher3.deviceemulator.models;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
|
||||
import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
|
||||
import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
|
||||
import static com.android.launcher3.ResourceUtils.getDimenByName;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.IOUtils;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
public class DeviceEmulationData {
|
||||
|
||||
public final int width;
|
||||
public final int height;
|
||||
public final int density;
|
||||
public final String name;
|
||||
public final String[] grids;
|
||||
public final Rect cutout;
|
||||
public final Map<String, Integer> resourceOverrides;
|
||||
|
||||
private static final String[] EMULATED_SYSTEM_RESOURCES = new String[]{
|
||||
NAVBAR_HEIGHT,
|
||||
NAVBAR_HEIGHT_LANDSCAPE,
|
||||
NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE,
|
||||
"status_bar_height",
|
||||
};
|
||||
|
||||
public DeviceEmulationData(int width, int height, int density, Rect cutout, String name,
|
||||
String[] grid,
|
||||
Map<String, Integer> resourceOverrides) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.density = density;
|
||||
this.name = name;
|
||||
this.grids = grid;
|
||||
this.cutout = cutout;
|
||||
this.resourceOverrides = resourceOverrides;
|
||||
}
|
||||
|
||||
public static DeviceEmulationData deviceFromJSON(JSONObject json) throws JSONException {
|
||||
int width = json.getInt("width");
|
||||
int height = json.getInt("height");
|
||||
int density = json.getInt("density");
|
||||
String name = json.getString("name");
|
||||
|
||||
JSONArray gridArray = json.getJSONArray("grids");
|
||||
String[] grids = new String[gridArray.length()];
|
||||
for (int i = 0, count = grids.length; i < count; i++) {
|
||||
grids[i] = gridArray.getString(i);
|
||||
}
|
||||
|
||||
IntArray deviceCutout = IntArray.fromConcatString(json.getString("cutout"));
|
||||
Rect cutout = new Rect(deviceCutout.get(0), deviceCutout.get(1), deviceCutout.get(2),
|
||||
deviceCutout.get(3));
|
||||
|
||||
|
||||
JSONObject resourceOverridesJson = json.getJSONObject("resourceOverrides");
|
||||
Map<String, Integer> resourceOverrides = new ArrayMap<>();
|
||||
for (String key : resourceOverridesJson.keySet()) {
|
||||
resourceOverrides.put(key, resourceOverridesJson.getInt(key));
|
||||
}
|
||||
return new DeviceEmulationData(width, height, density, cutout, name, grids,
|
||||
resourceOverrides);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("width", width);
|
||||
json.put("height", height);
|
||||
json.put("density", density);
|
||||
json.put("name", name);
|
||||
json.put("cutout", IntArray.wrap(
|
||||
cutout.left, cutout.top, cutout.right, cutout.bottom).toConcatString());
|
||||
|
||||
JSONArray gridArray = new JSONArray();
|
||||
Arrays.stream(grids).forEach(gridArray::put);
|
||||
json.put("grids", gridArray);
|
||||
|
||||
|
||||
JSONObject resourceOverrides = new JSONObject();
|
||||
for (Map.Entry<String, Integer> e : this.resourceOverrides.entrySet()) {
|
||||
resourceOverrides.put(e.getKey(), e.getValue());
|
||||
}
|
||||
json.put("resourceOverrides", resourceOverrides);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
public static DeviceEmulationData getCurrentDeviceData(Context context) {
|
||||
DisplayController.Info info = DisplayController.INSTANCE.get(context).getInfo();
|
||||
String[] grids = InvariantDeviceProfile.INSTANCE.get(context)
|
||||
.parseAllGridOptions(context).stream()
|
||||
.map(go -> go.name).toArray(String[]::new);
|
||||
String code = Build.MODEL.replaceAll("\\s", "").toLowerCase();
|
||||
|
||||
Map<String, Integer> resourceOverrides = new ArrayMap<>();
|
||||
for (String s : EMULATED_SYSTEM_RESOURCES) {
|
||||
resourceOverrides.put(s, getDimenByName(s, context.getResources(), 0));
|
||||
}
|
||||
return new DeviceEmulationData(info.currentSize.x, info.currentSize.y,
|
||||
info.densityDpi, info.cutout, code, grids, resourceOverrides);
|
||||
}
|
||||
|
||||
public static DeviceEmulationData getDevice(String deviceCode) throws Exception {
|
||||
return DeviceEmulationData.deviceFromJSON(readJSON().getJSONObject(deviceCode));
|
||||
}
|
||||
|
||||
private static JSONObject readJSON() throws Exception {
|
||||
Context context = getInstrumentation().getContext();
|
||||
Resources myRes = context.getResources();
|
||||
int resId = myRes.getIdentifier("devices", "raw", context.getPackageName());
|
||||
try (InputStream is = myRes.openRawResource(resId)) {
|
||||
return new JSONObject(new String(IOUtils.toByteArray(is)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user