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 Nfc, 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 profile. 71 private final boolean mDisableInstallShortcutListeners; 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 disableInstallShortcutListeners, 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 mDisableInstallShortcutListeners = disableInstallShortcutListeners; 90 } 91 92 public void run() { 93 if (mNewProfile) { 94 disableNfcBluetoothSharing(); 95 } 96 deleteNonRequiredApps(); 97 } 98 99 private void disableNfcBluetoothSharing() { 100 ProvisionLogger.logd("Disabling Nfc and Bluetooth sharing."); 101 disableComponent(new ComponentName("com.android.nfc", "com.android.nfc.BeamShareActivity")); 102 disableComponent(new ComponentName("com.android.bluetooth", 103 "com.android.bluetooth.opp.BluetoothOppLauncherActivity")); 104 } 105 106 private void deleteNonRequiredApps() { 107 ProvisionLogger.logd("Deleting non required apps."); 108 109 File file = new File(mContext.getFilesDir() + File.separator + "system_apps" 110 + File.separator + "user" + mUserId + ".xml"); 111 file.getParentFile().mkdirs(); // Creating the folder if it does not exist 112 113 Set<String> currentApps = getCurrentSystemApps(); 114 Set<String> previousApps; 115 if (mNewProfile) { 116 // If this userId was a managed profile before, file may exist. In this case, we ignore 117 // what is in this file. 118 previousApps = new HashSet<String>(); 119 } else { 120 if (file.exists()) { 121 previousApps = readSystemApps(file); 122 } else { 123 // If for some reason, the system apps have not been written to file before, we will 124 // not delete any system apps this time. 125 writeSystemApps(currentApps, file); 126 mCallback.onSuccess(); 127 return; 128 } 129 } 130 writeSystemApps(currentApps, file); 131 Set<String> newApps = currentApps; 132 newApps.removeAll(previousApps); 133 134 if (mDisableInstallShortcutListeners) { 135 Intent actionShortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); 136 if (previousApps.isEmpty()) { 137 // Here, all the apps are in newApps. 138 // It is faster to do it this way than to go through all the apps one by one. 139 disableReceivers(actionShortcut); 140 } else { 141 // Here, all the apps are not in newApps. So we have to go through all the new 142 // apps one by one. 143 for (String newApp : newApps) { 144 actionShortcut.setPackage(newApp); 145 disableReceivers(actionShortcut); 146 } 147 } 148 } 149 Set<String> packagesToDelete = newApps; 150 packagesToDelete.removeAll(getRequiredApps()); 151 packagesToDelete.retainAll(getCurrentAppsWithLauncher()); 152 // com.android.server.telecom should not handle CALL intents in the managed profile. 153 if (mNewProfile) { 154 packagesToDelete.add("com.android.server.telecom"); 155 } 156 int size = packagesToDelete.size(); 157 if (size > 0) { 158 PackageDeleteObserver packageDeleteObserver = 159 new PackageDeleteObserver(packagesToDelete.size()); 160 for (String packageName : packagesToDelete) { 161 try { 162 mIpm.deletePackageAsUser(packageName, packageDeleteObserver, mUserId, 163 PackageManager.DELETE_SYSTEM_APP); 164 } catch (RemoteException neverThrown) { 165 // Never thrown, as we are making local calls. 166 ProvisionLogger.loge("This should not happen.", neverThrown); 167 } 168 } 169 } else { 170 mCallback.onSuccess(); 171 } 172 } 173 174 private void disableReceivers(Intent intent) { 175 List<ResolveInfo> receivers = mPm.queryBroadcastReceivers(intent, 0, mUserId); 176 for (ResolveInfo ri : receivers) { 177 // One of ri.activityInfo, ri.serviceInfo, ri.providerInfo is not null. Let's find which 178 // one. 179 ComponentInfo ci; 180 if (ri.activityInfo != null) { 181 ci = ri.activityInfo; 182 } else if (ri.serviceInfo != null) { 183 ci = ri.serviceInfo; 184 } else { 185 ci = ri.providerInfo; 186 } 187 disableComponent(new ComponentName(ci.packageName, ci.name)); 188 } 189 } 190 191 private void disableComponent(ComponentName toDisable) { 192 try { 193 mIpm.setComponentEnabledSetting(toDisable, 194 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP, 195 mUserId); 196 } catch (RemoteException neverThrown) { 197 ProvisionLogger.loge("This should not happen.", neverThrown); 198 } catch (Exception e) { 199 ProvisionLogger.logw("Component not found, not disabling it: " 200 + toDisable.toShortString()); 201 } 202 } 203 204 /** 205 * Returns the set of package names of apps that are in the system image, 206 * whether they have been deleted or not. 207 */ 208 private Set<String> getCurrentSystemApps() { 209 Set<String> apps = new HashSet<String>(); 210 List<ApplicationInfo> aInfos = null; 211 try { 212 aInfos = mIpm.getInstalledApplications( 213 PackageManager.GET_UNINSTALLED_PACKAGES, mUserId).getList(); 214 } catch (RemoteException neverThrown) { 215 // Never thrown, as we are making local calls. 216 ProvisionLogger.loge("This should not happen.", neverThrown); 217 } 218 for (ApplicationInfo aInfo : aInfos) { 219 if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 220 apps.add(aInfo.packageName); 221 } 222 } 223 return apps; 224 } 225 226 private Set<String> getCurrentAppsWithLauncher() { 227 Intent launcherIntent = new Intent(Intent.ACTION_MAIN); 228 launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); 229 List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent, 230 PackageManager.GET_UNINSTALLED_PACKAGES, mUserId); 231 Set<String> apps = new HashSet<String>(); 232 for (ResolveInfo resolveInfo : resolveInfos) { 233 apps.add(resolveInfo.activityInfo.packageName); 234 } 235 return apps; 236 } 237 238 private void writeSystemApps(Set<String> packageNames, File file) { 239 try { 240 FileOutputStream stream = new FileOutputStream(file, false); 241 XmlSerializer serializer = new FastXmlSerializer(); 242 serializer.setOutput(stream, "utf-8"); 243 serializer.startDocument(null, true); 244 serializer.startTag(null, TAG_SYSTEM_APPS); 245 for (String packageName : packageNames) { 246 serializer.startTag(null, TAG_PACKAGE_LIST_ITEM); 247 serializer.attribute(null, ATTR_VALUE, packageName); 248 serializer.endTag(null, TAG_PACKAGE_LIST_ITEM); 249 } 250 serializer.endTag(null, TAG_SYSTEM_APPS); 251 serializer.endDocument(); 252 stream.close(); 253 } catch (IOException e) { 254 ProvisionLogger.loge("IOException trying to write the system apps", e); 255 } 256 } 257 258 private Set<String> readSystemApps(File file) { 259 Set<String> result = new HashSet<String>(); 260 if (!file.exists()) { 261 return result; 262 } 263 try { 264 FileInputStream stream = new FileInputStream(file); 265 266 XmlPullParser parser = Xml.newPullParser(); 267 parser.setInput(stream, null); 268 269 int type = parser.next(); 270 int outerDepth = parser.getDepth(); 271 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 272 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 273 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 274 continue; 275 } 276 String tag = parser.getName(); 277 if (tag.equals(TAG_PACKAGE_LIST_ITEM)) { 278 result.add(parser.getAttributeValue(null, ATTR_VALUE)); 279 } else { 280 ProvisionLogger.loge("Unknown tag: " + tag); 281 } 282 } 283 stream.close(); 284 } catch (IOException e) { 285 ProvisionLogger.loge("IOException trying to read the system apps", e); 286 } catch (XmlPullParserException e) { 287 ProvisionLogger.loge("XmlPullParserException trying to read the system apps", e); 288 } 289 return result; 290 } 291 292 protected Set<String> getRequiredApps() { 293 HashSet<String> requiredApps = new HashSet<String> (Arrays.asList( 294 mContext.getResources().getStringArray(mReqAppsList))); 295 requiredApps.addAll(Arrays.asList( 296 mContext.getResources().getStringArray(mVendorReqAppsList))); 297 requiredApps.add(mMdmPackageName); 298 return requiredApps; 299 } 300 301 /** 302 * Runs the next task when all packages have been deleted or shuts down the activity if package 303 * deletion fails. 304 */ 305 class PackageDeleteObserver extends IPackageDeleteObserver.Stub { 306 private final AtomicInteger mPackageCount = new AtomicInteger(0); 307 308 public PackageDeleteObserver(int packageCount) { 309 this.mPackageCount.set(packageCount); 310 } 311 312 @Override 313 public void packageDeleted(String packageName, int returnCode) { 314 if (returnCode != PackageManager.DELETE_SUCCEEDED) { 315 ProvisionLogger.logw( 316 "Could not finish the provisioning: package deletion failed"); 317 mCallback.onError(); 318 } 319 int currentPackageCount = mPackageCount.decrementAndGet(); 320 if (currentPackageCount == 0) { 321 ProvisionLogger.logi("All non-required system apps have been uninstalled."); 322 mCallback.onSuccess(); 323 } 324 } 325 } 326 327 public abstract static class Callback { 328 public abstract void onSuccess(); 329 public abstract void onError(); 330 } 331 } 332