diff options
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); + } } |