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.PackageParser; 29 import android.net.Uri; 30 import android.os.AsyncTask; 31 import android.os.Bundle; 32 import android.util.Log; 33 import android.view.View; 34 import android.widget.Button; 35 import android.widget.ProgressBar; 36 37 import com.android.internal.app.AlertActivity; 38 import com.android.internal.content.PackageHelper; 39 40 import java.io.File; 41 import java.io.FileInputStream; 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.io.OutputStream; 45 46 /** 47 * Send package to the package manager and handle results from package manager. Once the 48 * installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}. 49 * <p>This has two phases: First send the data to the package manager, then wait until the package 50 * manager processed the result.</p> 51 */ 52 public class InstallInstalling extends AlertActivity { 53 private static final String LOG_TAG = InstallInstalling.class.getSimpleName(); 54 55 private static final String SESSION_ID = "com.android.packageinstaller.SESSION_ID"; 56 private static final String INSTALL_ID = "com.android.packageinstaller.INSTALL_ID"; 57 58 private static final String BROADCAST_ACTION = 59 "com.android.packageinstaller.ACTION_INSTALL_COMMIT"; 60 61 /** Listens to changed to the session and updates progress bar */ 62 private PackageInstaller.SessionCallback mSessionCallback; 63 64 /** Task that sends the package to the package installer */ 65 private InstallingAsyncTask mInstallingTask; 66 67 /** Id of the session to install the package */ 68 private int mSessionId; 69 70 /** Id of the install event we wait for */ 71 private int mInstallId; 72 73 /** URI of package to install */ 74 private Uri mPackageURI; 75 76 /** The button that can cancel this dialog */ 77 private Button mCancelButton; 78 79 @Override onCreate(@ullable Bundle savedInstanceState)80 protected void onCreate(@Nullable Bundle savedInstanceState) { 81 super.onCreate(savedInstanceState); 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.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 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0); 146 params.setAppPackageName(pkg.packageName); 147 params.setInstallLocation(pkg.installLocation); 148 params.setSize( 149 PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride)); 150 } catch (PackageParser.PackageParserException e) { 151 Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults."); 152 Log.e(LOG_TAG, 153 "Cannot calculate installed size " + file + ". Try only apk size."); 154 params.setSize(file.length()); 155 } catch (IOException e) { 156 Log.e(LOG_TAG, 157 "Cannot calculate installed size " + file + ". Try only apk size."); 158 params.setSize(file.length()); 159 } 160 161 try { 162 mInstallId = InstallEventReceiver 163 .addObserver(this, EventResultPersister.GENERATE_NEW_ID, 164 this::launchFinishBasedOnResult); 165 } catch (EventResultPersister.OutOfIdsException e) { 166 launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 167 } 168 169 try { 170 mSessionId = getPackageManager().getPackageInstaller().createSession(params); 171 } catch (IOException e) { 172 launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 173 } 174 } 175 176 mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE); 177 178 mSessionCallback = new InstallSessionCallback(); 179 } 180 } 181 182 /** 183 * Launch the "success" version of the final package installer dialog 184 */ launchSuccess()185 private void launchSuccess() { 186 Intent successIntent = new Intent(getIntent()); 187 successIntent.setClass(this, InstallSuccess.class); 188 successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 189 190 startActivity(successIntent); 191 finish(); 192 } 193 194 /** 195 * Launch the "failure" version of the final package installer dialog 196 * 197 * @param legacyStatus The status as used internally in the package manager. 198 * @param statusMessage The status description. 199 */ launchFailure(int legacyStatus, String statusMessage)200 private void launchFailure(int legacyStatus, String statusMessage) { 201 Intent failureIntent = new Intent(getIntent()); 202 failureIntent.setClass(this, InstallFailed.class); 203 failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 204 failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus); 205 failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage); 206 207 startActivity(failureIntent); 208 finish(); 209 } 210 211 @Override onStart()212 protected void onStart() { 213 super.onStart(); 214 215 getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback); 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 onStop()254 protected void onStop() { 255 super.onStop(); 256 257 getPackageManager().getPackageInstaller().unregisterSessionCallback(mSessionCallback); 258 } 259 260 @Override onDestroy()261 protected void onDestroy() { 262 if (mInstallingTask != null) { 263 mInstallingTask.cancel(true); 264 synchronized (mInstallingTask) { 265 while (!mInstallingTask.isDone) { 266 try { 267 mInstallingTask.wait(); 268 } catch (InterruptedException e) { 269 Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel", 270 e); 271 } 272 } 273 } 274 } 275 276 InstallEventReceiver.removeObserver(this, mInstallId); 277 278 super.onDestroy(); 279 } 280 281 /** 282 * Launch the appropriate finish activity (success or failed) for the installation result. 283 * 284 * @param statusCode The installation result. 285 * @param legacyStatus The installation as used internally in the package manager. 286 * @param statusMessage The detailed installation result. 287 */ launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage)288 private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) { 289 if (statusCode == PackageInstaller.STATUS_SUCCESS) { 290 launchSuccess(); 291 } else { 292 launchFailure(legacyStatus, statusMessage); 293 } 294 } 295 296 297 private class InstallSessionCallback extends PackageInstaller.SessionCallback { 298 @Override onCreated(int sessionId)299 public void onCreated(int sessionId) { 300 // empty 301 } 302 303 @Override onBadgingChanged(int sessionId)304 public void onBadgingChanged(int sessionId) { 305 // empty 306 } 307 308 @Override onActiveChanged(int sessionId, boolean active)309 public void onActiveChanged(int sessionId, boolean active) { 310 // empty 311 } 312 313 @Override onProgressChanged(int sessionId, float progress)314 public void onProgressChanged(int sessionId, float progress) { 315 if (sessionId == mSessionId) { 316 ProgressBar progressBar = requireViewById(R.id.progress); 317 progressBar.setMax(Integer.MAX_VALUE); 318 progressBar.setProgress((int) (Integer.MAX_VALUE * progress)); 319 } 320 } 321 322 @Override onFinished(int sessionId, boolean success)323 public void onFinished(int sessionId, boolean success) { 324 // empty, finish is handled by InstallResultReceiver 325 } 326 } 327 328 /** 329 * Send the package to the package installer and then register a event result observer that 330 * will call {@link #launchFinishBasedOnResult(int, int, String)} 331 */ 332 private final class InstallingAsyncTask extends AsyncTask<Void, Void, 333 PackageInstaller.Session> { 334 volatile boolean isDone; 335 336 @Override doInBackground(Void... params)337 protected PackageInstaller.Session doInBackground(Void... params) { 338 PackageInstaller.Session session; 339 try { 340 session = getPackageManager().getPackageInstaller().openSession(mSessionId); 341 } catch (IOException e) { 342 return null; 343 } 344 345 session.setStagingProgress(0); 346 347 try { 348 File file = new File(mPackageURI.getPath()); 349 350 try (InputStream in = new FileInputStream(file)) { 351 long sizeBytes = file.length(); 352 try (OutputStream out = session 353 .openWrite("PackageInstaller", 0, sizeBytes)) { 354 byte[] buffer = new byte[1024 * 1024]; 355 while (true) { 356 int numRead = in.read(buffer); 357 358 if (numRead == -1) { 359 session.fsync(out); 360 break; 361 } 362 363 if (isCancelled()) { 364 session.close(); 365 break; 366 } 367 368 out.write(buffer, 0, numRead); 369 if (sizeBytes > 0) { 370 float fraction = ((float) numRead / (float) sizeBytes); 371 session.addProgress(fraction); 372 } 373 } 374 } 375 } 376 377 return session; 378 } catch (IOException | SecurityException e) { 379 Log.e(LOG_TAG, "Could not write package", e); 380 381 session.close(); 382 383 return null; 384 } finally { 385 synchronized (this) { 386 isDone = true; 387 notifyAll(); 388 } 389 } 390 } 391 392 @Override onPostExecute(PackageInstaller.Session session)393 protected void onPostExecute(PackageInstaller.Session session) { 394 if (session != null) { 395 Intent broadcastIntent = new Intent(BROADCAST_ACTION); 396 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 397 broadcastIntent.setPackage(getPackageName()); 398 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); 399 400 PendingIntent pendingIntent = PendingIntent.getBroadcast( 401 InstallInstalling.this, 402 mInstallId, 403 broadcastIntent, 404 PendingIntent.FLAG_UPDATE_CURRENT); 405 406 session.commit(pendingIntent.getIntentSender()); 407 mCancelButton.setEnabled(false); 408 setFinishOnTouchOutside(false); 409 } else { 410 getPackageManager().getPackageInstaller().abandonSession(mSessionId); 411 412 if (!isCancelled()) { 413 launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null); 414 } 415 } 416 } 417 } 418 } 419