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 17package com.android.packageinstaller; 18 19import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN; 20 21import android.app.Activity; 22import android.app.PendingIntent; 23import android.content.Intent; 24import android.content.pm.ApplicationInfo; 25import android.content.pm.PackageInstaller; 26import android.content.pm.PackageManager; 27import android.content.pm.PackageParser; 28import android.net.Uri; 29import android.os.AsyncTask; 30import android.os.Bundle; 31import android.support.annotation.Nullable; 32import android.util.Log; 33import android.widget.Button; 34import android.widget.ProgressBar; 35 36import com.android.internal.content.PackageHelper; 37 38import java.io.File; 39import java.io.FileInputStream; 40import java.io.IOException; 41import java.io.InputStream; 42import 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 */ 50public 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.installFlags = PackageManager.INSTALL_FULL_APP; 115 params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER); 116 params.originatingUri = getIntent() 117 .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); 118 params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, 119 UID_UNKNOWN); 120 params.installerPackageName = 121 getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); 122 123 File file = new File(mPackageURI.getPath()); 124 try { 125 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0); 126 params.setAppPackageName(pkg.packageName); 127 params.setInstallLocation(pkg.installLocation); 128 params.setSize( 129 PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride)); 130 } catch (PackageParser.PackageParserException e) { 131 Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults."); 132 Log.e(LOG_TAG, 133 "Cannot calculate installed size " + file + ". Try only apk size."); 134 params.setSize(file.length()); 135 } catch (IOException e) { 136 Log.e(LOG_TAG, 137 "Cannot calculate installed size " + file + ". Try only apk size."); 138 params.setSize(file.length()); 139 } 140 141 try { 142 mInstallId = InstallEventReceiver 143 .addObserver(this, EventResultPersister.GENERATE_NEW_ID, 144 this::launchFinishBasedOnResult); 145 } catch (EventResultPersister.OutOfIdsException e) { 146 launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 147 } 148 149 try { 150 mSessionId = getPackageManager().getPackageInstaller().createSession(params); 151 } catch (IOException e) { 152 launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 153 } 154 } 155 156 mCancelButton = (Button) findViewById(R.id.cancel_button); 157 158 mCancelButton.setOnClickListener(view -> { 159 if (mInstallingTask != null) { 160 mInstallingTask.cancel(true); 161 } 162 163 if (mSessionId > 0) { 164 getPackageManager().getPackageInstaller().abandonSession(mSessionId); 165 mSessionId = 0; 166 } 167 168 setResult(RESULT_CANCELED); 169 finish(); 170 }); 171 172 mSessionCallback = new InstallSessionCallback(); 173 } 174 } 175 176 /** 177 * Launch the "success" version of the final package installer dialog 178 */ 179 private void launchSuccess() { 180 Intent successIntent = new Intent(getIntent()); 181 successIntent.setClass(this, InstallSuccess.class); 182 successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 183 184 startActivity(successIntent); 185 finish(); 186 } 187 188 /** 189 * Launch the "failure" version of the final package installer dialog 190 * 191 * @param legacyStatus The status as used internally in the package manager. 192 * @param statusMessage The status description. 193 */ 194 private void launchFailure(int legacyStatus, String statusMessage) { 195 Intent failureIntent = new Intent(getIntent()); 196 failureIntent.setClass(this, InstallFailed.class); 197 failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 198 failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus); 199 failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage); 200 201 startActivity(failureIntent); 202 finish(); 203 } 204 205 @Override 206 protected void onStart() { 207 super.onStart(); 208 209 getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback); 210 } 211 212 @Override 213 protected void onResume() { 214 super.onResume(); 215 216 // This is the first onResume in a single life of the activity 217 if (mInstallingTask == null) { 218 PackageInstaller installer = getPackageManager().getPackageInstaller(); 219 PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId); 220 221 if (sessionInfo != null && !sessionInfo.isActive()) { 222 mInstallingTask = new InstallingAsyncTask(); 223 mInstallingTask.execute(); 224 } else { 225 // we will receive a broadcast when the install is finished 226 mCancelButton.setEnabled(false); 227 setFinishOnTouchOutside(false); 228 } 229 } 230 } 231 232 @Override 233 protected void onSaveInstanceState(Bundle outState) { 234 super.onSaveInstanceState(outState); 235 236 outState.putInt(SESSION_ID, mSessionId); 237 outState.putInt(INSTALL_ID, mInstallId); 238 } 239 240 @Override 241 public void onBackPressed() { 242 if (mCancelButton.isEnabled()) { 243 super.onBackPressed(); 244 } 245 } 246 247 @Override 248 protected void onStop() { 249 super.onStop(); 250 251 getPackageManager().getPackageInstaller().unregisterSessionCallback(mSessionCallback); 252 } 253 254 @Override 255 protected void onDestroy() { 256 if (mInstallingTask != null) { 257 mInstallingTask.cancel(true); 258 synchronized (mInstallingTask) { 259 while (!mInstallingTask.isDone) { 260 try { 261 mInstallingTask.wait(); 262 } catch (InterruptedException e) { 263 Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel", 264 e); 265 } 266 } 267 } 268 } 269 270 InstallEventReceiver.removeObserver(this, mInstallId); 271 272 super.onDestroy(); 273 } 274 275 /** 276 * Launch the appropriate finish activity (success or failed) for the installation result. 277 * 278 * @param statusCode The installation result. 279 * @param legacyStatus The installation as used internally in the package manager. 280 * @param statusMessage The detailed installation result. 281 */ 282 private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) { 283 if (statusCode == PackageInstaller.STATUS_SUCCESS) { 284 launchSuccess(); 285 } else { 286 launchFailure(legacyStatus, statusMessage); 287 } 288 } 289 290 291 private class InstallSessionCallback extends PackageInstaller.SessionCallback { 292 @Override 293 public void onCreated(int sessionId) { 294 // empty 295 } 296 297 @Override 298 public void onBadgingChanged(int sessionId) { 299 // empty 300 } 301 302 @Override 303 public void onActiveChanged(int sessionId, boolean active) { 304 // empty 305 } 306 307 @Override 308 public void onProgressChanged(int sessionId, float progress) { 309 if (sessionId == mSessionId) { 310 ProgressBar progressBar = (ProgressBar)findViewById(R.id.progress_bar); 311 progressBar.setMax(Integer.MAX_VALUE); 312 progressBar.setProgress((int) (Integer.MAX_VALUE * progress)); 313 } 314 } 315 316 @Override 317 public void onFinished(int sessionId, boolean success) { 318 // empty, finish is handled by InstallResultReceiver 319 } 320 } 321 322 /** 323 * Send the package to the package installer and then register a event result observer that 324 * will call {@link #launchFinishBasedOnResult(int, int, String)} 325 */ 326 private final class InstallingAsyncTask extends AsyncTask<Void, Void, 327 PackageInstaller.Session> { 328 volatile boolean isDone; 329 330 @Override 331 protected PackageInstaller.Session doInBackground(Void... params) { 332 PackageInstaller.Session session; 333 try { 334 session = getPackageManager().getPackageInstaller().openSession(mSessionId); 335 } catch (IOException e) { 336 return null; 337 } 338 339 session.setStagingProgress(0); 340 341 try { 342 File file = new File(mPackageURI.getPath()); 343 344 try (InputStream in = new FileInputStream(file)) { 345 long sizeBytes = file.length(); 346 try (OutputStream out = session 347 .openWrite("PackageInstaller", 0, sizeBytes)) { 348 byte[] buffer = new byte[1024 * 1024]; 349 while (true) { 350 int numRead = in.read(buffer); 351 352 if (numRead == -1) { 353 session.fsync(out); 354 break; 355 } 356 357 if (isCancelled()) { 358 session.close(); 359 break; 360 } 361 362 out.write(buffer, 0, numRead); 363 if (sizeBytes > 0) { 364 float fraction = ((float) numRead / (float) sizeBytes); 365 session.addProgress(fraction); 366 } 367 } 368 } 369 } 370 371 return session; 372 } catch (IOException | SecurityException e) { 373 Log.e(LOG_TAG, "Could not write package", e); 374 375 session.close(); 376 377 return null; 378 } finally { 379 synchronized (this) { 380 isDone = true; 381 notifyAll(); 382 } 383 } 384 } 385 386 @Override 387 protected void onPostExecute(PackageInstaller.Session session) { 388 if (session != null) { 389 Intent broadcastIntent = new Intent(BROADCAST_ACTION); 390 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 391 broadcastIntent.setPackage( 392 getPackageManager().getPermissionControllerPackageName()); 393 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); 394 395 PendingIntent pendingIntent = PendingIntent.getBroadcast( 396 InstallInstalling.this, 397 mInstallId, 398 broadcastIntent, 399 PendingIntent.FLAG_UPDATE_CURRENT); 400 401 session.commit(pendingIntent.getIntentSender()); 402 mCancelButton.setEnabled(false); 403 setFinishOnTouchOutside(false); 404 } else { 405 getPackageManager().getPackageInstaller().abandonSession(mSessionId); 406 407 if (!isCancelled()) { 408 launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null); 409 } 410 } 411 } 412 } 413} 414