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