Merge "Improve performance of file copy in PackageInstaller." into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
168cbb27eb
@@ -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<PackageLite> 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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<PackageLite> 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<PackageLite> parseMonolithicPackageLite(ParseInput input,
|
||||
FileDescriptor packageFd, String debugPathName, int flags) {
|
||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
|
||||
try {
|
||||
final ParseResult<ApkLite> 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.
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Uri, Void, Boolean> {
|
||||
@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<Void, Integer, PackageInstaller.SessionInfo> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<File> 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user