diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index de66f050c007d..56f6f8206d305 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2072,6 +2072,25 @@ public class PackageInstaller { return new InstallInfo(result); } + /** + * Parse a single APK file passed as an FD to get install relevant information about + * the package wrapped in {@link InstallInfo}. + * @throws PackageParsingException if the package source file(s) provided is(are) not valid, + * or the parser isn't able to parse the supplied source(s). + * @hide + */ + @NonNull + public InstallInfo readInstallInfo(@NonNull ParcelFileDescriptor pfd, + @Nullable String debugPathName, int flags) throws PackageParsingException { + final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + final ParseResult result = ApkLiteParseUtils.parseMonolithicPackageLite(input, + pfd.getFileDescriptor(), debugPathName, flags); + if (result.isError()) { + throw new PackageParsingException(result.getErrorCode(), result.getErrorMessage()); + } + return new InstallInfo(result); + } + // (b/239722738) This class serves as a bridge between the PackageLite class, which // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java) // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or @@ -2125,6 +2144,21 @@ public class PackageInstaller { public long calculateInstalledSize(@NonNull SessionParams params) throws IOException { return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride); } + + /** + * @param params {@link SessionParams} of the installation + * @param pfd of an APK opened for read + * @return Total disk space occupied by an application after installation. + * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files, + * and all relevant native code. + * @throws IOException when size of native binaries cannot be calculated. + * @hide + */ + public long calculateInstalledSize(@NonNull SessionParams params, + @NonNull ParcelFileDescriptor pfd) throws IOException { + return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride, + pfd.getFileDescriptor()); + } } /** diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index a4339d41dfd2e..d209b35ac810b 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -109,7 +109,7 @@ public class ApkLiteParseUtils { } /** - * Parse lightweight details about a single APK files. + * Parse lightweight details about a single APK file. */ public static ParseResult parseMonolithicPackageLite(ParseInput input, File packageFile, int flags) { @@ -134,6 +134,33 @@ public class ApkLiteParseUtils { } } + /** + * Parse lightweight details about a single APK file passed as an FD. + */ + public static ParseResult parseMonolithicPackageLite(ParseInput input, + FileDescriptor packageFd, String debugPathName, int flags) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); + try { + final ParseResult result = parseApkLite(input, packageFd, debugPathName, + flags); + if (result.isError()) { + return input.error(result); + } + + final ApkLite baseApk = result.getResult(); + final String packagePath = debugPathName; + return input.success( + new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */, + null /* isFeatureSplits */, null /* usesSplitNames */, + null /* configForSplit */, null /* splitApkPaths */, + null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(), + null /* requiredSplitTypes */, null, /* splitTypes */ + baseApk.isAllowUpdateOwnership())); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + } + /** * Parse lightweight details about a directory of APKs. * diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java index 4c5875b1f2d9f..7b17cbdd3a1ec 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java @@ -16,14 +16,14 @@ package com.android.packageinstaller; +import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID; + import android.app.Activity; import android.content.Intent; import android.os.Bundle; import androidx.annotation.Nullable; -import java.io.File; - /** * Trampoline activity. Calls PackageInstallerActivity and deletes staged install file onResult. */ @@ -52,8 +52,13 @@ public class DeleteStagedFileOnResult extends Activity { super.onDestroy(); if (isFinishing()) { - File sourceFile = new File(getIntent().getData().getPath()); - new Thread(sourceFile::delete).start(); + // While we expect PIA/InstallStaging to abandon/commit the session, still there + // might be cases when the session becomes orphan. + int sessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0); + try { + getPackageManager().getPackageInstaller().abandonSession(sessionId); + } catch (SecurityException ignored) { + } } } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java index c6217ece800da..7bea339712598 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java @@ -16,17 +16,17 @@ package com.android.packageinstaller; +import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID; + import android.app.PendingIntent; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; -import android.content.pm.PackageInstaller.InstallInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Process; import android.util.Log; import android.view.View; import android.widget.Button; @@ -34,10 +34,7 @@ import android.widget.Button; import androidx.annotation.Nullable; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; /** * Send package to the package manager and handle results from package manager. Once the @@ -77,7 +74,7 @@ public class InstallInstalling extends AlertActivity { .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); mPackageURI = getIntent().getData(); - if ("package".equals(mPackageURI.getScheme())) { + if (PackageInstallerActivity.SCHEME_PACKAGE.equals(mPackageURI.getScheme())) { try { getPackageManager().installExistingPackage(appInfo.packageName); launchSuccess(); @@ -86,6 +83,8 @@ public class InstallInstalling extends AlertActivity { PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); } } else { + // ContentResolver.SCHEME_FILE + // STAGED_SESSION_ID extra contains an ID of a previously staged install session. final File sourceFile = new File(mPackageURI.getPath()); PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile); @@ -122,41 +121,6 @@ public class InstallInstalling extends AlertActivity { // Does not happen } } else { - PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( - PackageInstaller.SessionParams.MODE_FULL_INSTALL); - final Uri referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER); - params.setPackageSource( - referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE - : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE); - params.setInstallAsInstantApp(false); - params.setReferrerUri(referrerUri); - params.setOriginatingUri(getIntent() - .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI)); - params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, - Process.INVALID_UID)); - params.setInstallerPackageName(getIntent().getStringExtra( - Intent.EXTRA_INSTALLER_PACKAGE_NAME)); - params.setInstallReason(PackageManager.INSTALL_REASON_USER); - - File file = new File(mPackageURI.getPath()); - try { - final InstallInfo result = getPackageManager().getPackageInstaller() - .readInstallInfo(file, 0); - params.setAppPackageName(result.getPackageName()); - params.setInstallLocation(result.getInstallLocation()); - try { - params.setSize(result.calculateInstalledSize(params)); - } catch (IOException e) { - e.printStackTrace(); - params.setSize(file.length()); - } - } catch (PackageInstaller.PackageParsingException e) { - - Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.", e); - Log.e(LOG_TAG, - "Cannot calculate installed size " + file + ". Try only apk size."); - params.setSize(file.length()); - } try { mInstallId = InstallEventReceiver .addObserver(this, EventResultPersister.GENERATE_NEW_ID, @@ -166,9 +130,14 @@ public class InstallInstalling extends AlertActivity { PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); } - try { - mSessionId = getPackageManager().getPackageInstaller().createSession(params); - } catch (IOException e) { + mSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0); + // Try to open session previously staged in InstallStaging. + try (PackageInstaller.Session ignored = + getPackageManager().getPackageInstaller().openSession( + mSessionId)) { + Log.d(LOG_TAG, "Staged session is valid, proceeding with the install"); + } catch (IOException | SecurityException e) { + Log.e(LOG_TAG, "Invalid session id passed", e); launchFailure(PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); } @@ -293,57 +262,9 @@ public class InstallInstalling extends AlertActivity { @Override protected PackageInstaller.Session doInBackground(Void... params) { - PackageInstaller.Session session; try { - session = getPackageManager().getPackageInstaller().openSession(mSessionId); + return getPackageManager().getPackageInstaller().openSession(mSessionId); } catch (IOException e) { - synchronized (this) { - isDone = true; - notifyAll(); - } - return null; - } - - session.setStagingProgress(0); - - try { - File file = new File(mPackageURI.getPath()); - - try (InputStream in = new FileInputStream(file)) { - long sizeBytes = file.length(); - long totalRead = 0; - try (OutputStream out = session - .openWrite("PackageInstaller", 0, sizeBytes)) { - byte[] buffer = new byte[1024 * 1024]; - while (true) { - int numRead = in.read(buffer); - - if (numRead == -1) { - session.fsync(out); - break; - } - - if (isCancelled()) { - session.close(); - break; - } - - out.write(buffer, 0, numRead); - if (sizeBytes > 0) { - totalRead += numRead; - float fraction = ((float) totalRead / (float) sizeBytes); - session.setStagingProgress(fraction); - } - } - } - } - - return session; - } catch (IOException | SecurityException e) { - Log.e(LOG_TAG, "Could not write package", e); - - session.close(); - return null; } finally { synchronized (this) { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java index 68de2f67bd94f..097e47f58db12 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java @@ -16,6 +16,10 @@ package com.android.packageinstaller; +import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH; + +import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID; + import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -23,40 +27,49 @@ import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; +import android.content.res.AssetFileDescriptor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.Process; import android.util.Log; import android.view.View; +import android.widget.ProgressBar; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** - * If a package gets installed from an content URI this step loads the package and turns it into - * and installation from a file. Then it re-starts the installation as usual. + * If a package gets installed from a content URI this step stages the installation session + * reading bytes from the URI. */ public class InstallStaging extends AlertActivity { private static final String LOG_TAG = InstallStaging.class.getSimpleName(); - private static final String STAGED_FILE = "STAGED_FILE"; + private static final String STAGED_SESSION_ID = "STAGED_SESSION_ID"; + + private @Nullable PackageInstaller mInstaller; /** Currently running task that loads the file from the content URI into a file */ private @Nullable StagingAsyncTask mStagingTask; - /** The file the package is in */ - private @Nullable File mStagedFile; + /** The session the package is in */ + private int mStagedSessionId; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mInstaller = getPackageManager().getPackageInstaller(); + setFinishOnTouchOutside(true); mAlert.setIcon(R.drawable.ic_file_download); mAlert.setTitle(getString(R.string.app_name_unknown)); @@ -66,6 +79,9 @@ public class InstallStaging extends AlertActivity { if (mStagingTask != null) { mStagingTask.cancel(true); } + + cleanupStagingSession(); + setResult(RESULT_CANCELED); finish(); }, null); @@ -73,11 +89,7 @@ public class InstallStaging extends AlertActivity { requireViewById(R.id.staging).setVisibility(View.VISIBLE); if (savedInstanceState != null) { - mStagedFile = new File(savedInstanceState.getString(STAGED_FILE)); - - if (!mStagedFile.exists()) { - mStagedFile = null; - } + mStagedSessionId = savedInstanceState.getInt(STAGED_SESSION_ID, 0); } } @@ -85,21 +97,41 @@ public class InstallStaging extends AlertActivity { protected void onResume() { super.onResume(); - // This is the first onResume in a single life of the activity + // This is the first onResume in a single life of the activity. if (mStagingTask == null) { - // File does not exist, or became invalid - if (mStagedFile == null) { - // Create file delayed to be able to show error + if (mStagedSessionId > 0) { + final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo( + mStagedSessionId); + if (info == null || !info.isActive() || info.getResolvedBaseApkPath() == null) { + Log.w(LOG_TAG, "Session " + mStagedSessionId + " in funky state; ignoring"); + if (info != null) { + cleanupStagingSession(); + } + mStagedSessionId = 0; + } + } + + // Session does not exist, or became invalid. + if (mStagedSessionId <= 0) { + // Create session here to be able to show error. + final Uri packageUri = getIntent().getData(); + final AssetFileDescriptor afd = openAssetFileDescriptor(packageUri); try { - mStagedFile = TemporaryFileManager.getStagedFile(this); + ParcelFileDescriptor pfd = afd != null ? afd.getParcelFileDescriptor() : null; + PackageInstaller.SessionParams params = createSessionParams( + mInstaller, getIntent(), pfd, packageUri.toString()); + mStagedSessionId = mInstaller.createSession(params); } catch (IOException e) { + Log.w(LOG_TAG, "Failed to create a staging session", e); showError(); return; + } finally { + PackageUtil.safeClose(afd); } } mStagingTask = new StagingAsyncTask(); - mStagingTask.execute(getIntent().getData()); + mStagingTask.execute(); } } @@ -107,7 +139,7 @@ public class InstallStaging extends AlertActivity { protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putString(STAGED_FILE, mStagedFile.getPath()); + outState.putInt(STAGED_SESSION_ID, mStagedSessionId); } @Override @@ -119,6 +151,65 @@ public class InstallStaging extends AlertActivity { super.onDestroy(); } + private AssetFileDescriptor openAssetFileDescriptor(Uri uri) { + try { + return getContentResolver().openAssetFileDescriptor(uri, "r"); + } catch (Exception e) { + Log.w(LOG_TAG, "Failed to open asset file descriptor", e); + return null; + } + } + + private static PackageInstaller.SessionParams createSessionParams( + @NonNull PackageInstaller installer, @NonNull Intent intent, + @Nullable ParcelFileDescriptor pfd, @NonNull String debugPathName) { + PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL); + final Uri referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER); + params.setPackageSource( + referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE + : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE); + params.setInstallAsInstantApp(false); + params.setReferrerUri(referrerUri); + params.setOriginatingUri(intent + .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI)); + params.setOriginatingUid(intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, + Process.INVALID_UID)); + params.setInstallerPackageName(intent.getStringExtra( + Intent.EXTRA_INSTALLER_PACKAGE_NAME)); + params.setInstallReason(PackageManager.INSTALL_REASON_USER); + + if (pfd != null) { + try { + final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd, + debugPathName, 0); + params.setAppPackageName(result.getPackageName()); + params.setInstallLocation(result.getInstallLocation()); + params.setSize(result.calculateInstalledSize(params, pfd)); + } catch (PackageInstaller.PackageParsingException | IOException e) { + Log.e(LOG_TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.", e); + Log.e(LOG_TAG, + "Cannot calculate installed size " + debugPathName + + ". Try only apk size."); + params.setSize(pfd.getStatSize()); + } + } else { + Log.e(LOG_TAG, "Cannot parse package " + debugPathName + ". Assuming defaults."); + } + return params; + } + + private void cleanupStagingSession() { + if (mStagedSessionId > 0) { + try { + mInstaller.abandonSession(mStagedSessionId); + } catch (SecurityException ignored) { + + } + mStagedSessionId = 0; + } + } + /** * Show an error message and set result as error. */ @@ -165,58 +256,109 @@ public class InstallStaging extends AlertActivity { } } - private final class StagingAsyncTask extends AsyncTask { - @Override - protected Boolean doInBackground(Uri... params) { - if (params == null || params.length <= 0) { - return false; - } - Uri packageUri = params[0]; - try (InputStream in = getContentResolver().openInputStream(packageUri)) { - // Despite the comments in ContentResolver#openInputStream the returned stream can - // be null. - if (in == null) { - return false; - } + private final class StagingAsyncTask extends + AsyncTask { + private ProgressBar mProgressBar = null; - try (OutputStream out = new FileOutputStream(mStagedFile)) { - byte[] buffer = new byte[1024 * 1024]; - int bytesRead; - while ((bytesRead = in.read(buffer)) >= 0) { - // Be nice and respond to a cancellation - if (isCancelled()) { - return false; - } - out.write(buffer, 0, bytesRead); - } - } - } catch (IOException | SecurityException | IllegalStateException - | IllegalArgumentException e) { - Log.w(LOG_TAG, "Error staging apk from content URI", e); - return false; + private long getContentSizeBytes() { + try (AssetFileDescriptor afd = openAssetFileDescriptor(getIntent().getData())) { + return afd != null ? afd.getLength() : UNKNOWN_LENGTH; + } catch (IOException ignored) { + return UNKNOWN_LENGTH; } - return true; } @Override - protected void onPostExecute(Boolean success) { - if (success) { - // Now start the installation again from a file - Intent installIntent = new Intent(getIntent()); - installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class); - installIntent.setData(Uri.fromFile(mStagedFile)); + protected void onPreExecute() { + final long sizeBytes = getContentSizeBytes(); - if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { - installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + mProgressBar = sizeBytes > 0 ? requireViewById(R.id.progress_indeterminate) : null; + if (mProgressBar != null) { + mProgressBar.setProgress(0); + mProgressBar.setMax(100); + mProgressBar.setIndeterminate(false); + } + } + + @Override + protected PackageInstaller.SessionInfo doInBackground(Void... params) { + Uri packageUri = getIntent().getData(); + try (PackageInstaller.Session session = mInstaller.openSession(mStagedSessionId); + InputStream in = getContentResolver().openInputStream(packageUri)) { + session.setStagingProgress(0); + + if (in == null) { + return null; } - installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(installIntent); + long sizeBytes = getContentSizeBytes(); - InstallStaging.this.finish(); - } else { - showError(); + long totalRead = 0; + try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) { + byte[] buffer = new byte[1024 * 1024]; + while (true) { + int numRead = in.read(buffer); + + if (numRead == -1) { + session.fsync(out); + break; + } + + if (isCancelled()) { + break; + } + + out.write(buffer, 0, numRead); + if (sizeBytes > 0) { + totalRead += numRead; + float fraction = ((float) totalRead / (float) sizeBytes); + session.setStagingProgress(fraction); + publishProgress((int) (fraction * 100.0)); + } + } + } + + return mInstaller.getSessionInfo(mStagedSessionId); + } catch (IOException | SecurityException | IllegalStateException + | IllegalArgumentException e) { + Log.w(LOG_TAG, "Error staging apk from content URI", e); + return null; } } + + @Override + protected void onProgressUpdate(Integer... progress) { + if (mProgressBar != null && progress != null && progress.length > 0) { + mProgressBar.setProgress(progress[0], true); + } + } + + @Override + protected void onPostExecute(PackageInstaller.SessionInfo sessionInfo) { + if (sessionInfo == null || !sessionInfo.isActive() + || sessionInfo.getResolvedBaseApkPath() == null) { + Log.w(LOG_TAG, "Session info is invalid: " + sessionInfo); + cleanupStagingSession(); + showError(); + return; + } + + // Pass the staged session to the installer. + Intent installIntent = new Intent(getIntent()); + installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class); + installIntent.setData(Uri.fromFile(new File(sessionInfo.getResolvedBaseApkPath()))); + + installIntent.putExtra(EXTRA_STAGED_SESSION_ID, mStagedSessionId); + + if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { + installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + } + + installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + + startActivity(installIntent); + + InstallStaging.this.finish(); + } } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index ac32020e842a1..16850296e0fc5 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -148,10 +148,11 @@ public class InstallStart extends Activity { // [IMPORTANT] This path is deprecated, but should still work. Only necessary // features should be added. - // Copy file to prevent it from being changed underneath this process + // Stage a session with this file to prevent it from being changed underneath + // this process. nextActivity.setClass(this, InstallStaging.class); - } else if (packageUri != null && packageUri.getScheme().equals( - PackageInstallerActivity.SCHEME_PACKAGE)) { + } else if (packageUri != null && PackageInstallerActivity.SCHEME_PACKAGE.equals( + packageUri.getScheme())) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { Intent result = new Intent(); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index 3ba2acb113d35..7e294eee024fb 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -82,6 +82,7 @@ public class PackageInstallerActivity extends AlertActivity { static final String EXTRA_CALLING_PACKAGE = "EXTRA_CALLING_PACKAGE"; static final String EXTRA_CALLING_ATTRIBUTION_TAG = "EXTRA_CALLING_ATTRIBUTION_TAG"; static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO"; + static final String EXTRA_STAGED_SESSION_ID = "EXTRA_STAGED_SESSION_ID"; private static final String ALLOW_UNKNOWN_SOURCES_KEY = PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY"; @@ -403,6 +404,10 @@ public class PackageInstallerActivity extends AlertActivity { mReferrerURI = null; mPendingUserActionReason = info.getPendingUserActionReason(); } else { + // Two possible callers: + // 1. InstallStart with "SCHEME_PACKAGE". + // 2. InstallStaging with "SCHEME_FILE" and EXTRA_STAGED_SESSION_ID with staged + // session id. mSessionId = -1; packageSource = intent.getData(); mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); @@ -721,14 +726,16 @@ public class PackageInstallerActivity extends AlertActivity { } private void startInstall() { + String installerPackageName = getIntent().getStringExtra( + Intent.EXTRA_INSTALLER_PACKAGE_NAME); + int stagedSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0); + // Start subactivity to actually install the application Intent newIntent = new Intent(); newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo); newIntent.setData(mPackageURI); newIntent.setClass(this, InstallInstalling.class); - String installerPackageName = getIntent().getStringExtra( - Intent.EXTRA_INSTALLER_PACKAGE_NAME); if (mOriginatingURI != null) { newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI); } @@ -745,6 +752,9 @@ public class PackageInstallerActivity extends AlertActivity { if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); } + if (stagedSessionId > 0) { + newIntent.putExtra(EXTRA_STAGED_SESSION_ID, stagedSessionId); + } newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI); startActivity(newIntent); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 698159f24bb79..0270591acceb4 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -33,7 +33,9 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.io.Closeable; import java.io.File; +import java.io.IOException; /** * This is a utility class for defining some utility methods and constants @@ -190,4 +192,19 @@ public class PackageUtil { } return targetSdkVersion; } + + + /** + * Quietly close a closeable resource (e.g. a stream or file). The input may already + * be closed and it may even be null. + */ + static void safeClose(Closeable resource) { + if (resource != null) { + try { + resource.close(); + } catch (IOException ioe) { + // Catch and discard the error + } + } + } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 29c5adaea8440..97e7f6f417030 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1148,12 +1148,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.userId = userId; info.installerPackageName = mInstallSource.mInstallerPackageName; info.installerAttributionTag = mInstallSource.mInstallerAttributionTag; + info.resolvedBaseCodePath = null; if (mContext.checkCallingOrSelfPermission( Manifest.permission.READ_INSTALLED_SESSION_PATHS) - == PackageManager.PERMISSION_GRANTED && mResolvedBaseFile != null) { - info.resolvedBaseCodePath = mResolvedBaseFile.getAbsolutePath(); - } else { - info.resolvedBaseCodePath = null; + == PackageManager.PERMISSION_GRANTED) { + File file = mResolvedBaseFile; + if (file == null) { + // Try to guess mResolvedBaseFile file. + final List addedFiles = getAddedApksLocked(); + if (addedFiles.size() > 0) { + file = addedFiles.get(0); + } + } + if (file != null) { + info.resolvedBaseCodePath = file.getAbsolutePath(); + } } info.progress = progress; info.sealed = mSealed; @@ -1355,9 +1364,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private String[] getStageDirContentsLocked() { + if (stageDir == null) { + return EmptyArray.STRING; + } String[] result = stageDir.list(); if (result == null) { - result = EmptyArray.STRING; + return EmptyArray.STRING; } return result; }