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