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