Merge "Add support for switching off sandboxing via device config flags" into sc-dev

This commit is contained in:
Tom Natan
2021-05-24 15:23:35 +00:00
committed by Android (Google) Code Review
5 changed files with 396 additions and 1 deletions

View File

@@ -1348,7 +1348,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
public boolean neverSandboxDisplayApis() { public boolean neverSandboxDisplayApis() {
return CompatChanges.isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS, return CompatChanges.isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS,
applicationInfo.packageName, applicationInfo.packageName,
UserHandle.getUserHandleForUid(applicationInfo.uid)); UserHandle.getUserHandleForUid(applicationInfo.uid))
|| ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo);
} }
/** /**

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2021 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 android.content.pm;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
import android.provider.DeviceConfig;
import android.util.Slog;
import java.util.Arrays;
import java.util.List;
/**
* Class for processing flags in the Device Config namespace 'constrain_display_apis'.
*
* @hide
*/
public final class ConstrainDisplayApisConfig {
private static final String TAG = ConstrainDisplayApisConfig.class.getSimpleName();
/**
* A string flag whose value holds a comma separated list of package entries in the format
* '<package-name>:<min-version-code>?:<max-version-code>?' for which Display APIs should never
* be constrained.
*/
private static final String FLAG_NEVER_CONSTRAIN_DISPLAY_APIS = "never_constrain_display_apis";
/**
* A boolean flag indicating whether Display APIs should never be constrained for all
* packages. If true, {@link #FLAG_NEVER_CONSTRAIN_DISPLAY_APIS} is ignored.
*/
private static final String FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES =
"never_constrain_display_apis_all_packages";
/**
* Returns true if either the flag 'never_constrain_display_apis_all_packages' is true or the
* flag 'never_constrain_display_apis' contains a package entry that matches the given {@code
* applicationInfo}.
*
* @param applicationInfo Information about the application/package.
*/
public static boolean neverConstrainDisplayApis(ApplicationInfo applicationInfo) {
if (DeviceConfig.getBoolean(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false)) {
return true;
}
String configStr = DeviceConfig.getString(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ "");
// String#split returns a non-empty array given an empty string.
if (configStr.isEmpty()) {
return false;
}
for (String packageEntryString : configStr.split(",")) {
if (matchesApplicationInfo(packageEntryString, applicationInfo)) {
return true;
}
}
return false;
}
/**
* Parses the given {@code packageEntryString} and returns true if {@code
* applicationInfo.packageName} matches the package name in the config and {@code
* applicationInfo.longVersionCode} is within the version range in the config.
*
* <p>Logs a warning and returns false in case the given {@code packageEntryString} is invalid.
*
* @param packageEntryStr A package entry expected to be in the format
* '<package-name>:<min-version-code>?:<max-version-code>?'.
* @param applicationInfo Information about the application/package.
*/
private static boolean matchesApplicationInfo(String packageEntryStr,
ApplicationInfo applicationInfo) {
List<String> packageAndVersions = Arrays.asList(packageEntryStr.split(":", 3));
if (packageAndVersions.size() != 3) {
Slog.w(TAG, "Invalid package entry in flag 'never_constrain_display_apis': "
+ packageEntryStr);
return false;
}
String packageName = packageAndVersions.get(0);
String minVersionCodeStr = packageAndVersions.get(1);
String maxVersionCodeStr = packageAndVersions.get(2);
if (!packageName.equals(applicationInfo.packageName)) {
return false;
}
long version = applicationInfo.longVersionCode;
try {
if (!minVersionCodeStr.isEmpty() && version < Long.parseLong(minVersionCodeStr)) {
return false;
}
if (!maxVersionCodeStr.isEmpty() && version > Long.parseLong(maxVersionCodeStr)) {
return false;
}
} catch (NumberFormatException e) {
Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryStr);
return false;
}
return true;
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) 2021 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 android.content.pm;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.annotation.Nullable;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Test class for {@link ConstrainDisplayApisConfig}.
*
* Build/Install/Run:
* atest FrameworksCoreTests:ConstrainDisplayApisConfigTest
*/
@SmallTest
@Presubmit
public final class ConstrainDisplayApisConfigTest {
private Properties mInitialConstrainDisplayApisFlags;
@Before
public void setUp() throws Exception {
mInitialConstrainDisplayApisFlags = DeviceConfig.getProperties(
NAMESPACE_CONSTRAIN_DISPLAY_APIS);
clearConstrainDisplayApisFlags();
}
@After
public void tearDown() throws Exception {
DeviceConfig.setProperties(mInitialConstrainDisplayApisFlags);
}
@Test
public void neverConstrainDisplayApis_allPackagesFlagTrue_returnsTrue() {
setNeverConstrainDisplayApisAllPackagesFlag("true");
// Setting 'never_constrain_display_apis' as well to make sure it is ignored.
setNeverConstrainDisplayApisFlag("com.android.other:1:2,com.android.other2::");
testNeverConstrainDisplayApis("com.android.test", /* version= */ 5, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.other", /* version= */ 0, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.other", /* version= */ 3, /* expected= */ true);
}
@Test
public void neverConstrainDisplayApis_flagsNoSet_returnsFalse() {
testNeverConstrainDisplayApis("com.android.test", /* version= */ 1, /* expected= */ false);
}
@Test
public void neverConstrainDisplayApis_flagsHasSingleEntry_returnsTrueForPackageWithinRange() {
setNeverConstrainDisplayApisFlag("com.android.test:1:1");
testNeverConstrainDisplayApis("com.android.other", /* version= */ 5, /* expected= */ false);
testNeverConstrainDisplayApis("com.android.test", /* version= */ 0, /* expected= */ false);
testNeverConstrainDisplayApis("com.android.test", /* version= */ 1, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.test", /* version= */ 2, /* expected= */ false);
}
@Test
public void neverConstrainDisplayApis_flagHasEntries_returnsTrueForPackagesWithinRange() {
setNeverConstrainDisplayApisFlag("com.android.test1::,com.android.test2:1:3,"
+ "com.android.test3:5:,com.android.test4::8");
// Package 'com.android.other'
testNeverConstrainDisplayApis("com.android.other", /* version= */ 5, /* expected= */ false);
// Package 'com.android.test1'
testNeverConstrainDisplayApis("com.android.test1", /* version= */ 5, /* expected= */ true);
// Package 'com.android.test2'
testNeverConstrainDisplayApis("com.android.test2", /* version= */ 0, /* expected= */ false);
testNeverConstrainDisplayApis("com.android.test2", /* version= */ 1, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.test2", /* version= */ 2, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.test2", /* version= */ 3, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.test2", /* version= */ 4, /* expected= */ false);
// Package 'com.android.test3'
testNeverConstrainDisplayApis("com.android.test3", /* version= */ 4, /* expected= */ false);
testNeverConstrainDisplayApis("com.android.test3", /* version= */ 5, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.test3", /* version= */ 6, /* expected= */ true);
// Package 'com.android.test4'
testNeverConstrainDisplayApis("com.android.test4", /* version= */ 7, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.test4", /* version= */ 8, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.test4", /* version= */ 9, /* expected= */ false);
}
@Test
public void neverConstrainDisplayApis_flagHasInvalidEntries_ignoresInvalidEntries() {
// We add a valid entry before and after the invalid ones to make sure they are applied.
setNeverConstrainDisplayApisFlag("com.android.test1::,com.android.test2:1,"
+ "com.android.test3:5:ten,com.android.test4:5::,com.android.test5::");
testNeverConstrainDisplayApis("com.android.test1", /* version= */ 5, /* expected= */ true);
testNeverConstrainDisplayApis("com.android.test2", /* version= */ 2, /* expected= */ false);
testNeverConstrainDisplayApis("com.android.test3", /* version= */ 7, /* expected= */ false);
testNeverConstrainDisplayApis("com.android.test4", /* version= */ 7, /* expected= */ false);
testNeverConstrainDisplayApis("com.android.test5", /* version= */ 5, /* expected= */ true);
}
private static void testNeverConstrainDisplayApis(String packageName, long version,
boolean expected) {
boolean result = ConstrainDisplayApisConfig.neverConstrainDisplayApis(
buildApplicationInfo(packageName, version));
if (expected) {
assertTrue(result);
} else {
assertFalse(result);
}
}
private static ApplicationInfo buildApplicationInfo(String packageName, long version) {
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.packageName = packageName;
applicationInfo.longVersionCode = version;
return applicationInfo;
}
private static void setNeverConstrainDisplayApisFlag(@Nullable String value) {
DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS, "never_constrain_display_apis",
value, /* makeDefault= */ false);
}
private static void setNeverConstrainDisplayApisAllPackagesFlag(@Nullable String value) {
DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
"never_constrain_display_apis_all_packages",
value, /* makeDefault= */ false);
}
private static void clearConstrainDisplayApisFlags() {
setNeverConstrainDisplayApisFlag(null);
setNeverConstrainDisplayApisAllPackagesFlag(null);
}
}

View File

@@ -42,6 +42,7 @@
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
<uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"/> <uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"/>
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
<!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) --> <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
<application android:debuggable="true" <application android:debuggable="true"

