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.annotation.Nullable; 22 import android.app.PendingIntent; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.PackageInstaller; 27 import android.content.pm.PackageManager; 28 import android.content.pm.parsing.ApkLiteParseUtils; 29 import android.content.pm.parsing.PackageLite; 30 import android.content.pm.parsing.result.ParseResult; 31 import android.content.pm.parsing.result.ParseTypeImpl; 32 import android.net.Uri; 33 import android.os.AsyncTask; 34 import android.os.Bundle; 35 import android.util.Log; 36 import android.view.View; 37 import android.widget.Button; 38 39 import com.android.internal.app.AlertActivity; 40 import com.android.internal.content.PackageHelper; 41 42 import java.io.File; 43 import java.io.FileInputStream; 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.io.OutputStream; 47 48 /** 49 * Send package to the package manager and handle results from package manager. Once the 50 * installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}. 51 * <p>This has two phases: First send the data to the package manager, then wait until the package 52 * manager processed the result.</p> 53 */ 54 public class InstallInstalling extends AlertActivity { 55 private static final String LOG_TAG = InstallInstalling.class.getSimpleName(); 56 57 private static final String SESSION_ID = "com.android.packageinstaller.SESSION_ID"; 58 private static final String INSTALL_ID = "com.android.packageinstaller.INSTALL_ID"; 59 60 private static final String BROADCAST_ACTION = 61 "com.android.packageinstaller.ACTION_INSTALL_COMMIT"; 62 63 /** Task that sends the package to the package installer */ 64 private InstallingAsyncTask mInstallingTask; 65 66 /** Id of the session to install the package */ 67 private int mSessionId; 68 69 /** Id of the install event we wait for */ 70 private int mInstallId; 71 72 /** URI of package to install */ 73 private Uri mPackageURI; 74 75 /** The button that can cancel this dialog */ 76 private Button mCancelButton; 77 78 @Override onCreate(@ullable Bundle savedInstanceState)79 protected void onCreate(@Nullable Bundle savedInstanceState) { 80 super.onCreate(savedInstanceState); 81 82 ApplicationInfo appInfo = getIntent() 83 .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); 84 mPackageURI = getIntent().getData(); 85 86 if ("package".equals(mPackageURI.getScheme())) { 87 try { 88 getPackageManager().installExistingPackage(appInfo.packageName); 89 launchSuccess(); 90 } catch (PackageManager.NameNotFoundException e) { 91 launchFailure(PackageInstaller.STATUS_FAILURE, 92 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 93 } 94 } else { 95 final File sourceFile = new File(mPackageURI.getPath()); 96 PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile); 97 98 mAlert.setIcon(as.icon); 99 mAlert.setTitle(as.label); 100 mAlert.setView(R.layout.install_content_view); 101 mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel), 102 (ignored, ignored2) -> { 103 if (mInstallingTask != null) { 104 mInstallingTask.cancel(true); 105 } 106 107 if (mSessionId > 0) { 108 getPackageManager().getPackageInstaller().abandonSession(mSessionId); 109 mSessionId = 0; 110 } 111 112 setResult(RESULT_CANCELED); 113 finish(); 114 }, null); 115 setupAlert(); 116 requireViewById(R.id.installing).setVisibility(View.VISIBLE); 117 118 if (savedInstanceState != null) { 119 mSessionId = savedInstanceState.getInt(SESSION_ID); 120 mInstallId = savedInstanceState.getInt(INSTALL_ID); 121 122 // Reregister for result; might instantly call back if result was delivered while 123 // activity was destroyed 124 try { 125 InstallEventReceiver.addObserver(this, mInstallId, 126 this::launchFinishBasedOnResult); 127 } catch (EventResultPersister.OutOfIdsException e) { 128 // Does not happen 129 } 130 } else { 131 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( 132 PackageInstaller.SessionParams.MODE_FULL_INSTALL); 133 params.setInstallAsInstantApp(false); 134 params.setReferrerUri(getIntent().getParcelableExtra(Intent.EXTRA_REFERRER)); 135 params.setOriginatingUri(getIntent() 136 .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI)); 137 params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, 138 UID_UNKNOWN)); 139 params.setInstallerPackageName(getIntent().getStringExtra( 140 Intent.EXTRA_INSTALLER_PACKAGE_NAME)); 141 params.setInstallReason(PackageManager.INSTALL_REASON_USER); 142 143 File file = new File(mPackageURI.getPath()); 144 try { 145 final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); 146 final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( 147 input.reset(), file, /* flags */ 0); 148 if (result.isError()) { 149 Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults."); 150 Log.e(LOG_TAG, 151 "Cannot calculate installed size " + file + ". Try only apk size."); 152 params.setSize(file.length()); 153 } else { 154 final PackageLite pkg = result.getResult(); 155 params.setAppPackageName(pkg.getPackageName()); 156 params.setInstallLocation(pkg.getInstallLocation()); 157 params.setSize( 158 PackageHelper.calculateInstalledSize(pkg, params.abiOverride)); 159 } 160 } catch (IOException e) { 161 Log.e(LOG_TAG, 162 "Cannot calculate installed size " + file + ". Try only apk size."); 163 params.setSize(file.length()); 164 } 165 166 try { 167 mInstallId = InstallEventReceiver 168 .addObserver(this, EventResultPersister.GENERATE_NEW_ID, 169 this::launchFinishBasedOnResult); 170 } catch (EventResultPersister.OutOfIdsException e) { 171 launchFailure(PackageInstaller.STATUS_FAILURE, 172 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 173 } 174 175 try { 176 mSessionId = getPackageManager().getPackageInstaller().createSession(params); 177 } catch (IOException e) { 178 launchFailure(PackageInstaller.STATUS_FAILURE, 179 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 180 } 181 } 182 183 mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE); 184 } 185 } 186 187 /** 188 * Launch the "success" version of the final package installer dialog 189 */ launchSuccess()190 private void launchSuccess() { 191 Intent successIntent = new Intent(getIntent()); 192 successIntent.setClass(this, InstallSuccess.class); 193 successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 194 195 startActivity(successIntent); 196 finish(); 197 } 198 199 /** 200 * Launch the "failure" version of the final package installer dialog 201 * 202 * @param statusCode The generic status code as returned by the package installer. 203 * @param legacyStatus The status as used internally in the package manager. 204 * @param statusMessage The status description. 205 */ launchFailure(int statusCode, int legacyStatus, String statusMessage)206 private void launchFailure(int statusCode, int legacyStatus, String statusMessage) { 207 Intent failureIntent = new Intent(getIntent()); 208 failureIntent.setClass(this, InstallFailed.class); 209 failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 210 failureIntent.putExtra(PackageInstaller.EXTRA_STATUS, statusCode); 211 failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus); 212 failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage); 213 214 startActivity(failureIntent); 215 finish(); 216 } 217 218 @Override onResume()219 protected void onResume() { 220 super.onResume(); 221 222 // This is the first onResume in a single life of the activity 223 if (mInstallingTask == null) { 224 PackageInstaller installer = getPackageManager().getPackageInstaller(); 225 PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId); 226 227 if (sessionInfo != null && !sessionInfo.isActive()) { 228 mInstallingTask = new InstallingAsyncTask(); 229 mInstallingTask.execute(); 230 } else { 231 // we will receive a broadcast when the install is finished 232 mCancelButton.setEnabled(false); 233 setFinishOnTouchOutside(false); 234 } 235 } 236 } 237 238 @Override onSaveInstanceState(Bundle outState)239 protected void onSaveInstanceState(Bundle outState) { 240 super.onSaveInstanceState(outState); 241 242 outState.putInt(SESSION_ID, mSessionId); 243 outState.putInt(INSTALL_ID, mInstallId); 244 } 245 246 @Override onBackPressed()247 public void onBackPressed() { 248 if (mCancelButton.isEnabled()) { 249 super.onBackPressed(); 250 } 251 } 252 253 @Override onDestroy()254 protected void onDestroy() { 255 if (mInstallingTask != null) { 256 mInstallingTask.cancel(true); 257 synchronized (mInstallingTask) { 258 while (!mInstallingTask.isDone) { 259 try { 260 mInstallingTask.wait(); 261 } catch (InterruptedException e) { 262 Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel", 263 e); 264 } 265 } 266 } 267 } 268 269 InstallEventReceiver.removeObserver(this, mInstallId); 270 271 super.onDestroy(); 272 } 273 274 /** 275 * Launch the appropriate finish activity (success or failed) for the installation result. 276 * 277 * @param statusCode The installation result. 278 * @param legacyStatus The installation as used internally in the package manager. 279 * @param statusMessage The detailed installation result. 280 */ launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage)281 private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) { 282 if (statusCode == PackageInstaller.STATUS_SUCCESS) { 283 launchSuccess(); 284 } else { 285 launchFailure(statusCode, legacyStatus, statusMessage); 286 } 287 } 288 289 /** 290 * Send the package to the package installer and then register a event result observer that 291 * will call {@link #launchFinishBasedOnResult(int, int, String)} 292 */ 293 private final class InstallingAsyncTask extends AsyncTask<Void, Void, 294 PackageInstaller.Session> { 295 volatile boolean isDone; 296 297 @Override doInBackground(Void... params)298 protected PackageInstaller.Session doInBackground(Void... params) { 299 PackageInstaller.Session session; 300 try { 301 session = getPackageManager().getPackageInstaller().openSession(mSessionId); 302 } catch (IOException e) { 303 synchronized (this) { 304 isDone = true; 305 notifyAll(); 306 } 307 return null; 308 } 309 310 session.setStagingProgress(0); 311 312 try { 313 File file = new File(mPackageURI.getPath()); 314 315 try (InputStream in = new FileInputStream(file)) { 316 long sizeBytes = file.length(); 317 try (OutputStream out = session 318 .openWrite("PackageInstaller", 0, sizeBytes)) { 319 byte[] buffer = new byte[1024 * 1024]; 320 while (true) { 321 int numRead = in.read(buffer); 322 323 if (numRead == -1) { 324 session.fsync(out); 325 break; 326 } 327 328 if (isCancelled()) { 329 session.close(); 330 break; 331 } 332 333 out.write(buffer, 0, numRead); 334 if (sizeBytes > 0) { 335 float fraction = ((float) numRead / (float) sizeBytes); 336 session.addProgress(fraction); 337 } 338 } 339 } 340 } 341 342 return session; 343 } catch (IOException | SecurityException e) { 344 Log.e(LOG_TAG, "Could not write package", e); 345 346 session.close(); 347 348 return null; 349 } finally { 350 synchronized (this) { 351 isDone = true; 352 notifyAll(); 353 } 354 } 355 } 356 357 @Override onPostExecute(PackageInstaller.Session session)358 protected void onPostExecute(PackageInstaller.Session session) { 359 if (session != null) { 360 Intent broadcastIntent = new Intent(BROADCAST_ACTION); 361 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 362 broadcastIntent.setPackage(getPackageName()); 363 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); 364 365 PendingIntent pendingIntent = PendingIntent.getBroadcast( 366 InstallInstalling.this, 367 mInstallId, 368 broadcastIntent, 369 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); 370 371 session.commit(pendingIntent.getIntentSender()); 372 mCancelButton.setEnabled(false); 373 setFinishOnTouchOutside(false); 374 } else { 375 getPackageManager().getPackageInstaller().abandonSession(mSessionId); 376 377 if (!isCancelled()) { 378 launchFailure(PackageInstaller.STATUS_FAILURE, 379 PackageManager.INSTALL_FAILED_INVALID_APK, null); 380 } 381 } 382 } 383 } 384 } 385