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