Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8bc22f0 authored by Nikita Ioffe's avatar Nikita Ioffe
Browse files

Don't create app data dirs if app requests not to

App can set a android.internal.PROPERTY_NO_APP_DATA_STORAGE  property in it's
AndroidManifest.xml which will tell platform to avoid creating data
directories for it.

This property is intentionally not exposed as public API because not
having private app storage is a very niche requirement.

This change also logic to prevent app updates from changing value of the
property, i.e.: if an installed app doesn't specify value of the
PROPERTY_NO_APP_DATA_STORAGE property (or has it set to false), then any
update to this app shouldn't have this property specified (or explicitly
set it to false). If an app has PROPERTY_NO_APP_DATA_STORAGE set to
true, then all updates should keep that property set to true.

Note: this change only takes into account internal storage. Removing
app's external storage will be done in the follow up cl.

Bug: 211761016
Test: atest PackageManagerShellTest
Change-Id: I3e541d947a77f5c050bf706f64009f21c24dcbc9
parent fb42cf7e
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -144,6 +144,20 @@ public abstract class PackageManager {
    public static final String PROPERTY_MEDIA_CAPABILITIES =
            "android.media.PROPERTY_MEDIA_CAPABILITIES";

    /**
     * Application level property that an app can specify to opt-out from having private data
     * directories both on the internal and external storages.
     *
     * <p>Changing the value of this property during app update is not supported, and such updates
     * will be rejected.
     *
     * <p>This should only be set by platform apps that know what they are doing.
     *
     * @hide
     */
    public static final String PROPERTY_NO_APP_DATA_STORAGE =
            "android.internal.PROPERTY_NO_APP_DATA_STORAGE";

    /**
     * A property value set within the manifest.
     * <p>
+33 −3
Original line number Diff line number Diff line
@@ -101,6 +101,12 @@ final class AppDataHelper {
            mPm.mSettings.writeKernelMappingLPr(ps);
        }

        // TODO(b/211761016): should we still create the profile dirs?
        if (!shouldHaveAppStorage(pkg)) {
            Slog.w(TAG, "Skipping preparing app data for " + pkg.getPackageName());
            return;
        }

        Installer.Batch batch = new Installer.Batch();
        UserManagerInternal umInternal = mInjector.getUserManagerInternal();
        StorageManagerInternal smInternal = mInjector.getLocalService(
@@ -161,6 +167,10 @@ final class AppDataHelper {
            Slog.wtf(TAG, "Package was null!", new Throwable());
            return CompletableFuture.completedFuture(null);
        }
        if (!shouldHaveAppStorage(pkg)) {
            Slog.w(TAG, "Skipping preparing app data for " + pkg.getPackageName());
            return CompletableFuture.completedFuture(null);
        }
        return prepareAppDataLeaf(batch, pkg, previousAppId, userId, flags);
    }

@@ -381,7 +391,7 @@ final class AppDataHelper {
            for (File file : files) {
                final String packageName = file.getName();
                try {
                    assertPackageKnownAndInstalled(volumeUuid, packageName, userId);
                    assertPackageStorageValid(volumeUuid, packageName, userId);
                } catch (PackageManagerException e) {
                    logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
                    try {
@@ -398,7 +408,7 @@ final class AppDataHelper {
            for (File file : files) {
                final String packageName = file.getName();
                try {
                    assertPackageKnownAndInstalled(volumeUuid, packageName, userId);
                    assertPackageStorageValid(volumeUuid, packageName, userId);
                } catch (PackageManagerException e) {
                    logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
                    try {
@@ -446,7 +456,11 @@ final class AppDataHelper {
        return result;
    }

    private void assertPackageKnownAndInstalled(String volumeUuid, String packageName, int userId)
    /**
     * Asserts that storage path is valid by checking that {@code packageName} is present,
     * installed for the given {@code userId} and can have app data.
     */
    private void assertPackageStorageValid(String volumeUuid, String packageName, int userId)
            throws PackageManagerException {
        synchronized (mPm.mLock) {
            // Normalize package name to handle renamed packages
@@ -462,6 +476,13 @@ final class AppDataHelper {
            } else if (!ps.getInstalled(userId)) {
                throw new PackageManagerException(
                        "Package " + packageName + " not installed for user " + userId);
            } else if (ps.getPkg() == null) {
                throw new PackageManagerException("Package " + packageName + " is not parsed yet");
            } else {
                if (!shouldHaveAppStorage(ps.getPkg())) {
                    throw new PackageManagerException(
                            "Package " + packageName + " shouldn't have storage");
                }
            }
        }
    }
@@ -603,4 +624,13 @@ final class AppDataHelper {
            Slog.w(TAG, String.valueOf(e));
        }
    }

    /**
     * Returns {@code true} if app's internal storage should be created for this {@code pkg}.
     */
    private boolean shouldHaveAppStorage(AndroidPackage pkg) {
        PackageManager.Property noAppDataProp =
                pkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
        return noAppDataProp == null || !noAppDataProp.getBoolean();
    }
}
+26 −0
Original line number Diff line number Diff line
@@ -941,6 +941,16 @@ final class InstallPackageHelper {
                    if (result.needsNewAppId()) {
                        request.mInstallResult.mRemovedInfo.mAppIdChanging = true;
                    }
                    if (!checkNoAppStorageIsConsistent(
                            result.mRequest.mOldPkg, result.mPkgSetting.getPkg())) {
                        // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
                        //  signatures. Is there a better error code?
                        request.mInstallResult.setError(
                                INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                "Update attempted to change value of "
                                        + PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
                        return;
                    }
                    createdAppId.put(packageName, optimisticallyRegisterAppId(result));
                    versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
                            mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
@@ -1038,6 +1048,22 @@ final class InstallPackageHelper {
        }
    }

    @GuardedBy("mPm.mInstallLock")
    private boolean checkNoAppStorageIsConsistent(AndroidPackage oldPkg, AndroidPackage newPkg) {
        if (oldPkg == null) {
            // New install, nothing to check against.
            return true;
        }
        final PackageManager.Property curProp =
                oldPkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
        final PackageManager.Property newProp =
                newPkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
        if (curProp == null || !curProp.getBoolean()) {
            return newProp == null || !newProp.getBoolean();
        }
        return newProp != null && newProp.getBoolean();
    }

    @GuardedBy("mPm.mInstallLock")
    private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
            throws PrepareFailure {