1 /* 2 ** 3 ** Copyright 2007, The Android Open Source Project 4 ** 5 ** Licensed under the Apache License, Version 2.0 (the "License"); 6 ** you may not use this file except in compliance with the License. 7 ** You may obtain a copy of the License at 8 ** 9 ** http://www.apache.org/licenses/LICENSE-2.0 10 ** 11 ** Unless required by applicable law or agreed to in writing, software 12 ** distributed under the License is distributed on an "AS IS" BASIS, 13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ** See the License for the specific language governing permissions and 15 ** limitations under the License. 16 */ 17 package com.android.packageinstaller; 18 19 import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY; 20 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 21 22 import android.Manifest; 23 import android.app.Activity; 24 import android.app.AlertDialog; 25 import android.app.AppOpsManager; 26 import android.app.Dialog; 27 import android.app.DialogFragment; 28 import android.content.ActivityNotFoundException; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.content.DialogInterface; 32 import android.content.Intent; 33 import android.content.pm.ApplicationInfo; 34 import android.content.pm.InstallSourceInfo; 35 import android.content.pm.PackageInfo; 36 import android.content.pm.PackageInstaller; 37 import android.content.pm.PackageInstaller.SessionInfo; 38 import android.content.pm.PackageManager; 39 import android.content.pm.PackageManager.ApplicationInfoFlags; 40 import android.content.pm.PackageManager.NameNotFoundException; 41 import android.graphics.drawable.BitmapDrawable; 42 import android.net.Uri; 43 import android.os.Bundle; 44 import android.os.Handler; 45 import android.os.Looper; 46 import android.os.Process; 47 import android.os.UserHandle; 48 import android.os.UserManager; 49 import android.provider.Settings; 50 import android.text.TextUtils; 51 import android.util.Log; 52 import android.view.View; 53 import android.widget.Button; 54 import android.widget.TextView; 55 56 import androidx.annotation.NonNull; 57 58 import java.io.File; 59 import java.util.ArrayList; 60 import java.util.List; 61 62 /** 63 * This activity is launched when a new application is installed via side loading 64 * The package is first parsed and the user is notified of parse errors via a dialog. 65 * If the package is successfully parsed, the user is notified to turn on the install unknown 66 * applications setting. A memory check is made at this point and the user is notified of out 67 * of memory conditions if any. If the package is already existing on the device, 68 * a confirmation dialog (to replace the existing package) is presented to the user. 69 * Based on the user response the package is then installed by launching InstallAppConfirm 70 * sub activity. All state transitions are handled in this activity 71 */ 72 public class PackageInstallerActivity extends AlertActivity { 73 private static final String TAG = "PackageInstaller"; 74 75 private static final int REQUEST_TRUST_EXTERNAL_SOURCE = 1; 76 77 static final String SCHEME_PACKAGE = "package"; 78 79 static final String EXTRA_CALLING_PACKAGE = "EXTRA_CALLING_PACKAGE"; 80 static final String EXTRA_CALLING_ATTRIBUTION_TAG = "EXTRA_CALLING_ATTRIBUTION_TAG"; 81 static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO"; 82 static final String EXTRA_STAGED_SESSION_ID = "EXTRA_STAGED_SESSION_ID"; 83 static final String EXTRA_APP_SNIPPET = "EXTRA_APP_SNIPPET"; 84 private static final String ALLOW_UNKNOWN_SOURCES_KEY = 85 PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY"; 86 87 private int mSessionId = -1; 88 private Uri mPackageURI; 89 private Uri mOriginatingURI; 90 private Uri mReferrerURI; 91 private int mOriginatingUid = Process.INVALID_UID; 92 private String mOriginatingPackage; // The package name corresponding to #mOriginatingUid 93 private int mActivityResultCode = Activity.RESULT_CANCELED; 94 private int mPendingUserActionReason = -1; 95 96 private final boolean mLocalLOGV = false; 97 PackageManager mPm; 98 AppOpsManager mAppOpsManager; 99 UserManager mUserManager; 100 PackageInstaller mInstaller; 101 PackageInfo mPkgInfo; 102 String mCallingPackage; 103 private String mCallingAttributionTag; 104 ApplicationInfo mSourceInfo; 105 106 /** 107 * A collection of unknown sources listeners that are actively listening for app ops mode 108 * changes 109 */ 110 private List<UnknownSourcesListener> mActiveUnknownSourcesListeners = new ArrayList<>(1); 111 112 // ApplicationInfo object primarily used for already existing applications 113 private ApplicationInfo mAppInfo; 114 115 // Buttons to indicate user acceptance 116 private Button mOk; 117 118 private PackageUtil.AppSnippet mAppSnippet; 119 120 static final String PREFS_ALLOWED_SOURCES = "allowed_sources"; 121 122 // Dialog identifiers used in showDialog 123 private static final int DLG_BASE = 0; 124 private static final int DLG_PACKAGE_ERROR = DLG_BASE + 1; 125 private static final int DLG_OUT_OF_SPACE = DLG_BASE + 2; 126 private static final int DLG_INSTALL_ERROR = DLG_BASE + 3; 127 private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 4; 128 private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 5; 129 130 // If unknown sources are temporary allowed 131 private boolean mAllowUnknownSources; 132 133 // Would the mOk button be enabled if this activity would be resumed 134 private boolean mEnableOk = false; 135 startInstallConfirm()136 private void startInstallConfirm() { 137 TextView viewToEnable; 138 139 if (mAppInfo != null) { 140 viewToEnable = requireViewById(R.id.install_confirm_question_update); 141 142 final CharSequence existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(); 143 final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage); 144 if (!TextUtils.isEmpty(existingUpdateOwnerLabel) 145 && mPendingUserActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) { 146 viewToEnable.setText( 147 getString(R.string.install_confirm_question_update_owner_reminder, 148 requestedUpdateOwnerLabel, existingUpdateOwnerLabel)); 149 mOk.setText(R.string.update_anyway); 150 } else { 151 mOk.setText(R.string.update); 152 } 153 } else { 154 // This is a new application with no permissions. 155 viewToEnable = requireViewById(R.id.install_confirm_question); 156 } 157 158 viewToEnable.setVisibility(View.VISIBLE); 159 160 mEnableOk = true; 161 mOk.setEnabled(true); 162 mOk.setFilterTouchesWhenObscured(true); 163 } 164 getExistingUpdateOwnerLabel()165 private CharSequence getExistingUpdateOwnerLabel() { 166 try { 167 final String packageName = mPkgInfo.packageName; 168 final InstallSourceInfo sourceInfo = mPm.getInstallSourceInfo(packageName); 169 final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName(); 170 return getApplicationLabel(existingUpdateOwner); 171 } catch (NameNotFoundException e) { 172 return null; 173 } 174 } 175 getApplicationLabel(String packageName)176 private CharSequence getApplicationLabel(String packageName) { 177 try { 178 final ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 179 ApplicationInfoFlags.of(0)); 180 return mPm.getApplicationLabel(appInfo); 181 } catch (NameNotFoundException e) { 182 return null; 183 } 184 } 185 186 /** 187 * Replace any dialog shown by the dialog with the one for the given {@link #createDialog(int)}. 188 * 189 * @param id The dialog type to add 190 */ showDialogInner(int id)191 private void showDialogInner(int id) { 192 if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + id + ")"); 193 DialogFragment currentDialog = 194 (DialogFragment) getFragmentManager().findFragmentByTag("dialog"); 195 if (currentDialog != null) { 196 currentDialog.dismissAllowingStateLoss(); 197 } 198 199 DialogFragment newDialog = createDialog(id); 200 if (newDialog != null) { 201 getFragmentManager().beginTransaction() 202 .add(newDialog, "dialog").commitAllowingStateLoss(); 203 } 204 } 205 206 /** 207 * Create a new dialog. 208 * 209 * @param id The id of the dialog (determines dialog type) 210 * 211 * @return The dialog 212 */ createDialog(int id)213 private DialogFragment createDialog(int id) { 214 if (mLocalLOGV) Log.i(TAG, "createDialog(" + id + ")"); 215 switch (id) { 216 case DLG_PACKAGE_ERROR: 217 return PackageUtil.SimpleErrorDialog.newInstance(R.string.Parse_error_dlg_text); 218 case DLG_OUT_OF_SPACE: 219 return OutOfSpaceDialog.newInstance( 220 mPm.getApplicationLabel(mPkgInfo.applicationInfo)); 221 case DLG_INSTALL_ERROR: 222 return InstallErrorDialog.newInstance( 223 mPm.getApplicationLabel(mPkgInfo.applicationInfo)); 224 case DLG_EXTERNAL_SOURCE_BLOCKED: 225 return ExternalSourcesBlockedDialog.newInstance(mOriginatingPackage); 226 case DLG_ANONYMOUS_SOURCE: 227 return AnonymousSourceDialog.newInstance(); 228 } 229 return null; 230 } 231 232 @Override onActivityResult(int request, int result, Intent data)233 public void onActivityResult(int request, int result, Intent data) { 234 if (request == REQUEST_TRUST_EXTERNAL_SOURCE) { 235 // Log the fact that the app is requesting an install, and is now allowed to do it 236 // (before this point we could only log that it's requesting an install, but isn't 237 // allowed to do it yet). 238 String appOpStr = 239 AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES); 240 int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid, 241 mOriginatingPackage, mCallingAttributionTag, 242 "Successfully started package installation activity"); 243 if (appOpMode == AppOpsManager.MODE_ALLOWED) { 244 // The user has just allowed this package to install other packages 245 // (via Settings). 246 mAllowUnknownSources = true; 247 DialogFragment currentDialog = 248 (DialogFragment) getFragmentManager().findFragmentByTag("dialog"); 249 if (currentDialog != null) { 250 currentDialog.dismissAllowingStateLoss(); 251 } 252 initiateInstall(); 253 } else { 254 finish(); 255 } 256 } else { 257 finish(); 258 } 259 } 260 getPackageNameForUid(int sourceUid)261 private String getPackageNameForUid(int sourceUid) { 262 String[] packagesForUid = mPm.getPackagesForUid(sourceUid); 263 if (packagesForUid == null) { 264 return null; 265 } 266 if (packagesForUid.length > 1) { 267 if (mCallingPackage != null) { 268 for (String packageName : packagesForUid) { 269 if (packageName.equals(mCallingPackage)) { 270 return packageName; 271 } 272 } 273 } 274 Log.i(TAG, "Multiple packages found for source uid " + sourceUid); 275 } 276 return packagesForUid[0]; 277 } 278 isInstallRequestFromUnknownSource(Intent intent)279 private boolean isInstallRequestFromUnknownSource(Intent intent) { 280 if (mCallingPackage != null && intent.getBooleanExtra( 281 Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) { 282 if (mSourceInfo != null && mSourceInfo.isPrivilegedApp()) { 283 // Privileged apps can bypass unknown sources check if they want. 284 return false; 285 } 286 } 287 if (mSourceInfo != null && checkPermission(Manifest.permission.INSTALL_PACKAGES, 288 -1 /* pid */, mSourceInfo.uid) == PackageManager.PERMISSION_GRANTED) { 289 return false; 290 } 291 return true; 292 } 293 initiateInstall()294 private void initiateInstall() { 295 String pkgName = mPkgInfo.packageName; 296 // Check if there is already a package on the device with this name 297 // but it has been renamed to something else. 298 String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName }); 299 if (oldName != null && oldName.length > 0 && oldName[0] != null) { 300 pkgName = oldName[0]; 301 mPkgInfo.packageName = pkgName; 302 mPkgInfo.applicationInfo.packageName = pkgName; 303 } 304 // Check if package is already installed. display confirmation dialog if replacing pkg 305 try { 306 // This is a little convoluted because we want to get all uninstalled 307 // apps, but this may include apps with just data, and if it is just 308 // data we still want to count it as "installed". 309 mAppInfo = mPm.getApplicationInfo(pkgName, 310 PackageManager.MATCH_UNINSTALLED_PACKAGES); 311 if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { 312 mAppInfo = null; 313 } 314 } catch (NameNotFoundException e) { 315 mAppInfo = null; 316 } 317 318 startInstallConfirm(); 319 } 320 setPmResult(int pmResult)321 void setPmResult(int pmResult) { 322 Intent result = new Intent(); 323 result.putExtra(Intent.EXTRA_INSTALL_RESULT, pmResult); 324 setResult(pmResult == PackageManager.INSTALL_SUCCEEDED 325 ? RESULT_OK : RESULT_FIRST_USER, result); 326 } 327 generateStubPackageInfo(String packageName)328 private static PackageInfo generateStubPackageInfo(String packageName) { 329 final PackageInfo info = new PackageInfo(); 330 final ApplicationInfo aInfo = new ApplicationInfo(); 331 info.applicationInfo = aInfo; 332 info.packageName = info.applicationInfo.packageName = packageName; 333 return info; 334 } 335 336 @Override onCreate(Bundle icicle)337 protected void onCreate(Bundle icicle) { 338 if (mLocalLOGV) Log.i(TAG, "creating for user " + UserHandle.myUserId()); 339 getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 340 341 super.onCreate(null); 342 343 if (icicle != null) { 344 mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY); 345 } 346 setFinishOnTouchOutside(true); 347 348 mPm = getPackageManager(); 349 mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); 350 mInstaller = mPm.getPackageInstaller(); 351 mUserManager = (UserManager) getSystemService(Context.USER_SERVICE); 352 353 final Intent intent = getIntent(); 354 final String action = intent.getAction(); 355 356 mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE); 357 mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG); 358 mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO); 359 mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, 360 Process.INVALID_UID); 361 mOriginatingPackage = (mOriginatingUid != Process.INVALID_UID) 362 ? getPackageNameForUid(mOriginatingUid) : null; 363 364 final Object packageSource; 365 if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(action)) { 366 final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 367 -1 /* defaultValue */); 368 final SessionInfo info = mInstaller.getSessionInfo(sessionId); 369 String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null; 370 if (info == null || !info.isSealed() || resolvedPath == null) { 371 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring"); 372 finish(); 373 return; 374 } 375 376 mSessionId = sessionId; 377 packageSource = Uri.fromFile(new File(resolvedPath)); 378 mOriginatingURI = null; 379 mReferrerURI = null; 380 mPendingUserActionReason = info.getPendingUserActionReason(); 381 } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(action)) { 382 final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 383 -1 /* defaultValue */); 384 final SessionInfo info = mInstaller.getSessionInfo(sessionId); 385 if (info == null || !info.isPreApprovalRequested()) { 386 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring"); 387 finish(); 388 return; 389 } 390 391 mSessionId = sessionId; 392 packageSource = info; 393 mOriginatingURI = null; 394 mReferrerURI = null; 395 mPendingUserActionReason = info.getPendingUserActionReason(); 396 } else { 397 // Two possible callers: 398 // 1. InstallStart with "SCHEME_PACKAGE". 399 // 2. InstallStaging with "SCHEME_FILE" and EXTRA_STAGED_SESSION_ID with staged 400 // session id. 401 mSessionId = -1; 402 packageSource = intent.getData(); 403 mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); 404 mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER); 405 mPendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE; 406 } 407 408 // if there's nothing to do, quietly slip into the ether 409 if (packageSource == null) { 410 Log.w(TAG, "Unspecified source"); 411 setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI); 412 finish(); 413 return; 414 } 415 416 final boolean wasSetUp = processAppSnippet(packageSource); 417 418 if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp); 419 420 if (!wasSetUp) { 421 return; 422 } 423 } 424 425 @Override onResume()426 protected void onResume() { 427 super.onResume(); 428 429 if (mLocalLOGV) Log.i(TAG, "onResume(): mAppSnippet=" + mAppSnippet); 430 431 if (mAppSnippet != null) { 432 // load dummy layout with OK button disabled until we override this layout in 433 // startInstallConfirm 434 bindUi(); 435 checkIfAllowedAndInitiateInstall(); 436 } 437 438 if (mOk != null) { 439 mOk.setEnabled(mEnableOk); 440 } 441 } 442 443 @Override onPause()444 protected void onPause() { 445 super.onPause(); 446 447 if (mOk != null) { 448 // Don't allow the install button to be clicked as there might be overlays 449 mOk.setEnabled(false); 450 } 451 } 452 453 @Override onSaveInstanceState(Bundle outState)454 protected void onSaveInstanceState(Bundle outState) { 455 super.onSaveInstanceState(outState); 456 457 outState.putBoolean(ALLOW_UNKNOWN_SOURCES_KEY, mAllowUnknownSources); 458 } 459 460 @Override onDestroy()461 protected void onDestroy() { 462 super.onDestroy(); 463 while (!mActiveUnknownSourcesListeners.isEmpty()) { 464 unregister(mActiveUnknownSourcesListeners.get(0)); 465 } 466 } 467 bindUi()468 private void bindUi() { 469 mAlert.setIcon(mAppSnippet.icon); 470 mAlert.setTitle(mAppSnippet.label); 471 mAlert.setView(R.layout.install_content_view); 472 mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install), 473 (ignored, ignored2) -> { 474 if (mOk.isEnabled()) { 475 if (mSessionId != -1) { 476 setActivityResult(RESULT_OK); 477 finish(); 478 } else { 479 startInstall(); 480 } 481 } 482 }, null); 483 mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel), 484 (ignored, ignored2) -> { 485 // Cancel and finish 486 setActivityResult(RESULT_CANCELED); 487 finish(); 488 }, null); 489 setupAlert(); 490 491 mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); 492 mOk.setEnabled(false); 493 494 if (!mOk.isInTouchMode()) { 495 mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus(); 496 } 497 } 498 setActivityResult(int resultCode)499 private void setActivityResult(int resultCode) { 500 mActivityResultCode = resultCode; 501 super.setResult(resultCode); 502 } 503 504 @Override finish()505 public void finish() { 506 if (mSessionId != -1) { 507 if (mActivityResultCode == Activity.RESULT_OK) { 508 mInstaller.setPermissionsResult(mSessionId, true); 509 } else { 510 mInstaller.setPermissionsResult(mSessionId, false); 511 } 512 } 513 super.finish(); 514 } 515 516 /** 517 * Check if it is allowed to install the package and initiate install if allowed. 518 */ checkIfAllowedAndInitiateInstall()519 private void checkIfAllowedAndInitiateInstall() { 520 if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) { 521 if (mLocalLOGV) Log.i(TAG, "install allowed"); 522 initiateInstall(); 523 } else { 524 handleUnknownSources(); 525 } 526 } 527 handleUnknownSources()528 private void handleUnknownSources() { 529 if (mOriginatingPackage == null) { 530 Log.i(TAG, "No source found for package " + mPkgInfo.packageName); 531 showDialogInner(DLG_ANONYMOUS_SOURCE); 532 return; 533 } 534 // Shouldn't use static constant directly, see b/65534401. 535 final String appOpStr = 536 AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES); 537 final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid, 538 mOriginatingPackage, mCallingAttributionTag, 539 "Started package installation activity"); 540 if (mLocalLOGV) Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode); 541 switch (appOpMode) { 542 case AppOpsManager.MODE_DEFAULT: 543 mAppOpsManager.setMode(appOpStr, mOriginatingUid, 544 mOriginatingPackage, AppOpsManager.MODE_ERRORED); 545 // fall through 546 case AppOpsManager.MODE_ERRORED: 547 showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED); 548 break; 549 case AppOpsManager.MODE_ALLOWED: 550 initiateInstall(); 551 break; 552 default: 553 Log.e(TAG, "Invalid app op mode " + appOpMode 554 + " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid); 555 finish(); 556 break; 557 } 558 } 559 560 /** 561 * Parse the Uri and set up the installer for this package. 562 * 563 * @param packageUri The URI to parse 564 * 565 * @return {@code true} iff the installer could be set up 566 */ processPackageUri(final Uri packageUri)567 private boolean processPackageUri(final Uri packageUri) { 568 mPackageURI = packageUri; 569 final String scheme = packageUri.getScheme(); 570 final String packageName = packageUri.getSchemeSpecificPart(); 571 572 if (mLocalLOGV) Log.i(TAG, "processPackageUri(): uri=" + packageUri + ", scheme=" + scheme); 573 574 switch (scheme) { 575 case SCHEME_PACKAGE: { 576 for (UserHandle handle : mUserManager.getUserHandles(true)) { 577 PackageManager pmForUser = createContextAsUser(handle, 0) 578 .getPackageManager(); 579 try { 580 if (pmForUser.canPackageQuery(mCallingPackage, packageName)) { 581 mPkgInfo = pmForUser.getPackageInfo(packageName, 582 PackageManager.GET_PERMISSIONS 583 | PackageManager.MATCH_UNINSTALLED_PACKAGES); 584 } 585 } catch (NameNotFoundException e) { 586 } 587 } 588 if (mPkgInfo == null) { 589 Log.w(TAG, "Requested package " + packageUri.getSchemeSpecificPart() 590 + " not available. Discontinuing installation"); 591 showDialogInner(DLG_PACKAGE_ERROR); 592 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); 593 return false; 594 } 595 CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo); 596 if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label); 597 mAppSnippet = new PackageUtil.AppSnippet(label, 598 mPm.getApplicationIcon(mPkgInfo.applicationInfo)); 599 } break; 600 601 case ContentResolver.SCHEME_FILE: { 602 File sourceFile = new File(packageUri.getPath()); 603 mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile, 604 PackageManager.GET_PERMISSIONS); 605 606 // Check for parse errors 607 if (mPkgInfo == null) { 608 Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); 609 showDialogInner(DLG_PACKAGE_ERROR); 610 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); 611 return false; 612 } 613 if (mLocalLOGV) Log.i(TAG, "creating snippet for local file " + sourceFile); 614 mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); 615 } break; 616 617 default: { 618 throw new IllegalArgumentException("Unexpected URI scheme " + packageUri); 619 } 620 } 621 622 return true; 623 } 624 625 /** 626 * Use the SessionInfo and set up the installer for pre-commit install session. 627 * 628 * @param info The SessionInfo to compose 629 * 630 * @return {@code true} iff the installer could be set up 631 */ processSessionInfo(@onNull SessionInfo info)632 private boolean processSessionInfo(@NonNull SessionInfo info) { 633 mPkgInfo = generateStubPackageInfo(info.getAppPackageName()); 634 mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(), 635 info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon()) 636 : getPackageManager().getDefaultActivityIcon()); 637 return true; 638 } 639 640 /** 641 * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install 642 * session) to set up the installer for this install. 643 * 644 * @param source The source of package URI or SessionInfo 645 * 646 * @return {@code true} iff the installer could be set up 647 */ processAppSnippet(@onNull Object source)648 private boolean processAppSnippet(@NonNull Object source) { 649 if (source instanceof Uri) { 650 return processPackageUri((Uri) source); 651 } else if (source instanceof SessionInfo) { 652 return processSessionInfo((SessionInfo) source); 653 } 654 655 return false; 656 } 657 658 @Override onBackPressed()659 public void onBackPressed() { 660 if (mSessionId != -1) { 661 setActivityResult(RESULT_CANCELED); 662 } 663 super.onBackPressed(); 664 } 665 startInstall()666 private void startInstall() { 667 String installerPackageName = getIntent().getStringExtra( 668 Intent.EXTRA_INSTALLER_PACKAGE_NAME); 669 int stagedSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0); 670 671 // Start subactivity to actually install the application 672 Intent newIntent = new Intent(); 673 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, 674 mPkgInfo.applicationInfo); 675 newIntent.setData(mPackageURI); 676 newIntent.setClass(this, InstallInstalling.class); 677 if (mOriginatingURI != null) { 678 newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI); 679 } 680 if (mReferrerURI != null) { 681 newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI); 682 } 683 if (mOriginatingUid != Process.INVALID_UID) { 684 newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid); 685 } 686 if (installerPackageName != null) { 687 newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, 688 installerPackageName); 689 } 690 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 691 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); 692 } 693 if (stagedSessionId > 0) { 694 newIntent.putExtra(EXTRA_STAGED_SESSION_ID, stagedSessionId); 695 } 696 if (mAppSnippet != null) { 697 newIntent.putExtra(EXTRA_APP_SNIPPET, mAppSnippet); 698 } 699 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 700 if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI); 701 startActivity(newIntent); 702 finish(); 703 } 704 705 /** 706 * Dialog to show when the source of apk can not be identified 707 */ 708 public static class AnonymousSourceDialog extends DialogFragment { newInstance()709 static AnonymousSourceDialog newInstance() { 710 return new AnonymousSourceDialog(); 711 } 712 713 @Override onCreateDialog(Bundle savedInstanceState)714 public Dialog onCreateDialog(Bundle savedInstanceState) { 715 return new AlertDialog.Builder(getActivity()) 716 .setMessage(R.string.anonymous_source_warning) 717 .setPositiveButton(R.string.anonymous_source_continue, 718 ((dialog, which) -> { 719 PackageInstallerActivity activity = ((PackageInstallerActivity) 720 getActivity()); 721 722 activity.mAllowUnknownSources = true; 723 activity.initiateInstall(); 724 })) 725 .setNegativeButton(R.string.cancel, ((dialog, which) -> getActivity().finish())) 726 .create(); 727 } 728 729 @Override onCancel(DialogInterface dialog)730 public void onCancel(DialogInterface dialog) { 731 getActivity().finish(); 732 } 733 } 734 735 /** 736 * An error dialog shown when the device is out of space 737 */ 738 public static class OutOfSpaceDialog extends AppErrorDialog { newInstance(@onNull CharSequence applicationLabel)739 static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) { 740 OutOfSpaceDialog dialog = new OutOfSpaceDialog(); 741 dialog.setArgument(applicationLabel); 742 return dialog; 743 } 744 745 @Override createDialog(@onNull CharSequence argument)746 protected Dialog createDialog(@NonNull CharSequence argument) { 747 String dlgText = getString(R.string.out_of_space_dlg_text, argument); 748 return new AlertDialog.Builder(getActivity()) 749 .setMessage(dlgText) 750 .setPositiveButton(R.string.manage_applications, (dialog, which) -> { 751 // launch manage applications 752 Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); 753 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 754 startActivity(intent); 755 getActivity().finish(); 756 }) 757 .setNegativeButton(R.string.cancel, (dialog, which) -> getActivity().finish()) 758 .create(); 759 } 760 } 761 762 /** 763 * A generic install-error dialog 764 */ 765 public static class InstallErrorDialog extends AppErrorDialog { 766 static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) { 767 InstallErrorDialog dialog = new InstallErrorDialog(); 768 dialog.setArgument(applicationLabel); 769 return dialog; 770 } 771 772 @Override 773 protected Dialog createDialog(@NonNull CharSequence argument) { 774 return new AlertDialog.Builder(getActivity()) 775 .setNeutralButton(R.string.ok, (dialog, which) -> getActivity().finish()) 776 .setMessage(getString(R.string.install_failed_msg, argument)) 777 .create(); 778 } 779 } 780 781 private class UnknownSourcesListener implements AppOpsManager.OnOpChangedListener { 782 783 @Override 784 public void onOpChanged(String op, String packageName) { 785 if (!mOriginatingPackage.equals(packageName)) { 786 return; 787 } 788 unregister(this); 789 mActiveUnknownSourcesListeners.remove(this); 790 if (isDestroyed()) { 791 return; 792 } 793 new Handler(Looper.getMainLooper()).postDelayed(() -> { 794 if (!isDestroyed()) { 795 startActivity(getIntent()); 796 // The start flag (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP) doesn't 797 // work for the multiple user case, i.e. the caller task user and started 798 // Activity user are not the same. To avoid having multiple PIAs in the task, 799 // finish the current PackageInstallerActivity 800 finish(); 801 } 802 }, 500); 803 804 } 805 806 } 807 808 private void register(UnknownSourcesListener listener) { 809 mAppOpsManager.startWatchingMode( 810 AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, mOriginatingPackage, 811 listener); 812 mActiveUnknownSourcesListeners.add(listener); 813 } 814 815 private void unregister(UnknownSourcesListener listener) { 816 mAppOpsManager.stopWatchingMode(listener); 817 mActiveUnknownSourcesListeners.remove(listener); 818 } 819 820 /** 821 * An error dialog shown when external sources are not allowed 822 */ 823 public static class ExternalSourcesBlockedDialog extends AppErrorDialog { 824 static AppErrorDialog newInstance(@NonNull String originationPkg) { 825 ExternalSourcesBlockedDialog dialog = 826 new ExternalSourcesBlockedDialog(); 827 dialog.setArgument(originationPkg); 828 return dialog; 829 } 830 831 @Override 832 protected Dialog createDialog(@NonNull CharSequence argument) { 833 834 final PackageInstallerActivity activity = (PackageInstallerActivity)getActivity(); 835 try { 836 PackageManager pm = activity.getPackageManager(); 837 838 ApplicationInfo sourceInfo = pm.getApplicationInfo(argument.toString(), 0); 839 840 return new AlertDialog.Builder(activity) 841 .setTitle(pm.getApplicationLabel(sourceInfo)) 842 .setIcon(pm.getApplicationIcon(sourceInfo)) 843 .setMessage(R.string.untrusted_external_source_warning) 844 .setPositiveButton(R.string.external_sources_settings, 845 (dialog, which) -> { 846 Intent settingsIntent = new Intent(); 847 settingsIntent.setAction( 848 Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); 849 final Uri packageUri = Uri.parse("package:" + argument); 850 settingsIntent.setData(packageUri); 851 settingsIntent.setFlags(FLAG_ACTIVITY_NO_HISTORY); 852 try { 853 activity.register(activity.new UnknownSourcesListener()); 854 activity.startActivityForResult(settingsIntent, 855 REQUEST_TRUST_EXTERNAL_SOURCE); 856 } catch (ActivityNotFoundException exc) { 857 Log.e(TAG, "Settings activity not found for action: " 858 + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); 859 } 860 }) 861 .setNegativeButton(R.string.cancel, 862 (dialog, which) -> activity.finish()) 863 .create(); 864 } catch (NameNotFoundException e) { 865 Log.e(TAG, "Did not find app info for " + argument); 866 activity.finish(); 867 return null; 868 } 869 } 870 } 871 872 /** 873 * Superclass for all error dialogs. Stores a single CharSequence argument 874 */ 875 public abstract static class AppErrorDialog extends DialogFragment { 876 private static final String ARGUMENT_KEY = AppErrorDialog.class.getName() + "ARGUMENT_KEY"; 877 878 protected void setArgument(@NonNull CharSequence argument) { 879 Bundle args = new Bundle(); 880 args.putCharSequence(ARGUMENT_KEY, argument); 881 setArguments(args); 882 } 883 884 protected abstract Dialog createDialog(@NonNull CharSequence argument); 885 886 @Override 887 public Dialog onCreateDialog(Bundle savedInstanceState) { 888 return createDialog(getArguments().getString(ARGUMENT_KEY)); 889 } 890 891 @Override 892 public void onCancel(DialogInterface dialog) { 893 getActivity().finish(); 894 } 895 } 896 } 897