Apply override scale from entry points on client

This provides a sandbox-like scaled environment on client side.

- This prevents the metrics and config from being scaled multiple
  times because the entry points are only called from server side,
  there won't have reentrancy for applying the scale.
- App's configuration callback method can get scaled config.
- If override scale is enabled, the legacy compat mode will be
  skipped. That avoids mixing different approaches of scaling.
  - Legacy compat mode is to simulate a 320dp x 480dp screen
    size with density 160 (the surface scale and config scale
    are different).
  - Override scale is to downscale the app for better performance
    (the surface scale and config scale are the same).

Bug: 238416387
Bug: 240335717
Test: atest ActivityThreadTest#testOverrideScale
Test: atest CtsWindowManagerDeviceTestCases:CompatScaleTests
Test: am compat enable DOWNSCALED $pkg
      am compat enable DOWNSCALE_50 $pkg
Change-Id: I0ddf333996f229063511273f8aeb3867c9ba8bda
This commit is contained in:
Riddle Hsu
2022-08-29 18:33:29 -06:00
committed by Xiang Wang
parent 56fe9580ff
commit a0530caa33
13 changed files with 222 additions and 36 deletions

View File

@@ -1177,9 +1177,16 @@ public final class ActivityThread extends ClientTransactionHandler
data.mSerializedSystemFontMap = serializedSystemFontMap;
data.startRequestedElapsedTime = startRequestedElapsedTime;
data.startRequestedUptime = startRequestedUptime;
updateCompatOverrideScale(compatInfo);
CompatibilityInfo.applyOverrideScaleIfNeeded(config);
sendMessage(H.BIND_APPLICATION, data);
}
private void updateCompatOverrideScale(CompatibilityInfo info) {
CompatibilityInfo.setOverrideInvertedScale(
info.hasOverrideScaling() ? info.applicationInvertedScale : 1f);
}
public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = entryPoint;
@@ -1756,6 +1763,7 @@ public final class ActivityThread extends ClientTransactionHandler
UpdateCompatibilityData ucd = new UpdateCompatibilityData();
ucd.pkg = pkg;
ucd.info = info;
updateCompatOverrideScale(info);
sendMessage(H.UPDATE_PACKAGE_COMPATIBILITY_INFO, ucd);
}

View File

