1 /* 2 * Copyright 2014, 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.managedprovisioning.task; 18 19 import android.content.BroadcastReceiver; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.ComponentInfo; 25 import android.content.pm.IPackageDeleteObserver; 26 import android.content.pm.IPackageManager; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager.NameNotFoundException; 29 import android.content.pm.PackageManager; 30 import android.content.pm.ResolveInfo; 31 import android.content.res.Resources; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.util.Xml; 35 import android.view.inputmethod.InputMethodInfo; 36 import android.view.inputmethod.InputMethodManager; 37 38 import com.android.internal.util.FastXmlSerializer; 39 import com.android.managedprovisioning.ProvisionLogger; 40 import com.android.managedprovisioning.R; 41 import com.android.managedprovisioning.Utils; 42 43 import java.io.File; 44 import java.io.FileInputStream; 45 import java.io.FileOutputStream; 46 import java.io.IOException; 47 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.Collections; 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.Set; 54 import java.util.concurrent.atomic.AtomicInteger; 55 56 import org.xmlpull.v1.XmlPullParser; 57 import org.xmlpull.v1.XmlPullParserException; 58 import org.xmlpull.v1.XmlSerializer; 59 60 /** 61 * Deletes all system apps with a launcher that are not in the required set of packages. 62 * Furthermore deletes all disallowed apps. 63 * 64 * Note: If an app is mistakenly listed as both required and disallowed, it will be treated as 65 * required. 66 * 67 * This task may be run when a profile (both for managed device and managed profile) is created. 68 * In that case the newProfile flag should be true. 69 * 70 * It should also be run after a system update with newProfile false, if 71 * {@link #shouldDeleteNonRequiredApps} returns true. Note that only newly installed system apps 72 * will be deleted. 73 */ 74 public class DeleteNonRequiredAppsTask { 75 private final Callback mCallback; 76 private final Context mContext; 77 private final String mMdmPackageName; 78 private final IPackageManager mIpm; 79 private final PackageManager mPm; 80 private final List<String> mRequiredAppsList; 81 private final List<String> mDisallowedAppsList; 82 private final List<String> mVendorRequiredAppsList; 83 private final List<String> mVendorDisallowedAppsList; 84 private final int mUserId; 85 private final int mProvisioningType; 86 private final boolean mNewProfile; // If we are provisioning a new managed profile/device. 87 private final boolean mLeaveAllSystemAppsEnabled; 88 89 private static final String TAG_SYSTEM_APPS = "system-apps"; 90 private static final String TAG_PACKAGE_LIST_ITEM = "item"; 91 private static final String ATTR_VALUE = "value"; 92 93 public static final int DEVICE_OWNER = 0; 94 public static final int PROFILE_OWNER = 1; 95 96 /** 97 * Provisioning type should be either {@link #DEVICE_OWNER} or {@link #PROFILE_OWNER}. 98 **/ 99 public DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int provisioningType, 100 boolean newProfile, int userId, boolean leaveAllSystemAppsEnabled, Callback callback) { 101 102 mCallback = callback; 103 mContext = context; 104 mMdmPackageName = mdmPackageName; 105 mProvisioningType = provisioningType; 106 mUserId = userId; 107 mNewProfile = newProfile; 108 mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled; 109 mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 110 mPm = context.getPackageManager(); 111 112 int requiredAppsListArray; 113 int vendorRequiredAppsListArray; 114 int disallowedAppsListArray; 115 int vendorDisallowedAppsListArray; 116 if (mProvisioningType == DEVICE_OWNER) { 117 requiredAppsListArray = R.array.required_apps_managed_device; 118 disallowedAppsListArray = R.array.disallowed_apps_managed_device; 119 vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_device; 120 vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_device; 121 } else if (mProvisioningType == PROFILE_OWNER) { 122 requiredAppsListArray = R.array.required_apps_managed_profile; 123 disallowedAppsListArray = R.array.disallowed_apps_managed_profile; 124 vendorRequiredAppsListArray = R.array.vendor_required_apps_managed_profile; 125 vendorDisallowedAppsListArray = R.array.vendor_disallowed_apps_managed_profile; 126 } else { 127 throw new IllegalArgumentException("Provisioning type " + mProvisioningType + 128 " not supported."); 129 } 130 131 Resources resources = mContext.getResources(); 132 mRequiredAppsList = Arrays.asList(resources.getStringArray(requiredAppsListArray)); 133 mDisallowedAppsList = Arrays.asList(resources.getStringArray(disallowedAppsListArray)); 134 mVendorRequiredAppsList = Arrays.asList( 135 resources.getStringArray(vendorRequiredAppsListArray)); 136 mVendorDisallowedAppsList = Arrays.asList( 137 resources.getStringArray(vendorDisallowedAppsListArray)); 138 } 139 140 public void run() { 141 if (mLeaveAllSystemAppsEnabled) { 142 ProvisionLogger.logd("Not deleting non-required apps."); 143 mCallback.onSuccess(); 144 return; 145 } 146 ProvisionLogger.logd("Deleting non required apps."); 147 148 File systemAppsFile = getSystemAppsFile(mContext, mUserId); 149 systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist 150 151 Set<String> currentApps = Utils.getCurrentSystemApps(mUserId); 152 Set<String> previousApps; 153 if (mNewProfile) { 154 // Provisioning case. 155 previousApps = new HashSet<String>(); 156 } else { 157 // OTA case. 158 if (!systemAppsFile.exists()) { 159 ProvisionLogger.loge("Could not find the system apps file " + 160 systemAppsFile.getAbsolutePath()); 161 mCallback.onError(); 162 return; 163 } 164 previousApps = readSystemApps(systemAppsFile); 165 } 166 167 writeSystemApps(currentApps, systemAppsFile); 168 Set<String> newApps = currentApps; 169 newApps.removeAll(previousApps); 170 171 // Newly installed system apps are uninstalled when they are not required and are either 172 // disallowed or have a launcher icon. 173 Set<String> packagesToDelete = newApps; 174 packagesToDelete.removeAll(getRequiredApps()); 175 Set<String> packagesToRetain = getCurrentAppsWithLauncher(); 176 // Don't delete the system input method packages in case of Device owner provisioning. 177 if (mProvisioningType == DEVICE_OWNER) { 178 packagesToRetain.removeAll(getSystemInputMethods()); 179 } 180 packagesToRetain.addAll(getDisallowedApps()); 181 packagesToDelete.retainAll(packagesToRetain); 182 183 if (packagesToDelete.isEmpty()) { 184 mCallback.onSuccess(); 185 return; 186 } 187 removeNonInstalledPackages(packagesToDelete); 188 189 PackageDeleteObserver packageDeleteObserver = 190 new PackageDeleteObserver(packagesToDelete.size()); 191 for (String packageName : packagesToDelete) { 192 try { 193 mIpm.deletePackageAsUser(packageName, packageDeleteObserver, mUserId, 194 PackageManager.DELETE_SYSTEM_APP); 195 } catch (RemoteException neverThrown) { 196 // Never thrown, as we are making local calls. 197 ProvisionLogger.loge("This should not happen.", neverThrown); 198 } 199 } 200 } 201 202 /** 203 * Remove all packages from the set that are not installed. 204 */ 205 private void removeNonInstalledPackages(Set<String> packages) { 206 Set<String> toBeRemoved = new HashSet<String>(); 207 for (String packageName : packages) { 208 try { 209 PackageInfo info = mIpm.getPackageInfo(packageName, 0 /* default flags */, mUserId); 210 if (info == null) { 211 toBeRemoved.add(packageName); 212 } 213 } catch (RemoteException neverThrown) { 214 // Never thrown, as we are making local calls. 215 ProvisionLogger.loge("This should not happen.", neverThrown); 216 } 217 } 218 packages.removeAll(toBeRemoved); 219 } 220 221 /** 222 * Returns if this task should be run on OTA. 223 * This is indicated by the presence of the system apps file. 224 */ 225 public static boolean shouldDeleteNonRequiredApps(Context context, int userId) { 226 return getSystemAppsFile(context, userId).exists(); 227 } 228 229 static File getSystemAppsFile(Context context, int userId) { 230 return new File(context.getFilesDir() + File.separator + "system_apps" 231 + File.separator + "user" + userId + ".xml"); 232 } 233 234 private Set<String> getCurrentAppsWithLauncher() { 235 Intent launcherIntent = new Intent(Intent.ACTION_MAIN); 236 launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); 237 List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent, 238 PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS, 239 mUserId); 240 Set<String> apps = new HashSet<String>(); 241 for (ResolveInfo resolveInfo : resolveInfos) { 242 apps.add(resolveInfo.activityInfo.packageName); 243 } 244 return apps; 245 } 246 247 private Set<String> getSystemInputMethods() { 248 final InputMethodManager inputMethodManager = 249 (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); 250 List<InputMethodInfo> inputMethods = inputMethodManager.getInputMethodList(); 251 Set<String> systemInputMethods = new HashSet<String>(); 252 for (InputMethodInfo inputMethodInfo : inputMethods) { 253 ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo; 254 if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 255 systemInputMethods.add(inputMethodInfo.getPackageName()); 256 } 257 } 258 return systemInputMethods; 259 } 260 261 private void writeSystemApps(Set<String> packageNames, File systemAppsFile) { 262 try { 263 FileOutputStream stream = new FileOutputStream(systemAppsFile, false); 264 XmlSerializer serializer = new FastXmlSerializer(); 265 serializer.setOutput(stream, "utf-8"); 266 serializer.startDocument(null, true); 267 serializer.startTag(null, TAG_SYSTEM_APPS); 268 for (String packageName : packageNames) { 269 serializer.startTag(null, TAG_PACKAGE_LIST_ITEM); 270 serializer.attribute(null, ATTR_VALUE, packageName); 271 serializer.endTag(null, TAG_PACKAGE_LIST_ITEM); 272 } 273 serializer.endTag(null, TAG_SYSTEM_APPS); 274 serializer.endDocument(); 275 stream.close(); 276 } catch (IOException e) { 277 ProvisionLogger.loge("IOException trying to write the system apps", e); 278 } 279 } 280 281 private Set<String> readSystemApps(File systemAppsFile) { 282 Set<String> result = new HashSet<String>(); 283 if (!systemAppsFile.exists()) { 284 return result; 285 } 286 try { 287 FileInputStream stream = new FileInputStream(systemAppsFile); 288 289 XmlPullParser parser = Xml.newPullParser(); 290 parser.setInput(stream, null); 291 292 int type = parser.next(); 293 int outerDepth = parser.getDepth(); 294 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 295 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 296 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 297 continue; 298 } 299 String tag = parser.getName(); 300 if (tag.equals(TAG_PACKAGE_LIST_ITEM)) { 301 result.add(parser.getAttributeValue(null, ATTR_VALUE)); 302 } else { 303 ProvisionLogger.loge("Unknown tag: " + tag); 304 } 305 } 306 stream.close(); 307 } catch (IOException e) { 308 ProvisionLogger.loge("IOException trying to read the system apps", e); 309 } catch (XmlPullParserException e) { 310 ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e); 311 } 312 return result; 313 } 314 315 protected Set<String> getRequiredApps() { 316 HashSet<String> requiredApps = new HashSet<String>(); 317 requiredApps.addAll(mRequiredAppsList); 318 requiredApps.addAll(mVendorRequiredAppsList); 319 requiredApps.add(mMdmPackageName); 320 return requiredApps; 321 } 322 323 private Set<String> getDisallowedApps() { 324 HashSet<String> disallowedApps = new HashSet<String>(); 325 disallowedApps.addAll(mDisallowedAppsList); 326 disallowedApps.addAll(mVendorDisallowedAppsList); 327 return disallowedApps; 328 } 329 330 /** 331 * Runs the next task when all packages have been deleted or shuts down the activity if package 332 * deletion fails. 333 */ 334 class PackageDeleteObserver extends IPackageDeleteObserver.Stub { 335 private final AtomicInteger mPackageCount = new AtomicInteger(0); 336 337 public PackageDeleteObserver(int packageCount) { 338 this.mPackageCount.set(packageCount); 339 } 340 341 @Override 342 public void packageDeleted(String packageName, int returnCode) { 343 if (returnCode != PackageManager.DELETE_SUCCEEDED) { 344 ProvisionLogger.logw( 345 "Could not finish the provisioning: package deletion failed"); 346 mCallback.onError(); 347 return; 348 } 349 int currentPackageCount = mPackageCount.decrementAndGet(); 350 if (currentPackageCount == 0) { 351 ProvisionLogger.logi("All non-required system apps with launcher icon, " 352 + "and all disallowed apps have been uninstalled."); 353 mCallback.onSuccess(); 354 } 355 } 356 } 357 358 public abstract static class Callback { 359 public abstract void onSuccess(); 360 public abstract void onError(); 361 } 362 } 363