1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.devicepolicy; 18 19 import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG; 20 21 import android.annotation.Nullable; 22 import android.app.admin.flags.Flags; 23 import android.content.pm.PackageManagerInternal; 24 import android.util.ArraySet; 25 26 import com.android.server.utils.Slogf; 27 28 import java.util.Arrays; 29 import java.util.Collections; 30 import java.util.List; 31 import java.util.Set; 32 33 /** 34 * Helper class for calling into PackageManagerInternal.setPackagesSuspendedByAdmin. 35 * Two main things this class encapsulates: 36 * 1. Handling of the DPM internal suspension exemption list 37 * 2. Calculating the failed packages result in the context of coexistence 38 * 39 * 1 is handled by the two internal methods {@link #suspendWithExemption(Set)} and 40 * {@link #unsuspendWithExemption(Set)} where the exemption list is taken into consideration 41 * before and after calling {@link PackageManagerInternal#setPackagesSuspendedByAdmin}. 42 * In order to compute 2, the resolved package suspension state before and after suspension is 43 * needed as multiple admins can both suspend the same packages under coexistence. 44 */ 45 public class PackageSuspender { 46 47 private final Set<String> mSuspendedPackageBefore; 48 private final Set<String> mSuspendedPackageAfter; 49 private final List<String> mExemptedPackages; 50 private final PackageManagerInternal mPackageManager; 51 private final int mUserId; 52 PackageSuspender(@ullable Set<String> suspendedPackageBefore, @Nullable Set<String> suspendedPackageAfter, List<String> exemptedPackages, PackageManagerInternal pmi, int userId)53 public PackageSuspender(@Nullable Set<String> suspendedPackageBefore, 54 @Nullable Set<String> suspendedPackageAfter, List<String> exemptedPackages, 55 PackageManagerInternal pmi, int userId) { 56 mSuspendedPackageBefore = 57 suspendedPackageBefore != null ? suspendedPackageBefore : Collections.emptySet(); 58 mSuspendedPackageAfter = 59 suspendedPackageAfter != null ? suspendedPackageAfter : Collections.emptySet(); 60 mExemptedPackages = exemptedPackages; 61 mPackageManager = pmi; 62 mUserId = userId; 63 } 64 65 /** 66 * Suspend packages that are requested by a single admin 67 * 68 * @return an array of packages that the admin has requested to suspend but could not be 69 * suspended, due to DPM and PackageManager exemption list. 70 * 71 */ suspend(Set<String> packages)72 public String[] suspend(Set<String> packages) { 73 // When suspending, call PM with the list of packages admin has requested, even if some 74 // of these packages are already in suspension (some other admin might have already 75 // suspended them). We do it this way so that we can simply return the failed list from 76 // PackageManager to the caller as the accurate list of unsuspended packages. 77 // This is different from the unsuspend() logic, please see below. 78 // 79 // For example admin A already suspended package 1, 2 and 3, but package 3 is 80 // PackageManager-exempted. Now admin B wants to suspend package 2, 3 and 4 (2 and 4 are 81 // suspendable). We need to return package 3 as the unsuspended package here, and we ask 82 // PackageManager to suspend package 2, 3 and 4 here (who will return package 3 in the 83 // failed list, and package 2 is already suspended). 84 Set<String> result = suspendWithExemption(packages); 85 return result.toArray(String[]::new); 86 } 87 88 /** 89 * Suspend packages considering the exemption list. 90 * 91 * @return the set of packages that couldn't be suspended, either due to the exemption list, 92 * or due to failures from PackageManagerInternal itself. 93 */ suspendWithExemption(Set<String> packages)94 private Set<String> suspendWithExemption(Set<String> packages) { 95 Set<String> packagesToSuspend = new ArraySet<>(packages); 96 // Any original packages that are also in the exempted list will not be suspended and hence 97 // will appear in the final result. 98 Set<String> result = new ArraySet<>(mExemptedPackages); 99 result.retainAll(packagesToSuspend); 100 // Remove exempted packages before calling PackageManager 101 packagesToSuspend.removeAll(mExemptedPackages); 102 String[] failedPackages = mPackageManager.setPackagesSuspendedByAdmin( 103 mUserId, packagesToSuspend.toArray(String[]::new), true); 104 if (failedPackages == null) { 105 Slogf.w(LOG_TAG, "PM failed to suspend packages (%s)", packages); 106 return packages; 107 } else { 108 result.addAll(Arrays.asList(failedPackages)); 109 return result; 110 } 111 } 112 113 /** 114 * Unsuspend packages that are requested by a single admin 115 * 116 * @return an array of packages that the admin has requested to unsuspend but could not be 117 * unsuspended, due to other admin's policy or PackageManager restriction. 118 * 119 */ unsuspend(Set<String> packages)120 public String[] unsuspend(Set<String> packages) { 121 // Unlike suspend(), when unsuspending, take suspension by other admins into account: only 122 // packages not suspended by other admins are passed to PackageManager. 123 Set<String> packagesToUnsuspend = new ArraySet<>( 124 Flags.unsuspendNotSuspended() ? packages : mSuspendedPackageBefore); 125 packagesToUnsuspend.removeAll(mSuspendedPackageAfter); 126 127 // To calculate the result (which packages are not unsuspended), start with packages that 128 // are still subject to another admin's suspension policy. This is calculated by 129 // intersecting the packages argument with mSuspendedPackageAfter. 130 Set<String> result = new ArraySet<>(packages); 131 result.retainAll(mSuspendedPackageAfter); 132 // Remove mExemptedPackages since they can't be suspended to start with. 133 result.removeAll(mExemptedPackages); 134 // Finally make the unsuspend() request and add packages that PackageManager can't unsuspend 135 // to the result. 136 result.addAll(unsuspendWithExemption(packagesToUnsuspend)); 137 return result.toArray(String[]::new); 138 } 139 140 /** 141 * Unsuspend packages considering the exemption list. 142 * 143 * @return the set of packages that couldn't be unsuspended, either due to the exemption list, 144 * or due to failures from PackageManagerInternal itself. 145 */ unsuspendWithExemption(Set<String> packages)146 private Set<String> unsuspendWithExemption(Set<String> packages) { 147 // when unsuspending, no need to consider exemption list since by definition they can't 148 // be suspended to begin with. 149 String[] failedPackages = mPackageManager.setPackagesSuspendedByAdmin( 150 mUserId, packages.toArray(String[]::new), false); 151 if (failedPackages == null) { 152 Slogf.w(LOG_TAG, "PM failed to unsuspend packages (%s)", packages); 153 } 154 return new ArraySet<>(failedPackages); 155 } 156 } 157