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 DeleteNonRequiredAppsTask(Context context, String mdmPackageName, int userId, int requiredAppsList, int vendorRequiredAppsList, boolean newProfile, boolean disableInstallShortcutListeners, Callback callback)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 run()92 public void run() { 93 if (mNewProfile) { 94 disableNfcBluetoothSharing(); 95 } 96 deleteNonRequiredApps(); 97 } 98 disableNfcBluetoothSharing()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 deleteNonRequiredApps()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 disableReceivers(Intent intent)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 disableComponent(ComponentName toDisable)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 */ getCurrentSystemApps()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 getCurrentAppsWithLauncher()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 writeSystemApps(Set<String> packageNames, File file)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 readSystemApps(File file)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 getRequiredApps()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 PackageDeleteObserver(int packageCount)308 public PackageDeleteObserver(int packageCount) { 309 this.mPackageCount.set(packageCount); 310 } 311 312 @Override packageDeleted(String packageName, int returnCode)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 { onSuccess()328 public abstract void onSuccess(); onError()329 public abstract void onError(); 330 } 331 } 332