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