Merge "Give the tests the ability to emulate other devices screens" into tm-dev

This commit is contained in:
Sebastián Franco
2022-04-27 17:25:55 +00:00
committed by Android (Google) Code Review
6 changed files with 390 additions and 11 deletions
@@ -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;
+45
View File
@@ -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)));
}
}
}