View File

@@ -24,6 +24,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90; import static android.view.Surface.ROTATION_90;
@@ -54,6 +55,7 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doCallRealMethod;
import android.annotation.Nullable;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal;
import android.app.WindowConfiguration; import android.app.WindowConfiguration;
@@ -64,6 +66,8 @@ import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.view.WindowManager; import android.view.WindowManager;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;
@@ -71,6 +75,8 @@ import androidx.test.filters.MediumTest;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TestRule; import org.junit.rules.TestRule;
@@ -91,6 +97,19 @@ public class SizeCompatTests extends WindowTestsBase {
private Task mTask; private Task mTask;
private ActivityRecord mActivity; private ActivityRecord mActivity;
private Properties mInitialConstrainDisplayApisFlags;
@Before
public void setUp() throws Exception {
mInitialConstrainDisplayApisFlags = DeviceConfig.getProperties(
NAMESPACE_CONSTRAIN_DISPLAY_APIS);
clearConstrainDisplayApisFlags();
}
@After
public void tearDown() throws Exception {
DeviceConfig.setProperties(mInitialConstrainDisplayApisFlags);
}
private void setUpApp(DisplayContent display) { private void setUpApp(DisplayContent display) {
mTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build(); mTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build();
@@ -790,6 +809,90 @@ public class SizeCompatTests extends WindowTestsBase {
assertActivityMaxBoundsSandboxed(activity); assertActivityMaxBoundsSandboxed(activity);
} }
@Test
public void testNeverConstrainDisplayApisDeviceConfig_allPackagesFlagTrue_sandboxNotApplied() {
setUpDisplaySizeWithApp(1000, 1200);
setNeverConstrainDisplayApisAllPackagesFlag("true");
// Setting 'never_constrain_display_apis' as well to make sure it is ignored.
setNeverConstrainDisplayApisFlag("com.android.other::,com.android.other2::");
// Make the task root resizable.
mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
// Create an activity with a max aspect ratio on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
// Activity max bounds should not be sandboxed, even though it is letterboxed.
assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
.isEqualTo(activity.getDisplayArea().getBounds());
}
@Test
public void testNeverConstrainDisplayApisDeviceConfig_packageInRange_sandboxingNotApplied() {
setUpDisplaySizeWithApp(1000, 1200);
setNeverConstrainDisplayApisFlag(
"com.android.frameworks.wmtests:20:,com.android.other::,"
+ "com.android.frameworks.wmtests:0:10");
// Make the task root resizable.
mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
// Create an activity with a max aspect ratio on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
// Activity max bounds should not be sandboxed, even though it is letterboxed.
assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
.isEqualTo(activity.getDisplayArea().getBounds());
}
@Test
public void testNeverConstrainDisplayApisDeviceConfig_packageOutsideRange_sandboxingApplied() {
setUpDisplaySizeWithApp(1000, 1200);
setNeverConstrainDisplayApisFlag("com.android.other::,com.android.frameworks.wmtests:1:5");
// Make the task root resizable.
mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
// Create an activity with a max aspect ratio on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
// Activity max bounds should be sandboxed due to letterboxed and the mismatch with flag.
assertActivityMaxBoundsSandboxed(activity);
}
@Test
public void testNeverConstrainDisplayApisDeviceConfig_packageNotInFlag_sandboxingApplied() {
setUpDisplaySizeWithApp(1000, 1200);
setNeverConstrainDisplayApisFlag("com.android.other::,com.android.other2::");
// Make the task root resizable.
mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
// Create an activity with a max aspect ratio on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
// Activity max bounds should be sandboxed due to letterboxed and the mismatch with flag.
assertActivityMaxBoundsSandboxed(activity);
}
@Test @Test
@EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS}) @EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
public void testAlwaysSandboxDisplayApis_configEnabled_sandboxingApplied_unresizable() { public void testAlwaysSandboxDisplayApis_configEnabled_sandboxingApplied_unresizable() {
@@ -1927,4 +2030,20 @@ public class SizeCompatTests extends WindowTestsBase {
displayContent.computeScreenConfiguration(c); displayContent.computeScreenConfiguration(c);
displayContent.onRequestedOverrideConfigurationChanged(c); displayContent.onRequestedOverrideConfigurationChanged(c);
} }
private static void setNeverConstrainDisplayApisFlag(@Nullable String value) {
DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS, "never_constrain_display_apis",
value, /* makeDefault= */ false);
}
private static void setNeverConstrainDisplayApisAllPackagesFlag(@Nullable String value) {
DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
"never_constrain_display_apis_all_packages",
value, /* makeDefault= */ false);
}
private static void clearConstrainDisplayApisFlags() {
setNeverConstrainDisplayApisFlag(null);
setNeverConstrainDisplayApisAllPackagesFlag(null);
}
} }