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

Commit 54b0dec7 authored by Martynas Petuška's avatar Martynas Petuška Committed by Android (Google) Code Review
Browse files

Merge "Recursive work app resolution" into main

parents c11cf1dd 49dbad73
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