blob: f4c44051c9422606088a4217304f5a134273837d [file] [log] [blame]
/*
* Copyright 2017, 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 static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
import static android.app.admin.DevicePolicyManager.REQUIRED_APP_MANAGED_DEVICE;
import static android.app.admin.DevicePolicyManager.REQUIRED_APP_MANAGED_PROFILE;
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 java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.view.inputmethod.InputMethodInfo;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.ApexManager;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Class that provides the apps that are not required on a managed device / profile according to the
* overlays provided via (vendor_|)required_apps_managed_(profile|device).xml.
*/
public class OverlayPackagesProvider {
protected static final String TAG = "OverlayPackagesProvider";
private static final Map<String, String> sActionToMetadataKeyMap = new HashMap<>();
{
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<>();
{
sAllowedActions.add(ACTION_PROVISION_MANAGED_USER);
sAllowedActions.add(ACTION_PROVISION_MANAGED_PROFILE);
sAllowedActions.add(ACTION_PROVISION_MANAGED_DEVICE);
}
private final PackageManager mPm;
private final Context mContext;
private final Injector mInjector;
public OverlayPackagesProvider(Context context) {
this(context, new DefaultInjector());
}
@VisibleForTesting
interface Injector {
@NonNull
List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
String getActiveApexPackageNameContainingPackage(String packageName);
String getDevicePolicyManagementRoleHolderPackageName(Context context);
}
private static final class DefaultInjector implements Injector {
@NonNull
@Override
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
return InputMethodManagerInternal.get().getInputMethodListAsUser(userId);
}
@Override
public String getActiveApexPackageNameContainingPackage(String packageName) {
return ApexManager.getInstance().getActiveApexPackageNameContainingPackage(packageName);
}
@Override
public String getDevicePolicyManagementRoleHolderPackageName(Context context) {
return Binder.withCleanCallingIdentity(() -> {
RoleManager roleManager = context.getSystemService(RoleManager.class);
List<String> roleHolders =
roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
if (roleHolders.isEmpty()) {
return null;
}
return roleHolders.get(0);
});
}
}
@VisibleForTesting
OverlayPackagesProvider(Context context, Injector injector) {
mContext = context;
mPm = checkNotNull(context.getPackageManager());
mInjector = checkNotNull(injector);
}
/**
* 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.
*
* Note: If an app is mistakenly listed as both required and disallowed, it will be treated as
* disallowed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userId The userId for which the non-required apps needs to be computed.
* @param provisioningAction action indicating type of provisioning, should be one of
* {@link ACTION_PROVISION_MANAGED_DEVICE}, {@link
* ACTION_PROVISION_MANAGED_PROFILE} or
* {@link ACTION_PROVISION_MANAGED_USER}.
* @return the set of non-required apps.
*/
@NonNull
public Set<String> getNonRequiredApps(@NonNull ComponentName admin, int userId,
@NonNull String provisioningAction) {
requireNonNull(admin);
checkArgument(sAllowedActions.contains(provisioningAction));
final Set<String> nonRequiredApps = getLaunchableApps(userId);
// Newly installed system apps are uninstalled when they are not required and are either
// disallowed or have a launcher icon.
nonRequiredApps.removeAll(getRequiredApps(provisioningAction, admin.getPackageName()));
nonRequiredApps.removeAll(getSystemInputMethods(userId));
nonRequiredApps.addAll(getDisallowedApps(provisioningAction));
nonRequiredApps.removeAll(
getRequiredAppsMainlineModules(nonRequiredApps, provisioningAction));
nonRequiredApps.removeAll(getDeviceManagerRoleHolders());
return nonRequiredApps;
}
private Set<String> getDeviceManagerRoleHolders() {
HashSet<String> result = new HashSet<>();
String deviceManagerRoleHolderPackageName =
mInjector.getDevicePolicyManagementRoleHolderPackageName(mContext);
if (deviceManagerRoleHolderPackageName != null) {
result.add(deviceManagerRoleHolderPackageName);
}
return result;
}
/**
* 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,
String provisioningAction) {
final Set<String> result = new HashSet<>();
for (String packageName : packageNames) {
if (!isMainlineModule(packageName)) {
continue;
}
if (!isRequiredAppDeclaredInMetadata(packageName, provisioningAction)) {
continue;
}
result.add(packageName);
}
return result;
}
private boolean isRequiredAppDeclaredInMetadata(String packageName, String provisioningAction) {
PackageInfo packageInfo;
try {
packageInfo = mPm.getPackageInfo(packageName, GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.metaData == null) {
return false;
}
final String metadataKey = sActionToMetadataKeyMap.get(provisioningAction);
return packageInfo.applicationInfo.metaData.getBoolean(metadataKey);
}
/**
* Returns {@code true} if the provided package name is a mainline module.
* <p>There are 2 types of mainline modules: a regular mainline module and apk-in-apex module.
*/
private boolean isMainlineModule(String packageName) {
return isRegularMainlineModule(packageName) || isApkInApexMainlineModule(packageName);
}
private boolean isRegularMainlineModule(String packageName) {
try {
mPm.getModuleInfo(packageName, /* flags= */ 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
private boolean isApkInApexMainlineModule(String packageName) {
final String apexPackageName =
mInjector.getActiveApexPackageNameContainingPackage(packageName);
return apexPackageName != null;
}
private Set<String> getLaunchableApps(int userId) {
final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
final List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
final Set<String> apps = new ArraySet<>();
for (ResolveInfo resolveInfo : resolveInfos) {
apps.add(resolveInfo.activityInfo.packageName);
}
return apps;
}
private Set<String> getSystemInputMethods(int userId) {
final List<InputMethodInfo> inputMethods = mInjector.getInputMethodListAsUser(userId);
final Set<String> systemInputMethods = new ArraySet<>();
for (InputMethodInfo inputMethodInfo : inputMethods) {
ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo;
if (applicationInfo.isSystemApp()) {
systemInputMethods.add(inputMethodInfo.getPackageName());
}
}
return systemInputMethods;
}
private Set<String> getRequiredApps(String provisioningAction, String dpcPackageName) {
final Set<String> requiredApps = new ArraySet<>();
requiredApps.addAll(getRequiredAppsSet(provisioningAction));
requiredApps.addAll(getVendorRequiredAppsSet(provisioningAction));
requiredApps.add(dpcPackageName);
return requiredApps;
}
private Set<String> getDisallowedApps(String provisioningAction) {
final Set<String> disallowedApps = new ArraySet<>();
disallowedApps.addAll(getDisallowedAppsSet(provisioningAction));
disallowedApps.addAll(getVendorDisallowedAppsSet(provisioningAction));
return disallowedApps;
}
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)));
}
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)));
}
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)));
}
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.");
}
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);
pw.decreaseIndent();
}
}