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.InstallLocationUtils; 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 final Uri referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER); 134 params.setPackageSource( 135 referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE 136 : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE); 137 params.setInstallAsInstantApp(false); 138 params.setReferrerUri(referrerUri); 139 params.setOriginatingUri(getIntent() 140 .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI)); 141 params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, 142 UID_UNKNOWN)); 143 params.setInstallerPackageName(getIntent().getStringExtra( 144 Intent.EXTRA_INSTALLER_PACKAGE_NAME)); 145 params.setInstallReason(PackageManager.INSTALL_REASON_USER); 146 147 File file = new File(mPackageURI.getPath()); 148 try { 149 final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); 150 final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( 151 input.reset(), file, /* flags */ 0); 152 if (result.isError()) { 153 Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults."); 154 Log.e(LOG_TAG, 155 "Cannot calculate installed size " + file + ". Try only apk size."); 156 params.setSize(file.length()); 157 } else { 158 final PackageLite pkg = result.getResult(); 159 params.setAppPackageName(pkg.getPackageName()); 160 params.setInstallLocation(pkg.getInstallLocation()); 161 params.setSize(InstallLocationUtils.calculateInstalledSize(pkg, 162 params.abiOverride)); 163 } 164 } catch (IOException e) { 165 Log.e(LOG_TAG, 166 "Cannot calculate installed size " + file + ". Try only apk size."); 167 params.setSize(file.length()); 168 } 169 170 try { 171 mInstallId = InstallEventReceiver 172 .addObserver(this, EventResultPersister.GENERATE_NEW_ID, 173 this::launchFinishBasedOnResult); 174 } catch (EventResultPersister.OutOfIdsException e) { 175 launchFailure(PackageInstaller.STATUS_FAILURE, 176 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 177 } 178 179 try { 180 mSessionId = getPackageManager().getPackageInstaller().createSession(params); 181 } catch (IOException e) { 182 launchFailure(PackageInstaller.STATUS_FAILURE, 183 PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); 184 } 185 } 186 187 mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE); 188 } 189 } 190 191 /** 192 * Launch the "success" version of the final package installer dialog 193 */ launchSuccess()194 private void launchSuccess() { 195 Intent successIntent = new Intent(getIntent()); 196 successIntent.setClass(this, InstallSuccess.class); 197 successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 198 199 startActivity(successIntent); 200 finish(); 201 } 202 203 /** 204 * Launch the "failure" version of the final package installer dialog 205 * 206 * @param statusCode The generic status code as returned by the package installer. 207 * @param legacyStatus The status as used internally in the package manager. 208 * @param statusMessage The status description. 209 */ launchFailure(int statusCode, int legacyStatus, String statusMessage)210 private void launchFailure(int statusCode, int legacyStatus, String statusMessage) { 211 Intent failureIntent = new Intent(getIntent()); 212 failureIntent.setClass(this, InstallFailed.class); 213 failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 214 failureIntent.putExtra(PackageInstaller.EXTRA_STATUS, statusCode); 215 failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus); 216 failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage); 217 218 startActivity(failureIntent); 219 finish(); 220 } 221 222 @Override onResume()223 protected void onResume() { 224 super.onResume(); 225 226 // This is the first onResume in a single life of the activity 227 if (mInstallingTask == null) { 228 PackageInstaller installer = getPackageManager().getPackageInstaller(); 229 PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId); 230 231 if (sessionInfo != null && !sessionInfo.isActive()) { 232 mInstallingTask = new InstallingAsyncTask(); 233 mInstallingTask.execute(); 234 } else { 235 // we will receive a broadcast when the install is finished 236 mCancelButton.setEnabled(false); 237 setFinishOnTouchOutside(false); 238 } 239 } 240 } 241 242 @Override onSaveInstanceState(Bundle outState)243 protected void onSaveInstanceState(Bundle outState) { 244 super.onSaveInstanceState(outState); 245 246 outState.putInt(SESSION_ID, mSessionId); 247 outState.putInt(INSTALL_ID, mInstallId); 248 } 249 250 @Override onBackPressed()251 public void onBackPressed() { 252 if (mCancelButton.isEnabled()) { 253 super.onBackPressed(); 254 } 255 } 256 257 @Override onDestroy()258 protected void onDestroy() { 259 if (mInstallingTask != null) { 260 mInstallingTask.cancel(true); 261 synchronized (mInstallingTask) { 262 while (!mInstallingTask.isDone) { 263 try { 264 mInstallingTask.wait(); 265 } catch (InterruptedException e) { 266 Log.i(LOG_TAG, "Interrupted while waiting for installing task to cancel", 267 e); 268 } 269 } 270 } 271 } 272 273 InstallEventReceiver.removeObserver(this, mInstallId); 274 275 super.onDestroy(); 276 } 277 278 /** 279 * Launch the appropriate finish activity (success or failed) for the installation result. 280 * 281 * @param statusCode The installation result. 282 * @param legacyStatus The installation as used internally in the package manager. 283 * @param statusMessage The detailed installation result. 284 */ launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage)285 private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) { 286 if (statusCode == PackageInstaller.STATUS_SUCCESS) { 287 launchSuccess(); 288 } else { 289 launchFailure(statusCode, legacyStatus, statusMessage); 290 } 291 } 292 293 /** 294 * Send the package to the package installer and then register a event result observer that 295 * will call {@link #launchFinishBasedOnResult(int, int, String)} 296 */ 297 private final class InstallingAsyncTask extends AsyncTask<Void, Void, 298 PackageInstaller.Session> { 299 volatile boolean isDone; 300 301 @Override doInBackground(Void... params)302 protected PackageInstaller.Session doInBackground(Void... params) { 303 PackageInstaller.Session session; 304 try { 305 session = getPackageManager().getPackageInstaller().openSession(mSessionId); 306 } catch (IOException e) { 307 synchronized (this) { 308 isDone = true; 309 notifyAll(); 310 } 311 return null; 312 } 313 314 session.setStagingProgress(0); 315 316 try { 317 File file = new File(mPackageURI.getPath()); 318 319 try (InputStream in = new FileInputStream(file)) { 320 long sizeBytes = file.length(); 321 try (OutputStream out = session 322 .openWrite("PackageInstaller", 0, sizeBytes)) { 323 byte[] buffer = new byte[1024 * 1024]; 324 while (true) { 325 int numRead = in.read(buffer); 326 327 if (numRead == -1) { 328 session.fsync(out); 329 break; 330 } 331 332 if (isCancelled()) { 333 session.close(); 334 break; 335 } 336 337 out.write(buffer, 0, numRead); 338 if (sizeBytes > 0) { 339 float fraction = ((float) numRead / (float) sizeBytes); 340 session.addProgress(fraction); 341 } 342 } 343 } 344 } 345 346 return session; 347 } catch (IOException | SecurityException e) { 348 Log.e(LOG_TAG, "Could not write package", e); 349 350 session.close(); 351 352 return null; 353 } finally { 354 synchronized (this) { 355 isDone = true; 356 notifyAll(); 357 } 358 } 359 } 360 361 @Override onPostExecute(PackageInstaller.Session session)362 protected void onPostExecute(PackageInstaller.Session session) { 363 if (session != null) { 364 Intent broadcastIntent = new Intent(BROADCAST_ACTION); 365 broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 366 broadcastIntent.setPackage(getPackageName()); 367 broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); 368 369 PendingIntent pendingIntent = PendingIntent.getBroadcast( 370 InstallInstalling.this, 371 mInstallId, 372 broadcastIntent, 373 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); 374 375 session.commit(pendingIntent.getIntentSender()); 376 mCancelButton.setEnabled(false); 377 setFinishOnTouchOutside(false); 378 } else { 379 getPackageManager().getPackageInstaller().abandonSession(mSessionId); 380 381 if (!isCancelled()) { 382 launchFailure(PackageInstaller.STATUS_FAILURE, 383 PackageManager.INSTALL_FAILED_INVALID_APK, null); 384 } 385 } 386 } 387 } 388 } 389