Loading core/java/android/content/pm/PackageInstaller.java +65 −7 Original line number Diff line number Diff line Loading @@ -285,6 +285,9 @@ public class PackageInstaller { * * @throws IOException if parameters were unsatisfiable, such as lack of * disk space or unavailable media. * @throws SecurityException when installation services are unavailable, * such as when called from a restricted user. * @throws IllegalArgumentException when {@link SessionParams} is invalid. * @return positive, non-zero unique ID that represents the created session. * This ID remains consistent across device reboots until the * session is finalized. IDs are not reused during a given boot. Loading @@ -303,6 +306,11 @@ public class PackageInstaller { /** * Open an existing session to actively perform work. To succeed, the caller * must be the owner of the install session. * * @throws IOException if parameters were unsatisfiable, such as lack of * disk space or unavailable media. * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public @NonNull Session openSession(int sessionId) throws IOException { try { Loading @@ -319,6 +327,9 @@ public class PackageInstaller { * Update the icon representing the app being installed in a specific * session. This should be roughly * {@link ActivityManager#getLauncherLargeIconSize()} in both dimensions. * * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public void updateSessionAppIcon(int sessionId, @Nullable Bitmap appIcon) { try { Loading @@ -331,6 +342,9 @@ public class PackageInstaller { /** * Update the label representing the app being installed in a specific * session. * * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public void updateSessionAppLabel(int sessionId, @Nullable CharSequence appLabel) { try { Loading @@ -341,6 +355,15 @@ public class PackageInstaller { } } /** * Completely abandon the given session, destroying all staged data and * rendering it invalid. Abandoned sessions will be reported to * {@link SessionCallback} listeners as failures. This is equivalent to * opening the session and calling {@link Session#abandon()}. * * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public void abandonSession(int sessionId) { try { mInstaller.abandonSession(sessionId); Loading @@ -350,7 +373,11 @@ public class PackageInstaller { } /** * Return details for a specific session. * Return details for a specific session. No special permissions are * required to retrieve these details. * * @return details for the requested session, or {@code null} if the session * does not exist. */ public @Nullable SessionInfo getSessionInfo(int sessionId) { try { Loading @@ -361,7 +388,7 @@ public class PackageInstaller { } /** * Return list of all active install sessions, regardless of the installer. * Return list of all known install sessions, regardless of the installer. */ public @NonNull List<SessionInfo> getAllSessions() { final ApplicationInfo info = mContext.getApplicationInfo(); Loading @@ -379,7 +406,7 @@ public class PackageInstaller { } /** * Return list of all install sessions owned by the calling app. * Return list of all known install sessions owned by the calling app. */ public @NonNull List<SessionInfo> getMySessions() { try { Loading Loading @@ -547,7 +574,8 @@ public class PackageInstaller { } /** * Register to watch for session lifecycle events. * Register to watch for session lifecycle events. No special permissions * are required to watch for these events. */ public void registerSessionCallback(@NonNull SessionCallback callback) { registerSessionCallback(callback, new Handler()); Loading @@ -560,7 +588,8 @@ public class PackageInstaller { } /** * Register to watch for session lifecycle events. * Register to watch for session lifecycle events. No special permissions * are required to watch for these events. * * @param handler to dispatch callback events through, otherwise uses * calling thread. Loading Loading @@ -593,7 +622,7 @@ public class PackageInstaller { } /** * Unregister an existing callback. * Unregister a previously registered callback. */ public void unregisterSessionCallback(@NonNull SessionCallback callback) { synchronized (mDelegates) { Loading Loading @@ -686,6 +715,12 @@ public class PackageInstaller { * start at the beginning of the file. * @param lengthBytes total size of the file being written, used to * preallocate the underlying disk space, or -1 if unknown. * The system may clear various caches as needed to allocate * this space. * @throws IOException if trouble opening the file for writing, such as * lack of disk space or unavailable media. * @throws SecurityException if called after the session has been * committed or abandoned. */ public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, long lengthBytes) throws IOException { Loading Loading @@ -719,6 +754,9 @@ public class PackageInstaller { * <p> * This returns all names which have been previously written through * {@link #openWrite(String, long, long)} as part of this session. * * @throws SecurityException if called after the session has been * committed or abandoned. */ public @NonNull String[] getNames() throws IOException { try { Loading @@ -738,6 +776,9 @@ public class PackageInstaller { * through {@link #openWrite(String, long, long)} as part of this * session. For example, this stream may be used to calculate a * {@link MessageDigest} of a written APK before committing. * * @throws SecurityException if called after the session has been * committed or abandoned. */ public @NonNull InputStream openRead(@NonNull String name) throws IOException { try { Loading @@ -759,6 +800,9 @@ public class PackageInstaller { * Once this method is called, no additional mutations may be performed * on the session. If the device reboots before the session has been * finalized, you may commit the session again. * * @throws SecurityException if streams opened through * {@link #openWrite(String, long, long)} are still open. */ public void commit(@NonNull IntentSender statusReceiver) { try { Loading @@ -783,7 +827,9 @@ public class PackageInstaller { /** * Completely abandon this session, destroying all staged data and * rendering it invalid. * rendering it invalid. Abandoned sessions will be reported to * {@link SessionCallback} listeners as failures. This is equivalent to * opening the session and calling {@link Session#abandon()}. */ public void abandon() { try { Loading Loading @@ -936,6 +982,18 @@ public class PackageInstaller { this.referrerUri = referrerUri; } /** {@hide} */ public void setInstallFlagsInternal() { installFlags |= PackageManager.INSTALL_INTERNAL; installFlags &= ~PackageManager.INSTALL_EXTERNAL; } /** {@hide} */ public void setInstallFlagsExternal() { installFlags |= PackageManager.INSTALL_EXTERNAL; installFlags &= ~PackageManager.INSTALL_INTERNAL; } /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); Loading core/java/android/os/FileBridge.java +10 −8 Original line number Diff line number Diff line Loading @@ -75,6 +75,13 @@ public class FileBridge extends Thread { return mClosed; } public void forceClose() { IoUtils.closeQuietly(mTarget); IoUtils.closeQuietly(mServer); IoUtils.closeQuietly(mClient); mClosed = true; } public void setTargetFile(FileDescriptor target) { mTarget = target; } Loading @@ -89,7 +96,6 @@ public class FileBridge extends Thread { try { while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) { final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN); if (cmd == CMD_WRITE) { // Shuttle data into local file int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN); Loading Loading @@ -118,15 +124,10 @@ public class FileBridge extends Thread { } } } catch (ErrnoException e) { Log.wtf(TAG, "Failed during bridge", e); } catch (IOException e) { } catch (ErrnoException | IOException e) { Log.wtf(TAG, "Failed during bridge", e); } finally { IoUtils.closeQuietly(mTarget); IoUtils.closeQuietly(mServer); IoUtils.closeQuietly(mClient); mClosed = true; forceClose(); } } Loading @@ -151,6 +152,7 @@ public class FileBridge extends Thread { writeCommandAndBlock(CMD_CLOSE, "close()"); } finally { IoBridge.closeAndSignalBlockedThreads(mClient); IoUtils.closeQuietly(mClientPfd); } } Loading core/java/com/android/internal/content/PackageHelper.java +4 −1 Original line number Diff line number Diff line Loading @@ -390,8 +390,11 @@ public class PackageHelper { if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) { final File target = new UserEnvironment(UserHandle.USER_OWNER) .getExternalStorageDirectory(); // External is only an option when size is known if (sizeBytes > 0) { fitsOnExternal = (sizeBytes <= storage.getStorageBytesUntilLow(target)); } } if (prefer == RECOMMEND_INSTALL_INTERNAL) { if (fitsOnInternal) { Loading core/java/com/android/internal/util/XmlUtils.java +10 −0 Original line number Diff line number Diff line Loading @@ -1440,6 +1440,16 @@ public class XmlUtils { return Boolean.parseBoolean(value); } public static boolean readBooleanAttribute(XmlPullParser in, String name, boolean defaultValue) { final String value = in.getAttributeValue(null, name); if (value == null) { return defaultValue; } else { return Boolean.parseBoolean(value); } } public static void writeBooleanAttribute(XmlSerializer out, String name, boolean value) throws IOException { out.attribute(null, name, Boolean.toString(value)); Loading services/core/java/com/android/server/pm/PackageInstallerService.java +47 −98 Original line number Diff line number Diff line Loading @@ -42,7 +42,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; Loading @@ -50,15 +49,11 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Environment; import android.os.Environment.UserEnvironment; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; Loading @@ -70,7 +65,6 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; Loading Loading @@ -126,6 +120,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static final String ATTR_CREATED_MILLIS = "createdMillis"; private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; private static final String ATTR_PREPARED = "prepared"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; Loading @@ -148,7 +143,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final PackageManagerService mPm; private final AppOpsManager mAppOps; private final StorageManager mStorage; private final File mStagingDir; private final HandlerThread mInstallThread; Loading Loading @@ -190,7 +184,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mContext = context; mPm = pm; mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mStorage = StorageManager.from(mContext); mStagingDir = stagingDir; Loading Loading @@ -266,7 +259,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { try { final int sessionId = allocateSessionIdLocked(); mLegacySessions.put(sessionId, true); return prepareInternalStageDir(sessionId); final File stageDir = buildInternalStageDir(sessionId); prepareInternalStageDir(stageDir); return stageDir; } catch (IllegalStateException e) { throw new IOException(e); } Loading Loading @@ -345,6 +340,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR); final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null; final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); final SessionParams params = new SessionParams( Loading @@ -362,7 +358,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, createdMillis, stageDir, stageCid, sealed); createdMillis, stageDir, stageCid, prepared, sealed); } private void writeSessionsLocked() { Loading Loading @@ -410,6 +406,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { if (session.stageCid != null) { writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.stageCid); } writeBooleanAttribute(out, ATTR_PREPARED, session.isPrepared()); writeBooleanAttribute(out, ATTR_SEALED, session.isSealed()); writeIntAttribute(out, ATTR_MODE, params.mode); Loading Loading @@ -483,14 +480,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } // TODO: treat INHERIT_EXISTING as install for user // Figure out where we're going to be staging session data final boolean stageInternal; if (params.mode == SessionParams.MODE_FULL_INSTALL) { // Brand new install, use best resolved location. This also verifies // that target has enough free space for the install. if (params.mode == SessionParams.MODE_FULL_INSTALL || params.mode == SessionParams.MODE_INHERIT_EXISTING) { // Resolve best location for install, based on combination of // requested install flags, delta size, and manifest settings. final long ident = Binder.clearCallingIdentity(); try { final int resolved = PackageHelper.resolveInstallLocation(mContext, Loading @@ -498,46 +491,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.installFlags); if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) { stageInternal = true; params.setInstallFlagsInternal(); } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { stageInternal = false; params.setInstallFlagsExternal(); } else { throw new IOException("No storage with enough free space; res=" + resolved); } } finally { Binder.restoreCallingIdentity(ident); } } else if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { // Inheriting existing install, so stay on the same storage medium. final ApplicationInfo existingApp = mPm.getApplicationInfo(params.appPackageName, 0, userId); if (existingApp == null) { throw new IllegalStateException( "Missing existing app " + params.appPackageName); } final long existingSize; try { final PackageLite existingPkg = PackageParser.parsePackageLite( new File(existingApp.getCodePath()), 0); existingSize = PackageHelper.calculateInstalledSize(existingPkg, false, params.abiOverride); } catch (PackageParserException e) { throw new IllegalStateException( "Failed to calculate size of " + params.appPackageName); } if ((existingApp.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0) { // Internal we can link existing install into place, so we only // need enough space for the new data. checkInternalStorage(params.sizeBytes); stageInternal = true; } else { // External we're going to copy existing install into our // container, so we need footprint of both. checkExternalStorage(params.sizeBytes + existingSize); stageInternal = false; } } else { throw new IllegalArgumentException("Invalid install mode: " + params.mode); } Loading @@ -563,15 +525,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // We're staging to exactly one location File stageDir = null; String stageCid = null; if (stageInternal) { stageDir = prepareInternalStageDir(sessionId); if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { stageDir = buildInternalStageDir(sessionId); } else { stageCid = prepareExternalStageCid(sessionId, params.sizeBytes); stageCid = buildExternalStageCid(sessionId); } session = new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, createdMillis, stageDir, stageCid, false); createdMillis, stageDir, stageCid, false, false); mSessions.put(sessionId, session); } Loading Loading @@ -615,32 +577,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } private void checkInternalStorage(long sizeBytes) throws IOException { if (sizeBytes <= 0) return; final File target = Environment.getDataDirectory(); final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); mPm.freeStorage(targetBytes); if (target.getUsableSpace() < targetBytes) { throw new IOException("Not enough internal space to write " + sizeBytes + " bytes"); } } private void checkExternalStorage(long sizeBytes) throws IOException { if (sizeBytes <= 0) return; final File target = new UserEnvironment(UserHandle.USER_OWNER) .getExternalStorageDirectory(); final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); if (target.getUsableSpace() < targetBytes) { throw new IOException("Not enough external space to write " + sizeBytes + " bytes"); @Override public IPackageInstallerSession openSession(int sessionId) { try { return openSessionInternal(sessionId); } catch (IOException e) { throw ExceptionUtils.wrap(e); } } @Override public IPackageInstallerSession openSession(int sessionId) { private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException { synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); if (session == null || !isCallingUidOwner(session)) { Loading @@ -665,40 +611,37 @@ public class PackageInstallerService extends IPackageInstaller.Stub { throw new IllegalStateException("Failed to allocate session ID"); } private File prepareInternalStageDir(int sessionId) throws IOException { final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp"); private File buildInternalStageDir(int sessionId) { return new File(mStagingDir, "vmdl" + sessionId + ".tmp"); } if (file.exists()) { throw new IOException("Session dir already exists: " + file); static void prepareInternalStageDir(File stageDir) throws IOException { if (stageDir.exists()) { throw new IOException("Session dir already exists: " + stageDir); } try { Os.mkdir(file.getAbsolutePath(), 0755); Os.chmod(file.getAbsolutePath(), 0755); Os.mkdir(stageDir.getAbsolutePath(), 0755); Os.chmod(stageDir.getAbsolutePath(), 0755); } catch (ErrnoException e) { // This purposefully throws if directory already exists throw new IOException("Failed to prepare session dir", e); throw new IOException("Failed to prepare session dir: " + stageDir, e); } if (!SELinux.restorecon(file)) { throw new IOException("Failed to restorecon session dir"); if (!SELinux.restorecon(stageDir)) { throw new IOException("Failed to restorecon session dir: " + stageDir); } return file; } private String prepareExternalStageCid(int sessionId, long sizeBytes) throws IOException { if (sizeBytes <= 0) { throw new IOException("Session must provide valid size for ASEC"); private String buildExternalStageCid(int sessionId) { return "smdl" + sessionId + ".tmp"; } final String cid = "smdl" + sessionId + ".tmp"; if (PackageHelper.createSdDir(sizeBytes, cid, PackageManagerService.getEncryptKey(), static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException { if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(), Process.SYSTEM_UID, true) == null) { throw new IOException("Failed to create ASEC"); throw new IOException("Failed to create session cid: " + stageCid); } return cid; } @Override Loading Loading @@ -1031,6 +974,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub { writeSessionsAsync(); } public void onSessionPrepared(PackageInstallerSession session) { // We prepared the destination to write into; we want to persist // this, but it's not critical enough to block for. writeSessionsAsync(); } public void onSessionSealed(PackageInstallerSession session) { // It's very important that we block until we've recorded the // session as being sealed, since we never want to allow mutation Loading Loading
core/java/android/content/pm/PackageInstaller.java +65 −7 Original line number Diff line number Diff line Loading @@ -285,6 +285,9 @@ public class PackageInstaller { * * @throws IOException if parameters were unsatisfiable, such as lack of * disk space or unavailable media. * @throws SecurityException when installation services are unavailable, * such as when called from a restricted user. * @throws IllegalArgumentException when {@link SessionParams} is invalid. * @return positive, non-zero unique ID that represents the created session. * This ID remains consistent across device reboots until the * session is finalized. IDs are not reused during a given boot. Loading @@ -303,6 +306,11 @@ public class PackageInstaller { /** * Open an existing session to actively perform work. To succeed, the caller * must be the owner of the install session. * * @throws IOException if parameters were unsatisfiable, such as lack of * disk space or unavailable media. * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public @NonNull Session openSession(int sessionId) throws IOException { try { Loading @@ -319,6 +327,9 @@ public class PackageInstaller { * Update the icon representing the app being installed in a specific * session. This should be roughly * {@link ActivityManager#getLauncherLargeIconSize()} in both dimensions. * * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public void updateSessionAppIcon(int sessionId, @Nullable Bitmap appIcon) { try { Loading @@ -331,6 +342,9 @@ public class PackageInstaller { /** * Update the label representing the app being installed in a specific * session. * * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public void updateSessionAppLabel(int sessionId, @Nullable CharSequence appLabel) { try { Loading @@ -341,6 +355,15 @@ public class PackageInstaller { } } /** * Completely abandon the given session, destroying all staged data and * rendering it invalid. Abandoned sessions will be reported to * {@link SessionCallback} listeners as failures. This is equivalent to * opening the session and calling {@link Session#abandon()}. * * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public void abandonSession(int sessionId) { try { mInstaller.abandonSession(sessionId); Loading @@ -350,7 +373,11 @@ public class PackageInstaller { } /** * Return details for a specific session. * Return details for a specific session. No special permissions are * required to retrieve these details. * * @return details for the requested session, or {@code null} if the session * does not exist. */ public @Nullable SessionInfo getSessionInfo(int sessionId) { try { Loading @@ -361,7 +388,7 @@ public class PackageInstaller { } /** * Return list of all active install sessions, regardless of the installer. * Return list of all known install sessions, regardless of the installer. */ public @NonNull List<SessionInfo> getAllSessions() { final ApplicationInfo info = mContext.getApplicationInfo(); Loading @@ -379,7 +406,7 @@ public class PackageInstaller { } /** * Return list of all install sessions owned by the calling app. * Return list of all known install sessions owned by the calling app. */ public @NonNull List<SessionInfo> getMySessions() { try { Loading Loading @@ -547,7 +574,8 @@ public class PackageInstaller { } /** * Register to watch for session lifecycle events. * Register to watch for session lifecycle events. No special permissions * are required to watch for these events. */ public void registerSessionCallback(@NonNull SessionCallback callback) { registerSessionCallback(callback, new Handler()); Loading @@ -560,7 +588,8 @@ public class PackageInstaller { } /** * Register to watch for session lifecycle events. * Register to watch for session lifecycle events. No special permissions * are required to watch for these events. * * @param handler to dispatch callback events through, otherwise uses * calling thread. Loading Loading @@ -593,7 +622,7 @@ public class PackageInstaller { } /** * Unregister an existing callback. * Unregister a previously registered callback. */ public void unregisterSessionCallback(@NonNull SessionCallback callback) { synchronized (mDelegates) { Loading Loading @@ -686,6 +715,12 @@ public class PackageInstaller { * start at the beginning of the file. * @param lengthBytes total size of the file being written, used to * preallocate the underlying disk space, or -1 if unknown. * The system may clear various caches as needed to allocate * this space. * @throws IOException if trouble opening the file for writing, such as * lack of disk space or unavailable media. * @throws SecurityException if called after the session has been * committed or abandoned. */ public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, long lengthBytes) throws IOException { Loading Loading @@ -719,6 +754,9 @@ public class PackageInstaller { * <p> * This returns all names which have been previously written through * {@link #openWrite(String, long, long)} as part of this session. * * @throws SecurityException if called after the session has been * committed or abandoned. */ public @NonNull String[] getNames() throws IOException { try { Loading @@ -738,6 +776,9 @@ public class PackageInstaller { * through {@link #openWrite(String, long, long)} as part of this * session. For example, this stream may be used to calculate a * {@link MessageDigest} of a written APK before committing. * * @throws SecurityException if called after the session has been * committed or abandoned. */ public @NonNull InputStream openRead(@NonNull String name) throws IOException { try { Loading @@ -759,6 +800,9 @@ public class PackageInstaller { * Once this method is called, no additional mutations may be performed * on the session. If the device reboots before the session has been * finalized, you may commit the session again. * * @throws SecurityException if streams opened through * {@link #openWrite(String, long, long)} are still open. */ public void commit(@NonNull IntentSender statusReceiver) { try { Loading @@ -783,7 +827,9 @@ public class PackageInstaller { /** * Completely abandon this session, destroying all staged data and * rendering it invalid. * rendering it invalid. Abandoned sessions will be reported to * {@link SessionCallback} listeners as failures. This is equivalent to * opening the session and calling {@link Session#abandon()}. */ public void abandon() { try { Loading Loading @@ -936,6 +982,18 @@ public class PackageInstaller { this.referrerUri = referrerUri; } /** {@hide} */ public void setInstallFlagsInternal() { installFlags |= PackageManager.INSTALL_INTERNAL; installFlags &= ~PackageManager.INSTALL_EXTERNAL; } /** {@hide} */ public void setInstallFlagsExternal() { installFlags |= PackageManager.INSTALL_EXTERNAL; installFlags &= ~PackageManager.INSTALL_INTERNAL; } /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); Loading
core/java/android/os/FileBridge.java +10 −8 Original line number Diff line number Diff line Loading @@ -75,6 +75,13 @@ public class FileBridge extends Thread { return mClosed; } public void forceClose() { IoUtils.closeQuietly(mTarget); IoUtils.closeQuietly(mServer); IoUtils.closeQuietly(mClient); mClosed = true; } public void setTargetFile(FileDescriptor target) { mTarget = target; } Loading @@ -89,7 +96,6 @@ public class FileBridge extends Thread { try { while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) { final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN); if (cmd == CMD_WRITE) { // Shuttle data into local file int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN); Loading Loading @@ -118,15 +124,10 @@ public class FileBridge extends Thread { } } } catch (ErrnoException e) { Log.wtf(TAG, "Failed during bridge", e); } catch (IOException e) { } catch (ErrnoException | IOException e) { Log.wtf(TAG, "Failed during bridge", e); } finally { IoUtils.closeQuietly(mTarget); IoUtils.closeQuietly(mServer); IoUtils.closeQuietly(mClient); mClosed = true; forceClose(); } } Loading @@ -151,6 +152,7 @@ public class FileBridge extends Thread { writeCommandAndBlock(CMD_CLOSE, "close()"); } finally { IoBridge.closeAndSignalBlockedThreads(mClient); IoUtils.closeQuietly(mClientPfd); } } Loading
core/java/com/android/internal/content/PackageHelper.java +4 −1 Original line number Diff line number Diff line Loading @@ -390,8 +390,11 @@ public class PackageHelper { if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) { final File target = new UserEnvironment(UserHandle.USER_OWNER) .getExternalStorageDirectory(); // External is only an option when size is known if (sizeBytes > 0) { fitsOnExternal = (sizeBytes <= storage.getStorageBytesUntilLow(target)); } } if (prefer == RECOMMEND_INSTALL_INTERNAL) { if (fitsOnInternal) { Loading
core/java/com/android/internal/util/XmlUtils.java +10 −0 Original line number Diff line number Diff line Loading @@ -1440,6 +1440,16 @@ public class XmlUtils { return Boolean.parseBoolean(value); } public static boolean readBooleanAttribute(XmlPullParser in, String name, boolean defaultValue) { final String value = in.getAttributeValue(null, name); if (value == null) { return defaultValue; } else { return Boolean.parseBoolean(value); } } public static void writeBooleanAttribute(XmlSerializer out, String name, boolean value) throws IOException { out.attribute(null, name, Boolean.toString(value)); Loading
services/core/java/com/android/server/pm/PackageInstallerService.java +47 −98 Original line number Diff line number Diff line Loading @@ -42,7 +42,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; Loading @@ -50,15 +49,11 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Environment; import android.os.Environment.UserEnvironment; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; Loading @@ -70,7 +65,6 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; Loading Loading @@ -126,6 +120,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static final String ATTR_CREATED_MILLIS = "createdMillis"; private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; private static final String ATTR_PREPARED = "prepared"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; Loading @@ -148,7 +143,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final PackageManagerService mPm; private final AppOpsManager mAppOps; private final StorageManager mStorage; private final File mStagingDir; private final HandlerThread mInstallThread; Loading Loading @@ -190,7 +184,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mContext = context; mPm = pm; mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mStorage = StorageManager.from(mContext); mStagingDir = stagingDir; Loading Loading @@ -266,7 +259,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { try { final int sessionId = allocateSessionIdLocked(); mLegacySessions.put(sessionId, true); return prepareInternalStageDir(sessionId); final File stageDir = buildInternalStageDir(sessionId); prepareInternalStageDir(stageDir); return stageDir; } catch (IllegalStateException e) { throw new IOException(e); } Loading Loading @@ -345,6 +340,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR); final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null; final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); final SessionParams params = new SessionParams( Loading @@ -362,7 +358,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, createdMillis, stageDir, stageCid, sealed); createdMillis, stageDir, stageCid, prepared, sealed); } private void writeSessionsLocked() { Loading Loading @@ -410,6 +406,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { if (session.stageCid != null) { writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.stageCid); } writeBooleanAttribute(out, ATTR_PREPARED, session.isPrepared()); writeBooleanAttribute(out, ATTR_SEALED, session.isSealed()); writeIntAttribute(out, ATTR_MODE, params.mode); Loading Loading @@ -483,14 +480,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } // TODO: treat INHERIT_EXISTING as install for user // Figure out where we're going to be staging session data final boolean stageInternal; if (params.mode == SessionParams.MODE_FULL_INSTALL) { // Brand new install, use best resolved location. This also verifies // that target has enough free space for the install. if (params.mode == SessionParams.MODE_FULL_INSTALL || params.mode == SessionParams.MODE_INHERIT_EXISTING) { // Resolve best location for install, based on combination of // requested install flags, delta size, and manifest settings. final long ident = Binder.clearCallingIdentity(); try { final int resolved = PackageHelper.resolveInstallLocation(mContext, Loading @@ -498,46 +491,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.installFlags); if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) { stageInternal = true; params.setInstallFlagsInternal(); } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { stageInternal = false; params.setInstallFlagsExternal(); } else { throw new IOException("No storage with enough free space; res=" + resolved); } } finally { Binder.restoreCallingIdentity(ident); } } else if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { // Inheriting existing install, so stay on the same storage medium. final ApplicationInfo existingApp = mPm.getApplicationInfo(params.appPackageName, 0, userId); if (existingApp == null) { throw new IllegalStateException( "Missing existing app " + params.appPackageName); } final long existingSize; try { final PackageLite existingPkg = PackageParser.parsePackageLite( new File(existingApp.getCodePath()), 0); existingSize = PackageHelper.calculateInstalledSize(existingPkg, false, params.abiOverride); } catch (PackageParserException e) { throw new IllegalStateException( "Failed to calculate size of " + params.appPackageName); } if ((existingApp.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0) { // Internal we can link existing install into place, so we only // need enough space for the new data. checkInternalStorage(params.sizeBytes); stageInternal = true; } else { // External we're going to copy existing install into our // container, so we need footprint of both. checkExternalStorage(params.sizeBytes + existingSize); stageInternal = false; } } else { throw new IllegalArgumentException("Invalid install mode: " + params.mode); } Loading @@ -563,15 +525,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // We're staging to exactly one location File stageDir = null; String stageCid = null; if (stageInternal) { stageDir = prepareInternalStageDir(sessionId); if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { stageDir = buildInternalStageDir(sessionId); } else { stageCid = prepareExternalStageCid(sessionId, params.sizeBytes); stageCid = buildExternalStageCid(sessionId); } session = new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, createdMillis, stageDir, stageCid, false); createdMillis, stageDir, stageCid, false, false); mSessions.put(sessionId, session); } Loading Loading @@ -615,32 +577,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } private void checkInternalStorage(long sizeBytes) throws IOException { if (sizeBytes <= 0) return; final File target = Environment.getDataDirectory(); final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); mPm.freeStorage(targetBytes); if (target.getUsableSpace() < targetBytes) { throw new IOException("Not enough internal space to write " + sizeBytes + " bytes"); } } private void checkExternalStorage(long sizeBytes) throws IOException { if (sizeBytes <= 0) return; final File target = new UserEnvironment(UserHandle.USER_OWNER) .getExternalStorageDirectory(); final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); if (target.getUsableSpace() < targetBytes) { throw new IOException("Not enough external space to write " + sizeBytes + " bytes"); @Override public IPackageInstallerSession openSession(int sessionId) { try { return openSessionInternal(sessionId); } catch (IOException e) { throw ExceptionUtils.wrap(e); } } @Override public IPackageInstallerSession openSession(int sessionId) { private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException { synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); if (session == null || !isCallingUidOwner(session)) { Loading @@ -665,40 +611,37 @@ public class PackageInstallerService extends IPackageInstaller.Stub { throw new IllegalStateException("Failed to allocate session ID"); } private File prepareInternalStageDir(int sessionId) throws IOException { final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp"); private File buildInternalStageDir(int sessionId) { return new File(mStagingDir, "vmdl" + sessionId + ".tmp"); } if (file.exists()) { throw new IOException("Session dir already exists: " + file); static void prepareInternalStageDir(File stageDir) throws IOException { if (stageDir.exists()) { throw new IOException("Session dir already exists: " + stageDir); } try { Os.mkdir(file.getAbsolutePath(), 0755); Os.chmod(file.getAbsolutePath(), 0755); Os.mkdir(stageDir.getAbsolutePath(), 0755); Os.chmod(stageDir.getAbsolutePath(), 0755); } catch (ErrnoException e) { // This purposefully throws if directory already exists throw new IOException("Failed to prepare session dir", e); throw new IOException("Failed to prepare session dir: " + stageDir, e); } if (!SELinux.restorecon(file)) { throw new IOException("Failed to restorecon session dir"); if (!SELinux.restorecon(stageDir)) { throw new IOException("Failed to restorecon session dir: " + stageDir); } return file; } private String prepareExternalStageCid(int sessionId, long sizeBytes) throws IOException { if (sizeBytes <= 0) { throw new IOException("Session must provide valid size for ASEC"); private String buildExternalStageCid(int sessionId) { return "smdl" + sessionId + ".tmp"; } final String cid = "smdl" + sessionId + ".tmp"; if (PackageHelper.createSdDir(sizeBytes, cid, PackageManagerService.getEncryptKey(), static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException { if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(), Process.SYSTEM_UID, true) == null) { throw new IOException("Failed to create ASEC"); throw new IOException("Failed to create session cid: " + stageCid); } return cid; } @Override Loading Loading @@ -1031,6 +974,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub { writeSessionsAsync(); } public void onSessionPrepared(PackageInstallerSession session) { // We prepared the destination to write into; we want to persist // this, but it's not critical enough to block for. writeSessionsAsync(); } public void onSessionSealed(PackageInstallerSession session) { // It's very important that we block until we've recorded the // session as being sealed, since we never want to allow mutation Loading