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 android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.app.DialogFragment;
     23 import android.content.Context;
     24 import android.content.DialogInterface;
     25 import android.content.Intent;
     26 import android.content.pm.PackageManager;
     27 import android.net.Uri;
     28 import android.os.AsyncTask;
     29 import android.os.Bundle;
     30 import android.support.annotation.Nullable;
     31 import android.util.Log;
     32 
     33 import java.io.File;
     34 import java.io.FileOutputStream;
     35 import java.io.IOException;
     36 import java.io.InputStream;
     37 import java.io.OutputStream;
     38 
     39 /**
     40  * If a package gets installed from an content URI this step loads the package and turns it into
     41  * and installation from a file. Then it re-starts the installation as usual.
     42  */
     43 public class InstallStaging extends Activity {
     44     private static final String LOG_TAG = InstallStaging.class.getSimpleName();
     45 
     46     private static final String STAGED_FILE = "STAGED_FILE";
     47 
     48     /** Currently running task that loads the file from the content URI into a file */
     49     private @Nullable StagingAsyncTask mStagingTask;
     50 
     51     /** The file the package is in */
     52     private @Nullable File mStagedFile;
     53 
     54     @Override
     55     protected void onCreate(@Nullable Bundle savedInstanceState) {
     56         super.onCreate(savedInstanceState);
     57 
     58         setContentView(R.layout.install_staging);
     59 
     60         if (savedInstanceState != null) {
     61             mStagedFile = new File(savedInstanceState.getString(STAGED_FILE));
     62 
     63             if (!mStagedFile.exists()) {
     64                 mStagedFile = null;
     65             }
     66         }
     67 
     68         findViewById(R.id.cancel_button).setOnClickListener(view -> {
     69             if (mStagingTask != null) {
     70                 mStagingTask.cancel(true);
     71             }
     72             setResult(RESULT_CANCELED);
     73             finish();
     74         });
     75     }
     76 
     77     @Override
     78     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     79         setResult(resultCode, data);
     80 
     81         if (mStagedFile != null) {
     82             mStagedFile.delete();
     83         }
     84 
     85         // This is executed before onResume but after the mStagingTask completed, hence no need
     86         // to deal with the task.
     87         finish();
     88     }
     89 
     90     @Override
     91     protected void onResume() {
     92         super.onResume();
     93 
     94         // This is the first onResume in a single life of the activity
     95         if (mStagingTask == null) {
     96             // File does not exist, or became invalid
     97             if (mStagedFile == null) {
     98                 // Create file delayed to be able to show error
     99                 try {
    100                     mStagedFile = TemporaryFileManager.getStagedFile(this);
    101                 } catch (IOException e) {
    102                     showError();
    103                     return;
    104                 }
    105             }
    106 
    107             mStagingTask = new StagingAsyncTask();
    108             mStagingTask.execute(getIntent().getData());
    109         }
    110     }
    111 
    112     @Override
    113     protected void onSaveInstanceState(Bundle outState) {
    114         super.onSaveInstanceState(outState);
    115 
    116         outState.putString(STAGED_FILE, mStagedFile.getPath());
    117     }
    118 
    119     @Override
    120     protected void onDestroy() {
    121         if (mStagingTask != null) {
    122             mStagingTask.cancel(true);
    123         }
    124 
    125         super.onDestroy();
    126     }
    127 
    128     /**
    129      * Show an error message and set result as error.
    130      */
    131     private void showError() {
    132         (new ErrorDialog()).showAllowingStateLoss(getFragmentManager(), "error");
    133 
    134         Intent result = new Intent();
    135         result.putExtra(Intent.EXTRA_INSTALL_RESULT,
    136                 PackageManager.INSTALL_FAILED_INVALID_APK);
    137         setResult(RESULT_FIRST_USER, result);
    138     }
    139 
    140     /**
    141      * Dialog for errors while staging.
    142      */
    143     public static class ErrorDialog extends DialogFragment {
    144         private Activity mActivity;
    145 
    146         @Override
    147         public void onAttach(Context context) {
    148             super.onAttach(context);
    149 
    150             mActivity = (Activity) context;
    151         }
    152 
    153         @Override
    154         public Dialog onCreateDialog(Bundle savedInstanceState) {
    155             AlertDialog alertDialog = new AlertDialog.Builder(mActivity)
    156                     .setMessage(R.string.Parse_error_dlg_text)
    157                     .setPositiveButton(R.string.ok,
    158                             (dialog, which) -> mActivity.finish())
    159                     .create();
    160             alertDialog.setCanceledOnTouchOutside(false);
    161 
    162             return alertDialog;
    163         }
    164 
    165         @Override
    166         public void onCancel(DialogInterface dialog) {
    167             super.onCancel(dialog);
    168 
    169             mActivity.finish();
    170         }
    171     }
    172 
    173     private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
    174         @Override
    175         protected Boolean doInBackground(Uri... params) {
    176             if (params == null || params.length <= 0) {
    177                 return false;
    178             }
    179             Uri packageUri = params[0];
    180             try (InputStream in = getContentResolver().openInputStream(packageUri)) {
    181                 // Despite the comments in ContentResolver#openInputStream the returned stream can
    182                 // be null.
    183                 if (in == null) {
    184                     return false;
    185                 }
    186 
    187                 try (OutputStream out = new FileOutputStream(mStagedFile)) {
    188                     byte[] buffer = new byte[1024 * 1024];
    189                     int bytesRead;
    190                     while ((bytesRead = in.read(buffer)) >= 0) {
    191                         // Be nice and respond to a cancellation
    192                         if (isCancelled()) {
    193                             return false;
    194                         }
    195                         out.write(buffer, 0, bytesRead);
    196                     }
    197                 }
    198             } catch (IOException | SecurityException e) {
    199                 Log.w(LOG_TAG, "Error staging apk from content URI", e);
    200                 return false;
    201             }
    202             return true;
    203         }
    204 
    205         @Override
    206         protected void onPostExecute(Boolean success) {
    207             if (success) {
    208                 // Now start the installation again from a file
    209                 Intent installIntent = new Intent(getIntent());
    210                 installIntent.setClass(InstallStaging.this, PackageInstallerActivity.class);
    211                 installIntent.setData(Uri.fromFile(mStagedFile));
    212                 installIntent
    213                         .setFlags(installIntent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    214                 installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
    215                 startActivityForResult(installIntent, 0);
    216             } else {
    217                 showError();
    218             }
    219         }
    220     }
    221 }
    222