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.PackageManager.NameNotFoundException; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ResolveInfo; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.util.Xml; 33 34 import com.android.internal.util.FastXmlSerializer; 35 import com.android.managedprovisioning.ProvisionLogger; 36 import com.android.managedprovisioning.R; 37 38 import java.io.File; 39 import java.io.FileInputStream; 40 import java.io.FileOutputStream; 41 import java.io.IOException; 42 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Set; 48 import java.util.concurrent.atomic.AtomicInteger; 49 50 import org.xmlpull.v1.XmlPullParser; 51 import org.xmlpull.v1.XmlPullParserException; 52 import org.xmlpull.v1.XmlSerializer; 53 54 /** 55 * Removes all system apps with a launcher that are not required. 56 * Also disables sharing via Bluetooth, and components that listen to 57 * ACTION_INSTALL_SHORTCUT. 58 * This class is called a first time when a user is created, but also after a system update. 59 * In this case, it checks if the system apps that have been added need to be disabled. 60 */ 61 public class DeleteNonRequiredAppsTask { 62 private final Callback mCallback; 63 private final Context mContext; 64 private final IPackageManager mIpm; 65 private final String mMdmPackageName; 66 private final PackageManager mPm; 67 private final int mReqAppsList; 68 private final int mVendorReqAppsList; 69 private final int mUserId; 70 private final boolean mNewProfile; // If we are provisioning a new managed profile/device. 71 private final boolean mDisableInstallShortcutListenersAndTelecom; 72 73 private static final String TAG_SYSTEM_APPS = "system-apps"; 74 private static final String TAG_PACKAGE_LIST_ITEM = "item"; 75 private static final String ATTR_VALUE = "value"; 76 77 public DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int userId, 78 int requiredAppsList, int vendorRequiredAppsList, boolean newProfile, 79 boolean disableInstallShortcutListenersAndTelecom, Callback callback) { 80 mCallback = callback; 81 mContext = context; 82 mMdmPackageName = mdmPackageName; 83 mUserId = userId; 84 mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 85 mPm = context.getPackageManager(); 86 mReqAppsList = requiredAppsList; 87 mVendorReqAppsList = vendorRequiredAppsList; 88 mNewProfile = newProfile; 89 mDisableInstallShortcutListenersAndTelecom = disableInstallShortcutListenersAndTelecom; 90 } 91 92 public void run() { 93 if (mNewProfile) { 94 disableBluetoothSharing(); 95 } 96 deleteNonRequiredApps(); 97 } 98 99 /** 100 * Returns if this task should be run on OTA. 101 * This is indicated by the presence of the system apps file. 102 */ 103 public static boolean shouldDeleteNonRequiredApps(Context context, int userId) { 104 return getSystemAppsFile(context, userId).exists(); 105 } 106 107 private void disableBluetoothSharing() { 108 ProvisionLogger.logd("Disabling Bluetooth sharing."); 109 disableComponent(new ComponentName("com.android.bluetooth", 110 "com.android.bluetooth.opp.BluetoothOppLauncherActivity")); 111 } 112 113 private void deleteNonRequiredApps() { 114 ProvisionLogger.logd("Deleting non required apps."); 115 116 File systemAppsFile = getSystemAppsFile(mContext, mUserId); 117 systemAppsFile.getParentFile().mkdirs(); // Creating the folder if it does not exist 118 119 Set<String> currentApps = getCurrentSystemApps(); 120 Set<String> previousApps; 121 if (mNewProfile) { 122 // Provisioning case. 123 124 // If this userId was a managed profile before, file may exist. In this case, we ignore 125 // what is in this file. 126 previousApps = new HashSet<String>(); 127 } else { 128 // OTA case. 129 130 if (!systemAppsFile.exists()) { 131 // Error, this task should not have been run. 132 ProvisionLogger.loge("No system apps list found for user " + mUserId); 133 mCallback.onError(); 134 return; 135 } 136 137 previousApps = readSystemApps(systemAppsFile); 138 } 139 writeSystemApps(currentApps, systemAppsFile); 140 Set<String> newApps = currentApps; 141 newApps.removeAll(previousApps); 142 143 if (mDisableInstallShortcutListenersAndTelecom) { 144 Intent actionShortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); 145 if (previousApps.isEmpty()) { 146 // Here, all the apps are in newApps. 147 // It is faster to do it this way than to go through all the apps one by one. 148 disableReceivers(actionShortcut); 149 } else { 150 // Here, all the apps are not in newApps. So we have to go through all the new 151 // apps one by one. 152 for (String newApp : newApps) { 153 actionShortcut.setPackage(newApp); 154 disableReceivers(actionShortcut); 155 } 156 } 157 } 158 Set<String> packagesToDelete = newApps; 159 packagesToDelete.removeAll(getRequiredApps()); 160 packagesToDelete.retainAll(getCurrentAppsWithLauncher()); 161 // com.android.server.telecom should not handle CALL intents in the managed profile. 162 if (mDisableInstallShortcutListenersAndTelecom && mNewProfile) { 163 packagesToDelete.add("com.android.server.telecom"); 164 } 165 if (packagesToDelete.isEmpty()) { 166 mCallback.onSuccess(); 167 return; 168 } 169 PackageDeleteObserver packageDeleteObserver = 170 new PackageDeleteObserver(packagesToDelete.size()); 171 for (String packageName : packagesToDelete) { 172 try { 173 mIpm.deletePackageAsUser(packageName, packageDeleteObserver, mUserId, 174 PackageManager.DELETE_SYSTEM_APP); 175 } catch (RemoteException neverThrown) { 176 // Never thrown, as we are making local calls. 177 ProvisionLogger.loge("This should not happen.", neverThrown); 178 } 179 } 180 } 181 182 static File getSystemAppsFile(Context context, int userId) { 183 return new File(context.getFilesDir() + File.separator + "system_apps" 184 + File.separator + "user" + userId + ".xml"); 185 } 186 187 /** 188 * Disable all components that can handle the specified broadcast intent. 189 */ 190 private void disableReceivers(Intent intent) { 191 List<ResolveInfo> receivers = mPm.queryBroadcastReceivers(intent, 0, mUserId); 192 for (ResolveInfo ri : receivers) { 193 // One of ri.activityInfo, ri.serviceInfo, ri.providerInfo is not null. Let's find which 194 // one. 195 ComponentInfo ci; 196 if (ri.activityInfo != null) { 197 ci = ri.activityInfo; 198 } else if (ri.serviceInfo != null) { 199 ci = ri.serviceInfo; 200 } else { 201 ci = ri.providerInfo; 202 } 203 disableComponent(new ComponentName(ci.packageName, ci.name)); 204 } 205 } 206 207 private void disableComponent(ComponentName toDisable) { 208 try { 209 mIpm.setComponentEnabledSetting(toDisable, 210 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP, 211 mUserId); 212 } catch (RemoteException neverThrown) { 213 ProvisionLogger.loge("This should not happen.", neverThrown); 214 } catch (Exception e) { 215 ProvisionLogger.logw("Component not found, not disabling it: " 216 + toDisable.toShortString()); 217 } 218 } 219 220 /** 221 * Returns the set of package names of apps that are in the system image, 222 * whether they have been deleted or not. 223 */ 224 private Set<String> getCurrentSystemApps() { 225 Set<String> apps = new HashSet<String>(); 226 List<ApplicationInfo> aInfos = null; 227 try { 228 aInfos = mIpm.getInstalledApplications( 229 PackageManager.GET_UNINSTALLED_PACKAGES, mUserId).getList(); 230 } catch (RemoteException neverThrown) { 231 // Never thrown, as we are making local calls. 232 ProvisionLogger.loge("This should not happen.", neverThrown); 233 } 234 for (ApplicationInfo aInfo : aInfos) { 235 if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 236 apps.add(aInfo.packageName); 237 } 238 } 239 return apps; 240 } 241 242 private Set<String> getCurrentAppsWithLauncher() { 243 Intent launcherIntent = new Intent(Intent.ACTION_MAIN); 244 launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); 245 List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent, 246 PackageManager.GET_UNINSTALLED_PACKAGES, mUserId); 247 Set<String> apps = new HashSet<String>(); 248 for (ResolveInfo resolveInfo : resolveInfos) { 249 apps.add(resolveInfo.activityInfo.packageName); 250 } 251 return apps; 252 } 253 254 private void writeSystemApps(Set<String> packageNames, File systemAppsFile) { 255 try { 256 FileOutputStream stream = new FileOutputStream(systemAppsFile, false); 257 XmlSerializer serializer = new FastXmlSerializer(); 258 serializer.setOutput(stream, "utf-8"); 259 serializer.startDocument(null, true); 260 serializer.startTag(null, TAG_SYSTEM_APPS); 261 for (String packageName : packageNames) { 262 serializer.startTag(null, TAG_PACKAGE_LIST_ITEM); 263 serializer.attribute(null, ATTR_VALUE, packageName); 264 serializer.endTag(null, TAG_PACKAGE_LIST_ITEM); 265 } 266 serializer.endTag(null, TAG_SYSTEM_APPS); 267 serializer.endDocument(); 268 stream.close(); 269 } catch (IOException e) { 270 ProvisionLogger.loge("IOException trying to write the system apps", e); 271 } 272 } 273 274 private Set<String> readSystemApps(File systemAppsFile) { 275 Set<String> result = new HashSet<String>(); 276 if (!systemAppsFile.exists()) { 277 return result; 278 } 279 try { 280 FileInputStream stream = new FileInputStream(systemAppsFile); 281 282 XmlPullParser parser = Xml.newPullParser(); 283 parser.setInput(stream, null); 284 285 int type = parser.next(); 286 int outerDepth = parser.getDepth(); 287 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 288 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 289 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 290 continue; 291 } 292 String tag = parser.getName(); 293 if (tag.equals(TAG_PACKAGE_LIST_ITEM)) { 294 result.add(parser.getAttributeValue(null, ATTR_VALUE)); 295 } else { 296 ProvisionLogger.loge("Unknown tag: " + tag); 297 } 298 } 299 stream.close(); 300 } catch (IOException e) { 301 ProvisionLogger.loge("IOException trying to read the system apps", e); 302 } catch (XmlPullParserException e) { 303 ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e); 304 } 305 return result; 306 } 307 308 protected Set<String> getRequiredApps() { 309 HashSet<String> requiredApps = new HashSet<String> (Arrays.asList( 310 mContext.getResources().getStringArray(mReqAppsList))); 311 requiredApps.addAll(Arrays.asList( 312 mContext.getResources().getStringArray(mVendorReqAppsList))); 313 requiredApps.add(mMdmPackageName); 314 return requiredApps; 315 } 316 317 /** 318 * Runs the next task when all packages have been deleted or shuts down the activity if package 319 * deletion fails. 320 */ 321 class PackageDeleteObserver extends IPackageDeleteObserver.Stub { 322 private final AtomicInteger mPackageCount = new AtomicInteger(0); 323 324 public PackageDeleteObserver(int packageCount) { 325 this.mPackageCount.set(packageCount); 326 } 327 328 @Override 329 public void packageDeleted(String packageName, int returnCode) { 330 if (returnCode != PackageManager.DELETE_SUCCEEDED) { 331 ProvisionLogger.logw( 332 "Could not finish the provisioning: package deletion failed"); 333 mCallback.onError(); 334 } 335 int currentPackageCount = mPackageCount.decrementAndGet(); 336 if (currentPackageCount == 0) { 337 ProvisionLogger.logi("All non-required system apps have been uninstalled."); 338 mCallback.onSuccess(); 339 } 340 } 341 } 342 343 public abstract static class Callback { 344 public abstract void onSuccess(); 345 public abstract void onError(); 346 } 347 } 348