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.content.pm.PackageManagerInternal; 23 import android.util.ArraySet; 24 25 import com.android.server.utils.Slogf; 26 27 import java.util.Arrays; 28 import java.util.Collections; 29 import java.util.List; 30 import java.util.Set; 31 32 /** 33 * Helper class for calling into PackageManagerInternal.setPackagesSuspendedByAdmin. 34 * Two main things this class encapsulates: 35 * 1. Handling of the DPM internal suspension exemption list 36 * 2. Calculating the failed packages result in the context of coexistence 37 * 38 * 1 is handled by the two internal methods {@link #suspendWithExemption(Set)} and 39 * {@link #unsuspendWithExemption(Set)} where the exemption list is taken into consideration 40 * before and after calling {@link PackageManagerInternal#setPackagesSuspendedByAdmin}. 41 * In order to compute 2, the resolved package suspension state before and after suspension is 42 * needed as multiple admins can both suspend the same packages under coexistence. 43 */ 44 public class PackageSuspender { 45 46 private final Set<String> mSuspendedPackageBefore; 47 private final Set<String> mSuspendedPackageAfter; 48 private final List<String> mExemptedPackages; 49 private final PackageManagerInternal mPackageManager; 50 private final int mUserId; 51 PackageSuspender(@ullable Set<String> suspendedPackageBefore, @Nullable Set<String> suspendedPackageAfter, List<String> exemptedPackages, PackageManagerInternal pmi, int userId)52 public PackageSuspender(@Nullable Set<String> suspendedPackageBefore, 53 @Nullable Set<String> suspendedPackageAfter, List<String> exemptedPackages, 54 PackageManagerInternal pmi, int userId) { 55 mSuspendedPackageBefore = 56 suspendedPackageBefore != null ? suspendedPackageBefore : Collections.emptySet(); 57 mSuspendedPackageAfter = 58 suspendedPackageAfter != null ? suspendedPackageAfter : Collections.emptySet(); 59 mExemptedPackages = exemptedPackages; 60 mPackageManager = pmi; 61 mUserId = userId; 62 } 63 64 /** 65 * Suspend packages that are requested by a single admin 66 * 67 * @return a list of packages that the admin has requested to suspend but could not be 68 * suspended, due to DPM and PackageManager exemption list. 69 * 70 */ suspend(Set<String> packages)71 public String[] suspend(Set<String> packages) { 72 // When suspending, call PM with the list of packages admin has requested, even if some 73 // of these packages are already in suspension (some other admin might have already 74 // suspended them). We do it this way so that we can simply return the failed list from 75 // PackageManager to the caller as the accurate list of unsuspended packages. 76 // This is different from the unsuspend() logic, please see below. 77 // 78 // For example admin A already suspended package 1, 2 and 3, but package 3 is 79 // PackageManager-exempted. Now admin B wants to suspend package 2, 3 and 4 (2 and 4 are 80 // suspendable). We need to return package 3 as the unsuspended package here, and we ask 81 // PackageManager to suspend package 2, 3 and 4 here (who will return package 3 in the 82 // failed list, and package 2 is already suspended). 83 Set<String> result = suspendWithExemption(packages); 84 return result.toArray(String[]::new); 85 } 86 87 /** 88 * Suspend packages considering the exemption list. 89 * 90 * @return the list of packages that couldn't be suspended, either due to the exemption list, 91 * or due to failures from PackageManagerInternal itself. 92 */ suspendWithExemption(Set<String> packages)93 private Set<String> suspendWithExemption(Set<String> packages) { 94 Set<String> packagesToSuspend = new ArraySet<>(packages); 95 // Any original packages that are also in the exempted list will not be suspended and hence 96 // will appear in the final result. 97 Set<String> result = new ArraySet<>(mExemptedPackages); 98 result.retainAll(packagesToSuspend); 99 // Remove exempted packages before calling PackageManager 100 packagesToSuspend.removeAll(mExemptedPackages); 101 String[] failedPackages = mPackageManager.setPackagesSuspendedByAdmin( 102 mUserId, packagesToSuspend.toArray(String[]::new), true); 103 if (failedPackages == null) { 104 Slogf.w(LOG_TAG, "PM failed to suspend packages (%s)", packages); 105 return packages; 106 } else { 107 result.addAll(Arrays.asList(failedPackages)); 108 return result; 109 } 110 } 111 112 /** 113 * Unsuspend packages that are requested by a single admin 114 * 115 * @return a list of packages that the admin has requested to unsuspend but could not be 116 * unsuspended, due to other amdin's policy or PackageManager restriction. 117 * 118 */ unsuspend(Set<String> packages)119 public String[] unsuspend(Set<String> packages) { 120 // Unlike suspend(), when unsuspending, call PackageManager with the delta of resolved 121 // suspended packages list and not what the admin has requested. This is because some 122 // packages might still be subject to another admin's suspension request. 123 Set<String> packagesToUnsuspend = new ArraySet<>(mSuspendedPackageBefore); 124 packagesToUnsuspend.removeAll(mSuspendedPackageAfter); 125 126 // To calculate the result (which packages are not unsuspended), start with packages that 127 // are still subject to another admin's suspension policy. This is calculated by 128 // intersecting the packages argument with mSuspendedPackageAfter. 129 Set<String> result = new ArraySet<>(packages); 130 result.retainAll(mSuspendedPackageAfter); 131 // Remove mExemptedPackages since they can't be suspended to start with. 132 result.removeAll(mExemptedPackages); 133 // Finally make the unsuspend() request and add packages that PackageManager can't unsuspend 134 // to the result. 135 result.addAll(unsuspendWithExemption(packagesToUnsuspend)); 136 return result.toArray(String[]::new); 137 } 138 139 /** 140 * Unsuspend packages considering the exemption list. 141 * 142 * @return the list of packages that couldn't be unsuspended, either due to the exemption list, 143 * or due to failures from PackageManagerInternal itself. 144 */ unsuspendWithExemption(Set<String> packages)145 private Set<String> unsuspendWithExemption(Set<String> packages) { 146 // when unsuspending, no need to consider exemption list since by definition they can't 147 // be suspended to begin with. 148 String[] failedPackages = mPackageManager.setPackagesSuspendedByAdmin( 149 mUserId, packages.toArray(String[]::new), false); 150 if (failedPackages == null) { 151 Slogf.w(LOG_TAG, "PM failed to unsuspend packages (%s)", packages); 152 } 153 return new ArraySet<>(failedPackages); 154 } 155 } 156