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