PackageManagerServiceUtils.java revision 420d58a9d867a3ce96fb4ea98bd30ee4d44eab4d
1/* 2 * Copyright (C) 2016 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 17package com.android.server.pm; 18 19import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE; 20import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; 21import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION; 22import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION; 23import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; 24import static com.android.server.pm.PackageManagerService.STUB_SUFFIX; 25import static com.android.server.pm.PackageManagerService.TAG; 26import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; 27 28import com.android.internal.content.NativeLibraryHelper; 29import com.android.internal.util.ArrayUtils; 30import com.android.internal.util.FastPrintWriter; 31import com.android.server.EventLogTags; 32import com.android.server.pm.dex.DexManager; 33import com.android.server.pm.dex.PackageDexUsage; 34 35import android.annotation.NonNull; 36import android.app.AppGlobals; 37import android.content.Intent; 38import android.content.pm.PackageManager; 39import android.content.pm.PackageParser; 40import android.content.pm.ResolveInfo; 41import android.content.pm.Signature; 42import android.os.Build; 43import android.os.Debug; 44import android.os.Environment; 45import android.os.FileUtils; 46import android.os.Process; 47import android.os.RemoteException; 48import android.os.UserHandle; 49import android.service.pm.PackageServiceDumpProto; 50import android.system.ErrnoException; 51import android.system.Os; 52import android.util.ArraySet; 53import android.util.Log; 54import android.util.Slog; 55import android.util.jar.StrictJarFile; 56import android.util.proto.ProtoOutputStream; 57 58import dalvik.system.VMRuntime; 59 60import libcore.io.IoUtils; 61import libcore.io.Libcore; 62import libcore.io.Streams; 63 64import java.io.BufferedReader; 65import java.io.File; 66import java.io.FileInputStream; 67import java.io.FileOutputStream; 68import java.io.FileReader; 69import java.io.FilenameFilter; 70import java.io.IOException; 71import java.io.InputStream; 72import java.io.OutputStream; 73import java.io.PrintWriter; 74import java.security.cert.CertificateEncodingException; 75import java.security.cert.CertificateException; 76import java.text.SimpleDateFormat; 77import java.util.ArrayList; 78import java.util.Arrays; 79import java.util.Collection; 80import java.util.Collections; 81import java.util.Date; 82import java.util.Iterator; 83import java.util.LinkedList; 84import java.util.List; 85import java.util.function.Predicate; 86import java.util.zip.GZIPInputStream; 87import java.util.zip.ZipEntry; 88 89/** 90 * Class containing helper methods for the PackageManagerService. 91 * 92 * {@hide} 93 */ 94public class PackageManagerServiceUtils { 95 private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000; 96 97 private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) { 98 List<ResolveInfo> ris = null; 99 try { 100 ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId) 101 .getList(); 102 } catch (RemoteException e) { 103 } 104 ArraySet<String> pkgNames = new ArraySet<String>(); 105 if (ris != null) { 106 for (ResolveInfo ri : ris) { 107 pkgNames.add(ri.activityInfo.packageName); 108 } 109 } 110 return pkgNames; 111 } 112 113 // Sort a list of apps by their last usage, most recently used apps first. The order of 114 // packages without usage data is undefined (but they will be sorted after the packages 115 // that do have usage data). 116 public static void sortPackagesByUsageDate(List<PackageParser.Package> pkgs, 117 PackageManagerService packageManagerService) { 118 if (!packageManagerService.isHistoricalPackageUsageAvailable()) { 119 return; 120 } 121 122 Collections.sort(pkgs, (pkg1, pkg2) -> 123 Long.compare(pkg2.getLatestForegroundPackageUseTimeInMills(), 124 pkg1.getLatestForegroundPackageUseTimeInMills())); 125 } 126 127 // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the 128 // package will be removed from {@code packages} and added to {@code result} with its 129 // dependencies. If usage data is available, the positive packages will be sorted by usage 130 // data (with {@code sortTemp} as temporary storage). 131 private static void applyPackageFilter(Predicate<PackageParser.Package> filter, 132 Collection<PackageParser.Package> result, 133 Collection<PackageParser.Package> packages, 134 @NonNull List<PackageParser.Package> sortTemp, 135 PackageManagerService packageManagerService) { 136 for (PackageParser.Package pkg : packages) { 137 if (filter.test(pkg)) { 138 sortTemp.add(pkg); 139 } 140 } 141 142 sortPackagesByUsageDate(sortTemp, packageManagerService); 143 packages.removeAll(sortTemp); 144 145 for (PackageParser.Package pkg : sortTemp) { 146 result.add(pkg); 147 148 Collection<PackageParser.Package> deps = 149 packageManagerService.findSharedNonSystemLibraries(pkg); 150 if (!deps.isEmpty()) { 151 deps.removeAll(result); 152 result.addAll(deps); 153 packages.removeAll(deps); 154 } 155 } 156 157 sortTemp.clear(); 158 } 159 160 // Sort apps by importance for dexopt ordering. Important apps are given 161 // more priority in case the device runs out of space. 162 public static List<PackageParser.Package> getPackagesForDexopt( 163 Collection<PackageParser.Package> packages, 164 PackageManagerService packageManagerService) { 165 ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages); 166 LinkedList<PackageParser.Package> result = new LinkedList<>(); 167 ArrayList<PackageParser.Package> sortTemp = new ArrayList<>(remainingPkgs.size()); 168 169 // Give priority to core apps. 170 applyPackageFilter((pkg) -> pkg.coreApp, result, remainingPkgs, sortTemp, 171 packageManagerService); 172 173 // Give priority to system apps that listen for pre boot complete. 174 Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); 175 final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM); 176 applyPackageFilter((pkg) -> pkgNames.contains(pkg.packageName), result, remainingPkgs, 177 sortTemp, packageManagerService); 178 179 // Give priority to apps used by other apps. 180 DexManager dexManager = packageManagerService.getDexManager(); 181 applyPackageFilter((pkg) -> 182 dexManager.getPackageUseInfoOrDefault(pkg.packageName) 183 .isAnyCodePathUsedByOtherApps(), 184 result, remainingPkgs, sortTemp, packageManagerService); 185 186 // Filter out packages that aren't recently used, add all remaining apps. 187 // TODO: add a property to control this? 188 Predicate<PackageParser.Package> remainingPredicate; 189 if (!remainingPkgs.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) { 190 if (DEBUG_DEXOPT) { 191 Log.i(TAG, "Looking at historical package use"); 192 } 193 // Get the package that was used last. 194 PackageParser.Package lastUsed = Collections.max(remainingPkgs, (pkg1, pkg2) -> 195 Long.compare(pkg1.getLatestForegroundPackageUseTimeInMills(), 196 pkg2.getLatestForegroundPackageUseTimeInMills())); 197 if (DEBUG_DEXOPT) { 198 Log.i(TAG, "Taking package " + lastUsed.packageName + " as reference in time use"); 199 } 200 long estimatedPreviousSystemUseTime = 201 lastUsed.getLatestForegroundPackageUseTimeInMills(); 202 // Be defensive if for some reason package usage has bogus data. 203 if (estimatedPreviousSystemUseTime != 0) { 204 final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS; 205 remainingPredicate = 206 (pkg) -> pkg.getLatestForegroundPackageUseTimeInMills() >= cutoffTime; 207 } else { 208 // No meaningful historical info. Take all. 209 remainingPredicate = (pkg) -> true; 210 } 211 sortPackagesByUsageDate(remainingPkgs, packageManagerService); 212 } else { 213 // No historical info. Take all. 214 remainingPredicate = (pkg) -> true; 215 } 216 applyPackageFilter(remainingPredicate, result, remainingPkgs, sortTemp, 217 packageManagerService); 218 219 if (DEBUG_DEXOPT) { 220 Log.i(TAG, "Packages to be dexopted: " + packagesToString(result)); 221 Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgs)); 222 } 223 224 return result; 225 } 226 227 /** 228 * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>. 229 * Package is considered active, if: 230 * 1) It was active in foreground. 231 * 2) It was active in background and also used by other apps. 232 * 233 * If it doesn't have sufficient information about the package, it return <code>false</code>. 234 */ 235 public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis, 236 long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo, 237 long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) { 238 239 if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) { 240 return false; 241 } 242 243 // If the app was active in foreground during the threshold period. 244 boolean isActiveInForeground = (currentTimeInMillis 245 - latestForegroundPackageUseTimeInMillis) 246 < thresholdTimeinMillis; 247 248 if (isActiveInForeground) { 249 return false; 250 } 251 252 // If the app was active in background during the threshold period and was used 253 // by other packages. 254 boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis 255 - latestPackageUseTimeInMillis) 256 < thresholdTimeinMillis) 257 && packageUseInfo.isAnyCodePathUsedByOtherApps(); 258 259 return !isActiveInBackgroundAndUsedByOtherPackages; 260 } 261 262 /** 263 * Returns the canonicalized path of {@code path} as per {@code realpath(3)} 264 * semantics. 265 */ 266 public static String realpath(File path) throws IOException { 267 try { 268 return Os.realpath(path.getAbsolutePath()); 269 } catch (ErrnoException ee) { 270 throw ee.rethrowAsIOException(); 271 } 272 } 273 274 public static String packagesToString(Collection<PackageParser.Package> c) { 275 StringBuilder sb = new StringBuilder(); 276 for (PackageParser.Package pkg : c) { 277 if (sb.length() > 0) { 278 sb.append(", "); 279 } 280 sb.append(pkg.packageName); 281 } 282 return sb.toString(); 283 } 284 285 /** 286 * Verifies that the given string {@code isa} is a valid supported isa on 287 * the running device. 288 */ 289 public static boolean checkISA(String isa) { 290 for (String abi : Build.SUPPORTED_ABIS) { 291 if (VMRuntime.getInstructionSet(abi).equals(isa)) { 292 return true; 293 } 294 } 295 return false; 296 } 297 298 public static long getLastModifiedTime(PackageParser.Package pkg) { 299 final File srcFile = new File(pkg.codePath); 300 if (!srcFile.isDirectory()) { 301 return srcFile.lastModified(); 302 } 303 final File baseFile = new File(pkg.baseCodePath); 304 long maxModifiedTime = baseFile.lastModified(); 305 if (pkg.splitCodePaths != null) { 306 for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) { 307 final File splitFile = new File(pkg.splitCodePaths[i]); 308 maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified()); 309 } 310 } 311 return maxModifiedTime; 312 } 313 314 /** 315 * Checks that the archive located at {@code fileName} has uncompressed dex file and so 316 * files that can be direclty mapped. 317 */ 318 public static void logApkHasUncompressedCode(String fileName) { 319 StrictJarFile jarFile = null; 320 try { 321 jarFile = new StrictJarFile(fileName, 322 false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/); 323 Iterator<ZipEntry> it = jarFile.iterator(); 324 while (it.hasNext()) { 325 ZipEntry entry = it.next(); 326 if (entry.getName().endsWith(".dex")) { 327 if (entry.getMethod() != ZipEntry.STORED) { 328 Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " + 329 entry.getName()); 330 } else if ((entry.getDataOffset() & 0x3) != 0) { 331 Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " + 332 entry.getName()); 333 } 334 } else if (entry.getName().endsWith(".so")) { 335 if (entry.getMethod() != ZipEntry.STORED) { 336 Slog.wtf(TAG, "APK " + fileName + " has compressed native code " + 337 entry.getName()); 338 } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) { 339 Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " + 340 entry.getName()); 341 } 342 } 343 } 344 } catch (IOException ignore) { 345 Slog.wtf(TAG, "Error when parsing APK " + fileName); 346 } finally { 347 try { 348 if (jarFile != null) { 349 jarFile.close(); 350 } 351 } catch (IOException ignore) {} 352 } 353 return; 354 } 355 356 /** 357 * Checks that the APKs in the given package have uncompressed dex file and so 358 * files that can be direclty mapped. 359 */ 360 public static void logPackageHasUncompressedCode(PackageParser.Package pkg) { 361 logApkHasUncompressedCode(pkg.baseCodePath); 362 if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { 363 for (int i = 0; i < pkg.splitCodePaths.length; i++) { 364 logApkHasUncompressedCode(pkg.splitCodePaths[i]); 365 } 366 } 367 } 368 369 private static File getSettingsProblemFile() { 370 File dataDir = Environment.getDataDirectory(); 371 File systemDir = new File(dataDir, "system"); 372 File fname = new File(systemDir, "uiderrors.txt"); 373 return fname; 374 } 375 376 public static void dumpCriticalInfo(ProtoOutputStream proto) { 377 try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) { 378 String line = null; 379 while ((line = in.readLine()) != null) { 380 if (line.contains("ignored: updated version")) continue; 381 proto.write(PackageServiceDumpProto.MESSAGES, line); 382 } 383 } catch (IOException ignored) { 384 } 385 } 386 387 public static void dumpCriticalInfo(PrintWriter pw, String msg) { 388 try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) { 389 String line = null; 390 while ((line = in.readLine()) != null) { 391 if (line.contains("ignored: updated version")) continue; 392 if (msg != null) { 393 pw.print(msg); 394 } 395 pw.println(line); 396 } 397 } catch (IOException ignored) { 398 } 399 } 400 401 public static void logCriticalInfo(int priority, String msg) { 402 Slog.println(priority, TAG, msg); 403 EventLogTags.writePmCriticalInfo(msg); 404 try { 405 File fname = getSettingsProblemFile(); 406 FileOutputStream out = new FileOutputStream(fname, true); 407 PrintWriter pw = new FastPrintWriter(out); 408 SimpleDateFormat formatter = new SimpleDateFormat(); 409 String dateString = formatter.format(new Date(System.currentTimeMillis())); 410 pw.println(dateString + ": " + msg); 411 pw.close(); 412 FileUtils.setPermissions( 413 fname.toString(), 414 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH, 415 -1, -1); 416 } catch (java.io.IOException e) { 417 } 418 } 419 420 public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) { 421 if (callingUid == Process.SHELL_UID) { 422 if (userHandle >= 0 423 && PackageManagerService.sUserManager.hasUserRestriction( 424 restriction, userHandle)) { 425 throw new SecurityException("Shell does not have permission to access user " 426 + userHandle); 427 } else if (userHandle < 0) { 428 Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user " 429 + userHandle + "\n\t" + Debug.getCallers(3)); 430 } 431 } 432 } 433 434 /** 435 * Derive the value of the {@code cpuAbiOverride} based on the provided 436 * value and an optional stored value from the package settings. 437 */ 438 public static String deriveAbiOverride(String abiOverride, PackageSetting settings) { 439 String cpuAbiOverride = null; 440 if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) { 441 cpuAbiOverride = null; 442 } else if (abiOverride != null) { 443 cpuAbiOverride = abiOverride; 444 } else if (settings != null) { 445 cpuAbiOverride = settings.cpuAbiOverrideString; 446 } 447 return cpuAbiOverride; 448 } 449 450 /** 451 * Compares two sets of signatures. Returns: 452 * <br /> 453 * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null, 454 * <br /> 455 * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null, 456 * <br /> 457 * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null, 458 * <br /> 459 * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical, 460 * <br /> 461 * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ. 462 */ 463 public static int compareSignatures(Signature[] s1, Signature[] s2) { 464 if (s1 == null) { 465 return s2 == null 466 ? PackageManager.SIGNATURE_NEITHER_SIGNED 467 : PackageManager.SIGNATURE_FIRST_NOT_SIGNED; 468 } 469 470 if (s2 == null) { 471 return PackageManager.SIGNATURE_SECOND_NOT_SIGNED; 472 } 473 474 if (s1.length != s2.length) { 475 return PackageManager.SIGNATURE_NO_MATCH; 476 } 477 478 // Since both signature sets are of size 1, we can compare without HashSets. 479 if (s1.length == 1) { 480 return s1[0].equals(s2[0]) ? 481 PackageManager.SIGNATURE_MATCH : 482 PackageManager.SIGNATURE_NO_MATCH; 483 } 484 485 ArraySet<Signature> set1 = new ArraySet<Signature>(); 486 for (Signature sig : s1) { 487 set1.add(sig); 488 } 489 ArraySet<Signature> set2 = new ArraySet<Signature>(); 490 for (Signature sig : s2) { 491 set2.add(sig); 492 } 493 // Make sure s2 contains all signatures in s1. 494 if (set1.equals(set2)) { 495 return PackageManager.SIGNATURE_MATCH; 496 } 497 return PackageManager.SIGNATURE_NO_MATCH; 498 } 499 500 /** 501 * Used for backward compatibility to make sure any packages with 502 * certificate chains get upgraded to the new style. {@code existingSigs} 503 * will be in the old format (since they were stored on disk from before the 504 * system upgrade) and {@code scannedSigs} will be in the newer format. 505 */ 506 private static boolean matchSignaturesCompat(String packageName, 507 PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) { 508 ArraySet<Signature> existingSet = new ArraySet<Signature>(); 509 for (Signature sig : packageSignatures.mSignatures) { 510 existingSet.add(sig); 511 } 512 ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>(); 513 for (Signature sig : parsedSignatures.signatures) { 514 try { 515 Signature[] chainSignatures = sig.getChainSignatures(); 516 for (Signature chainSig : chainSignatures) { 517 scannedCompatSet.add(chainSig); 518 } 519 } catch (CertificateEncodingException e) { 520 scannedCompatSet.add(sig); 521 } 522 } 523 // make sure the expanded scanned set contains all signatures in the existing one 524 if (scannedCompatSet.equals(existingSet)) { 525 // migrate the old signatures to the new scheme 526 packageSignatures.assignSignatures(parsedSignatures); 527 return true; 528 } 529 return false; 530 } 531 532 private static boolean matchSignaturesRecover(String packageName, 533 Signature[] existingSignatures, Signature[] parsedSignatures) { 534 String msg = null; 535 try { 536 if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) { 537 logCriticalInfo(Log.INFO, 538 "Recovered effectively matching certificates for " + packageName); 539 return true; 540 } 541 } catch (CertificateException e) { 542 msg = e.getMessage(); 543 } 544 logCriticalInfo(Log.INFO, 545 "Failed to recover certificates for " + packageName + ": " + msg); 546 return false; 547 } 548 549 /** 550 * Verifies that signatures match. 551 * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}. 552 * @throws PackageManagerException if the signatures did not match. 553 */ 554 public static boolean verifySignatures(PackageSetting pkgSetting, 555 PackageParser.SigningDetails parsedSignatures, boolean compareCompat, 556 boolean compareRecover) 557 throws PackageManagerException { 558 final String packageName = pkgSetting.name; 559 boolean compatMatch = false; 560 if (pkgSetting.signatures.mSignatures != null) { 561 // Already existing package. Make sure signatures match 562 boolean match = compareSignatures(pkgSetting.signatures.mSignatures, 563 parsedSignatures.signatures) 564 == PackageManager.SIGNATURE_MATCH; 565 if (!match && compareCompat) { 566 match = matchSignaturesCompat(packageName, pkgSetting.signatures, 567 parsedSignatures); 568 compatMatch = match; 569 } 570 if (!match && compareRecover) { 571 match = matchSignaturesRecover( 572 packageName, pkgSetting.signatures.mSignatures, 573 parsedSignatures.signatures); 574 } 575 if (!match) { 576 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, 577 "Package " + packageName + 578 " signatures do not match previously installed version; ignoring!"); 579 } 580 } 581 // Check for shared user signatures 582 if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) { 583 // Already existing package. Make sure signatures match 584 boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures, 585 parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH; 586 if (!match && compareCompat) { 587 match = matchSignaturesCompat( 588 packageName, pkgSetting.sharedUser.signatures, parsedSignatures); 589 } 590 if (!match && compareRecover) { 591 match = matchSignaturesRecover(packageName, 592 pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures.signatures); 593 compatMatch |= match; 594 } 595 if (!match) { 596 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, 597 "Package " + packageName 598 + " has no signatures that match those in shared user " 599 + pkgSetting.sharedUser.name + "; ignoring!"); 600 } 601 } 602 return compatMatch; 603 } 604 605 public static int decompressFile(File srcFile, File dstFile) throws ErrnoException { 606 if (DEBUG_COMPRESSION) { 607 Slog.i(TAG, "Decompress file" 608 + "; src: " + srcFile.getAbsolutePath() 609 + ", dst: " + dstFile.getAbsolutePath()); 610 } 611 try ( 612 InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile)); 613 OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/); 614 ) { 615 Streams.copy(fileIn, fileOut); 616 Os.chmod(dstFile.getAbsolutePath(), 0644); 617 return PackageManager.INSTALL_SUCCEEDED; 618 } catch (IOException e) { 619 logCriticalInfo(Log.ERROR, "Failed to decompress file" 620 + "; src: " + srcFile.getAbsolutePath() 621 + ", dst: " + dstFile.getAbsolutePath()); 622 } 623 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 624 } 625 626 public static File[] getCompressedFiles(String codePath) { 627 final File stubCodePath = new File(codePath); 628 final String stubName = stubCodePath.getName(); 629 630 // The layout of a compressed package on a given partition is as follows : 631 // 632 // Compressed artifacts: 633 // 634 // /partition/ModuleName/foo.gz 635 // /partation/ModuleName/bar.gz 636 // 637 // Stub artifact: 638 // 639 // /partition/ModuleName-Stub/ModuleName-Stub.apk 640 // 641 // In other words, stub is on the same partition as the compressed artifacts 642 // and in a directory that's suffixed with "-Stub". 643 int idx = stubName.lastIndexOf(STUB_SUFFIX); 644 if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) { 645 return null; 646 } 647 648 final File stubParentDir = stubCodePath.getParentFile(); 649 if (stubParentDir == null) { 650 Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath); 651 return null; 652 } 653 654 final File compressedPath = new File(stubParentDir, stubName.substring(0, idx)); 655 final File[] files = compressedPath.listFiles(new FilenameFilter() { 656 @Override 657 public boolean accept(File dir, String name) { 658 return name.toLowerCase().endsWith(COMPRESSED_EXTENSION); 659 } 660 }); 661 662 if (DEBUG_COMPRESSION && files != null && files.length > 0) { 663 Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files)); 664 } 665 666 return files; 667 } 668 669 public static boolean compressedFileExists(String codePath) { 670 final File[] compressedFiles = getCompressedFiles(codePath); 671 return compressedFiles != null && compressedFiles.length > 0; 672 } 673} 674