PackageManagerServiceUtils.java revision 7c4c55dcb6d386fb3843069a02c177df66df09c7
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, File srcFile) { 299 if (srcFile.isDirectory()) { 300 final File baseFile = new File(pkg.baseCodePath); 301 long maxModifiedTime = baseFile.lastModified(); 302 if (pkg.splitCodePaths != null) { 303 for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) { 304 final File splitFile = new File(pkg.splitCodePaths[i]); 305 maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified()); 306 } 307 } 308 return maxModifiedTime; 309 } 310 return srcFile.lastModified(); 311 } 312 313 /** 314 * Checks that the archive located at {@code fileName} has uncompressed dex file and so 315 * files that can be direclty mapped. 316 */ 317 public static void logApkHasUncompressedCode(String fileName) { 318 StrictJarFile jarFile = null; 319 try { 320 jarFile = new StrictJarFile(fileName, 321 false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/); 322 Iterator<ZipEntry> it = jarFile.iterator(); 323 while (it.hasNext()) { 324 ZipEntry entry = it.next(); 325 if (entry.getName().endsWith(".dex")) { 326 if (entry.getMethod() != ZipEntry.STORED) { 327 Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " + 328 entry.getName()); 329 } else if ((entry.getDataOffset() & 0x3) != 0) { 330 Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " + 331 entry.getName()); 332 } 333 } else if (entry.getName().endsWith(".so")) { 334 if (entry.getMethod() != ZipEntry.STORED) { 335 Slog.wtf(TAG, "APK " + fileName + " has compressed native code " + 336 entry.getName()); 337 } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) { 338 Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " + 339 entry.getName()); 340 } 341 } 342 } 343 } catch (IOException ignore) { 344 Slog.wtf(TAG, "Error when parsing APK " + fileName); 345 } finally { 346 try { 347 if (jarFile != null) { 348 jarFile.close(); 349 } 350 } catch (IOException ignore) {} 351 } 352 return; 353 } 354 355 /** 356 * Checks that the APKs in the given package have uncompressed dex file and so 357 * files that can be direclty mapped. 358 */ 359 public static void logPackageHasUncompressedCode(PackageParser.Package pkg) { 360 logApkHasUncompressedCode(pkg.baseCodePath); 361 if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { 362 for (int i = 0; i < pkg.splitCodePaths.length; i++) { 363 logApkHasUncompressedCode(pkg.splitCodePaths[i]); 364 } 365 } 366 } 367 368 private static File getSettingsProblemFile() { 369 File dataDir = Environment.getDataDirectory(); 370 File systemDir = new File(dataDir, "system"); 371 File fname = new File(systemDir, "uiderrors.txt"); 372 return fname; 373 } 374 375 public static void dumpCriticalInfo(ProtoOutputStream proto) { 376 try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) { 377 String line = null; 378 while ((line = in.readLine()) != null) { 379 if (line.contains("ignored: updated version")) continue; 380 proto.write(PackageServiceDumpProto.MESSAGES, line); 381 } 382 } catch (IOException ignored) { 383 } 384 } 385 386 public static void dumpCriticalInfo(PrintWriter pw, String msg) { 387 try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) { 388 String line = null; 389 while ((line = in.readLine()) != null) { 390 if (line.contains("ignored: updated version")) continue; 391 if (msg != null) { 392 pw.print(msg); 393 } 394 pw.println(line); 395 } 396 } catch (IOException ignored) { 397 } 398 } 399 400 public static void logCriticalInfo(int priority, String msg) { 401 Slog.println(priority, TAG, msg); 402 EventLogTags.writePmCriticalInfo(msg); 403 try { 404 File fname = getSettingsProblemFile(); 405 FileOutputStream out = new FileOutputStream(fname, true); 406 PrintWriter pw = new FastPrintWriter(out); 407 SimpleDateFormat formatter = new SimpleDateFormat(); 408 String dateString = formatter.format(new Date(System.currentTimeMillis())); 409 pw.println(dateString + ": " + msg); 410 pw.close(); 411 FileUtils.setPermissions( 412 fname.toString(), 413 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH, 414 -1, -1); 415 } catch (java.io.IOException e) { 416 } 417 } 418 419 public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) { 420 if (callingUid == Process.SHELL_UID) { 421 if (userHandle >= 0 422 && PackageManagerService.sUserManager.hasUserRestriction( 423 restriction, userHandle)) { 424 throw new SecurityException("Shell does not have permission to access user " 425 + userHandle); 426 } else if (userHandle < 0) { 427 Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user " 428 + userHandle + "\n\t" + Debug.getCallers(3)); 429 } 430 } 431 } 432 433 /** 434 * Derive the value of the {@code cpuAbiOverride} based on the provided 435 * value and an optional stored value from the package settings. 436 */ 437 public static String deriveAbiOverride(String abiOverride, PackageSetting settings) { 438 String cpuAbiOverride = null; 439 if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) { 440 cpuAbiOverride = null; 441 } else if (abiOverride != null) { 442 cpuAbiOverride = abiOverride; 443 } else if (settings != null) { 444 cpuAbiOverride = settings.cpuAbiOverrideString; 445 } 446 return cpuAbiOverride; 447 } 448 449 /** 450 * Compares two sets of signatures. Returns: 451 * <br /> 452 * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null, 453 * <br /> 454 * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null, 455 * <br /> 456 * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null, 457 * <br /> 458 * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical, 459 * <br /> 460 * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ. 461 */ 462 public static int compareSignatures(Signature[] s1, Signature[] s2) { 463 if (s1 == null) { 464 return s2 == null 465 ? PackageManager.SIGNATURE_NEITHER_SIGNED 466 : PackageManager.SIGNATURE_FIRST_NOT_SIGNED; 467 } 468 469 if (s2 == null) { 470 return PackageManager.SIGNATURE_SECOND_NOT_SIGNED; 471 } 472 473 if (s1.length != s2.length) { 474 return PackageManager.SIGNATURE_NO_MATCH; 475 } 476 477 // Since both signature sets are of size 1, we can compare without HashSets. 478 if (s1.length == 1) { 479 return s1[0].equals(s2[0]) ? 480 PackageManager.SIGNATURE_MATCH : 481 PackageManager.SIGNATURE_NO_MATCH; 482 } 483 484 ArraySet<Signature> set1 = new ArraySet<Signature>(); 485 for (Signature sig : s1) { 486 set1.add(sig); 487 } 488 ArraySet<Signature> set2 = new ArraySet<Signature>(); 489 for (Signature sig : s2) { 490 set2.add(sig); 491 } 492 // Make sure s2 contains all signatures in s1. 493 if (set1.equals(set2)) { 494 return PackageManager.SIGNATURE_MATCH; 495 } 496 return PackageManager.SIGNATURE_NO_MATCH; 497 } 498 499 /** 500 * Used for backward compatibility to make sure any packages with 501 * certificate chains get upgraded to the new style. {@code existingSigs} 502 * will be in the old format (since they were stored on disk from before the 503 * system upgrade) and {@code scannedSigs} will be in the newer format. 504 */ 505 private static boolean matchSignaturesCompat(String packageName, 506 PackageSignatures packageSignatures, Signature[] parsedSignatures) { 507 ArraySet<Signature> existingSet = new ArraySet<Signature>(); 508 for (Signature sig : packageSignatures.mSignatures) { 509 existingSet.add(sig); 510 } 511 ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>(); 512 for (Signature sig : parsedSignatures) { 513 try { 514 Signature[] chainSignatures = sig.getChainSignatures(); 515 for (Signature chainSig : chainSignatures) { 516 scannedCompatSet.add(chainSig); 517 } 518 } catch (CertificateEncodingException e) { 519 scannedCompatSet.add(sig); 520 } 521 } 522 // make sure the expanded scanned set contains all signatures in the existing one 523 if (scannedCompatSet.equals(existingSet)) { 524 // migrate the old signatures to the new scheme 525 packageSignatures.assignSignatures(parsedSignatures); 526 return true; 527 } 528 return false; 529 } 530 531 private static boolean matchSignaturesRecover(String packageName, 532 Signature[] existingSignatures, Signature[] parsedSignatures) { 533 String msg = null; 534 try { 535 if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) { 536 logCriticalInfo(Log.INFO, 537 "Recovered effectively matching certificates for " + packageName); 538 return true; 539 } 540 } catch (CertificateException e) { 541 msg = e.getMessage(); 542 } 543 logCriticalInfo(Log.INFO, 544 "Failed to recover certificates for " + packageName + ": " + msg); 545 return false; 546 } 547 548 /** 549 * Verifies that signatures match. 550 * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}. 551 * @throws PackageManagerException if the signatures did not match. 552 */ 553 public static boolean verifySignatures(PackageSetting pkgSetting, 554 Signature[] parsedSignatures, boolean compareCompat, boolean compareRecover) 555 throws PackageManagerException { 556 final String packageName = pkgSetting.name; 557 boolean compatMatch = false; 558 if (pkgSetting.signatures.mSignatures != null) { 559 // Already existing package. Make sure signatures match 560 boolean match = compareSignatures(pkgSetting.signatures.mSignatures, parsedSignatures) 561 == PackageManager.SIGNATURE_MATCH; 562 if (!match && compareCompat) { 563 match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures); 564 compatMatch = match; 565 } 566 if (!match && compareRecover) { 567 match = matchSignaturesRecover( 568 packageName, pkgSetting.signatures.mSignatures, parsedSignatures); 569 } 570 if (!match) { 571 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, 572 "Package " + packageName + 573 " signatures don't match previously installed version; ignoring!"); 574 } 575 } 576 // Check for shared user signatures 577 if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) { 578 // Already existing package. Make sure signatures match 579 boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures, 580 parsedSignatures) == PackageManager.SIGNATURE_MATCH; 581 if (!match) { 582 match = matchSignaturesCompat( 583 packageName, pkgSetting.sharedUser.signatures, parsedSignatures); 584 } 585 if (!match && compareCompat) { 586 match = matchSignaturesRecover( 587 packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures); 588 compatMatch |= match; 589 } 590 if (!match && compareRecover) { 591 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, 592 "Package " + packageName 593 + " has no signatures that match those in shared user " 594 + pkgSetting.sharedUser.name + "; ignoring!"); 595 } 596 } 597 return compatMatch; 598 } 599 600 public static int decompressFile(File srcFile, File dstFile) throws ErrnoException { 601 if (DEBUG_COMPRESSION) { 602 Slog.i(TAG, "Decompress file" 603 + "; src: " + srcFile.getAbsolutePath() 604 + ", dst: " + dstFile.getAbsolutePath()); 605 } 606 try ( 607 InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile)); 608 OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/); 609 ) { 610 Streams.copy(fileIn, fileOut); 611 Os.chmod(dstFile.getAbsolutePath(), 0644); 612 return PackageManager.INSTALL_SUCCEEDED; 613 } catch (IOException e) { 614 logCriticalInfo(Log.ERROR, "Failed to decompress file" 615 + "; src: " + srcFile.getAbsolutePath() 616 + ", dst: " + dstFile.getAbsolutePath()); 617 } 618 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 619 } 620 621 public static File[] getCompressedFiles(String codePath) { 622 final File stubCodePath = new File(codePath); 623 final String stubName = stubCodePath.getName(); 624 625 // The layout of a compressed package on a given partition is as follows : 626 // 627 // Compressed artifacts: 628 // 629 // /partition/ModuleName/foo.gz 630 // /partation/ModuleName/bar.gz 631 // 632 // Stub artifact: 633 // 634 // /partition/ModuleName-Stub/ModuleName-Stub.apk 635 // 636 // In other words, stub is on the same partition as the compressed artifacts 637 // and in a directory that's suffixed with "-Stub". 638 int idx = stubName.lastIndexOf(STUB_SUFFIX); 639 if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) { 640 return null; 641 } 642 643 final File stubParentDir = stubCodePath.getParentFile(); 644 if (stubParentDir == null) { 645 Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath); 646 return null; 647 } 648 649 final File compressedPath = new File(stubParentDir, stubName.substring(0, idx)); 650 final File[] files = compressedPath.listFiles(new FilenameFilter() { 651 @Override 652 public boolean accept(File dir, String name) { 653 return name.toLowerCase().endsWith(COMPRESSED_EXTENSION); 654 } 655 }); 656 657 if (DEBUG_COMPRESSION && files != null && files.length > 0) { 658 Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files)); 659 } 660 661 return files; 662 } 663 664 public static boolean compressedFileExists(String codePath) { 665 final File[] compressedFiles = getCompressedFiles(codePath); 666 return compressedFiles != null && compressedFiles.length > 0; 667 } 668} 669