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.app.Activity; 20 import android.app.ActivityManagerNative; 21 import android.app.AlertDialog; 22 import android.app.Dialog; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.DialogInterface.OnCancelListener; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageInstaller; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PackageManager.NameNotFoundException; 32 import android.content.pm.PackageParser; 33 import android.content.pm.PackageUserState; 34 import android.content.pm.VerificationParams; 35 import android.graphics.drawable.Drawable; 36 import android.net.Uri; 37 import android.os.AsyncTask; 38 import android.os.Build; 39 import android.os.Bundle; 40 import android.os.UserManager; 41 import android.provider.Settings; 42 import android.support.v4.view.ViewPager; 43 import android.util.Log; 44 import android.view.LayoutInflater; 45 import android.view.View; 46 import android.view.View.OnClickListener; 47 import android.view.ViewGroup; 48 import android.widget.AppSecurityPermissions; 49 import android.widget.Button; 50 import android.widget.ImageView; 51 import android.widget.TabHost; 52 import android.widget.TextView; 53 import com.android.packageinstaller.permission.utils.Utils; 54 55 import java.io.File; 56 import java.io.FileOutputStream; 57 import java.io.IOException; 58 import java.io.InputStream; 59 import java.io.OutputStream; 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 Activity implements OnCancelListener, OnClickListener { 72 private static final String TAG = "PackageInstaller"; 73 74 private static final int REQUEST_ENABLE_UNKNOWN_SOURCES = 1; 75 76 private static final String SCHEME_FILE = "file"; 77 private static final String SCHEME_CONTENT = "content"; 78 private static final String SCHEME_PACKAGE = "package"; 79 80 private int mSessionId = -1; 81 private Uri mPackageURI; 82 private Uri mOriginatingURI; 83 private Uri mReferrerURI; 84 private int mOriginatingUid = VerificationParams.NO_UID; 85 private File mContentUriApkStagingFile; 86 87 private AsyncTask<Uri, Void, File> mStagingAsynTask; 88 89 private boolean localLOGV = false; 90 PackageManager mPm; 91 UserManager mUserManager; 92 PackageInstaller mInstaller; 93 PackageInfo mPkgInfo; 94 ApplicationInfo mSourceInfo; 95 96 // ApplicationInfo object primarily used for already existing applications 97 private ApplicationInfo mAppInfo = null; 98 99 // View for install progress 100 View mInstallConfirm; 101 // Buttons to indicate user acceptance 102 private Button mOk; 103 private Button mCancel; 104 CaffeinatedScrollView mScrollView = null; 105 private boolean mOkCanInstall = false; 106 107 static final String PREFS_ALLOWED_SOURCES = "allowed_sources"; 108 109 private static final String TAB_ID_ALL = "all"; 110 private static final String TAB_ID_NEW = "new"; 111 112 // Dialog identifiers used in showDialog 113 private static final int DLG_BASE = 0; 114 private static final int DLG_UNKNOWN_SOURCES = DLG_BASE + 1; 115 private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2; 116 private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3; 117 private static final int DLG_INSTALL_ERROR = DLG_BASE + 4; 118 private static final int DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES = DLG_BASE + 6; 119 private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7; 120 startInstallConfirm()121 private void startInstallConfirm() { 122 TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost); 123 tabHost.setup(); 124 tabHost.setVisibility(View.VISIBLE); 125 ViewPager viewPager = (ViewPager)findViewById(R.id.pager); 126 TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager); 127 // If the app supports runtime permissions the new permissions will 128 // be requested at runtime, hence we do not show them at install. 129 boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion 130 >= Build.VERSION_CODES.M; 131 boolean permVisible = false; 132 mScrollView = null; 133 mOkCanInstall = false; 134 int msg = 0; 135 136 AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo); 137 final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL); 138 if (mAppInfo != null) { 139 msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 140 ? R.string.install_confirm_question_update_system 141 : R.string.install_confirm_question_update; 142 mScrollView = new CaffeinatedScrollView(this); 143 mScrollView.setFillViewport(true); 144 boolean newPermissionsFound = false; 145 if (!supportsRuntimePermissions) { 146 newPermissionsFound = 147 (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0); 148 if (newPermissionsFound) { 149 permVisible = true; 150 mScrollView.addView(perms.getPermissionsView( 151 AppSecurityPermissions.WHICH_NEW)); 152 } 153 } 154 if (!supportsRuntimePermissions && !newPermissionsFound) { 155 LayoutInflater inflater = (LayoutInflater)getSystemService( 156 Context.LAYOUT_INFLATER_SERVICE); 157 TextView label = (TextView)inflater.inflate(R.layout.label, null); 158 label.setText(R.string.no_new_perms); 159 mScrollView.addView(label); 160 } 161 adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator( 162 getText(R.string.newPerms)), mScrollView); 163 } else { 164 findViewById(R.id.tabscontainer).setVisibility(View.GONE); 165 findViewById(R.id.spacer).setVisibility(View.VISIBLE); 166 } 167 if (!supportsRuntimePermissions && N > 0) { 168 permVisible = true; 169 LayoutInflater inflater = (LayoutInflater)getSystemService( 170 Context.LAYOUT_INFLATER_SERVICE); 171 View root = inflater.inflate(R.layout.permissions_list, null); 172 if (mScrollView == null) { 173 mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview); 174 } 175 ((ViewGroup)root.findViewById(R.id.permission_list)).addView( 176 perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL)); 177 adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator( 178 getText(R.string.allPerms)), root); 179 } 180 if (!permVisible) { 181 if (mAppInfo != null) { 182 // This is an update to an application, but there are no 183 // permissions at all. 184 msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 185 ? R.string.install_confirm_question_update_system_no_perms 186 : R.string.install_confirm_question_update_no_perms; 187 188 findViewById(R.id.spacer).setVisibility(View.VISIBLE); 189 } else { 190 // This is a new application with no permissions. 191 msg = R.string.install_confirm_question_no_perms; 192 } 193 tabHost.setVisibility(View.INVISIBLE); 194 mScrollView = null; 195 } 196 if (msg != 0) { 197 ((TextView)findViewById(R.id.install_confirm_question)).setText(msg); 198 } 199 mInstallConfirm.setVisibility(View.VISIBLE); 200 mOk.setEnabled(true); 201 if (mScrollView == null) { 202 // There is nothing to scroll view, so the ok button is immediately 203 // set to install. 204 mOk.setText(R.string.install); 205 mOkCanInstall = true; 206 } else { 207 mScrollView.setFullScrollAction(new Runnable() { 208 @Override 209 public void run() { 210 mOk.setText(R.string.install); 211 mOkCanInstall = true; 212 } 213 }); 214 } 215 } 216 showDialogInner(int id)217 private void showDialogInner(int id) { 218 // TODO better fix for this? Remove dialog so that it gets created again 219 removeDialog(id); 220 showDialog(id); 221 } 222 223 @Override onCreateDialog(int id, Bundle bundle)224 public Dialog onCreateDialog(int id, Bundle bundle) { 225 switch (id) { 226 case DLG_UNKNOWN_SOURCES: 227 return new AlertDialog.Builder(this) 228 .setMessage(R.string.unknown_apps_dlg_text) 229 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 230 public void onClick(DialogInterface dialog, int which) { 231 Log.i(TAG, "Finishing off activity so that user can navigate to settings manually"); 232 finishAffinity(); 233 }}) 234 .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() { 235 public void onClick(DialogInterface dialog, int which) { 236 Log.i(TAG, "Launching settings"); 237 launchSecuritySettings(); 238 } 239 }) 240 .setOnCancelListener(this) 241 .create(); 242 case DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES: 243 return new AlertDialog.Builder(this) 244 .setMessage(R.string.unknown_apps_admin_dlg_text) 245 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 246 public void onClick(DialogInterface dialog, int which) { 247 finish(); 248 } 249 }) 250 .setOnCancelListener(this) 251 .create(); 252 case DLG_PACKAGE_ERROR : 253 return new AlertDialog.Builder(this) 254 .setMessage(R.string.Parse_error_dlg_text) 255 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 256 public void onClick(DialogInterface dialog, int which) { 257 finish(); 258 } 259 }) 260 .setOnCancelListener(this) 261 .create(); 262 case DLG_OUT_OF_SPACE: 263 // Guaranteed not to be null. will default to package name if not set by app 264 CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo); 265 String dlgText = getString(R.string.out_of_space_dlg_text, 266 appTitle.toString()); 267 return new AlertDialog.Builder(this) 268 .setMessage(dlgText) 269 .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() { 270 public void onClick(DialogInterface dialog, int which) { 271 //launch manage applications 272 Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); 273 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 274 startActivity(intent); 275 finish(); 276 } 277 }) 278 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 279 public void onClick(DialogInterface dialog, int which) { 280 Log.i(TAG, "Canceling installation"); 281 finish(); 282 } 283 }) 284 .setOnCancelListener(this) 285 .create(); 286 case DLG_INSTALL_ERROR : 287 // Guaranteed not to be null. will default to package name if not set by app 288 CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo); 289 String dlgText1 = getString(R.string.install_failed_msg, 290 appTitle1.toString()); 291 return new AlertDialog.Builder(this) 292 .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() { 293 public void onClick(DialogInterface dialog, int which) { 294 finish(); 295 } 296 }) 297 .setMessage(dlgText1) 298 .setOnCancelListener(this) 299 .create(); 300 case DLG_NOT_SUPPORTED_ON_WEAR: 301 return new AlertDialog.Builder(this) 302 .setMessage(R.string.wear_not_allowed_dlg_text) 303 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 304 public void onClick(DialogInterface dialog, int which) { 305 setResult(RESULT_OK); 306 clearCachedApkIfNeededAndFinish(); 307 } 308 }) 309 .setOnCancelListener(this) 310 .create(); 311 } 312 return null; 313 } 314 315 private void launchSecuritySettings() { 316 Intent launchSettingsIntent = new Intent(Settings.ACTION_SECURITY_SETTINGS); 317 startActivityForResult(launchSettingsIntent, REQUEST_ENABLE_UNKNOWN_SOURCES); 318 } 319 320 @Override 321 public void onActivityResult(int request, int result, Intent data) { 322 // If the settings app approved the install we are good to go regardless 323 // whether the untrusted sources setting is on. This allows partners to 324 // implement a "allow untrusted source once" feature. 325 if (request == REQUEST_ENABLE_UNKNOWN_SOURCES && result == RESULT_OK) { 326 initiateInstall(); 327 } else { 328 clearCachedApkIfNeededAndFinish(); 329 } 330 } 331 332 private boolean isInstallRequestFromUnknownSource(Intent intent) { 333 String callerPackage = getCallingPackage(); 334 if (callerPackage != null && intent.getBooleanExtra( 335 Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) { 336 try { 337 mSourceInfo = mPm.getApplicationInfo(callerPackage, 0); 338 if (mSourceInfo != null) { 339 if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) 340 != 0) { 341 // Privileged apps are not considered an unknown source. 342 return false; 343 } 344 } 345 } catch (NameNotFoundException e) { 346 } 347 } 348 349 return true; 350 } 351 352 /** 353 * @return whether unknown sources is enabled by user in Settings 354 */ 355 private boolean isUnknownSourcesEnabled() { 356 return Settings.Secure.getInt(getContentResolver(), 357 Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0; 358 } 359 360 /** 361 * @return whether the device admin restricts installation from unknown sources 362 */ 363 private boolean isUnknownSourcesAllowedByAdmin() { 364 return !mUserManager.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); 365 } 366 367 private void initiateInstall() { 368 String pkgName = mPkgInfo.packageName; 369 // Check if there is already a package on the device with this name 370 // but it has been renamed to something else. 371 String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName }); 372 if (oldName != null && oldName.length > 0 && oldName[0] != null) { 373 pkgName = oldName[0]; 374 mPkgInfo.packageName = pkgName; 375 mPkgInfo.applicationInfo.packageName = pkgName; 376 } 377 // Check if package is already installed. display confirmation dialog if replacing pkg 378 try { 379 // This is a little convoluted because we want to get all uninstalled 380 // apps, but this may include apps with just data, and if it is just 381 // data we still want to count it as "installed". 382 mAppInfo = mPm.getApplicationInfo(pkgName, 383 PackageManager.GET_UNINSTALLED_PACKAGES); 384 if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { 385 mAppInfo = null; 386 } 387 } catch (NameNotFoundException e) { 388 mAppInfo = null; 389 } 390 391 startInstallConfirm(); 392 } 393 394 void setPmResult(int pmResult) { 395 Intent result = new Intent(); 396 result.putExtra(Intent.EXTRA_INSTALL_RESULT, pmResult); 397 setResult(pmResult == PackageManager.INSTALL_SUCCEEDED 398 ? RESULT_OK : RESULT_FIRST_USER, result); 399 } 400 401 @Override 402 protected void onCreate(Bundle icicle) { 403 super.onCreate(icicle); 404 405 mPm = getPackageManager(); 406 mInstaller = mPm.getPackageInstaller(); 407 mUserManager = (UserManager) getSystemService(Context.USER_SERVICE); 408 409 final Intent intent = getIntent(); 410 mOriginatingUid = getOriginatingUid(intent); 411 412 final Uri packageUri; 413 414 if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) { 415 final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1); 416 final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId); 417 if (info == null || !info.sealed || info.resolvedBaseCodePath == null) { 418 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring"); 419 finish(); 420 return; 421 } 422 423 mSessionId = sessionId; 424 packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath)); 425 mOriginatingURI = null; 426 mReferrerURI = null; 427 } else { 428 mSessionId = -1; 429 packageUri = intent.getData(); 430 mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); 431 mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER); 432 } 433 434 // if there's nothing to do, quietly slip into the ether 435 if (packageUri == null) { 436 Log.w(TAG, "Unspecified source"); 437 setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI); 438 finish(); 439 return; 440 } 441 442 if (DeviceUtils.isWear(this)) { 443 showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR); 444 return; 445 } 446 447 //set view 448 setContentView(R.layout.install_start); 449 mInstallConfirm = findViewById(R.id.install_confirm_panel); 450 mInstallConfirm.setVisibility(View.INVISIBLE); 451 mOk = (Button)findViewById(R.id.ok_button); 452 mCancel = (Button)findViewById(R.id.cancel_button); 453 mOk.setOnClickListener(this); 454 mCancel.setOnClickListener(this); 455 456 // Block the install attempt on the Unknown Sources setting if necessary. 457 final boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent); 458 if (!requestFromUnknownSource) { 459 processPackageUri(packageUri); 460 return; 461 } 462 463 // If the admin prohibits it, or we're running in a managed profile, just show error 464 // and exit. Otherwise show an option to take the user to Settings to change the setting. 465 final boolean isManagedProfile = mUserManager.isManagedProfile(); 466 if (!isUnknownSourcesAllowedByAdmin()) { 467 startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)); 468 clearCachedApkIfNeededAndFinish(); 469 } else if (!isUnknownSourcesEnabled() && isManagedProfile) { 470 showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES); 471 } else if (!isUnknownSourcesEnabled()) { 472 // Ask user to enable setting first 473 474 showDialogInner(DLG_UNKNOWN_SOURCES); 475 } else { 476 processPackageUri(packageUri); 477 } 478 } 479 480 @Override 481 protected void onDestroy() { 482 if (mStagingAsynTask != null) { 483 mStagingAsynTask.cancel(true); 484 mStagingAsynTask = null; 485 } 486 super.onDestroy(); 487 } 488 489 private void processPackageUri(final Uri packageUri) { 490 mPackageURI = packageUri; 491 492 final String scheme = packageUri.getScheme(); 493 final PackageUtil.AppSnippet as; 494 495 switch (scheme) { 496 case SCHEME_PACKAGE: { 497 try { 498 mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(), 499 PackageManager.GET_PERMISSIONS 500 | PackageManager.GET_UNINSTALLED_PACKAGES); 501 } catch (NameNotFoundException e) { 502 } 503 if (mPkgInfo == null) { 504 Log.w(TAG, "Requested package " + packageUri.getScheme() 505 + " not available. Discontinuing installation"); 506 showDialogInner(DLG_PACKAGE_ERROR); 507 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); 508 return; 509 } 510 as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo), 511 mPm.getApplicationIcon(mPkgInfo.applicationInfo)); 512 } break; 513 514 case SCHEME_FILE: { 515 File sourceFile = new File(packageUri.getPath()); 516 PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile); 517 518 // Check for parse errors 519 if (parsed == null) { 520 Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); 521 showDialogInner(DLG_PACKAGE_ERROR); 522 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); 523 return; 524 } 525 mPkgInfo = PackageParser.generatePackageInfo(parsed, null, 526 PackageManager.GET_PERMISSIONS, 0, 0, null, 527 new PackageUserState()); 528 as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); 529 } break; 530 531 case SCHEME_CONTENT: { 532 mStagingAsynTask = new StagingAsyncTask(); 533 mStagingAsynTask.execute(packageUri); 534 return; 535 } 536 537 default: { 538 Log.w(TAG, "Unsupported scheme " + scheme); 539 setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI); 540 clearCachedApkIfNeededAndFinish(); 541 return; 542 } 543 } 544 545 PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet); 546 547 initiateInstall(); 548 } 549 550 /** Get the ApplicationInfo for the calling package, if available */ 551 private ApplicationInfo getSourceInfo() { 552 String callingPackage = getCallingPackage(); 553 if (callingPackage != null) { 554 try { 555 return mPm.getApplicationInfo(callingPackage, 0); 556 } catch (NameNotFoundException ex) { 557 // ignore 558 } 559 } 560 return null; 561 } 562 563 564 /** Get the originating uid if possible, or VerificationParams.NO_UID if not available */ 565 private int getOriginatingUid(Intent intent) { 566 // The originating uid from the intent. We only trust/use this if it comes from a 567 // system application 568 int uidFromIntent = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, 569 VerificationParams.NO_UID); 570 571 // Get the source info from the calling package, if available. This will be the 572 // definitive calling package, but it only works if the intent was started using 573 // startActivityForResult, 574 ApplicationInfo sourceInfo = getSourceInfo(); 575 if (sourceInfo != null) { 576 if (uidFromIntent != VerificationParams.NO_UID && 577 (mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { 578 return uidFromIntent; 579 580 } 581 // We either didn't get a uid in the intent, or we don't trust it. Use the 582 // uid of the calling package instead. 583 return sourceInfo.uid; 584 } 585 586 // We couldn't get the specific calling package. Let's get the uid instead 587 int callingUid; 588 try { 589 callingUid = ActivityManagerNative.getDefault() 590 .getLaunchedFromUid(getActivityToken()); 591 } catch (android.os.RemoteException ex) { 592 Log.w(TAG, "Could not determine the launching uid."); 593 // nothing else we can do 594 return VerificationParams.NO_UID; 595 } 596 597 // If we got a uid from the intent, we need to verify that the caller is a 598 // privileged system package before we use it 599 if (uidFromIntent != VerificationParams.NO_UID) { 600 String[] callingPackages = mPm.getPackagesForUid(callingUid); 601 if (callingPackages != null) { 602 for (String packageName: callingPackages) { 603 try { 604 ApplicationInfo applicationInfo = 605 mPm.getApplicationInfo(packageName, 0); 606 607 if ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) 608 != 0) { 609 return uidFromIntent; 610 } 611 } catch (NameNotFoundException ex) { 612 // ignore it, and try the next package 613 } 614 } 615 } 616 } 617 // We either didn't get a uid from the intent, or we don't trust it. Use the 618 // calling uid instead. 619 return callingUid; 620 } 621 622 @Override 623 public void onBackPressed() { 624 if (mSessionId != -1) { 625 mInstaller.setPermissionsResult(mSessionId, false); 626 } 627 super.onBackPressed(); 628 } 629 630 // Generic handling when pressing back key 631 public void onCancel(DialogInterface dialog) { 632 clearCachedApkIfNeededAndFinish(); 633 } 634 635 public void onClick(View v) { 636 if (v == mOk) { 637 if (mOkCanInstall || mScrollView == null) { 638 if (mSessionId != -1) { 639 mInstaller.setPermissionsResult(mSessionId, true); 640 clearCachedApkIfNeededAndFinish(); 641 } else { 642 startInstall(); 643 } 644 } else { 645 mScrollView.pageScroll(View.FOCUS_DOWN); 646 } 647 } else if (v == mCancel) { 648 // Cancel and finish 649 setResult(RESULT_CANCELED); 650 if (mSessionId != -1) { 651 mInstaller.setPermissionsResult(mSessionId, false); 652 } 653 clearCachedApkIfNeededAndFinish(); 654 } 655 } 656 657 private void startInstall() { 658 // Start subactivity to actually install the application 659 Intent newIntent = new Intent(); 660 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, 661 mPkgInfo.applicationInfo); 662 newIntent.setData(mPackageURI); 663 newIntent.setClass(this, InstallAppProgress.class); 664 String installerPackageName = getIntent().getStringExtra( 665 Intent.EXTRA_INSTALLER_PACKAGE_NAME); 666 if (mOriginatingURI != null) { 667 newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI); 668 } 669 if (mReferrerURI != null) { 670 newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI); 671 } 672 if (mOriginatingUid != VerificationParams.NO_UID) { 673 newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid); 674 } 675 if (installerPackageName != null) { 676 newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, 677 installerPackageName); 678 } 679 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 680 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); 681 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 682 } 683 if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI); 684 startActivity(newIntent); 685 finish(); 686 } 687 688 private void clearCachedApkIfNeededAndFinish() { 689 if (mContentUriApkStagingFile != null) { 690 mContentUriApkStagingFile.delete(); 691 mContentUriApkStagingFile = null; 692 } 693 finish(); 694 } 695 696 private final class StagingAsyncTask extends AsyncTask<Uri, Void, File> { 697 private static final long SHOW_EMPTY_STATE_DELAY_MILLIS = 300; 698 699 private final Runnable mEmptyStateRunnable = new Runnable() { 700 @Override 701 public void run() { 702 ((TextView) findViewById(R.id.app_name)).setText(R.string.app_name_unknown); 703 ((TextView) findViewById(R.id.install_confirm_question)) 704 .setText(R.string.message_staging); 705 mInstallConfirm.setVisibility(View.VISIBLE); 706 findViewById(android.R.id.tabhost).setVisibility(View.INVISIBLE); 707 findViewById(R.id.spacer).setVisibility(View.VISIBLE); 708 findViewById(R.id.ok_button).setEnabled(false); 709 Drawable icon = getDrawable(R.drawable.ic_file_download); 710 Utils.applyTint(PackageInstallerActivity.this, 711 icon, android.R.attr.colorControlNormal); 712 ((ImageView) findViewById(R.id.app_icon)).setImageDrawable(icon); 713 } 714 }; 715 716 @Override 717 protected void onPreExecute() { 718 getWindow().getDecorView().postDelayed(mEmptyStateRunnable, 719 SHOW_EMPTY_STATE_DELAY_MILLIS); 720 } 721 722 @Override 723 protected File doInBackground(Uri... params) { 724 if (params == null || params.length <= 0) { 725 return null; 726 } 727 Uri packageUri = params[0]; 728 File sourceFile = null; 729 try { 730 sourceFile = File.createTempFile("package", ".apk", getCacheDir()); 731 try ( 732 InputStream in = getContentResolver().openInputStream(packageUri); 733 OutputStream out = (in != null) ? new FileOutputStream( 734 sourceFile) : null; 735 ) { 736 // Despite the comments in ContentResolver#openInputStream 737 // the returned stream can be null. 738 if (in == null) { 739 return null; 740 } 741 byte[] buffer = new byte[4096]; 742 int bytesRead; 743 while ((bytesRead = in.read(buffer)) >= 0) { 744 // Be nice and respond to a cancellation 745 if (isCancelled()) { 746 return null; 747 } 748 out.write(buffer, 0, bytesRead); 749 } 750 } 751 } catch (IOException ioe) { 752 Log.w(TAG, "Error staging apk from content URI", ioe); 753 if (sourceFile != null) { 754 sourceFile.delete(); 755 } 756 } 757 return sourceFile; 758 } 759 760 @Override 761 protected void onPostExecute(File file) { 762 getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable); 763 if (isFinishing() || isDestroyed()) { 764 return; 765 } 766 if (file == null) { 767 showDialogInner(DLG_PACKAGE_ERROR); 768 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); 769 return; 770 } 771 mContentUriApkStagingFile = file; 772 Uri fileUri = Uri.fromFile(file); 773 processPackageUri(fileUri); 774 } 775 776 @Override 777 protected void onCancelled(File file) { 778 getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable); 779 } 780 }; 781 } 782