@@ -462,13 +462,30 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
/** @hide */
public void scale(float scale) {
mBounds.scale(scale);
mMaxBounds.scale(scale);
scaleBounds(scale, mBounds);
scaleBounds(scale, mMaxBounds);
if (mAppBounds != null) {
mAppBounds.scale(scale);
scaleBounds(scale, mAppBounds);
}
}
/**
* Size based scaling. This avoid inconsistent length when rounding 4 sides.
* E.g. left=12, right=18, scale=0.8. The scaled width can be:
* int((right - left) * scale + 0.5) = int(4.8 + 0.5) = 5
* But with rounding both left and right, the width will be inconsistent:
* int(right * scale + 0.5) - int(left * scale + 0.5) = int(14.9) - int(10.1) = 4
* @hide
*/
private static void scaleBounds(float scale, Rect bounds) {
final int w = bounds.width();
final int h = bounds.height();
bounds.left = (int) (bounds.left * scale + .5f);
bounds.top = (int) (bounds.top * scale + .5f);
bounds.right = bounds.left + (int) (w * scale + .5f);
bounds.bottom = bounds.top + (int) (h * scale + .5f);
}
/**
* Copies the fields from delta into this Configuration object, keeping
* track of which ones have changed. Any undefined fields in {@code delta}

View File

@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Parcel;
@@ -40,6 +41,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
@Override
public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration);
// Notify the client of an upcoming change in the token configuration. This ensures that
// batches of config change items only process the newest configuration.
client.updatePendingActivityConfiguration(token, mConfiguration);

View File

@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.ResultInfo;
import android.content.res.CompatibilityInfo;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Trace;
@@ -56,6 +57,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
@Override
public void preExecute(ClientTransactionHandler client, IBinder token) {
CompatibilityInfo.applyOverrideScaleIfNeeded(mConfig);
mActivityClientRecord = client.prepareRelaunchActivity(token, mPendingResults,
mPendingNewIntents, mConfigChanges, mConfig, mPreserveWindow);
}

View File

@@ -18,6 +18,7 @@ package android.app.servertransaction;
import android.annotation.Nullable;
import android.app.ClientTransactionHandler;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Parcel;
@@ -34,6 +35,7 @@ public class ConfigurationChangeItem extends ClientTransactionItem {
@Override
public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration);
client.updatePendingConfiguration(mConfiguration);
}

View File

@@ -30,6 +30,7 @@ import android.app.ResultInfo;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.BaseBundle;
import android.os.Bundle;
@@ -81,6 +82,8 @@ public class LaunchActivityItem extends ClientTransactionItem {
public void preExecute(ClientTransactionHandler client, IBinder token) {
client.countLaunchingActivities(1);
client.updateProcessState(mProcState, false);
CompatibilityInfo.applyOverrideScaleIfNeeded(mCurConfig);
CompatibilityInfo.applyOverrideScaleIfNeeded(mOverrideConfig);
client.updatePendingConfiguration(mCurConfig);
if (mActivityClientController != null) {
ActivityClient.setActivityClientController(mActivityClientController);

View File

@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Parcel;
@@ -40,6 +41,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
@Override
public void preExecute(ClientTransactionHandler client, IBinder token) {
CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration);
// Notify the client of an upcoming change in the token configuration. This ensures that
// batches of config change items only process the newest configuration.
client.updatePendingActivityConfiguration(token, mConfiguration);

View File

@@ -29,6 +29,7 @@ import android.os.Build.VERSION_CODES;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.MotionEvent;
@@ -111,6 +112,9 @@ public class CompatibilityInfo implements Parcelable {
*/
public final float applicationInvertedScale;
/** The process level override inverted scale. See {@link #HAS_OVERRIDE_SCALING}. */
private static float sOverrideInvertedScale = 1f;
@UnsupportedAppUsage
@Deprecated
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
@@ -125,6 +129,15 @@ public class CompatibilityInfo implements Parcelable {
if (appInfo.targetSdkVersion < VERSION_CODES.O) {
compatFlags |= NEEDS_COMPAT_RES;
}
if (overrideScale != 1.0f) {
applicationScale = overrideScale;
applicationInvertedScale = 1.0f / overrideScale;
applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
* applicationInvertedScale) + .5f);
mCompatibilityFlags = NEVER_NEEDS_COMPAT | HAS_OVERRIDE_SCALING;
// Override scale has the highest priority. So ignore other compatibility attributes.
return;
}
if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0
|| appInfo.largestWidthLimitDp != 0) {
// New style screen requirements spec.
@@ -254,13 +267,7 @@ public class CompatibilityInfo implements Parcelable {
compatFlags |= NEVER_NEEDS_COMPAT;
}
if (overrideScale != 1.0f) {
applicationScale = overrideScale;
applicationInvertedScale = 1.0f / overrideScale;
applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
* applicationInvertedScale) + .5f);
compatFlags |= HAS_OVERRIDE_SCALING;
} else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
applicationDensity = DisplayMetrics.DENSITY_DEVICE;
applicationScale = 1.0f;
applicationInvertedScale = 1.0f;
@@ -296,9 +303,14 @@ public class CompatibilityInfo implements Parcelable {
*/
@UnsupportedAppUsage
public boolean isScalingRequired() {
return (mCompatibilityFlags & (SCALING_REQUIRED | HAS_OVERRIDE_SCALING)) != 0;
return (mCompatibilityFlags & SCALING_REQUIRED) != 0;
}
/** Returns {@code true} if {@link #sOverrideInvertedScale} should be set. */
public boolean hasOverrideScaling() {
return (mCompatibilityFlags & HAS_OVERRIDE_SCALING) != 0;
}
@UnsupportedAppUsage
public boolean supportsScreen() {
return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
@@ -513,7 +525,19 @@ public class CompatibilityInfo implements Parcelable {
}
}
/** Applies the compatibility adjustment to the display metrics. */
public void applyDisplayMetricsIfNeeded(DisplayMetrics inoutDm, boolean applyToSize) {
if (hasOverrideScale()) {
scaleDisplayMetrics(sOverrideInvertedScale, inoutDm, applyToSize);
return;
}
if (!equals(DEFAULT_COMPATIBILITY_INFO)) {
applyToDisplayMetrics(inoutDm);
}
}
public void applyToDisplayMetrics(DisplayMetrics inoutDm) {
if (hasOverrideScale()) return;
if (!supportsScreen()) {
// This is a larger screen device and the app is not
// compatible with large screens, so diddle it.
@@ -524,18 +548,26 @@ public class CompatibilityInfo implements Parcelable {
}
if (isScalingRequired()) {
float invertedRatio = applicationInvertedScale;
inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
inoutDm.densityDpi = (int)((inoutDm.noncompatDensityDpi * invertedRatio) + .5f);
inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
scaleDisplayMetrics(applicationInvertedScale, inoutDm, true /* applyToSize */);
}
}
/** Scales the density of the given display metrics. */
private static void scaleDisplayMetrics(float invertedRatio, DisplayMetrics inoutDm,
boolean applyToSize) {
inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
inoutDm.densityDpi = (int) ((inoutDm.noncompatDensityDpi * invertedRatio) + .5f);
inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
if (applyToSize) {
inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
}
}
public void applyToConfiguration(int displayDensity, Configuration inoutConfig) {
if (hasOverrideScale()) return;
if (!supportsScreen()) {
// This is a larger screen device and the app is not
// compatible with large screens, so we are forcing it to
@@ -549,12 +581,45 @@ public class CompatibilityInfo implements Parcelable {
}
inoutConfig.densityDpi = displayDensity;
if (isScalingRequired()) {
float invertedRatio = applicationInvertedScale;
inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
inoutConfig.windowConfiguration.scale(invertedRatio);
scaleConfiguration(applicationInvertedScale, inoutConfig);
}
}
/** Scales the density and bounds of the given configuration. */
public static void scaleConfiguration(float invertedRatio, Configuration inoutConfig) {
inoutConfig.densityDpi = (int) ((inoutConfig.densityDpi * invertedRatio) + .5f);
inoutConfig.windowConfiguration.scale(invertedRatio);
}
/** @see #sOverrideInvertedScale */
public static void applyOverrideScaleIfNeeded(Configuration config) {
if (!hasOverrideScale()) return;
scaleConfiguration(sOverrideInvertedScale, config);
}
/** @see #sOverrideInvertedScale */
public static void applyOverrideScaleIfNeeded(MergedConfiguration mergedConfig) {
if (!hasOverrideScale()) return;
scaleConfiguration(sOverrideInvertedScale, mergedConfig.getGlobalConfiguration());
scaleConfiguration(sOverrideInvertedScale, mergedConfig.getOverrideConfiguration());
scaleConfiguration(sOverrideInvertedScale, mergedConfig.getMergedConfiguration());
}
/** Returns {@code true} if this process is in a environment with override scale. */
private static boolean hasOverrideScale() {
return sOverrideInvertedScale != 1f;
}
/** @see #sOverrideInvertedScale */
public static void setOverrideInvertedScale(float invertedRatio) {
sOverrideInvertedScale = invertedRatio;
}
/** @see #sOverrideInvertedScale */
public static float getOverrideInvertedScale() {
return sOverrideInvertedScale;
}
/**
* Compute the frame Rect for applications runs under compatibility mode.
*
@@ -632,6 +697,10 @@ public class CompatibilityInfo implements Parcelable {
sb.append(applicationScale);
sb.append("x");
}
if (hasOverrideScaling()) {
sb.append(" overrideInvScale=");
sb.append(applicationInvertedScale);
}
if (!supportsScreen()) {
sb.append(" resizing");
}

View File

@@ -723,9 +723,9 @@ public final class DisplayInfo implements Parcelable {
outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width;
outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
compatInfo.applyToDisplayMetrics(outMetrics);
}
// Apply to size if the configuration is EMPTY because the size is from real display info.
final boolean applyToSize = configuration != null && appBounds == null;
compatInfo.applyDisplayMetricsIfNeeded(outMetrics, applyToSize);
}
// For debugging purposes

View File

@@ -1787,6 +1787,7 @@ public final class ViewRootImpl implements ViewParent,
final ClientWindowFrames frames = (ClientWindowFrames) args.arg1;
final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2;
CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
final boolean forceNextWindowRelayout = args.argi1 != 0;
final int displayId = args.argi3;
final int resizeMode = args.argi5;
@@ -8242,6 +8243,7 @@ public final class ViewRootImpl implements ViewParent,
mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
}
mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls);

View File

@@ -28,6 +28,7 @@ import android.app.ActivityThread;
import android.app.IWindowToken;
import android.app.ResourcesManager;
import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.inputmethodservice.AbstractInputMethodService;
import android.os.Build;
@@ -221,6 +222,7 @@ public class WindowTokenClient extends IWindowToken.Stub {
if (context == null) {
return;
}
CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig);
final boolean displayChanged;
final boolean shouldUpdateResources;
final int diff;

View File

@@ -33,6 +33,7 @@ import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.Application;
import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.app.ResourcesManager;
@@ -46,6 +47,7 @@ import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.StopActivityItem;
import android.content.Context;
import android.content.Intent;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -177,6 +179,85 @@ public class ActivityThreadTest {
});
}
@Test
public void testOverrideScale() throws Exception {
final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
final Application app = activity.getApplication();
final ActivityThread activityThread = activity.getActivityThread();
final IApplicationThread appThread = activityThread.getApplicationThread();
final DisplayMetrics originalAppMetrics = new DisplayMetrics();
originalAppMetrics.setTo(app.getResources().getDisplayMetrics());
final Configuration originalAppConfig =
new Configuration(app.getResources().getConfiguration());
final DisplayMetrics originalActivityMetrics = new DisplayMetrics();
originalActivityMetrics.setTo(activity.getResources().getDisplayMetrics());
final Configuration originalActivityConfig =
new Configuration(activity.getResources().getConfiguration());
final Configuration newConfig = new Configuration(originalAppConfig);
newConfig.seq = BASE_SEQ + 1;
newConfig.smallestScreenWidthDp++;
final float originalScale = CompatibilityInfo.getOverrideInvertedScale();
float scale = 0.5f;
CompatibilityInfo.setOverrideInvertedScale(scale);
try {
// Send process level config change.
ClientTransaction transaction = newTransaction(activityThread, null);
transaction.addCallback(ConfigurationChangeItem.obtain(new Configuration(newConfig)));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertScreenScale(scale, app, originalAppConfig, originalAppMetrics);
// The activity's config doesn't change because ConfigurationChangeItem is process level
// that won't affect activity's override config.
assertEquals(originalActivityConfig.densityDpi,
activity.getResources().getConfiguration().densityDpi);
scale = 0.8f;
CompatibilityInfo.setOverrideInvertedScale(scale);
// Send activity level config change.
newConfig.seq++;
newConfig.smallestScreenWidthDp++;
transaction = newTransaction(activityThread, activity.getActivityToken());
transaction.addCallback(ActivityConfigurationChangeItem.obtain(
new Configuration(newConfig)));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics);
} finally {
CompatibilityInfo.setOverrideInvertedScale(originalScale);
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> restoreConfig(activityThread, originalAppConfig));
}
assertScreenScale(originalScale, app, originalAppConfig, originalAppMetrics);
}
private static void assertScreenScale(float scale, Context context,
Configuration origConfig, DisplayMetrics origMetrics) {
final int expectedDpi = (int) (origConfig.densityDpi * scale + .5f);
final float expectedDensity = origMetrics.density * scale;
final int expectedWidthPixels = (int) (origMetrics.widthPixels * scale + .5f);
final int expectedHeightPixels = (int) (origMetrics.heightPixels * scale + .5f);
final Configuration expectedConfig = new Configuration(origConfig);
CompatibilityInfo.scaleConfiguration(scale, expectedConfig);
final Rect expectedBounds = expectedConfig.windowConfiguration.getBounds();
final Rect expectedAppBounds = expectedConfig.windowConfiguration.getAppBounds();
final Rect expectedMaxBounds = expectedConfig.windowConfiguration.getMaxBounds();
final Configuration currentConfig = context.getResources().getConfiguration();
final DisplayMetrics currentMetrics = context.getResources().getDisplayMetrics();
assertEquals(expectedDpi, currentConfig.densityDpi);
assertEquals(expectedDpi, currentMetrics.densityDpi);
assertEquals(expectedDensity, currentMetrics.density, 0.001f);
assertEquals(expectedWidthPixels, currentMetrics.widthPixels);
assertEquals(expectedHeightPixels, currentMetrics.heightPixels);
assertEquals(expectedBounds, currentConfig.windowConfiguration.getBounds());
assertEquals(expectedAppBounds, currentConfig.windowConfiguration.getAppBounds());
assertEquals(expectedMaxBounds, currentConfig.windowConfiguration.getMaxBounds());
}
@Test
public void testHandleActivityConfigurationChanged() {
final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
@@ -459,19 +540,17 @@ public class ActivityThreadTest {
} finally {
// Make sure to reset the process config to prevent side effects to other
// tests.
Configuration activityThreadConfig = activityThread.getConfiguration();
activityThreadConfig.seq = originalAppConfig.seq - 1;
Configuration resourceManagerConfig = ResourcesManager.getInstance()
.getConfiguration();
resourceManagerConfig.seq = originalAppConfig.seq - 1;
activityThread.updatePendingConfiguration(originalAppConfig);
activityThread.handleConfigurationChanged(originalAppConfig);
restoreConfig(activityThread, originalAppConfig);
}
});
}
private static void restoreConfig(ActivityThread thread, Configuration originalConfig) {
thread.getConfiguration().seq = originalConfig.seq - 1;
ResourcesManager.getInstance().getConfiguration().seq = originalConfig.seq - 1;
thread.handleConfigurationChanged(originalConfig);
}
@Test
public void testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation() {
final TestActivity activity = mActivityTestRule.launchActivity(new Intent());

View File

@@ -671,11 +671,9 @@ public class WindowStateTests extends WindowTestsBase {
verify(t, never()).setMatrix(any(), anyInt(), anyInt(), anyInt(), anyInt());
// According to "dp * density / 160 = px", density is scaled and the size in dp is the same.
final CompatibilityInfo compatInfo = cmp.compatibilityInfoForPackageLocked(
mContext.getApplicationInfo());
final Configuration winConfig = w.getConfiguration();
final Configuration clientConfig = new Configuration(w.getConfiguration());
compatInfo.applyToConfiguration(clientConfig.densityDpi, clientConfig);
CompatibilityInfo.scaleConfiguration(w.mInvGlobalScale, clientConfig);
assertEquals(winConfig.screenWidthDp, clientConfig.screenWidthDp);
assertEquals(winConfig.screenHeightDp, clientConfig.screenHeightDp);