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

Commit 49dbad73 authored by Martynas Petuška's avatar Martynas Petuška
Browse files

Recursive work app resolution

Bug: 319084618
Test: atest com.android.server.devicepolicy.RecursiveStringArrayResourceResolverTest
Change-Id: I79294023cba1512370ba3437848f4c752c0db4fb
parent 4888f41d
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto
# proto-message: flag_declarations

package: "android.app.admin.flags"

flag {
@@ -180,3 +183,10 @@ flag {
  description: "Allow COPE admin to control screen brightness and timeout."
  bug: "323894620"
}

flag {
  name: "is_recursive_required_app_merging_enabled"
  namespace: "enterprise"
  description: "Guards a new flow for recursive required enterprise app list merging"
  bug: "319084618"
}
+1 −0
Original line number Diff line number Diff line
@@ -24,5 +24,6 @@ java_library_static {
        "app-compat-annotations",
        "service-permission.stubs.system_server",
        "device_policy_aconfig_flags_lib",
        "androidx.annotation_annotation",
    ],
}
+99 −105
Original line number Diff line number Diff line
@@ -25,15 +25,16 @@ import static android.app.admin.DevicePolicyManager.REQUIRED_APP_MANAGED_USER;
import static android.content.pm.PackageManager.GET_META_DATA;

import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.devicepolicy.DevicePolicyManagerService.dumpResources;
import static com.android.server.devicepolicy.DevicePolicyManagerService.dumpApps;

import static java.util.Objects.requireNonNull;

