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

Commit 51664ab0 authored by Tom Natan's avatar Tom Natan Committed by Automerger Merge Worker
Browse files

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

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14530955

Change-Id: I8f7ead573c2d4cf1343a71141137f1c78a23b458
parents 69c84b2f c4cf09ba
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);
    }
}