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

Commit 42884197 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Use inode numbers for CE storage.

Certain operations, such as clearing/destroying app data, or just
counting on-disk size, require us to know the CE storage directory
of a particular app.  To facilitate these operations, offer a method
to get the inode of a CE directory, and accept that inode number
for later operations.  Collect and store the inode number in
PackageUserState for future use when that user's CE storage is
still locked.  This design means it's safe to clear/destroy app
data in both CE/DE storage at the same time.

Move most installd-related methods to a uniform calling convention
that accepts a single parent PackageParser.Package, and internally
fans out to handle all "leaf" packages under that parent.

In previous releases, we started installing apps using a new
directory-based layout, where all app code, unpacked native libraries,
and optimized code is bundled together.  So now we only have a single
path to measure for code size.  This fixes several outstanding bugs
that were causing sizes to be miscounted for apps supporting multiple
architectures.

Fix a subtle bug in PackageSettings that would cause "notLaunched"
to be parsed incorrectly.

Bug: 27828915, 27197819
Change-Id: Ia582cf3550553292bde4bb4313367111332913ec
parent d4041db1
Loading
Loading
Loading
Loading
+10 −10
Original line number Diff line number Diff line
@@ -36,22 +36,21 @@ import com.android.internal.util.ArrayUtils;
 * @hide
 */
public class PackageUserState {
    public long ceDataInode;
    public boolean installed;
    public boolean stopped;
    public boolean notLaunched;
    public boolean installed;
    public boolean hidden; // Is the app restricted by owner / admin
    public boolean suspended;
    public int enabled;
    public boolean blockUninstall;

    public int enabled;
    public String lastDisableAppCaller;
    public int domainVerificationStatus;
    public int appLinkGeneration;

    public ArraySet<String> disabledComponents;
    public ArraySet<String> enabledComponents;

    public int domainVerificationStatus;
    public int appLinkGeneration;

