diff options
| -rw-r--r-- | core/java/android/content/pm/PackageInfo.java | 5 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageParser.java | 7 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 228 |
3 files changed, 221 insertions, 19 deletions
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 7f1198541668..cf0edcad7e2b 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -252,6 +252,9 @@ public class PackageInfo implements Parcelable { public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY; /** @hide */ + public boolean isStub; + + /** @hide */ public boolean coreApp; /** @hide */ @@ -324,6 +327,7 @@ public class PackageInfo implements Parcelable { dest.writeTypedArray(reqFeatures, parcelableFlags); dest.writeTypedArray(featureGroups, parcelableFlags); dest.writeInt(installLocation); + dest.writeInt(isStub ? 1 : 0); dest.writeInt(coreApp ? 1 : 0); dest.writeInt(requiredForAllUsers ? 1 : 0); dest.writeString(restrictedAccountType); @@ -375,6 +379,7 @@ public class PackageInfo implements Parcelable { reqFeatures = source.createTypedArray(FeatureInfo.CREATOR); featureGroups = source.createTypedArray(FeatureGroupInfo.CREATOR); installLocation = source.readInt(); + isStub = source.readInt() != 0; coreApp = source.readInt() != 0; requiredForAllUsers = source.readInt() != 0; restrictedAccountType = source.readString(); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index c76ca6dbbe49..69e3369ccba1 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -665,6 +665,7 @@ public class PackageParser { pi.sharedUserLabel = p.mSharedUserLabel; pi.applicationInfo = generateApplicationInfo(p, flags, state, userId); pi.installLocation = p.installLocation; + pi.isStub = p.isStub; pi.coreApp = p.coreApp; if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0 || (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { @@ -5879,10 +5880,10 @@ public class PackageParser { public byte[] restrictUpdateHash; - /** - * Set if the app or any of its components are visible to Instant Apps. - */ + /** Set if the app or any of its components are visible to instant applications. */ public boolean visibleToInstantApps; + /** Whether or not the package is a stub and must be replaced by the full version. */ + public boolean isStub; public Package(String packageName) { this.packageName = packageName; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 9e61d1336f98..baa285661b24 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -292,6 +292,7 @@ import dalvik.system.DexFile; import dalvik.system.VMRuntime; import libcore.io.IoUtils; +import libcore.io.Streams; import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; @@ -309,6 +310,8 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -340,6 +343,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPInputStream; /** * Keep track of all those APKs everywhere. @@ -393,6 +397,7 @@ public class PackageManagerService extends IPackageManager.Stub private static final boolean DEBUG_FILTERS = false; private static final boolean DEBUG_PERMISSIONS = false; private static final boolean DEBUG_SHARED_LIBRARIES = false; + private static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE; // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService // and PackageDexOptimizer. All these classes have their own flag to allow switching a single @@ -447,6 +452,8 @@ public class PackageManagerService extends IPackageManager.Stub static final int FLAGS_REMOVE_CHATTY = 1<<31; private static final String STATIC_SHARED_LIB_DELIMITER = "_"; + /** Extension of the compressed packages */ + private final static String COMPRESSED_EXTENSION = ".gz"; private static final int[] EMPTY_INT_ARRAY = new int[0]; @@ -2634,9 +2641,21 @@ public class PackageManagerService extends IPackageManager.Stub | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); // Prune any system packages that no longer exist. - final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>(); + final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>(); + // Stub packages must either be replaced with full versions in the /data + // partition or be disabled. + final List<String> stubSystemApps = new ArrayList<>(); if (!mOnlyCore) { - Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator(); + // do this first before mucking with mPackages for the "expecting better" case + final Iterator<PackageParser.Package> pkgIterator = mPackages.values().iterator(); + while (pkgIterator.hasNext()) { + final PackageParser.Package pkg = pkgIterator.next(); + if (pkg.isStub) { + stubSystemApps.add(pkg.packageName); + } + } + + final Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator(); while (psit.hasNext()) { PackageSetting ps = psit.next(); @@ -2724,36 +2743,37 @@ public class PackageManagerService extends IPackageManager.Stub | PackageParser.PARSE_FORWARD_LOCK, scanFlags | SCAN_REQUIRE_KNOWN, 0); - /** - * Remove disable package settings for any updated system - * apps that were removed via an OTA. If they're not a - * previously-updated app, remove them completely. - * Otherwise, just revoke their system-level permissions. - */ + // Remove disable package settings for updated system apps that were + // removed via an OTA. If the update is no longer present, remove the + // app completely. Otherwise, revoke their system privileges. for (String deletedAppName : possiblyDeletedUpdatedSystemApps) { PackageParser.Package deletedPkg = mPackages.get(deletedAppName); mSettings.removeDisabledSystemPackageLPw(deletedAppName); - String msg; + final String msg; if (deletedPkg == null) { + // should have found an update, but, we didn't; remove everything msg = "Updated system package " + deletedAppName - + " no longer exists; it's data will be wiped"; + + " no longer exists; removing its data"; // Actual deletion of code and data will be handled by later // reconciliation step } else { - msg = "Updated system app + " + deletedAppName - + " no longer present; removing system privileges for " - + deletedAppName; + // found an update; revoke system privileges + msg = "Updated system package + " + deletedAppName + + " no longer exists; revoking system privileges"; - deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; + // Don't do anything if a stub is removed from the system image. If + // we were to remove the uncompressed version from the /data partition, + // this is where it'd be done. - PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName); + final PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName); + deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM; } logCriticalInfo(Log.WARN, msg); } - /** + /* * Make sure all system apps that we expected to appear on * the userdata partition actually showed up. If they never * appeared, crawl back and revive the system version. @@ -2795,6 +2815,11 @@ public class PackageManagerService extends IPackageManager.Stub } } } + + // Uncompress and install any stubbed system applications. + // This must be done last to ensure all stubs are replaced or disabled. + decompressSystemApplications(stubSystemApps, scanFlags); + final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime; final int dataPackagesCount = mPackages.size() - systemPackagesCount; Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime @@ -3062,6 +3087,174 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + /** + * Uncompress and install stub applications. + * <p>In order to save space on the system partition, some applications are shipped in a + * compressed form. In addition the compressed bits for the full application, the + * system image contains a tiny stub comprised of only the Android manifest. + * <p>During the first boot, attempt to uncompress and install the full application. If + * the application can't be installed for any reason, disable the stub and prevent + * uncompressing the full application during future boots. + * <p>In order to forcefully attempt an installation of a full application, go to app + * settings and enable the application. + */ + private void decompressSystemApplications(@NonNull List<String> stubSystemApps, int scanFlags) { + for (int i = stubSystemApps.size() - 1; i >= 0; --i) { + final String pkgName = stubSystemApps.get(i); + // skip if the system package is already disabled + if (mSettings.isDisabledSystemPackageLPr(pkgName)) { + stubSystemApps.remove(i); + continue; + } + // skip if the package isn't installed (?!); this should never happen + final PackageParser.Package pkg = mPackages.get(pkgName); + if (pkg == null) { + stubSystemApps.remove(i); + continue; + } + // skip if the package has been disabled by the user + final PackageSetting ps = mSettings.mPackages.get(pkgName); + if (ps != null) { + final int enabledState = ps.getEnabled(UserHandle.USER_SYSTEM); + if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + stubSystemApps.remove(i); + continue; + } + } + + if (DEBUG_COMPRESSION) { + Slog.i(TAG, "Uncompressing system stub; pkg: " + pkgName); + } + + // uncompress the binary to its eventual destination on /data + final File scanFile = decompressPackage(pkg); + if (scanFile == null) { + continue; + } + + // install the package to replace the stub on /system + try { + mSettings.disableSystemPackageLPw(pkgName, true /*replaced*/); + removePackageLI(pkg, true /*chatty*/); + scanPackageTracedLI(scanFile, 0 /*reparseFlags*/, scanFlags, 0, null); + ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, + UserHandle.USER_SYSTEM, "android"); + stubSystemApps.remove(i); + continue; + } catch (PackageManagerException e) { + Slog.e(TAG, "Failed to parse uncompressed system package: " + e.getMessage()); + } + + // any failed attempt to install the package will be cleaned up later + } + + // disable any stub still left; these failed to install the full application + for (int i = stubSystemApps.size() - 1; i >= 0; --i) { + final String pkgName = stubSystemApps.get(i); + final PackageSetting ps = mSettings.mPackages.get(pkgName); + ps.setEnabled(PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + UserHandle.USER_SYSTEM, "android"); + logCriticalInfo(Log.ERROR, "Stub disabled; pkg: " + pkgName); + } + } + + private int decompressFile(File srcFile, File dstFile) throws ErrnoException { + if (DEBUG_COMPRESSION) { + Slog.i(TAG, "Decompress file" + + "; src: " + srcFile.getAbsolutePath() + + ", dst: " + dstFile.getAbsolutePath()); + } + try ( + InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile)); + OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/); + ) { + Streams.copy(fileIn, fileOut); + Os.chmod(dstFile.getAbsolutePath(), 0644); + return PackageManager.INSTALL_SUCCEEDED; + } catch (IOException e) { + logCriticalInfo(Log.ERROR, "Failed to decompress file" + + "; src: " + srcFile.getAbsolutePath() + + ", dst: " + dstFile.getAbsolutePath()); + } + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + + private File[] getCompressedFiles(String codePath) { + return new File(codePath).listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.toLowerCase().endsWith(COMPRESSED_EXTENSION); + } + }); + } + + private boolean compressedFileExists(String codePath) { + final File[] compressedFiles = getCompressedFiles(codePath); + return compressedFiles != null && compressedFiles.length > 0; + } + + /** + * Decompresses the given package on the system image onto + * the /data partition. + * @return The directory the package was decompressed into. Otherwise, {@code null}. + */ + private File decompressPackage(PackageParser.Package pkg) { + final File[] compressedFiles = getCompressedFiles(pkg.codePath); + if (compressedFiles == null || compressedFiles.length == 0) { + if (DEBUG_COMPRESSION) { + Slog.i(TAG, "No files to decompress"); + } + return null; + } + final File dstCodePath = + getNextCodePath(Environment.getDataAppDirectory(null), pkg.packageName); + int ret = PackageManager.INSTALL_SUCCEEDED; + try { + Os.mkdir(dstCodePath.getAbsolutePath(), 0755); + Os.chmod(dstCodePath.getAbsolutePath(), 0755); + for (File srcFile : compressedFiles) { + final String srcFileName = srcFile.getName(); + final String dstFileName = srcFileName.substring( + 0, srcFileName.length() - COMPRESSED_EXTENSION.length()); + final File dstFile = new File(dstCodePath, dstFileName); + ret = decompressFile(srcFile, dstFile); + if (ret != PackageManager.INSTALL_SUCCEEDED) { + logCriticalInfo(Log.ERROR, "Failed to decompress" + + "; pkg: " + pkg.packageName + + ", file: " + dstFileName); + break; + } + } + } catch (ErrnoException e) { + logCriticalInfo(Log.ERROR, "Failed to decompress" + + "; pkg: " + pkg.packageName + + ", err: " + e.errno); + } + if (ret == PackageManager.INSTALL_SUCCEEDED) { + final File libraryRoot = new File(dstCodePath, LIB_DIR_NAME); + NativeLibraryHelper.Handle handle = null; + try { + handle = NativeLibraryHelper.Handle.create(dstCodePath); + ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot, + null /*abiOverride*/); + } catch (IOException e) { + logCriticalInfo(Log.ERROR, "Failed to extract native libraries" + + "; pkg: " + pkg.packageName); + ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } finally { + IoUtils.closeQuietly(handle); + } + } + if (ret != PackageManager.INSTALL_SUCCEEDED) { + if (dstCodePath == null || !dstCodePath.exists()) { + return null; + } + removeCodePathLI(dstCodePath); + return null; + } + return dstCodePath; + } + private void updateInstantAppInstallerLocked(String modifiedPackage) { // we're only interested in updating the installer appliction when 1) it's not // already set or 2) the modified package is the installer @@ -10707,6 +10900,9 @@ public class PackageManagerService extends IPackageManager.Stub r.info.encryptionAware = r.info.directBootAware = true; } } + if (compressedFileExists(pkg.baseCodePath)) { + pkg.isStub = true; + } } else { // Only allow system apps to be flagged as core apps. pkg.coreApp = false; |