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.SharedPreferences; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.ManifestDigest; 30 import android.content.pm.PackageInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.PackageManager.NameNotFoundException; 33 import android.content.pm.PackageParser; 34 import android.content.pm.PackageUserState; 35 import android.content.pm.VerificationParams; 36 import android.net.Uri; 37 import android.os.Bundle; 38 import android.provider.Settings; 39 import android.support.v4.view.ViewPager; 40 import android.util.Log; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.View.OnClickListener; 44 import android.view.ViewGroup; 45 import android.widget.AppSecurityPermissions; 46 import android.widget.Button; 47 import android.widget.TabHost; 48 import android.widget.TextView; 49 50 import java.io.File; 51 52 /* 53 * This activity is launched when a new application is installed via side loading 54 * The package is first parsed and the user is notified of parse errors via a dialog. 55 * If the package is successfully parsed, the user is notified to turn on the install unknown 56 * applications setting. A memory check is made at this point and the user is notified of out 57 * of memory conditions if any. If the package is already existing on the device, 58 * a confirmation dialog (to replace the existing package) is presented to the user. 59 * Based on the user response the package is then installed by launching InstallAppConfirm 60 * sub activity. All state transitions are handled in this activity 61 */ 62 public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener { 63 private static final String TAG = "PackageInstaller"; 64 private Uri mPackageURI; 65 private Uri mOriginatingURI; 66 private Uri mReferrerURI; 67 private int mOriginatingUid = VerificationParams.NO_UID; 68 private ManifestDigest mPkgDigest; 69 70 private boolean localLOGV = false; 71 PackageManager mPm; 72 PackageInfo mPkgInfo; 73 ApplicationInfo mSourceInfo; 74 75 // ApplicationInfo object primarily used for already existing applications 76 private ApplicationInfo mAppInfo = null; 77 78 // View for install progress 79 View mInstallConfirm; 80 // Buttons to indicate user acceptance 81 private Button mOk; 82 private Button mCancel; 83 CaffeinatedScrollView mScrollView = null; 84 private boolean mOkCanInstall = false; 85 86 static final String PREFS_ALLOWED_SOURCES = "allowed_sources"; 87 88 // Dialog identifiers used in showDialog 89 private static final int DLG_BASE = 0; 90 private static final int DLG_UNKNOWN_APPS = DLG_BASE + 1; 91 private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2; 92 private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3; 93 private static final int DLG_INSTALL_ERROR = DLG_BASE + 4; 94 private static final int DLG_ALLOW_SOURCE = DLG_BASE + 5; 95 startInstallConfirm()96 private void startInstallConfirm() { 97 TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost); 98 tabHost.setup(); 99 ViewPager viewPager = (ViewPager)findViewById(R.id.pager); 100 TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager); 101 102 boolean permVisible = false; 103 mScrollView = null; 104 mOkCanInstall = false; 105 int msg = 0; 106 if (mPkgInfo != null) { 107 AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo); 108 final int NP = perms.getPermissionCount(AppSecurityPermissions.WHICH_PERSONAL); 109 final int ND = perms.getPermissionCount(AppSecurityPermissions.WHICH_DEVICE); 110 if (mAppInfo != null) { 111 msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 112 ? R.string.install_confirm_question_update_system 113 : R.string.install_confirm_question_update; 114 mScrollView = new CaffeinatedScrollView(this); 115 mScrollView.setFillViewport(true); 116 if (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0) { 117 permVisible = true; 118 mScrollView.addView(perms.getPermissionsView( 119 AppSecurityPermissions.WHICH_NEW)); 120 } else { 121 LayoutInflater inflater = (LayoutInflater)getSystemService( 122 Context.LAYOUT_INFLATER_SERVICE); 123 TextView label = (TextView)inflater.inflate(R.layout.label, null); 124 label.setText(R.string.no_new_perms); 125 mScrollView.addView(label); 126 } 127 adapter.addTab(tabHost.newTabSpec("new").setIndicator( 128 getText(R.string.newPerms)), mScrollView); 129 } else { 130 findViewById(R.id.tabscontainer).setVisibility(View.GONE); 131 findViewById(R.id.divider).setVisibility(View.VISIBLE); 132 } 133 if (NP > 0 || ND > 0) { 134 permVisible = true; 135 LayoutInflater inflater = (LayoutInflater)getSystemService( 136 Context.LAYOUT_INFLATER_SERVICE); 137 View root = inflater.inflate(R.layout.permissions_list, null); 138 if (mScrollView == null) { 139 mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview); 140 } 141 if (NP > 0) { 142 ((ViewGroup)root.findViewById(R.id.privacylist)).addView( 143 perms.getPermissionsView(AppSecurityPermissions.WHICH_PERSONAL)); 144 } else { 145 root.findViewById(R.id.privacylist).setVisibility(View.GONE); 146 } 147 if (ND > 0) { 148 ((ViewGroup)root.findViewById(R.id.devicelist)).addView( 149 perms.getPermissionsView(AppSecurityPermissions.WHICH_DEVICE)); 150 } else { 151 root.findViewById(R.id.devicelist).setVisibility(View.GONE); 152 } 153 adapter.addTab(tabHost.newTabSpec("all").setIndicator( 154 getText(R.string.allPerms)), root); 155 } 156 } 157 if (!permVisible) { 158 if (mAppInfo != null) { 159 // This is an update to an application, but there are no 160 // permissions at all. 161 msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 162 ? R.string.install_confirm_question_update_system_no_perms 163 : R.string.install_confirm_question_update_no_perms; 164 } else { 165 // This is a new application with no permissions. 166 msg = R.string.install_confirm_question_no_perms; 167 } 168 tabHost.setVisibility(View.GONE); 169 findViewById(R.id.filler).setVisibility(View.VISIBLE); 170 findViewById(R.id.divider).setVisibility(View.GONE); 171 mScrollView = null; 172 } 173 if (msg != 0) { 174 ((TextView)findViewById(R.id.install_confirm_question)).setText(msg); 175 } 176 mInstallConfirm.setVisibility(View.VISIBLE); 177 mOk = (Button)findViewById(R.id.ok_button); 178 mCancel = (Button)findViewById(R.id.cancel_button); 179 mOk.setOnClickListener(this); 180 mCancel.setOnClickListener(this); 181 if (mScrollView == null) { 182 // There is nothing to scroll view, so the ok button is immediately 183 // set to install. 184 mOk.setText(R.string.install); 185 mOkCanInstall = true; 186 } else { 187 mScrollView.setFullScrollAction(new Runnable() { 188 @Override 189 public void run() { 190 mOk.setText(R.string.install); 191 mOkCanInstall = true; 192 } 193 }); 194 } 195 } 196 showDialogInner(int id)197 private void showDialogInner(int id) { 198 // TODO better fix for this? Remove dialog so that it gets created again 199 removeDialog(id); 200 showDialog(id); 201 } 202 203 @Override onCreateDialog(int id, Bundle bundle)204 public Dialog onCreateDialog(int id, Bundle bundle) { 205 switch (id) { 206 case DLG_UNKNOWN_APPS: 207 return new AlertDialog.Builder(this) 208 .setTitle(R.string.unknown_apps_dlg_title) 209 .setMessage(R.string.unknown_apps_dlg_text) 210 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 211 public void onClick(DialogInterface dialog, int which) { 212 Log.i(TAG, "Finishing off activity so that user can navigate to settings manually"); 213 finish(); 214 }}) 215 .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() { 216 public void onClick(DialogInterface dialog, int which) { 217 Log.i(TAG, "Launching settings"); 218 launchSettingsAppAndFinish(); 219 } 220 }) 221 .setOnCancelListener(this) 222 .create(); 223 case DLG_PACKAGE_ERROR : 224 return new AlertDialog.Builder(this) 225 .setTitle(R.string.Parse_error_dlg_title) 226 .setMessage(R.string.Parse_error_dlg_text) 227 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 228 public void onClick(DialogInterface dialog, int which) { 229 finish(); 230 } 231 }) 232 .setOnCancelListener(this) 233 .create(); 234 case DLG_OUT_OF_SPACE: 235 // Guaranteed not to be null. will default to package name if not set by app 236 CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo); 237 String dlgText = getString(R.string.out_of_space_dlg_text, 238 appTitle.toString()); 239 return new AlertDialog.Builder(this) 240 .setTitle(R.string.out_of_space_dlg_title) 241 .setMessage(dlgText) 242 .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() { 243 public void onClick(DialogInterface dialog, int which) { 244 //launch manage applications 245 Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); 246 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 247 startActivity(intent); 248 finish(); 249 } 250 }) 251 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 252 public void onClick(DialogInterface dialog, int which) { 253 Log.i(TAG, "Canceling installation"); 254 finish(); 255 } 256 }) 257 .setOnCancelListener(this) 258 .create(); 259 case DLG_INSTALL_ERROR : 260 // Guaranteed not to be null. will default to package name if not set by app 261 CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo); 262 String dlgText1 = getString(R.string.install_failed_msg, 263 appTitle1.toString()); 264 return new AlertDialog.Builder(this) 265 .setTitle(R.string.install_failed) 266 .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() { 267 public void onClick(DialogInterface dialog, int which) { 268 finish(); 269 } 270 }) 271 .setMessage(dlgText1) 272 .setOnCancelListener(this) 273 .create(); 274 case DLG_ALLOW_SOURCE: 275 CharSequence appTitle2 = mPm.getApplicationLabel(mSourceInfo); 276 String dlgText2 = getString(R.string.allow_source_dlg_text, 277 appTitle2.toString()); 278 return new AlertDialog.Builder(this) 279 .setTitle(R.string.allow_source_dlg_title) 280 .setMessage(dlgText2) 281 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 282 public void onClick(DialogInterface dialog, int which) { 283 setResult(RESULT_CANCELED); 284 finish(); 285 }}) 286 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 287 public void onClick(DialogInterface dialog, int which) { 288 SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES, 289 Context.MODE_PRIVATE); 290 prefs.edit().putBoolean(mSourceInfo.packageName, true).apply(); 291 startInstallConfirm(); 292 } 293 }) 294 .setOnCancelListener(this) 295 .create(); 296 } 297 return null; 298 } 299 300 private void launchSettingsAppAndFinish() { 301 // Create an intent to launch SettingsTwo activity 302 Intent launchSettingsIntent = new Intent(Settings.ACTION_SECURITY_SETTINGS); 303 launchSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 304 startActivity(launchSettingsIntent); 305 finish(); 306 } 307 308 private boolean isInstallingUnknownAppsAllowed() { 309 return Settings.Global.getInt(getContentResolver(), 310 Settings.Global.INSTALL_NON_MARKET_APPS, 0) > 0; 311 } 312 313 private void initiateInstall() { 314 String pkgName = mPkgInfo.packageName; 315 // Check if there is already a package on the device with this name 316 // but it has been renamed to something else. 317 String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName }); 318 if (oldName != null && oldName.length > 0 && oldName[0] != null) { 319 pkgName = oldName[0]; 320 mPkgInfo.packageName = pkgName; 321 mPkgInfo.applicationInfo.packageName = pkgName; 322 } 323 // Check if package is already installed. display confirmation dialog if replacing pkg 324 try { 325 // This is a little convoluted because we want to get all uninstalled 326 // apps, but this may include apps with just data, and if it is just 327 // data we still want to count it as "installed". 328 mAppInfo = mPm.getApplicationInfo(pkgName, 329 PackageManager.GET_UNINSTALLED_PACKAGES); 330 if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { 331 mAppInfo = null; 332 } 333 } catch (NameNotFoundException e) { 334 mAppInfo = null; 335 } 336 startInstallConfirm(); 337 } 338 339 void setPmResult(int pmResult) { 340 Intent result = new Intent(); 341 result.putExtra(Intent.EXTRA_INSTALL_RESULT, pmResult); 342 setResult(pmResult == PackageManager.INSTALL_SUCCEEDED 343 ? RESULT_OK : RESULT_FIRST_USER, result); 344 } 345 346 @Override 347 protected void onCreate(Bundle icicle) { 348 super.onCreate(icicle); 349 350 // get intent information 351 final Intent intent = getIntent(); 352 mPackageURI = intent.getData(); 353 mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); 354 mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER); 355 mPm = getPackageManager(); 356 357 final String scheme = mPackageURI.getScheme(); 358 if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) { 359 Log.w(TAG, "Unsupported scheme " + scheme); 360 setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI); 361 return; 362 } 363 364 final PackageUtil.AppSnippet as; 365 if ("package".equals(mPackageURI.getScheme())) { 366 try { 367 mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(), 368 PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES); 369 } catch (NameNotFoundException e) { 370 } 371 if (mPkgInfo == null) { 372 Log.w(TAG, "Requested package " + mPackageURI.getScheme() 373 + " not available. Discontinuing installation"); 374 showDialogInner(DLG_PACKAGE_ERROR); 375 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); 376 return; 377 } 378 as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo), 379 mPm.getApplicationIcon(mPkgInfo.applicationInfo)); 380 } else { 381 final File sourceFile = new File(mPackageURI.getPath()); 382 PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile); 383 384 // Check for parse errors 385 if (parsed == null) { 386 Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); 387 showDialogInner(DLG_PACKAGE_ERROR); 388 setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK); 389 return; 390 } 391 mPkgInfo = PackageParser.generatePackageInfo(parsed, null, 392 PackageManager.GET_PERMISSIONS, 0, 0, null, 393 new PackageUserState()); 394 mPkgDigest = parsed.manifestDigest; 395 as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); 396 } 397 398 //set view 399 setContentView(R.layout.install_start); 400 mInstallConfirm = findViewById(R.id.install_confirm_panel); 401 mInstallConfirm.setVisibility(View.INVISIBLE); 402 PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet); 403 404 mOriginatingUid = getOriginatingUid(intent); 405 406 // Deal with install source. 407 String callerPackage = getCallingPackage(); 408 if (callerPackage != null && intent.getBooleanExtra( 409 Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) { 410 try { 411 mSourceInfo = mPm.getApplicationInfo(callerPackage, 0); 412 if (mSourceInfo != null) { 413 if ((mSourceInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { 414 // System apps don't need to be approved. 415 initiateInstall(); 416 return; 417 } 418 /* for now this is disabled, since the user would need to 419 * have enabled the global "unknown sources" setting in the 420 * first place in order to get here. 421 SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES, 422 Context.MODE_PRIVATE); 423 if (prefs.getBoolean(mSourceInfo.packageName, false)) { 424 // User has already allowed this one. 425 initiateInstall(); 426 return; 427 } 428 //ask user to enable setting first 429 showDialogInner(DLG_ALLOW_SOURCE); 430 return; 431 */ 432 } 433 } catch (NameNotFoundException e) { 434 } 435 } 436 437 // Check unknown sources. 438 if (!isInstallingUnknownAppsAllowed()) { 439 //ask user to enable setting first 440 showDialogInner(DLG_UNKNOWN_APPS); 441 return; 442 } 443 initiateInstall(); 444 } 445 446 /** Get the ApplicationInfo for the calling package, if available */ 447 private ApplicationInfo getSourceInfo() { 448 String callingPackage = getCallingPackage(); 449 if (callingPackage != null) { 450 try { 451 return mPm.getApplicationInfo(callingPackage, 0); 452 } catch (NameNotFoundException ex) { 453 // ignore 454 } 455 } 456 return null; 457 } 458 459 460 /** Get the originating uid if possible, or VerificationParams.NO_UID if not available */ 461 private int getOriginatingUid(Intent intent) { 462 // The originating uid from the intent. We only trust/use this if it comes from a 463 // system application 464 int uidFromIntent = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, 465 VerificationParams.NO_UID); 466 467 // Get the source info from the calling package, if available. This will be the 468 // definitive calling package, but it only works if the intent was started using 469 // startActivityForResult, 470 ApplicationInfo sourceInfo = getSourceInfo(); 471 if (sourceInfo != null) { 472 if (uidFromIntent != VerificationParams.NO_UID && 473 (mSourceInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 474 return uidFromIntent; 475 476 } 477 // We either didn't get a uid in the intent, or we don't trust it. Use the 478 // uid of the calling package instead. 479 return sourceInfo.uid; 480 } 481 482 // We couldn't get the specific calling package. Let's get the uid instead 483 int callingUid; 484 try { 485 callingUid = ActivityManagerNative.getDefault() 486 .getLaunchedFromUid(getActivityToken()); 487 } catch (android.os.RemoteException ex) { 488 Log.w(TAG, "Could not determine the launching uid."); 489 // nothing else we can do 490 return VerificationParams.NO_UID; 491 } 492 493 // If we got a uid from the intent, we need to verify that the caller is a 494 // system package before we use it 495 if (uidFromIntent != VerificationParams.NO_UID) { 496 String[] callingPackages = mPm.getPackagesForUid(callingUid); 497 if (callingPackages != null) { 498 for (String packageName: callingPackages) { 499 try { 500 ApplicationInfo applicationInfo = 501 mPm.getApplicationInfo(packageName, 0); 502 503 if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 504 return uidFromIntent; 505 } 506 } catch (NameNotFoundException ex) { 507 // ignore it, and try the next package 508 } 509 } 510 } 511 } 512 // We either didn't get a uid from the intent, or we don't trust it. Use the 513 // calling uid instead. 514 return callingUid; 515 } 516 517 // Generic handling when pressing back key 518 public void onCancel(DialogInterface dialog) { 519 finish(); 520 } 521 522 public void onClick(View v) { 523 if(v == mOk) { 524 if (mOkCanInstall || mScrollView == null) { 525 // Start subactivity to actually install the application 526 Intent newIntent = new Intent(); 527 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, 528 mPkgInfo.applicationInfo); 529 newIntent.setData(mPackageURI); 530 newIntent.setClass(this, InstallAppProgress.class); 531 newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest); 532 String installerPackageName = getIntent().getStringExtra( 533 Intent.EXTRA_INSTALLER_PACKAGE_NAME); 534 if (mOriginatingURI != null) { 535 newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI); 536 } 537 if (mReferrerURI != null) { 538 newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI); 539 } 540 if (mOriginatingUid != VerificationParams.NO_UID) { 541 newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid); 542 } 543 if (installerPackageName != null) { 544 newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, 545 installerPackageName); 546 } 547 if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { 548 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); 549 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 550 } 551 if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI); 552 startActivity(newIntent); 553 finish(); 554 } else { 555 mScrollView.pageScroll(View.FOCUS_DOWN); 556 } 557 } else if(v == mCancel) { 558 // Cancel and finish 559 setResult(RESULT_CANCELED); 560 finish(); 561 } 562 } 563 } 564