import android.annotation.ArrayRes;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.flags.Flags;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
@@ -67,13 +68,16 @@ public class OverlayPackagesProvider {

    protected static final String TAG = "OverlayPackagesProvider";
    private static final Map<String, String> sActionToMetadataKeyMap = new HashMap<>();
    {

    static {
        sActionToMetadataKeyMap.put(ACTION_PROVISION_MANAGED_USER, REQUIRED_APP_MANAGED_USER);
        sActionToMetadataKeyMap.put(ACTION_PROVISION_MANAGED_PROFILE, REQUIRED_APP_MANAGED_PROFILE);
        sActionToMetadataKeyMap.put(ACTION_PROVISION_MANAGED_DEVICE, REQUIRED_APP_MANAGED_DEVICE);
    }

    private static final Set<String> sAllowedActions = new HashSet<>();
    {

    static {
        sAllowedActions.add(ACTION_PROVISION_MANAGED_USER);
        sAllowedActions.add(ACTION_PROVISION_MANAGED_PROFILE);
        sAllowedActions.add(ACTION_PROVISION_MANAGED_DEVICE);
@@ -83,8 +87,13 @@ public class OverlayPackagesProvider {
    private final Context mContext;
    private final Injector mInjector;

    private final RecursiveStringArrayResourceResolver mRecursiveStringArrayResourceResolver;

    public OverlayPackagesProvider(Context context) {
        this(context, new DefaultInjector());
        this(
                context,
                new DefaultInjector(),
                new RecursiveStringArrayResourceResolver(context.getResources()));
    }

    @VisibleForTesting
@@ -113,8 +122,8 @@ public class OverlayPackagesProvider {
        public String getDevicePolicyManagementRoleHolderPackageName(Context context) {
            return Binder.withCleanCallingIdentity(() -> {
                RoleManager roleManager = context.getSystemService(RoleManager.class);
                List<String> roleHolders =
                        roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
                List<String> roleHolders = roleManager.getRoleHolders(
                        RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
                if (roleHolders.isEmpty()) {
                    return null;
                }
@@ -124,17 +133,20 @@ public class OverlayPackagesProvider {
    }

    @VisibleForTesting
    OverlayPackagesProvider(Context context, Injector injector) {
    OverlayPackagesProvider(Context context, Injector injector,
            RecursiveStringArrayResourceResolver recursiveStringArrayResourceResolver) {
        mContext = context;
        mPm = checkNotNull(context.getPackageManager());
        mInjector = checkNotNull(injector);
        mPm = requireNonNull(context.getPackageManager());
        mInjector = requireNonNull(injector);
        mRecursiveStringArrayResourceResolver = requireNonNull(
                recursiveStringArrayResourceResolver);
    }

    /**
     * Computes non-required apps. All the system apps with a launcher that are not in
     * the required set of packages, and all mainline modules that are not declared as required
     * via metadata in their manifests, will be considered as non-required apps.
     *
     * <p>
     * Note: If an app is mistakenly listed as both required and disallowed, it will be treated as
     * disallowed.
     *
@@ -176,12 +188,12 @@ public class OverlayPackagesProvider {
    /**
     * Returns a subset of {@code packageNames} whose packages are mainline modules declared as
     * required apps via their app metadata.
     *
     * @see DevicePolicyManager#REQUIRED_APP_MANAGED_USER
     * @see DevicePolicyManager#REQUIRED_APP_MANAGED_DEVICE
     * @see DevicePolicyManager#REQUIRED_APP_MANAGED_PROFILE
     */
    private Set<String> getRequiredAppsMainlineModules(
            Set<String> packageNames,
    private Set<String> getRequiredAppsMainlineModules(Set<String> packageNames,
            String provisioningAction) {
        final Set<String> result = new HashSet<>();
        for (String packageName : packageNames) {
@@ -225,8 +237,8 @@ public class OverlayPackagesProvider {
    }

    private boolean isApkInApexMainlineModule(String packageName) {
        final String apexPackageName =
                mInjector.getActiveApexPackageNameContainingPackage(packageName);
        final String apexPackageName = mInjector.getActiveApexPackageNameContainingPackage(
                packageName);
        return apexPackageName != null;
    }

@@ -274,112 +286,94 @@ public class OverlayPackagesProvider {
    }

    private Set<String> getRequiredAppsSet(String provisioningAction) {
        final int resId;
        switch (provisioningAction) {
            case ACTION_PROVISION_MANAGED_USER:
                resId = R.array.required_apps_managed_user;
                break;
            case ACTION_PROVISION_MANAGED_PROFILE:
                resId = R.array.required_apps_managed_profile;
                break;
            case ACTION_PROVISION_MANAGED_DEVICE:
                resId = R.array.required_apps_managed_device;
                break;
            default:
                throw new IllegalArgumentException("Provisioning type "
                        + provisioningAction + " not supported.");
        }
        return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId)));
        final int resId = switch (provisioningAction) {
            case ACTION_PROVISION_MANAGED_USER -> R.array.required_apps_managed_user;
            case ACTION_PROVISION_MANAGED_PROFILE -> R.array.required_apps_managed_profile;
            case ACTION_PROVISION_MANAGED_DEVICE -> R.array.required_apps_managed_device;
            default -> throw new IllegalArgumentException(
                    "Provisioning type " + provisioningAction + " not supported.");
        };
        return resolveStringArray(resId);
    }

    private Set<String> getDisallowedAppsSet(String provisioningAction) {
        final int resId;
        switch (provisioningAction) {
            case ACTION_PROVISION_MANAGED_USER:
                resId = R.array.disallowed_apps_managed_user;
                break;
            case ACTION_PROVISION_MANAGED_PROFILE:
                resId = R.array.disallowed_apps_managed_profile;
                break;
            case ACTION_PROVISION_MANAGED_DEVICE:
                resId = R.array.disallowed_apps_managed_device;
                break;
            default:
                throw new IllegalArgumentException("Provisioning type "
                        + provisioningAction + " not supported.");
        }
        return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId)));
        final int resId = switch (provisioningAction) {
            case ACTION_PROVISION_MANAGED_USER -> R.array.disallowed_apps_managed_user;
            case ACTION_PROVISION_MANAGED_PROFILE -> R.array.disallowed_apps_managed_profile;
            case ACTION_PROVISION_MANAGED_DEVICE -> R.array.disallowed_apps_managed_device;
            default -> throw new IllegalArgumentException(
                    "Provisioning type " + provisioningAction + " not supported.");
        };
        return resolveStringArray(resId);
    }

    private Set<String> getVendorRequiredAppsSet(String provisioningAction) {
        final int resId;
        switch (provisioningAction) {
            case ACTION_PROVISION_MANAGED_USER:
                resId = R.array.vendor_required_apps_managed_user;
                break;
            case ACTION_PROVISION_MANAGED_PROFILE:
                resId = R.array.vendor_required_apps_managed_profile;
                break;
            case ACTION_PROVISION_MANAGED_DEVICE:
                resId = R.array.vendor_required_apps_managed_device;
                break;
            default:
                throw new IllegalArgumentException("Provisioning type "
                        + provisioningAction + " not supported.");
        }
        return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId)));
        final int resId = switch (provisioningAction) {
            case ACTION_PROVISION_MANAGED_USER -> R.array.vendor_required_apps_managed_user;
            case ACTION_PROVISION_MANAGED_PROFILE -> R.array.vendor_required_apps_managed_profile;
            case ACTION_PROVISION_MANAGED_DEVICE -> R.array.vendor_required_apps_managed_device;
            default -> throw new IllegalArgumentException(
                    "Provisioning type " + provisioningAction + " not supported.");
        };
        return resolveStringArray(resId);
    }

    private Set<String> getVendorDisallowedAppsSet(String provisioningAction) {
        final int resId;
        switch (provisioningAction) {
            case ACTION_PROVISION_MANAGED_USER:
                resId = R.array.vendor_disallowed_apps_managed_user;
                break;
            case ACTION_PROVISION_MANAGED_PROFILE:
                resId = R.array.vendor_disallowed_apps_managed_profile;
                break;
            case ACTION_PROVISION_MANAGED_DEVICE:
                resId = R.array.vendor_disallowed_apps_managed_device;
                break;
            default:
                throw new IllegalArgumentException("Provisioning type "
                        + provisioningAction + " not supported.");
        }
        final int resId = switch (provisioningAction) {
            case ACTION_PROVISION_MANAGED_USER -> R.array.vendor_disallowed_apps_managed_user;
            case ACTION_PROVISION_MANAGED_PROFILE -> R.array.vendor_disallowed_apps_managed_profile;
            case ACTION_PROVISION_MANAGED_DEVICE -> R.array.vendor_disallowed_apps_managed_device;
            default -> throw new IllegalArgumentException(
                    "Provisioning type " + provisioningAction + " not supported.");
        };
        return resolveStringArray(resId);
    }

    private Set<String> resolveStringArray(@ArrayRes int resId) {
        if (Flags.isRecursiveRequiredAppMergingEnabled()) {
            return mRecursiveStringArrayResourceResolver.resolve(mContext.getPackageName(), resId);
        } else {
            return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId)));
        }
    }

    void dump(IndentingPrintWriter pw) {
        pw.println("OverlayPackagesProvider");
        pw.increaseIndent();

        dumpResources(pw, mContext, "required_apps_managed_device",
                R.array.required_apps_managed_device);
        dumpResources(pw, mContext, "required_apps_managed_user",
                R.array.required_apps_managed_user);
        dumpResources(pw, mContext, "required_apps_managed_profile",
                R.array.required_apps_managed_profile);

        dumpResources(pw, mContext, "disallowed_apps_managed_device",
                R.array.disallowed_apps_managed_device);
        dumpResources(pw, mContext, "disallowed_apps_managed_user",
                R.array.disallowed_apps_managed_user);
        dumpResources(pw, mContext, "disallowed_apps_managed_device",
                R.array.disallowed_apps_managed_device);

        dumpResources(pw, mContext, "vendor_required_apps_managed_device",
                R.array.vendor_required_apps_managed_device);
        dumpResources(pw, mContext, "vendor_required_apps_managed_user",
                R.array.vendor_required_apps_managed_user);
        dumpResources(pw, mContext, "vendor_required_apps_managed_profile",
                R.array.vendor_required_apps_managed_profile);

        dumpResources(pw, mContext, "vendor_disallowed_apps_managed_user",
                R.array.vendor_disallowed_apps_managed_user);
        dumpResources(pw, mContext, "vendor_disallowed_apps_managed_device",
                R.array.vendor_disallowed_apps_managed_device);
        dumpResources(pw, mContext, "vendor_disallowed_apps_managed_profile",
                R.array.vendor_disallowed_apps_managed_profile);
        dumpApps(pw, "required_apps_managed_device",
                resolveStringArray(R.array.required_apps_managed_device).toArray(String[]::new));
        dumpApps(pw, "required_apps_managed_user",
                resolveStringArray(R.array.required_apps_managed_user).toArray(String[]::new));
        dumpApps(pw, "required_apps_managed_profile",
                resolveStringArray(R.array.required_apps_managed_profile).toArray(String[]::new));

        dumpApps(pw, "disallowed_apps_managed_device",
                resolveStringArray(R.array.disallowed_apps_managed_device).toArray(String[]::new));
        dumpApps(pw, "disallowed_apps_managed_user",
                resolveStringArray(R.array.disallowed_apps_managed_user).toArray(String[]::new));
        dumpApps(pw, "disallowed_apps_managed_device",
                resolveStringArray(R.array.disallowed_apps_managed_device).toArray(String[]::new));

        dumpApps(pw, "vendor_required_apps_managed_device",
                resolveStringArray(R.array.vendor_required_apps_managed_device).toArray(
                        String[]::new));
        dumpApps(pw, "vendor_required_apps_managed_user",
                resolveStringArray(R.array.vendor_required_apps_managed_user).toArray(
                        String[]::new));
        dumpApps(pw, "vendor_required_apps_managed_profile",
                resolveStringArray(R.array.vendor_required_apps_managed_profile).toArray(
                        String[]::new));

        dumpApps(pw, "vendor_disallowed_apps_managed_user",
                resolveStringArray(R.array.vendor_disallowed_apps_managed_user).toArray(
                        String[]::new));
        dumpApps(pw, "vendor_disallowed_apps_managed_device",
                resolveStringArray(R.array.vendor_disallowed_apps_managed_device).toArray(
                        String[]::new));
        dumpApps(pw, "vendor_disallowed_apps_managed_profile",
                resolveStringArray(R.array.vendor_disallowed_apps_managed_profile).toArray(
                        String[]::new));

        pw.decreaseIndent();
    }
+147 −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 com.android.server.devicepolicy;

import android.annotation.SuppressLint;
import android.content.res.Resources;

import androidx.annotation.ArrayRes;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * A class encapsulating all the logic for recursive string-array resource resolution.
 */
public class RecursiveStringArrayResourceResolver {
    private static final String IMPORT_PREFIX = "#import:";
    private static final String SEPARATOR = "/";
    private static final String PWP = ".";

    private final Resources mResources;

    /**
     * @param resources Android resource access object to use when resolving resources
     */
    public RecursiveStringArrayResourceResolver(Resources resources) {
        this.mResources = resources;
    }

    /**
     * Resolves a given {@code <string-array/>} resource specified via
     * {@param rootId} in {@param pkg}. During resolution all values prefixed with
     * {@link #IMPORT_PREFIX} are expanded and injected
     * into the final list at the position of the import statement,
     * pushing all the following values (and their expansions) down.
     * Circular imports are tracked and skipped to avoid infinite resolution loops without losing
     * data.
     *
     * <p>
     * The import statements are expected in a form of
     * "{@link #IMPORT_PREFIX}{package}{@link #SEPARATOR}{resourceName}"
     * If the resource being imported is from the same package, its package can be specified as a
     * {@link #PWP} shorthand `.`
     * > e.g.:
     * >   {@code "#import:com.android.internal/disallowed_apps_managed_user"}
     * >   {@code "#import:./disallowed_apps_managed_user"}
     *
     * <p>
     * Any incorrect or unresolvable import statement
     * will cause the entire resolution to fail with an error.
     *
     * @param pkg    the package owning the resource
     * @param rootId the id of the {@code <string-array>} resource within {@param pkg} to start the
     *               resolution from
     * @return a flattened list of all the resolved string array values from the root resource
     * as well as all the imported arrays
     */
    public Set<String> resolve(String pkg, @ArrayRes int rootId) {
        return resolve(List.of(), pkg, rootId);
    }

    /**
     * A version of resolve that tracks already imported resources
     * to avoid circular imports and wasted work.
     *
     * @param cache a list of already resolved packages to be skipped for further resolution
     */
    private Set<String> resolve(Collection<String> cache, String pkg, @ArrayRes int rootId) {
        final var strings = mResources.getStringArray(rootId);
        final var runningCache = new ArrayList<>(cache);

        final var result = new HashSet<String>();
        for (var string : strings) {
            final String ref;
            if (string.startsWith(IMPORT_PREFIX)) {
                ref = string.substring(IMPORT_PREFIX.length());
            } else {
                ref = null;
            }

            if (ref == null) {
                result.add(string);
            } else if (!runningCache.contains(ref)) {
                final var next = resolveImport(runningCache, pkg, ref);
                runningCache.addAll(next);
                result.addAll(next);
            }
        }
        return result;
    }

    /**
     * Resolves an import of the {@code <string-array>} resource
     * in the context of {@param importingPackage} by the provided {@param ref}.
     *
     * @param cache            a list of already resolved packages to be passed along into chained
     *                         {@link #resolve} calls
     * @param importingPackage the package that owns the resource which defined the import being
     *                         processed.
     *                         It is also used to expand all {@link #PWP} shorthands in
     *                         {@param ref}
     * @param ref              reference to the resource to be imported in a form of
     *                         "{package}{@link #SEPARATOR}{resourceName}".
     *                         e.g.: {@code com.android.internal/disallowed_apps_managed_user}
     */
    private Set<String> resolveImport(
            Collection<String> cache,
            String importingPackage,
            String ref) {
        final var chunks = ref.split(SEPARATOR, 2);
        final var pkg = chunks[0];
        final var name = chunks[1];
        final String resolvedPkg;
        if (Objects.equals(pkg, PWP)) {
            resolvedPkg = importingPackage;
        } else {
            resolvedPkg = pkg;
        }
        @SuppressLint("DiscouragedApi") final var importId = mResources.getIdentifier(
                /* name = */ name,
                /* defType = */ "array",
                /* defPackage = */ resolvedPkg);
        if (importId == 0) {
            throw new Resources.NotFoundException(
                    /* name= */ String.format("%s:array/%s", resolvedPkg, name));
        }
        return resolve(cache, resolvedPkg, importId);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ android_test {
        "cts-wm-util",
        "platform-compat-test-rules",
        "mockito-target-minus-junit4",
        "mockito-kotlin2",
        "platform-test-annotations",
        "ShortcutManagerTestUtils",
        "truth",
Loading