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