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