Home | History | Annotate | Download | only in packageinstaller
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  *  Licensed under the Apache License, Version 2.0 (the "License");
      5  *  you may not use this file except in compliance with the License.
      6  *  You may obtain a copy of the License at
      7  *
      8  *       http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  *  Unless required by applicable law or agreed to in writing, software
     11  *  distributed under the License is distributed on an "AS IS" BASIS,
     12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  *  See the License for the specific language governing permissions and
     14  *  limitations under the License.
     15  */
     16 
     17 package com.android.packageinstaller;
     18 
     19 import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN;
     20 
     21 import android.app.Activity;
     22 import android.app.PendingIntent;
     23 import android.content.Intent;
     24 import android.content.pm.ApplicationInfo;
     25 import android.content.pm.PackageInstaller;
     26 import android.content.pm.PackageManager;
     27 import android.content.pm.PackageParser;
     28 import android.net.Uri;
     29 import android.os.AsyncTask;
     30 import android.os.Bundle;
     31 import android.support.annotation.Nullable;
     32 import android.util.Log;
     33 import android.widget.Button;
     34 import android.widget.ProgressBar;
     35 
     36 import com.android.internal.content.PackageHelper;
     37 
     38 import java.io.File;
     39 import java.io.FileInputStream;
     40 import java.io.IOException;
     41 import java.io.InputStream;
     42 import java.io.OutputStream;
     43 
     44 /**
     45  * Send package to the package manager and handle results from package manager. Once the
     46  * installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}.
     47  * <p>This has two phases: First send the data to the package manager, then wait until the package
     48  * manager processed the result.</p>
     49  */
     50 public class InstallInstalling extends Activity {
     51     private static final String LOG_TAG = InstallInstalling.class.getSimpleName();
     52 
     53     private static final String SESSION_ID = "com.android.packageinstaller.SESSION_ID";
     54     private static final String INSTALL_ID = "com.android.packageinstaller.INSTALL_ID";
     55 
     56     private static final String BROADCAST_ACTION =
     57             "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
     58 
     59     /** Listens to changed to the session and updates progress bar */
     60     private PackageInstaller.SessionCallback mSessionCallback;
     61 
     62     /** Task that sends the package to the package installer */
     63     private InstallingAsyncTask mInstallingTask;
     64 
     65     /** Id of the session to install the package */
     66     private int mSessionId;
     67 
     68     /** Id of the install event we wait for */
     69     private int mInstallId;
     70 
     71     /** URI of package to install */
     72     private Uri mPackageURI;
     73 
     74     /** The button that can cancel this dialog */
     75     private Button mCancelButton;
     76 
     77     @Override
     78     protected void onCreate(@Nullable Bundle savedInstanceState) {
     79         super.onCreate(savedInstanceState);
     80 
     81         setContentView(R.layout.install_installing);
     82 
     83         ApplicationInfo appInfo = getIntent()
     84                 .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
     85         mPackageURI = getIntent().getData();
     86 
     87         if ("package".equals(mPackageURI.getScheme())) {
     88             try {
     89                 getPackageManager().installExistingPackage(appInfo.packageName);
     90                 launchSuccess();
     91             } catch (PackageManager.NameNotFoundException e) {
     92                 launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
     93             }
     94         } else {
     95             final File sourceFile = new File(mPackageURI.getPath());
     96             PackageUtil.initSnippetForNewApp(this, PackageUtil.getAppSnippet(this, appInfo,
     97                     sourceFile), R.id.app_snippet);
     98 
     99             if (savedInstanceState != null) {
    100                 mSessionId = savedInstanceState.getInt(SESSION_ID);
    101                 mInstallId = savedInstanceState.getInt(INSTALL_ID);
    102 
    103                 // Reregister for result; might instantly call back if result was delivered while
    104                 // activity was destroyed
    105                 try {
    106                     InstallEventReceiver.addObserver(this, mInstallId,
    107                             this::launchFinishBasedOnResult);
    108                 } catch (EventResultPersister.OutOfIdsException e) {
    109                     // Does not happen
    110                 }
    111             } else {
    112                 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
    113                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
    114                 params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
    115                 params.originatingUri = getIntent()
    116                         .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
    117                 params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
    118                         UID_UNKNOWN);
    119 
    120                 File file = new File(mPackageURI.getPath());
    121                 try {
    122                     PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
    123                     params.setAppPackageName(pkg.packageName);
    124                     params.setInstallLocation(pkg.installLocation);
    125                     params.setSize(
    126                             PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
    127                 } catch (PackageParser.PackageParserException e) {
    128                     Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
    129                     Log.e(LOG_TAG,
    130                             "Cannot calculate installed size " + file + ". Try only apk size.");
    131                     params.setSize(file.length());
    132                 } catch (IOException e) {
    133                     Log.e(LOG_TAG,
    134                             "Cannot calculate installed size " + file + ". Try only apk size.");
    135                     params.setSize(file.length());
    136                 }
    137 
    138                 try {
    139                     mInstallId = InstallEventReceiver
    140                             .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
    141                                     this::launchFinishBasedOnResult);
    142                 } catch (EventResultPersister.OutOfIdsException e) {
    143                     launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
    144                 }
    145 
    146                 try {
    147                     mSessionId = getPackageManager().getPackageInstaller().createSession(params);
    148                 } catch (IOException e) {
    149                     launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
    150                 }
    151             }
    152 
    153             mCancelButton = (Button) findViewById(R.id.cancel_button);
    154 
    155             mCancelButton.setOnClickListener(view -> {
    156                 if (mInstallingTask != null) {
    157                     mInstallingTask.cancel(true);
    158                 }
    159 
    160                 if (mSessionId > 0) {
    161                     getPackageManager().getPackageInstaller().abandonSession(mSessionId);
    162                     mSessionId = 0;
    163                 }
    164 
    165                 setResult(RESULT_CANCELED);
    166                 finish();
    167             });
    168 
    169             mSessionCallback = new InstallSessionCallback();
    170         }
    171     }
    172 
    173     /**
    174      * Launch the "success" version of the final package installer dialog
    175      */
    176     private void launchSuccess() {
    177         Intent successIntent = new Intent(getIntent());
    178         successIntent.setClass(this, InstallSuccess.class);
    179         successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    180 
    181         startActivity(successIntent);
    182         finish();
    183     }
    184 
    185     /**
    186      * Launch the "failure" version of the final package installer dialog
    187      *
    188      * @param legacyStatus  The status as used internally in the package manager.
    189      * @param statusMessage The status description.
    190      */
    191     private void launchFailure(int legacyStatus, String statusMessage) {
    192         Intent failureIntent = new Intent(getIntent());
    193         failureIntent.setClass(this, InstallFailed.class);
    194         failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    195         failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus);
    196         failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
    197 
    198         startActivity(failureIntent);
    199         finish();
    200     }
    201 
    202     @Override
    203     protected void onStart() {
    204         super.onStart();
    205 
    206         getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback);
    207     }
    208 
    209     @Override
    210     protected void onResume() {
    211         super.onResume();
    212 
    213         // This is the first onResume in a single life of the activity
    214         if (mInstallingTask == null) {
    215             PackageInstaller installer = getPackageManager().getPackageInstaller();
    216             PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
    217 
    218             if (sessionInfo != null && !sessionInfo.isActive()) {
    219                 mInstallingTask = new InstallingAsyncTask();
    220                 mInstallingTask.execute();
    221             } else {
    222                 // we will receive a broadcast when the install is finished
    223                 mCancelButton.setEnabled(false);
    224                 setFinishOnTouchOutside(false);
    225             }
    226         }
    227     }
    228 
    229     @Override
    230     protected void onSaveInstanceState(Bundle outState) {
    231         super.onSaveInstanceState(outState);
    232 
    233         outState.putInt(SESSION_ID, mSessionId);
    234         outState.putInt(INSTALL_ID, mInstallId);
    235     }
    236 
    237     @Override
    238     public void onBackPressed() {
    239         if (mCancelButton.isEnabled()) {
    240             super.onBackPressed();
    241         }
    242     }
    243 
    244     @Override
    245     protected void onStop() {
    246         super.onStop();
    247 
    248         getPackageManager().getPackageInstaller().unregisterSessionCallback(mSessionCallback);
    249     }
    250 
    251     @Override
    252     protected void onDestroy() {
    253         if (mInstallingTask != null) {
    254             mInstallingTask.cancel(true);
    255             synchronized (mInstallingTask) {
    256                 while (!mInstallingTask.isDone) {
    257                     try {
    258                         mInstallingTask.wait();
    259                     } catch (InterruptedException e) {
    260                         Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel",
    261                                 e);
    262                     }
    263                 }
    264             }
    265         }
    266 
    267         InstallEventReceiver.removeObserver(this, mInstallId);
    268 
    269         super.onDestroy();
    270     }
    271 
    272     /**
    273      * Launch the appropriate finish activity (success or failed) for the installation result.
    274      *
    275      * @param statusCode    The installation result.
    276      * @param legacyStatus  The installation as used internally in the package manager.
    277      * @param statusMessage The detailed installation result.
    278      */
    279     private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
    280         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
    281             launchSuccess();
    282         } else {
    283             launchFailure(legacyStatus, statusMessage);
    284         }
    285     }
    286 
    287 
    288     private class InstallSessionCallback extends PackageInstaller.SessionCallback {
    289         @Override
    290         public void onCreated(int sessionId) {
    291             // empty
    292         }
    293 
    294         @Override
    295         public void onBadgingChanged(int sessionId) {
    296             // empty
    297         }
    298 
    299         @Override
    300         public void onActiveChanged(int sessionId, boolean active) {
    301             // empty
    302         }
    303 
    304         @Override
    305         public void onProgressChanged(int sessionId, float progress) {
    306             if (sessionId == mSessionId) {
    307                 ProgressBar progressBar = (ProgressBar)findViewById(R.id.progress_bar);
    308                 progressBar.setMax(Integer.MAX_VALUE);
    309                 progressBar.setProgress((int) (Integer.MAX_VALUE * progress));
    310             }
    311         }
    312 
    313         @Override
    314         public void onFinished(int sessionId, boolean success) {
    315             // empty, finish is handled by InstallResultReceiver
    316         }
    317     }
    318 
    319     /**
    320      * Send the package to the package installer and then register a event result observer that
    321      * will call {@link #launchFinishBasedOnResult(int, int, String)}
    322      */
    323     private final class InstallingAsyncTask extends AsyncTask<Void, Void,
    324             PackageInstaller.Session> {
    325         volatile boolean isDone;
    326 
    327         @Override
    328         protected PackageInstaller.Session doInBackground(Void... params) {
    329             PackageInstaller.Session session;
    330             try {
    331                 session = getPackageManager().getPackageInstaller().openSession(mSessionId);
    332             } catch (IOException e) {
    333                 return null;
    334             }
    335 
    336             session.setStagingProgress(0);
    337 
    338             try {
    339                 File file = new File(mPackageURI.getPath());
    340 
    341                 try (InputStream in = new FileInputStream(file)) {
    342                     long sizeBytes = file.length();
    343                     try (OutputStream out = session
    344                             .openWrite("PackageInstaller", 0, sizeBytes)) {
    345                         byte[] buffer = new byte[4096];
    346                         while (true) {
    347                             int numRead = in.read(buffer);
    348 
    349                             if (numRead == -1) {
    350                                 session.fsync(out);
    351                                 break;
    352                             }
    353 
    354                             if (isCancelled()) {
    355                                 session.close();
    356                                 break;
    357                             }
    358 
    359                             out.write(buffer, 0, numRead);
    360                             if (sizeBytes > 0) {
    361                                 float fraction = ((float) numRead / (float) sizeBytes);
    362                                 session.addProgress(fraction);
    363                             }
    364                         }
    365                     }
    366                 }
    367 
    368                 return session;
    369             } catch (IOException | SecurityException e) {
    370                 Log.e(LOG_TAG, "Could not write package", e);
    371 
    372                 session.close();
    373 
    374                 return null;
    375             } finally {
    376                 synchronized (this) {
    377                     isDone = true;
    378                     notifyAll();
    379                 }
    380             }
    381         }
    382 
    383         @Override
    384         protected void onPostExecute(PackageInstaller.Session session) {
    385             if (session != null) {
    386                 Intent broadcastIntent = new Intent(BROADCAST_ACTION);
    387                 broadcastIntent.setPackage(
    388                         getPackageManager().getPermissionControllerPackageName());
    389                 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
    390 
    391                 PendingIntent pendingIntent = PendingIntent.getBroadcast(
    392                         InstallInstalling.this,
    393                         mInstallId,
    394                         broadcastIntent,
    395                         PendingIntent.FLAG_UPDATE_CURRENT);
    396 
    397                 session.commit(pendingIntent.getIntentSender());
    398                 mCancelButton.setEnabled(false);
    399                 setFinishOnTouchOutside(false);
    400             } else {
    401                 getPackageManager().getPackageInstaller().abandonSession(mSessionId);
    402 
    403                 if (!isCancelled()) {
    404                     launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
    405                 }
    406             }
    407         }
    408     }
    409 }
    410