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 // load dummy layout with OK button disabled until we override this layout in 348 // startInstallConfirm 349 bindUi(); 350 checkIfAllowedAndInitiateInstall(); 351 } 352 353 @Override onResume()354 protected void onResume() { 355 super.onResume(); 356 357 if (mOk != null) { 358 mOk.setEnabled(mEnableOk); 359 } 360 } 361 362 @Override onPause()363 protected void onPause() { 364 super.onPause(); 365 366 if (mOk != null) { 367 // Don't allow the install button to be clicked as there might be overlays 368 mOk.setEnabled(false); 369 } 370 } 371 372 @Override onSaveInstanceState(Bundle outState)373 protected void onSaveInstanceState(Bundle outState) { 374 super.onSaveInstanceState(outState); 375 376 outState.putBoolean(ALLOW_UNKNOWN_SOURCES_KEY, mAllowUnknownSources); 377 } 378 bindUi()379 private void bindUi() { 380 mAlert.setIcon(mAppSnippet.icon); 381 mAlert.setTitle(mAppSnippet.label); 382 mAlert.setView(R.layout.install_content_view); 383 mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install), 384 (ignored, ignored2) -> { 385 if (mOk.isEnabled()) { 386 if (mSessionId != -1) { 387 mInstaller.setPermissionsResult(mSessionId, true); 388 finish(); 389 } else { 390 startInstall(); 391 } 392 } 393 }, null); 394 mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel), 395 (ignored, ignored2) -> { 396 // Cancel and finish 397 setResult(RESULT_CANCELED); 398 if (mSessionId != -1) { 399 mInstaller.setPermissionsResult(mSessionId, false); 400 } 401 finish(); 402 }, null); 403 setupAlert(); 404 405 mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); 406 mOk.setEnabled(false); 407 408 if (!mOk.isInTouchMode()) { 409 mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus(); 410 } 411 } 412 413 /** 414 * Check if it is allowed to install the package and initiate install if allowed. If not allowed 415 * show the appropriate dialog. 416 */ checkIfAllowedAndInitiateInstall()417 private void checkIfAllowedAndInitiateInstall() { 418 // Check for install apps user restriction first. 419 final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource( 420 UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle()); 421 if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) { 422 showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER); 423 return; 424 } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) { 425 startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)); 426 finish(); 427 return; 428 } 429 430 if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) { 431 initiateInstall(); 432 } else { 433 // Check for unknown sources restrictions. 434 final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource( 435 UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle()); 436 final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource( 437 UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle()); 438 final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM 439 & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource); 440 if (systemRestriction != 0) { 441 showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER); 442 } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) { 443 startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); 444 } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) { 445 startAdminSupportDetailsActivity( 446 UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY); 447 } else { 448 handleUnknownSources(); 449 } 450 } 451 } 452 startAdminSupportDetailsActivity(String restriction)453 private void startAdminSupportDetailsActivity(String restriction) { 454 // If the given restriction is set by an admin, display information about the 455 // admin enforcing the restriction for the affected user. 456 final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class); 457 final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction); 458 if (showAdminSupportDetailsIntent != null) { 459 startActivity(showAdminSupportDetailsIntent); 460 } 461 finish(); 462 } 463 handleUnknownSources()464 private void handleUnknownSources() { 465 if (mOriginatingPackage == null) { 466 Log.i(TAG, "No source found for package " + mPkgInfo.packageName); 467 showDialogInner(DLG_ANONYMOUS_SOURCE); 468 return; 469 } 470 // Shouldn't use static constant directly, see b/65534401. 471 final int appOpCode = 472 AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES); 473 final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode, 474 mOriginatingUid, mOriginatingPackage); 475 switch (appOpMode) { 476 case AppOpsManager.MODE_DEFAULT: 477 mAppOpsManager.setMode(appOpCode, mOriginatingUid, 478 mOriginatingPackage, AppOpsManager.MODE_ERRORED); 479 // fall through 480 case AppOpsManager.MODE_ERRORED: 481 showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED); 482 break; 483 case AppOpsManager.MODE_ALLOWED: 484 initiateInstall(); 485 break; 486 default: 487 Log.e(TAG, "Invalid app op mode " + appOpMode 488 + " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid); 489 finish(); 490 break; 491 } 492 } 493 494 /** 495 * Parse the Uri and set up the installer for this package. 496 * 497 * @param packageUri The URI to parse 498 * 499 * @return {@code true} iff the installer could be set up 500 */ processPackageUri(final Uri packageUri)501 private boolean processPackageUri(final Uri packageUri) { 502 mPackageURI = packageUri; 503 504 final String scheme = packageUri.getScheme(); 505 506 switch (scheme) { 507 case SCHEME_PACKAGE: { 508 try { 509 mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(), 510 PackageManager.GET_PERMISSIONS 511 | PackageManager.MATCH_UNINSTALLED_PACKAGES); 512 } catch (NameNotFoundException e) { 513 } 514 if (mPkgInfo == null) { 515 Log.w(TAG, "Requested package " + packageUri.getScheme() 516 + " not available. Discontinuing installation"); 517 showDialogInner(DLG_PACKAGE_ERROR); 518 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); 519 return false; 520 } 521 mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo), 522 mPm.getApplicationIcon(mPkgInfo.applicationInfo)); 523 } break; 524 525 case ContentResolver.SCHEME_FILE: { 526 File sourceFile = new File(packageUri.getPath()); 527 mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile, 528 PackageManager.GET_PERMISSIONS); 529 530 // Check for parse errors 531 if (mPkgInfo == null) { 532 Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); 533 showDialogInner(DLG_PACKAGE_ERROR); 534 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); 535 return false; 536 } 537 mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); 538 } break; 539 540 default: { 541 throw new IllegalArgumentException("Unexpected URI scheme " + packageUri); 542 } 543 } 544 545 return true; 546 } 547 548 @Override onBackPressed()549 public void onBackPressed() { 550 if (mSessionId != -1) { 551 mInstaller.setPermissionsResult(mSessionId, false); 552 } 553 super.onBackPressed(); 554 } 555 startInstall()556 private void startInstall() { 557 // Start subactivity to actually install the application 558 Intent newIntent = new Intent(); 559 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, 560 mPkgInfo.applicationInfo); 561 newIntent.setData(mPackageURI); 562 newIntent.setClass(this, InstallInstalling.class); 563 String installerPackageName = getIntent().getStringExtra( 564 Intent.EXTRA_INSTALLER_PACKAGE_NAME); 565 if (mOriginatingURI != null) { 566 newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI); 567 } 568 if (mReferrerURI != null) { 569 newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI); 570 } 571 if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) { 572 newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid); 573 } 574 if (installerPackageName != null) { 575 newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, 576 installerPackageName); 577 } 578 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 579 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); 580 } 581 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 582 if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI); 583 startActivity(newIntent); 584 finish(); 585 } 586 587 /** 588 * A simple error dialog showing a message 589 */ 590 public static class SimpleErrorDialog extends DialogFragment { 591 private static final String MESSAGE_KEY = 592 SimpleErrorDialog.class.getName() + "MESSAGE_KEY"; 593 newInstance(@tringRes int message)594 static SimpleErrorDialog newInstance(@StringRes int message) { 595 SimpleErrorDialog dialog = new SimpleErrorDialog(); 596 597 Bundle args = new Bundle(); 598 args.putInt(MESSAGE_KEY, message); 599 dialog.setArguments(args); 600 601 return dialog; 602 } 603 604 @Override onCreateDialog(Bundle savedInstanceState)605 public Dialog onCreateDialog(Bundle savedInstanceState) { 606 return new AlertDialog.Builder(getActivity()) 607 .setMessage(getArguments().getInt(MESSAGE_KEY)) 608 .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish()) 609 .create(); 610 } 611 } 612 613 /** 614 * Dialog to show when the source of apk can not be identified 615 */ 616 public static class AnonymousSourceDialog extends DialogFragment { newInstance()617 static AnonymousSourceDialog newInstance() { 618 return new AnonymousSourceDialog(); 619 } 620 621 @Override onCreateDialog(Bundle savedInstanceState)622 public Dialog onCreateDialog(Bundle savedInstanceState) { 623 return new AlertDialog.Builder(getActivity()) 624 .setMessage(R.string.anonymous_source_warning) 625 .setPositiveButton(R.string.anonymous_source_continue, 626 ((dialog, which) -> { 627 PackageInstallerActivity activity = ((PackageInstallerActivity) 628 getActivity()); 629 630 activity.mAllowUnknownSources = true; 631 activity.initiateInstall(); 632 })) 633 .setNegativeButton(R.string.cancel, ((dialog, which) -> getActivity().finish())) 634 .create(); 635 } 636 637 @Override onCancel(DialogInterface dialog)638 public void onCancel(DialogInterface dialog) { 639 getActivity().finish(); 640 } 641 } 642 643 /** 644 * An error dialog shown when the app is not supported on wear 645 */ 646 public static class NotSupportedOnWearDialog extends SimpleErrorDialog { newInstance()647 static SimpleErrorDialog newInstance() { 648 return SimpleErrorDialog.newInstance(R.string.wear_not_allowed_dlg_text); 649 } 650 651 @Override onCancel(DialogInterface dialog)652 public void onCancel(DialogInterface dialog) { 653 getActivity().setResult(RESULT_OK); 654 getActivity().finish(); 655 } 656 } 657 658 /** 659 * An error dialog shown when the device is out of space 660 */ 661 public static class OutOfSpaceDialog extends AppErrorDialog { newInstance(@onNull CharSequence applicationLabel)662 static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) { 663 OutOfSpaceDialog dialog = new OutOfSpaceDialog(); 664 dialog.setArgument(applicationLabel); 665 return dialog; 666 } 667 668 @Override createDialog(@onNull CharSequence argument)669 protected Dialog createDialog(@NonNull CharSequence argument) { 670 String dlgText = getString(R.string.out_of_space_dlg_text, argument); 671 return new AlertDialog.Builder(getActivity()) 672 .setMessage(dlgText) 673 .setPositiveButton(R.string.manage_applications, (dialog, which) -> { 674 // launch manage applications 675 Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); 676 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 677 startActivity(intent); 678 getActivity().finish(); 679 }) 680 .setNegativeButton(R.string.cancel, (dialog, which) -> getActivity().finish()) 681 .create(); 682 } 683 } 684 685 /** 686 * A generic install-error dialog 687 */ 688 public static class InstallErrorDialog extends AppErrorDialog { 689 static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) { 690 InstallErrorDialog dialog = new InstallErrorDialog(); 691 dialog.setArgument(applicationLabel); 692 return dialog; 693 } 694 695 @Override 696 protected Dialog createDialog(@NonNull CharSequence argument) { 697 return new AlertDialog.Builder(getActivity()) 698 .setNeutralButton(R.string.ok, (dialog, which) -> getActivity().finish()) 699 .setMessage(getString(R.string.install_failed_msg, argument)) 700 .create(); 701 } 702 } 703 704 /** 705 * An error dialog shown when external sources are not allowed 706 */ 707 public static class ExternalSourcesBlockedDialog extends AppErrorDialog { 708 static AppErrorDialog newInstance(@NonNull String originationPkg) { 709 ExternalSourcesBlockedDialog dialog = new ExternalSourcesBlockedDialog(); 710 dialog.setArgument(originationPkg); 711 return dialog; 712 } 713 714 @Override 715 protected Dialog createDialog(@NonNull CharSequence argument) { 716 try { 717 PackageManager pm = getActivity().getPackageManager(); 718 719 ApplicationInfo sourceInfo = pm.getApplicationInfo(argument.toString(), 0); 720 721 return new AlertDialog.Builder(getActivity()) 722 .setTitle(pm.getApplicationLabel(sourceInfo)) 723 .setIcon(pm.getApplicationIcon(sourceInfo)) 724 .setMessage(R.string.untrusted_external_source_warning) 725 .setPositiveButton(R.string.external_sources_settings, 726 (dialog, which) -> { 727 Intent settingsIntent = new Intent(); 728 settingsIntent.setAction( 729 Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); 730 final Uri packageUri = Uri.parse("package:" + argument); 731 settingsIntent.setData(packageUri); 732 try { 733 getActivity().startActivityForResult(settingsIntent, 734 REQUEST_TRUST_EXTERNAL_SOURCE); 735 } catch (ActivityNotFoundException exc) { 736 Log.e(TAG, "Settings activity not found for action: " 737 + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); 738 } 739 }) 740 .setNegativeButton(R.string.cancel, 741 (dialog, which) -> getActivity().finish()) 742 .create(); 743 } catch (NameNotFoundException e) { 744 Log.e(TAG, "Did not find app info for " + argument); 745 getActivity().finish(); 746 return null; 747 } 748 } 749 } 750 751 /** 752 * Superclass for all error dialogs. Stores a single CharSequence argument 753 */ 754 public abstract static class AppErrorDialog extends DialogFragment { 755 private static final String ARGUMENT_KEY = AppErrorDialog.class.getName() + "ARGUMENT_KEY"; 756 757 protected void setArgument(@NonNull CharSequence argument) { 758 Bundle args = new Bundle(); 759 args.putCharSequence(ARGUMENT_KEY, argument); 760 setArguments(args); 761 } 762 763 protected abstract Dialog createDialog(@NonNull CharSequence argument); 764 765 @Override 766 public Dialog onCreateDialog(Bundle savedInstanceState) { 767 return createDialog(getArguments().getString(ARGUMENT_KEY)); 768 } 769 770 @Override 771 public void onCancel(DialogInterface dialog) { 772 getActivity().finish(); 773 } 774 } 775 } 776