1 /* 2 * Copyright (C) 2009 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.internal.content; 18 19 import static android.net.TrafficStats.MB_IN_BYTES; 20 21 import android.content.Context; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.content.pm.PackageParser.PackageLite; 27 import android.os.Environment; 28 import android.os.Environment.UserEnvironment; 29 import android.os.FileUtils; 30 import android.os.IBinder; 31 import android.os.RemoteException; 32 import android.os.ServiceManager; 33 import android.os.UserHandle; 34 import android.os.storage.IMountService; 35 import android.os.storage.StorageManager; 36 import android.os.storage.StorageResultCode; 37 import android.util.Log; 38 39 import libcore.io.IoUtils; 40 41 import java.io.File; 42 import java.io.FileOutputStream; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.util.Collections; 46 import java.util.zip.ZipEntry; 47 import java.util.zip.ZipFile; 48 import java.util.zip.ZipOutputStream; 49 50 /** 51 * Constants used internally between the PackageManager 52 * and media container service transports. 53 * Some utility methods to invoke MountService api. 54 */ 55 public class PackageHelper { 56 public static final int RECOMMEND_INSTALL_INTERNAL = 1; 57 public static final int RECOMMEND_INSTALL_EXTERNAL = 2; 58 public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1; 59 public static final int RECOMMEND_FAILED_INVALID_APK = -2; 60 public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3; 61 public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4; 62 public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5; 63 public static final int RECOMMEND_FAILED_INVALID_URI = -6; 64 public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7; 65 66 private static final boolean localLOGV = false; 67 private static final String TAG = "PackageHelper"; 68 // App installation location settings values 69 public static final int APP_INSTALL_AUTO = 0; 70 public static final int APP_INSTALL_INTERNAL = 1; 71 public static final int APP_INSTALL_EXTERNAL = 2; 72 getMountService()73 public static IMountService getMountService() throws RemoteException { 74 IBinder service = ServiceManager.getService("mount"); 75 if (service != null) { 76 return IMountService.Stub.asInterface(service); 77 } else { 78 Log.e(TAG, "Can't get mount service"); 79 throw new RemoteException("Could not contact mount service"); 80 } 81 } 82 createSdDir(long sizeBytes, String cid, String sdEncKey, int uid, boolean isExternal)83 public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid, 84 boolean isExternal) { 85 // Round up to nearest MB, plus another MB for filesystem overhead 86 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; 87 try { 88 IMountService mountService = getMountService(); 89 90 if (localLOGV) 91 Log.i(TAG, "Size of container " + sizeMb + " MB"); 92 93 int rc = mountService.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid, 94 isExternal); 95 if (rc != StorageResultCode.OperationSucceeded) { 96 Log.e(TAG, "Failed to create secure container " + cid); 97 return null; 98 } 99 String cachePath = mountService.getSecureContainerPath(cid); 100 if (localLOGV) Log.i(TAG, "Created secure container " + cid + 101 " at " + cachePath); 102 return cachePath; 103 } catch (RemoteException e) { 104 Log.e(TAG, "MountService running?"); 105 } 106 return null; 107 } 108 resizeSdDir(long sizeBytes, String cid, String sdEncKey)109 public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) { 110 // Round up to nearest MB, plus another MB for filesystem overhead 111 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; 112 try { 113 IMountService mountService = getMountService(); 114 int rc = mountService.resizeSecureContainer(cid, sizeMb, sdEncKey); 115 if (rc == StorageResultCode.OperationSucceeded) { 116 return true; 117 } 118 } catch (RemoteException e) { 119 Log.e(TAG, "MountService running?"); 120 } 121 Log.e(TAG, "Failed to create secure container " + cid); 122 return false; 123 } 124 mountSdDir(String cid, String key, int ownerUid)125 public static String mountSdDir(String cid, String key, int ownerUid) { 126 return mountSdDir(cid, key, ownerUid, true); 127 } 128 mountSdDir(String cid, String key, int ownerUid, boolean readOnly)129 public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) { 130 try { 131 int rc = getMountService().mountSecureContainer(cid, key, ownerUid, readOnly); 132 if (rc != StorageResultCode.OperationSucceeded) { 133 Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc); 134 return null; 135 } 136 return getMountService().getSecureContainerPath(cid); 137 } catch (RemoteException e) { 138 Log.e(TAG, "MountService running?"); 139 } 140 return null; 141 } 142 unMountSdDir(String cid)143 public static boolean unMountSdDir(String cid) { 144 try { 145 int rc = getMountService().unmountSecureContainer(cid, true); 146 if (rc != StorageResultCode.OperationSucceeded) { 147 Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc); 148 return false; 149 } 150 return true; 151 } catch (RemoteException e) { 152 Log.e(TAG, "MountService running?"); 153 } 154 return false; 155 } 156 renameSdDir(String oldId, String newId)157 public static boolean renameSdDir(String oldId, String newId) { 158 try { 159 int rc = getMountService().renameSecureContainer(oldId, newId); 160 if (rc != StorageResultCode.OperationSucceeded) { 161 Log.e(TAG, "Failed to rename " + oldId + " to " + 162 newId + "with rc " + rc); 163 return false; 164 } 165 return true; 166 } catch (RemoteException e) { 167 Log.i(TAG, "Failed ot rename " + oldId + " to " + newId + 168 " with exception : " + e); 169 } 170 return false; 171 } 172 getSdDir(String cid)173 public static String getSdDir(String cid) { 174 try { 175 return getMountService().getSecureContainerPath(cid); 176 } catch (RemoteException e) { 177 Log.e(TAG, "Failed to get container path for " + cid + 178 " with exception " + e); 179 } 180 return null; 181 } 182 getSdFilesystem(String cid)183 public static String getSdFilesystem(String cid) { 184 try { 185 return getMountService().getSecureContainerFilesystemPath(cid); 186 } catch (RemoteException e) { 187 Log.e(TAG, "Failed to get container path for " + cid + 188 " with exception " + e); 189 } 190 return null; 191 } 192 finalizeSdDir(String cid)193 public static boolean finalizeSdDir(String cid) { 194 try { 195 int rc = getMountService().finalizeSecureContainer(cid); 196 if (rc != StorageResultCode.OperationSucceeded) { 197 Log.i(TAG, "Failed to finalize container " + cid); 198 return false; 199 } 200 return true; 201 } catch (RemoteException e) { 202 Log.e(TAG, "Failed to finalize container " + cid + 203 " with exception " + e); 204 } 205 return false; 206 } 207 destroySdDir(String cid)208 public static boolean destroySdDir(String cid) { 209 try { 210 if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid); 211 int rc = getMountService().destroySecureContainer(cid, true); 212 if (rc != StorageResultCode.OperationSucceeded) { 213 Log.i(TAG, "Failed to destroy container " + cid); 214 return false; 215 } 216 return true; 217 } catch (RemoteException e) { 218 Log.e(TAG, "Failed to destroy container " + cid + 219 " with exception " + e); 220 } 221 return false; 222 } 223 getSecureContainerList()224 public static String[] getSecureContainerList() { 225 try { 226 return getMountService().getSecureContainerList(); 227 } catch (RemoteException e) { 228 Log.e(TAG, "Failed to get secure container list with exception" + 229 e); 230 } 231 return null; 232 } 233 isContainerMounted(String cid)234 public static boolean isContainerMounted(String cid) { 235 try { 236 return getMountService().isSecureContainerMounted(cid); 237 } catch (RemoteException e) { 238 Log.e(TAG, "Failed to find out if container " + cid + " mounted"); 239 } 240 return false; 241 } 242 243 /** 244 * Extract public files for the single given APK. 245 */ extractPublicFiles(File apkFile, File publicZipFile)246 public static long extractPublicFiles(File apkFile, File publicZipFile) 247 throws IOException { 248 final FileOutputStream fstr; 249 final ZipOutputStream publicZipOutStream; 250 251 if (publicZipFile == null) { 252 fstr = null; 253 publicZipOutStream = null; 254 } else { 255 fstr = new FileOutputStream(publicZipFile); 256 publicZipOutStream = new ZipOutputStream(fstr); 257 Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile); 258 } 259 260 long size = 0L; 261 262 try { 263 final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath()); 264 try { 265 // Copy manifest, resources.arsc and res directory to public zip 266 for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) { 267 final String zipEntryName = zipEntry.getName(); 268 if ("AndroidManifest.xml".equals(zipEntryName) 269 || "resources.arsc".equals(zipEntryName) 270 || zipEntryName.startsWith("res/")) { 271 size += zipEntry.getSize(); 272 if (publicZipFile != null) { 273 copyZipEntry(zipEntry, privateZip, publicZipOutStream); 274 } 275 } 276 } 277 } finally { 278 try { privateZip.close(); } catch (IOException e) {} 279 } 280 281 if (publicZipFile != null) { 282 publicZipOutStream.finish(); 283 publicZipOutStream.flush(); 284 FileUtils.sync(fstr); 285 publicZipOutStream.close(); 286 FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR 287 | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1); 288 } 289 } finally { 290 IoUtils.closeQuietly(publicZipOutStream); 291 } 292 293 return size; 294 } 295 copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile, ZipOutputStream outZipStream)296 private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile, 297 ZipOutputStream outZipStream) throws IOException { 298 byte[] buffer = new byte[4096]; 299 int num; 300 301 ZipEntry newEntry; 302 if (zipEntry.getMethod() == ZipEntry.STORED) { 303 // Preserve the STORED method of the input entry. 304 newEntry = new ZipEntry(zipEntry); 305 } else { 306 // Create a new entry so that the compressed len is recomputed. 307 newEntry = new ZipEntry(zipEntry.getName()); 308 } 309 outZipStream.putNextEntry(newEntry); 310 311 final InputStream data = inZipFile.getInputStream(zipEntry); 312 try { 313 while ((num = data.read(buffer)) > 0) { 314 outZipStream.write(buffer, 0, num); 315 } 316 outZipStream.flush(); 317 } finally { 318 IoUtils.closeQuietly(data); 319 } 320 } 321 fixSdPermissions(String cid, int gid, String filename)322 public static boolean fixSdPermissions(String cid, int gid, String filename) { 323 try { 324 int rc = getMountService().fixPermissionsSecureContainer(cid, gid, filename); 325 if (rc != StorageResultCode.OperationSucceeded) { 326 Log.i(TAG, "Failed to fixperms container " + cid); 327 return false; 328 } 329 return true; 330 } catch (RemoteException e) { 331 Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e); 332 } 333 return false; 334 } 335 336 /** 337 * Given a requested {@link PackageInfo#installLocation} and calculated 338 * install size, pick the actual location to install the app. 339 */ resolveInstallLocation(Context context, String packageName, int installLocation, long sizeBytes, int installFlags)340 public static int resolveInstallLocation(Context context, String packageName, 341 int installLocation, long sizeBytes, int installFlags) { 342 ApplicationInfo existingInfo = null; 343 try { 344 existingInfo = context.getPackageManager().getApplicationInfo(packageName, 345 PackageManager.GET_UNINSTALLED_PACKAGES); 346 } catch (NameNotFoundException ignored) { 347 } 348 349 final int prefer; 350 final boolean checkBoth; 351 if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) { 352 prefer = RECOMMEND_INSTALL_INTERNAL; 353 checkBoth = false; 354 } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { 355 prefer = RECOMMEND_INSTALL_EXTERNAL; 356 checkBoth = false; 357 } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 358 prefer = RECOMMEND_INSTALL_INTERNAL; 359 checkBoth = false; 360 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 361 prefer = RECOMMEND_INSTALL_EXTERNAL; 362 checkBoth = true; 363 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 364 // When app is already installed, prefer same medium 365 if (existingInfo != null) { 366 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { 367 prefer = RECOMMEND_INSTALL_EXTERNAL; 368 } else { 369 prefer = RECOMMEND_INSTALL_INTERNAL; 370 } 371 } else { 372 prefer = RECOMMEND_INSTALL_INTERNAL; 373 } 374 checkBoth = true; 375 } else { 376 prefer = RECOMMEND_INSTALL_INTERNAL; 377 checkBoth = false; 378 } 379 380 final boolean emulated = Environment.isExternalStorageEmulated(); 381 final StorageManager storage = StorageManager.from(context); 382 383 boolean fitsOnInternal = false; 384 if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) { 385 final File target = Environment.getDataDirectory(); 386 fitsOnInternal = (sizeBytes <= storage.getStorageBytesUntilLow(target)); 387 } 388 389 boolean fitsOnExternal = false; 390 if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) { 391 final File target = new UserEnvironment(UserHandle.USER_OWNER) 392 .getExternalStorageDirectory(); 393 // External is only an option when size is known 394 if (sizeBytes > 0) { 395 fitsOnExternal = (sizeBytes <= storage.getStorageBytesUntilLow(target)); 396 } 397 } 398 399 if (prefer == RECOMMEND_INSTALL_INTERNAL) { 400 if (fitsOnInternal) { 401 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 402 } 403 } else if (!emulated && prefer == RECOMMEND_INSTALL_EXTERNAL) { 404 if (fitsOnExternal) { 405 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 406 } 407 } 408 409 if (checkBoth) { 410 if (fitsOnInternal) { 411 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 412 } else if (!emulated && fitsOnExternal) { 413 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 414 } 415 } 416 417 /* 418 * If they requested to be on the external media by default, return that 419 * the media was unavailable. Otherwise, indicate there was insufficient 420 * storage space available. 421 */ 422 if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) 423 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 424 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; 425 } else { 426 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 427 } 428 } 429 calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, String abiOverride)430 public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, 431 String abiOverride) throws IOException { 432 NativeLibraryHelper.Handle handle = null; 433 try { 434 handle = NativeLibraryHelper.Handle.create(pkg); 435 return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride); 436 } finally { 437 IoUtils.closeQuietly(handle); 438 } 439 } 440 calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle, boolean isForwardLocked, String abiOverride)441 public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle, 442 boolean isForwardLocked, String abiOverride) throws IOException { 443 long sizeBytes = 0; 444 445 // Include raw APKs, and possibly unpacked resources 446 for (String codePath : pkg.getAllCodePaths()) { 447 final File codeFile = new File(codePath); 448 sizeBytes += codeFile.length(); 449 450 if (isForwardLocked) { 451 sizeBytes += PackageHelper.extractPublicFiles(codeFile, null); 452 } 453 } 454 455 // Include all relevant native code 456 sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride); 457 458 return sizeBytes; 459 } 460 replaceEnd(String str, String before, String after)461 public static String replaceEnd(String str, String before, String after) { 462 if (!str.endsWith(before)) { 463 throw new IllegalArgumentException( 464 "Expected " + str + " to end with " + before); 465 } 466 return str.substring(0, str.length() - before.length()) + after; 467 } 468 } 469