summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/ActivityInfo.java3
-rw-r--r--core/java/android/content/pm/ConstrainDisplayApisConfig.java118
-rw-r--r--core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java156
-rw-r--r--services/tests/wmtests/AndroidManifest.xml1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java119
5 files changed, 396 insertions, 1 deletions
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 03f6380e279f..af5f9ceb14d8 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1348,7 +1348,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
public boolean neverSandboxDisplayApis() {
return CompatChanges.isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS,
applicationInfo.packageName,
- UserHandle.getUserHandleForUid(applicationInfo.uid));
+ UserHandle.getUserHandleForUid(applicationInfo.uid))
+ || ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo);
}
/**
diff --git a/core/java/android/content/pm/ConstrainDisplayApisConfig.java b/core/java/android/content/pm/ConstrainDisplayApisConfig.java
new file mode 100644
index 000000000000..1337347cdaf0
--- /dev/null
+++ b/core/java/android/content/pm/ConstrainDisplayApisConfig.java
@@ -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;
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
new file mode 100644
index 000000000000..1dfbfcd85a73
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
@@ -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);
+ }
+}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 985b2d5f24ba..c2298d0f3d20 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -42,6 +42,7 @@
<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.CAPTURE_BLACKOUT_CONTENT"/>
+ <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
<!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
<application android:debuggable="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 7224a0e30933..8e3bb69b7649 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -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.res.Configuration.ORIENTATION_LANDSCAPE;
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_270;
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.doCallRealMethod;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.WindowConfiguration;
@@ -64,6 +66,8 @@ import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
import android.view.WindowManager;
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.EnableCompatChanges;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -91,6 +97,19 @@ public class SizeCompatTests extends WindowTestsBase {
private Task mTask;
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) {
mTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build();
@@ -791,6 +810,90 @@ public class SizeCompatTests extends WindowTestsBase {
}
@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
@EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
public void testAlwaysSandboxDisplayApis_configEnabled_sandboxingApplied_unresizable() {
setUpDisplaySizeWithApp(1000, 1200);
@@ -1927,4 +2030,20 @@ public class SizeCompatTests extends WindowTestsBase {
displayContent.computeScreenConfiguration(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);
+ }
}