    public PackageUserState() {
        installed = true;
        hidden = false;
@@ -62,18 +61,19 @@ public class PackageUserState {
    }

    public PackageUserState(PackageUserState o) {
        ceDataInode = o.ceDataInode;
        installed = o.installed;
        stopped = o.stopped;
        notLaunched = o.notLaunched;
        enabled = o.enabled;
        hidden = o.hidden;
        suspended = o.suspended;
        lastDisableAppCaller = o.lastDisableAppCaller;
        disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
        enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
        blockUninstall = o.blockUninstall;
        enabled = o.enabled;
        lastDisableAppCaller = o.lastDisableAppCaller;
        domainVerificationStatus = o.domainVerificationStatus;
        appLinkGeneration = o.appLinkGeneration;
        disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
        enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
    }

    /**
+15 −23
Original line number Diff line number Diff line
@@ -103,21 +103,7 @@ public class InstallerConnection {
        }
    }

    public void execute(String cmd, Object... args) throws InstallerException {
        final String resRaw = executeForResult(cmd, args);
        int res = -1;
        try {
            res = Integer.parseInt(resRaw);
        } catch (NumberFormatException ignored) {
        }
        if (res != 0) {
            throw new InstallerException(
                    "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res);
        }
    }

    public String executeForResult(String cmd, Object... args)
            throws InstallerException {
    public String[] execute(String cmd, Object... args) throws InstallerException {
        final StringBuilder builder = new StringBuilder(cmd);
        for (Object arg : args) {
            String escaped;
@@ -135,7 +121,17 @@ public class InstallerConnection {
            }
            builder.append(' ').append(escaped);
        }
        return transact(builder.toString());
        final String[] resRaw = transact(builder.toString()).split(" ");
        int res = -1;
        try {
            res = Integer.parseInt(resRaw[0]);
        } catch (ArrayIndexOutOfBoundsException | NumberFormatException ignored) {
        }
        if (res != 0) {
            throw new InstallerException(
                    "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res);
        }
        return resRaw;
    }

    public void dexopt(String apkPath, int uid, String instructionSet, int dexoptNeeded,
@@ -160,19 +156,15 @@ public class InstallerConnection {
    }

    public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
        String rawReply = executeForResult("merge_profiles", uid, pkgName);
        if (rawReply == null) {
            throw new IllegalStateException("Unexpected null reply");
        }
        final String res[] = rawReply.split(" ");
        final String[] res = execute("merge_profiles", uid, pkgName);

        if ((res == null) || (res.length != 2)) {
            throw new InstallerException("Invalid size result: " + rawReply);
            throw new InstallerException("Invalid size result: " + Arrays.toString(res));
        }

        // Just as a sanity check. Anything != "true" will be interpreted as false by parseBoolean.
        if (!res[1].equals("true") && !res[1].equals("false")) {
            throw new InstallerException("Invalid boolean result: " + rawReply);
            throw new InstallerException("Invalid boolean result: " + Arrays.toString(res));
        }

        return Boolean.parseBoolean(res[1]);
+3 −2
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import libcore.util.EmptyArray;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -128,7 +129,7 @@ public class ArrayUtils {
    /**
     * Checks if given array is null or has zero elements.
     */
    public static boolean isEmpty(@Nullable List<?> array) {
    public static boolean isEmpty(@Nullable Collection<?> array) {
        return array == null || array.isEmpty();
    }

@@ -451,7 +452,7 @@ public class ArrayUtils {
        }
    }

    public static <T> boolean contains(@Nullable ArrayList<T> cur, T val) {
    public static <T> boolean contains(@Nullable Collection<T> cur, T val) {
        return (cur != null) ? cur.contains(val) : false;
    }

+23 −26
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import com.android.server.SystemService;

import dalvik.system.VMRuntime;

import java.util.Arrays;

public final class Installer extends SystemService {
    private static final String TAG = "Installer";

@@ -90,14 +92,14 @@ public final class Installer extends SystemService {
        mInstaller.execute("migrate_app_data", uuid, pkgname, userid, flags);
    }

    public void clearAppData(String uuid, String pkgname, int userid, int flags)
    public void clearAppData(String uuid, String pkgname, int userid, int flags, long ceDataInode)
            throws InstallerException {
        mInstaller.execute("clear_app_data", uuid, pkgname, userid, flags);
        mInstaller.execute("clear_app_data", uuid, pkgname, userid, flags, ceDataInode);
    }

    public void destroyAppData(String uuid, String pkgname, int userid, int flags)
    public void destroyAppData(String uuid, String pkgname, int userid, int flags, long ceDataInode)
            throws InstallerException {
        mInstaller.execute("destroy_app_data", uuid, pkgname, userid, flags);
        mInstaller.execute("destroy_app_data", uuid, pkgname, userid, flags, ceDataInode);
    }

    public void moveCompleteApp(String from_uuid, String to_uuid, String package_name,
@@ -107,31 +109,26 @@ public final class Installer extends SystemService {
                data_app_name, appid, seinfo, targetSdkVersion);
    }

    public void getAppSize(String uuid, String pkgname, int userid, int flags, String apkPath,
            String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets,
            PackageStats pStats) throws InstallerException {
        for (String instructionSet : instructionSets) {
            assertValidInstructionSet(instructionSet);
    public void getAppSize(String uuid, String pkgname, int userid, int flags, long ceDataInode,
            String codePath, PackageStats stats) throws InstallerException {
        final String[] res = mInstaller.execute("get_app_size", uuid, pkgname, userid, flags,
                ceDataInode, codePath);
        try {
            stats.codeSize += Long.parseLong(res[1]);
            stats.dataSize += Long.parseLong(res[2]);
            stats.cacheSize += Long.parseLong(res[3]);
        } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
            throw new InstallerException("Invalid size result: " + Arrays.toString(res));
        }

        // TODO: Extend getSizeInfo to look at the full subdirectory tree,
        // not just the first level.
        // TODO: Extend getSizeInfo to look at *all* instrution sets, not
        // just the primary.
        final String rawRes = mInstaller.executeForResult("get_app_size", uuid, pkgname, userid,
                flags, apkPath, libDirPath, fwdLockApkPath, asecPath, instructionSets[0]);
        final String res[] = rawRes.split(" ");

        if ((res == null) || (res.length != 5)) {
            throw new InstallerException("Invalid size result: " + rawRes);
    }

    public long getAppDataInode(String uuid, String pkgname, int userid, int flags)
            throws InstallerException {
        final String[] res = mInstaller.execute("get_app_data_inode", uuid, pkgname, userid, flags);
        try {
            pStats.codeSize = Long.parseLong(res[1]);
            pStats.dataSize = Long.parseLong(res[2]);
            pStats.cacheSize = Long.parseLong(res[3]);
            pStats.externalCodeSize = Long.parseLong(res[4]);
        } catch (NumberFormatException e) {
            throw new InstallerException("Invalid size result: " + rawRes);
            return Long.parseLong(res[1]);
        } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
            throw new InstallerException("Invalid inode result: " + Arrays.toString(res));
        }
    }

+310 −330

File changed.

Preview size limit exceeded, changes collapsed.

Loading