summaryrefslogtreecommitdiffstats
path: root/src/com/android/packageinstaller/InstallInstalling.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/packageinstaller/InstallInstalling.java')
-rwxr-xr-xsrc/com/android/packageinstaller/InstallInstalling.java409
1 files changed, 409 insertions, 0 deletions
diff --git a/src/com/android/packageinstaller/InstallInstalling.java b/src/com/android/packageinstaller/InstallInstalling.java
new file mode 100755
index 00000000..9f23e001
--- /dev/null
+++ b/src/com/android/packageinstaller/InstallInstalling.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller;
+
+import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.widget.Button;
+import android.widget.ProgressBar;
+
+import com.android.internal.content.PackageHelper;
+
+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
+ * installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}.
+ * <p>This has two phases: First send the data to the package manager, then wait until the package
+ * manager processed the result.</p>
+ */
+public class InstallInstalling extends Activity {
+ private static final String LOG_TAG = InstallInstalling.class.getSimpleName();
+
+ private static final String SESSION_ID = "com.android.packageinstaller.SESSION_ID";
+ private static final String INSTALL_ID = "com.android.packageinstaller.INSTALL_ID";
+
+ private static final String BROADCAST_ACTION =
+ "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
+
+ /** Listens to changed to the session and updates progress bar */
+ private PackageInstaller.SessionCallback mSessionCallback;
+
+ /** Task that sends the package to the package installer */
+ private InstallingAsyncTask mInstallingTask;
+
+ /** Id of the session to install the package */
+ private int mSessionId;
+
+ /** Id of the install event we wait for */
+ private int mInstallId;
+
+ /** URI of package to install */
+ private Uri mPackageURI;
+
+ /** The button that can cancel this dialog */
+ private Button mCancelButton;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.install_installing);
+
+ ApplicationInfo appInfo = getIntent()
+ .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
+ mPackageURI = getIntent().getData();
+
+ if ("package".equals(mPackageURI.getScheme())) {
+ try {
+ getPackageManager().installExistingPackage(appInfo.packageName);
+ launchSuccess();
+ } catch (PackageManager.NameNotFoundException e) {
+ launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
+ }
+ } else {
+ final File sourceFile = new File(mPackageURI.getPath());
+ PackageUtil.initSnippetForNewApp(this, PackageUtil.getAppSnippet(this, appInfo,
+ sourceFile), R.id.app_snippet);
+
+ if (savedInstanceState != null) {
+ mSessionId = savedInstanceState.getInt(SESSION_ID);
+ mInstallId = savedInstanceState.getInt(INSTALL_ID);
+
+ // Reregister for result; might instantly call back if result was delivered while
+ // activity was destroyed
+ try {
+ InstallEventReceiver.addObserver(this, mInstallId,
+ this::launchFinishBasedOnResult);
+ } catch (EventResultPersister.OutOfIdsException e) {
+ // Does not happen
+ }
+ } else {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
+ params.originatingUri = getIntent()
+ .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
+ params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
+ UID_UNKNOWN);
+
+ File file = new File(mPackageURI.getPath());
+ try {
+ PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
+ params.setAppPackageName(pkg.packageName);
+ params.setInstallLocation(pkg.installLocation);
+ params.setSize(
+ PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
+ } catch (PackageParser.PackageParserException e) {
+ Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
+ Log.e(LOG_TAG,
+ "Cannot calculate installed size " + file + ". Try only apk size.");
+ params.setSize(file.length());
+ } catch (IOException 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,
+ this::launchFinishBasedOnResult);
+ } catch (EventResultPersister.OutOfIdsException e) {
+ launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
+ }
+
+ try {
+ mSessionId = getPackageManager().getPackageInstaller().createSession(params);
+ } catch (IOException e) {
+ launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
+ }
+ }
+
+ mCancelButton = (Button) findViewById(R.id.cancel_button);
+
+ mCancelButton.setOnClickListener(view -> {
+ if (mInstallingTask != null) {
+ mInstallingTask.cancel(true);
+ }
+
+ if (mSessionId > 0) {
+ getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+ mSessionId = 0;
+ }
+
+ setResult(RESULT_CANCELED);
+ finish();
+ });
+
+ mSessionCallback = new InstallSessionCallback();
+ }
+ }
+
+ /**
+ * Launch the "success" version of the final package installer dialog
+ */
+ private void launchSuccess() {
+ Intent successIntent = new Intent(getIntent());
+ successIntent.setClass(this, InstallSuccess.class);
+ successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+
+ startActivity(successIntent);
+ finish();
+ }
+
+ /**
+ * Launch the "failure" version of the final package installer dialog
+ *
+ * @param legacyStatus The status as used internally in the package manager.
+ * @param statusMessage The status description.
+ */
+ private void launchFailure(int legacyStatus, String statusMessage) {
+ Intent failureIntent = new Intent(getIntent());
+ failureIntent.setClass(this, InstallFailed.class);
+ failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus);
+ failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
+
+ startActivity(failureIntent);
+ finish();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // This is the first onResume in a single life of the activity
+ if (mInstallingTask == null) {
+ PackageInstaller installer = getPackageManager().getPackageInstaller();
+ PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
+
+ if (sessionInfo != null && !sessionInfo.isActive()) {
+ mInstallingTask = new InstallingAsyncTask();
+ mInstallingTask.execute();
+ } else {
+ // we will receive a broadcast when the install is finished
+ mCancelButton.setEnabled(false);
+ setFinishOnTouchOutside(false);
+ }
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putInt(SESSION_ID, mSessionId);
+ outState.putInt(INSTALL_ID, mInstallId);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mCancelButton.isEnabled()) {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ getPackageManager().getPackageInstaller().unregisterSessionCallback(mSessionCallback);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mInstallingTask != null) {
+ mInstallingTask.cancel(true);
+ synchronized (mInstallingTask) {
+ while (!mInstallingTask.isDone) {
+ try {
+ mInstallingTask.wait();
+ } catch (InterruptedException e) {
+ Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel",
+ e);
+ }
+ }
+ }
+ }
+
+ InstallEventReceiver.removeObserver(this, mInstallId);
+
+ super.onDestroy();
+ }
+
+ /**
+ * Launch the appropriate finish activity (success or failed) for the installation result.
+ *
+ * @param statusCode The installation result.
+ * @param legacyStatus The installation as used internally in the package manager.
+ * @param statusMessage The detailed installation result.
+ */
+ private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
+ if (statusCode == PackageInstaller.STATUS_SUCCESS) {
+ launchSuccess();
+ } else {
+ launchFailure(legacyStatus, statusMessage);
+ }
+ }
+
+
+ private class InstallSessionCallback extends PackageInstaller.SessionCallback {
+ @Override
+ public void onCreated(int sessionId) {
+ // empty
+ }
+
+ @Override
+ public void onBadgingChanged(int sessionId) {
+ // empty
+ }
+
+ @Override
+ public void onActiveChanged(int sessionId, boolean active) {
+ // empty
+ }
+
+ @Override
+ public void onProgressChanged(int sessionId, float progress) {
+ if (sessionId == mSessionId) {
+ ProgressBar progressBar = (ProgressBar)findViewById(R.id.progress_bar);
+ progressBar.setMax(Integer.MAX_VALUE);
+ progressBar.setProgress((int) (Integer.MAX_VALUE * progress));
+ }
+ }
+
+ @Override
+ public void onFinished(int sessionId, boolean success) {
+ // empty, finish is handled by InstallResultReceiver
+ }
+ }
+
+ /**
+ * Send the package to the package installer and then register a event result observer that
+ * will call {@link #launchFinishBasedOnResult(int, int, String)}
+ */
+ private final class InstallingAsyncTask extends AsyncTask<Void, Void,
+ PackageInstaller.Session> {
+ volatile boolean isDone;
+
+ @Override
+ protected PackageInstaller.Session doInBackground(Void... params) {
+ PackageInstaller.Session session;
+ try {
+ session = getPackageManager().getPackageInstaller().openSession(mSessionId);
+ } catch (IOException e) {
+ return null;
+ }
+
+ session.setStagingProgress(0);
+
+ try {
+ File file = new File(mPackageURI.getPath());
+
+ try (InputStream in = new FileInputStream(file)) {
+ long sizeBytes = file.length();
+ try (OutputStream out = session
+ .openWrite("PackageInstaller", 0, sizeBytes)) {
+ byte[] buffer = new byte[4096];
+ 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) {
+ float fraction = ((float) numRead / (float) sizeBytes);
+ session.addProgress(fraction);
+ }
+ }
+ }
+ }
+
+ return session;
+ } catch (IOException | SecurityException e) {
+ Log.e(LOG_TAG, "Could not write package", e);
+
+ session.close();
+
+ return null;
+ } finally {
+ synchronized (this) {
+ isDone = true;
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(PackageInstaller.Session session) {
+ if (session != null) {
+ Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+ broadcastIntent.setPackage(
+ getPackageManager().getPermissionControllerPackageName());
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ InstallInstalling.this,
+ mInstallId,
+ broadcastIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ session.commit(pendingIntent.getIntentSender());
+ mCancelButton.setEnabled(false);
+ setFinishOnTouchOutside(false);
+ } else {
+ getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+
+ if (!isCancelled()) {
+ launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
+ }
+ }
+ }
+ }
+}