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