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