Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 021a43c4 authored by tomnatan's avatar tomnatan
Browse files

Add support for switching off sandboxing via device config flags

Bug: 187408243
Test: atest WmTests:SizeCompatTests
Test: atest FrameworksCoreTests:ConstrainDisplayApisConfigTest
Change-Id: I3ec02f3b2ba5c1825cdc530fd0f6f71abbe5bd10
parent 8df7b875
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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);
    }

    /**
+118 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+156 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -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"
+119 −0
Original line number Diff line number Diff line
@@ -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();
@@ -790,6 +809,90 @@ public class SizeCompatTests extends WindowTestsBase {
        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
    @EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
    public void testAlwaysSandboxDisplayApis_configEnabled_sandboxingApplied_unresizable() {
@@ -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);
    }
}