ProvisionParser.java revision e6c2456aa6c00ef78c6d1d1621511d7ef8507f83
1/* Copyright (C) 2010 The Android Open Source Project. 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16package com.android.exchange.adapter; 17 18import android.app.admin.DevicePolicyManager; 19import android.content.Context; 20import android.content.res.Resources; 21import android.os.storage.StorageManager; 22 23import com.android.emailcommon.provider.Policy; 24import com.android.exchange.EasSyncService; 25import com.android.exchange.R; 26 27import org.xmlpull.v1.XmlPullParser; 28import org.xmlpull.v1.XmlPullParserException; 29import org.xmlpull.v1.XmlPullParserFactory; 30 31import java.io.ByteArrayInputStream; 32import java.io.IOException; 33import java.io.InputStream; 34import java.lang.reflect.InvocationTargetException; 35import java.lang.reflect.Method; 36import java.util.ArrayList; 37 38/** 39 * Parse the result of the Provision command 40 */ 41public class ProvisionParser extends Parser { 42 private final EasSyncService mService; 43 private Policy mPolicy = null; 44 private String mSecuritySyncKey = null; 45 private boolean mRemoteWipe = false; 46 private boolean mIsSupportable = true; 47 private boolean smimeRequired = false; 48 private final Resources mResources; 49 50 public ProvisionParser(InputStream in, EasSyncService service) throws IOException { 51 super(in); 52 mService = service; 53 mResources = service.mContext.getResources(); 54 } 55 56 public Policy getPolicy() { 57 return mPolicy; 58 } 59 60 public String getSecuritySyncKey() { 61 return mSecuritySyncKey; 62 } 63 64 public void setSecuritySyncKey(String securitySyncKey) { 65 mSecuritySyncKey = securitySyncKey; 66 } 67 68 public boolean getRemoteWipe() { 69 return mRemoteWipe; 70 } 71 72 public boolean hasSupportablePolicySet() { 73 return (mPolicy != null) && mIsSupportable; 74 } 75 76 public void clearUnsupportablePolicies() { 77 mIsSupportable = true; 78 mPolicy.mProtocolPoliciesUnsupported = null; 79 } 80 81 private void addPolicyString(StringBuilder sb, int res) { 82 sb.append(mResources.getString(res)); 83 sb.append(Policy.POLICY_STRING_DELIMITER); 84 } 85 86 /** 87 * Complete setup of a Policy; we normalize it first (removing inconsistencies, etc.) and then 88 * generate the tokenized "protocol policies enforced" string. Note that unsupported policies 89 * must have been added prior to calling this method (this is only a possibility with wbxml 90 * policy documents, as all versions of the OS support the policies in xml documents). 91 */ 92 private void setPolicy(Policy policy) { 93 policy.normalize(); 94 StringBuilder sb = new StringBuilder(); 95 if (policy.mDontAllowAttachments) { 96 addPolicyString(sb, R.string.policy_dont_allow_attachments); 97 } 98 if (policy.mRequireManualSyncWhenRoaming) { 99 addPolicyString(sb, R.string.policy_require_manual_sync_roaming); 100 } 101 policy.mProtocolPoliciesEnforced = sb.toString(); 102 mPolicy = policy; 103 } 104 105 private boolean deviceSupportsEncryption() { 106 DevicePolicyManager dpm = (DevicePolicyManager) 107 mService.mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 108 int status = dpm.getStorageEncryptionStatus(); 109 return status != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; 110 } 111 112 private void parseProvisionDocWbxml() throws IOException { 113 Policy policy = new Policy(); 114 ArrayList<Integer> unsupportedList = new ArrayList<Integer>(); 115 boolean passwordEnabled = false; 116 117 while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) { 118 boolean tagIsSupported = true; 119 int res = 0; 120 switch (tag) { 121 case Tags.PROVISION_DEVICE_PASSWORD_ENABLED: 122 if (getValueInt() == 1) { 123 passwordEnabled = true; 124 if (policy.mPasswordMode == Policy.PASSWORD_MODE_NONE) { 125 policy.mPasswordMode = Policy.PASSWORD_MODE_SIMPLE; 126 } 127 } 128 break; 129 case Tags.PROVISION_MIN_DEVICE_PASSWORD_LENGTH: 130 policy.mPasswordMinLength = getValueInt(); 131 break; 132 case Tags.PROVISION_ALPHA_DEVICE_PASSWORD_ENABLED: 133 if (getValueInt() == 1) { 134 policy.mPasswordMode = Policy.PASSWORD_MODE_STRONG; 135 } 136 break; 137 case Tags.PROVISION_MAX_INACTIVITY_TIME_DEVICE_LOCK: 138 // EAS gives us seconds, which is, happily, what the PolicySet requires 139 policy.mMaxScreenLockTime = getValueInt(); 140 break; 141 case Tags.PROVISION_MAX_DEVICE_PASSWORD_FAILED_ATTEMPTS: 142 policy.mPasswordMaxFails = getValueInt(); 143 break; 144 case Tags.PROVISION_DEVICE_PASSWORD_EXPIRATION: 145 policy.mPasswordExpirationDays = getValueInt(); 146 break; 147 case Tags.PROVISION_DEVICE_PASSWORD_HISTORY: 148 policy.mPasswordHistory = getValueInt(); 149 break; 150 case Tags.PROVISION_ALLOW_CAMERA: 151 policy.mDontAllowCamera = (getValueInt() == 0); 152 break; 153 case Tags.PROVISION_ALLOW_SIMPLE_DEVICE_PASSWORD: 154 // Ignore this unless there's any MSFT documentation for what this means 155 // Hint: I haven't seen any that's more specific than "simple" 156 getValue(); 157 break; 158 // The following policies, if false, can't be supported at the moment 159 case Tags.PROVISION_ALLOW_STORAGE_CARD: 160 case Tags.PROVISION_ALLOW_UNSIGNED_APPLICATIONS: 161 case Tags.PROVISION_ALLOW_UNSIGNED_INSTALLATION_PACKAGES: 162 case Tags.PROVISION_ALLOW_WIFI: 163 case Tags.PROVISION_ALLOW_TEXT_MESSAGING: 164 case Tags.PROVISION_ALLOW_POP_IMAP_EMAIL: 165 case Tags.PROVISION_ALLOW_IRDA: 166 case Tags.PROVISION_ALLOW_HTML_EMAIL: 167 case Tags.PROVISION_ALLOW_BROWSER: 168 case Tags.PROVISION_ALLOW_CONSUMER_EMAIL: 169 case Tags.PROVISION_ALLOW_INTERNET_SHARING: 170 if (getValueInt() == 0) { 171 tagIsSupported = false; 172 switch(tag) { 173 case Tags.PROVISION_ALLOW_STORAGE_CARD: 174 res = R.string.policy_dont_allow_storage_cards; 175 break; 176 case Tags.PROVISION_ALLOW_UNSIGNED_APPLICATIONS: 177 res = R.string.policy_dont_allow_unsigned_apps; 178 break; 179 case Tags.PROVISION_ALLOW_UNSIGNED_INSTALLATION_PACKAGES: 180 res = R.string.policy_dont_allow_unsigned_installers; 181 break; 182 case Tags.PROVISION_ALLOW_WIFI: 183 res = R.string.policy_dont_allow_wifi; 184 break; 185 case Tags.PROVISION_ALLOW_TEXT_MESSAGING: 186 res = R.string.policy_dont_allow_text_messaging; 187 break; 188 case Tags.PROVISION_ALLOW_POP_IMAP_EMAIL: 189 res = R.string.policy_dont_allow_pop_imap; 190 break; 191 case Tags.PROVISION_ALLOW_IRDA: 192 res = R.string.policy_dont_allow_irda; 193 break; 194 case Tags.PROVISION_ALLOW_HTML_EMAIL: 195 res = R.string.policy_dont_allow_html; 196 policy.mDontAllowHtml = true; 197 break; 198 case Tags.PROVISION_ALLOW_BROWSER: 199 res = R.string.policy_dont_allow_browser; 200 break; 201 case Tags.PROVISION_ALLOW_CONSUMER_EMAIL: 202 res = R.string.policy_dont_allow_consumer_email; 203 break; 204 case Tags.PROVISION_ALLOW_INTERNET_SHARING: 205 res = R.string.policy_dont_allow_internet_sharing; 206 break; 207 } 208 if (res > 0) { 209 unsupportedList.add(res); 210 } 211 } 212 break; 213 case Tags.PROVISION_ATTACHMENTS_ENABLED: 214 policy.mDontAllowAttachments = getValueInt() != 1; 215 break; 216 // Bluetooth: 0 = no bluetooth; 1 = only hands-free; 2 = allowed 217 case Tags.PROVISION_ALLOW_BLUETOOTH: 218 if (getValueInt() != 2) { 219 tagIsSupported = false; 220 unsupportedList.add(R.string.policy_bluetooth_restricted); 221 } 222 break; 223 // We may now support device (internal) encryption; we'll check this capability 224 // below with the call to SecurityPolicy.isSupported() 225 case Tags.PROVISION_REQUIRE_DEVICE_ENCRYPTION: 226 if (getValueInt() == 1) { 227 if (!deviceSupportsEncryption()) { 228 tagIsSupported = false; 229 unsupportedList.add(R.string.policy_require_encryption); 230 } else { 231 policy.mRequireEncryption = true; 232 } 233 } 234 break; 235 // Note that DEVICE_ENCRYPTION_ENABLED refers to SD card encryption, which the OS 236 // does not yet support. 237 case Tags.PROVISION_DEVICE_ENCRYPTION_ENABLED: 238 if (getValueInt() == 1) { 239 log("Policy requires SD card encryption"); 240 // Let's see if this can be supported on our device... 241 if (deviceSupportsEncryption()) { 242 // NOTE: Private API! 243 // Go through volumes; if ANY are removable, we can't support this 244 // policy. 245 tagIsSupported = !hasRemovableStorage(); 246 if (tagIsSupported) { 247 // If this policy is requested, we MUST also require encryption 248 log("Device supports SD card encryption"); 249 policy.mRequireEncryption = true; 250 break; 251 } 252 } else { 253 log("Device doesn't support encryption; failing"); 254 tagIsSupported = false; 255 } 256 // If we fall through, we can't support the policy 257 unsupportedList.add(R.string.policy_require_sd_encryption); 258 } 259 break; 260 // Note this policy; we enforce it in ExchangeService 261 case Tags.PROVISION_REQUIRE_MANUAL_SYNC_WHEN_ROAMING: 262 policy.mRequireManualSyncWhenRoaming = getValueInt() == 1; 263 break; 264 // We are allowed to accept policies, regardless of value of this tag 265 // TODO: When we DO support a recovery password, we need to store the value in 266 // the account (so we know to utilize it) 267 case Tags.PROVISION_PASSWORD_RECOVERY_ENABLED: 268 // Read, but ignore, value 269 policy.mPasswordRecoveryEnabled = getValueInt() == 1; 270 break; 271 // The following policies, if true, can't be supported at the moment 272 case Tags.PROVISION_REQUIRE_SIGNED_SMIME_MESSAGES: 273 case Tags.PROVISION_REQUIRE_ENCRYPTED_SMIME_MESSAGES: 274 case Tags.PROVISION_REQUIRE_SIGNED_SMIME_ALGORITHM: 275 case Tags.PROVISION_REQUIRE_ENCRYPTION_SMIME_ALGORITHM: 276 if (getValueInt() == 1) { 277 tagIsSupported = false; 278 if (!smimeRequired) { 279 unsupportedList.add(R.string.policy_require_smime); 280 smimeRequired = true; 281 } 282 } 283 break; 284 case Tags.PROVISION_MAX_ATTACHMENT_SIZE: 285 int max = getValueInt(); 286 if (max > 0) { 287 policy.mMaxAttachmentSize = max; 288 } 289 break; 290 // Complex characters are supported 291 case Tags.PROVISION_MIN_DEVICE_PASSWORD_COMPLEX_CHARS: 292 policy.mPasswordComplexChars = getValueInt(); 293 break; 294 // The following policies are moot; they allow functionality that we don't support 295 case Tags.PROVISION_ALLOW_DESKTOP_SYNC: 296 case Tags.PROVISION_ALLOW_SMIME_ENCRYPTION_NEGOTIATION: 297 case Tags.PROVISION_ALLOW_SMIME_SOFT_CERTS: 298 case Tags.PROVISION_ALLOW_REMOTE_DESKTOP: 299 skipTag(); 300 break; 301 // We don't handle approved/unapproved application lists 302 case Tags.PROVISION_UNAPPROVED_IN_ROM_APPLICATION_LIST: 303 case Tags.PROVISION_APPROVED_APPLICATION_LIST: 304 // Parse and throw away the content 305 if (specifiesApplications(tag)) { 306 tagIsSupported = false; 307 if (tag == Tags.PROVISION_UNAPPROVED_IN_ROM_APPLICATION_LIST) { 308 unsupportedList.add(R.string.policy_app_blacklist); 309 } else { 310 unsupportedList.add(R.string.policy_app_whitelist); 311 } 312 } 313 break; 314 // We accept calendar age, since we never ask for more than two weeks, and that's 315 // the most restrictive policy 316 case Tags.PROVISION_MAX_CALENDAR_AGE_FILTER: 317 policy.mMaxCalendarLookback = getValueInt(); 318 break; 319 // We handle max email lookback 320 case Tags.PROVISION_MAX_EMAIL_AGE_FILTER: 321 policy.mMaxEmailLookback = getValueInt(); 322 break; 323 // We currently reject these next two policies 324 case Tags.PROVISION_MAX_EMAIL_BODY_TRUNCATION_SIZE: 325 case Tags.PROVISION_MAX_EMAIL_HTML_BODY_TRUNCATION_SIZE: 326 String value = getValue(); 327 // -1 indicates no required truncation 328 if (!value.equals("-1")) { 329 max = Integer.parseInt(value); 330 if (tag == Tags.PROVISION_MAX_EMAIL_BODY_TRUNCATION_SIZE) { 331 policy.mMaxTextTruncationSize = max; 332 unsupportedList.add(R.string.policy_text_truncation); 333 } else { 334 policy.mMaxHtmlTruncationSize = max; 335 unsupportedList.add(R.string.policy_html_truncation); 336 } 337 tagIsSupported = false; 338 } 339 break; 340 default: 341 skipTag(); 342 } 343 344 if (!tagIsSupported) { 345 log("Policy not supported: " + tag); 346 mIsSupportable = false; 347 } 348 } 349 350 // Make sure policy settings are valid; password not enabled trumps other password settings 351 if (!passwordEnabled) { 352 policy.mPasswordMode = Policy.PASSWORD_MODE_NONE; 353 } 354 355 if (!unsupportedList.isEmpty()) { 356 StringBuilder sb = new StringBuilder(); 357 for (int res: unsupportedList) { 358 addPolicyString(sb, res); 359 } 360 policy.mProtocolPoliciesUnsupported = sb.toString(); 361 } 362 363 setPolicy(policy); 364 } 365 366 /** 367 * Return whether or not either of the application list tags specifies any applications 368 * @param endTag the tag whose children we're walking through 369 * @return whether any applications were specified (by name or by hash) 370 * @throws IOException 371 */ 372 private boolean specifiesApplications(int endTag) throws IOException { 373 boolean specifiesApplications = false; 374 while (nextTag(endTag) != END) { 375 switch (tag) { 376 case Tags.PROVISION_APPLICATION_NAME: 377 case Tags.PROVISION_HASH: 378 specifiesApplications = true; 379 break; 380 default: 381 skipTag(); 382 } 383 } 384 return specifiesApplications; 385 } 386 387 /*package*/ void parseProvisionDocXml(String doc) throws IOException { 388 Policy policy = new Policy(); 389 390 try { 391 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 392 XmlPullParser parser = factory.newPullParser(); 393 parser.setInput(new ByteArrayInputStream(doc.getBytes()), "UTF-8"); 394 int type = parser.getEventType(); 395 if (type == XmlPullParser.START_DOCUMENT) { 396 type = parser.next(); 397 if (type == XmlPullParser.START_TAG) { 398 String tagName = parser.getName(); 399 if (tagName.equals("wap-provisioningdoc")) { 400 parseWapProvisioningDoc(parser, policy); 401 } 402 } 403 } 404 } catch (XmlPullParserException e) { 405 throw new IOException(); 406 } 407 408 setPolicy(policy); 409 } 410 411 /** 412 * Return true if password is required; otherwise false. 413 */ 414 private boolean parseSecurityPolicy(XmlPullParser parser, Policy policy) 415 throws XmlPullParserException, IOException { 416 boolean passwordRequired = true; 417 while (true) { 418 int type = parser.nextTag(); 419 if (type == XmlPullParser.END_TAG && parser.getName().equals("characteristic")) { 420 break; 421 } else if (type == XmlPullParser.START_TAG) { 422 String tagName = parser.getName(); 423 if (tagName.equals("parm")) { 424 String name = parser.getAttributeValue(null, "name"); 425 if (name.equals("4131")) { 426 String value = parser.getAttributeValue(null, "value"); 427 if (value.equals("1")) { 428 passwordRequired = false; 429 } 430 } 431 } 432 } 433 } 434 return passwordRequired; 435 } 436 437 private void parseCharacteristic(XmlPullParser parser, Policy policy) 438 throws XmlPullParserException, IOException { 439 boolean enforceInactivityTimer = true; 440 while (true) { 441 int type = parser.nextTag(); 442 if (type == XmlPullParser.END_TAG && parser.getName().equals("characteristic")) { 443 break; 444 } else if (type == XmlPullParser.START_TAG) { 445 if (parser.getName().equals("parm")) { 446 String name = parser.getAttributeValue(null, "name"); 447 String value = parser.getAttributeValue(null, "value"); 448 if (name.equals("AEFrequencyValue")) { 449 if (enforceInactivityTimer) { 450 if (value.equals("0")) { 451 policy.mMaxScreenLockTime = 1; 452 } else { 453 policy.mMaxScreenLockTime = 60*Integer.parseInt(value); 454 } 455 } 456 } else if (name.equals("AEFrequencyType")) { 457 // "0" here means we don't enforce an inactivity timeout 458 if (value.equals("0")) { 459 enforceInactivityTimer = false; 460 } 461 } else if (name.equals("DeviceWipeThreshold")) { 462 policy.mPasswordMaxFails = Integer.parseInt(value); 463 } else if (name.equals("CodewordFrequency")) { 464 // Ignore; has no meaning for us 465 } else if (name.equals("MinimumPasswordLength")) { 466 policy.mPasswordMinLength = Integer.parseInt(value); 467 } else if (name.equals("PasswordComplexity")) { 468 if (value.equals("0")) { 469 policy.mPasswordMode = Policy.PASSWORD_MODE_STRONG; 470 } else { 471 policy.mPasswordMode = Policy.PASSWORD_MODE_SIMPLE; 472 } 473 } 474 } 475 } 476 } 477 } 478 479 private void parseRegistry(XmlPullParser parser, Policy policy) 480 throws XmlPullParserException, IOException { 481 while (true) { 482 int type = parser.nextTag(); 483 if (type == XmlPullParser.END_TAG && parser.getName().equals("characteristic")) { 484 break; 485 } else if (type == XmlPullParser.START_TAG) { 486 String name = parser.getName(); 487 if (name.equals("characteristic")) { 488 parseCharacteristic(parser, policy); 489 } 490 } 491 } 492 } 493 494 private void parseWapProvisioningDoc(XmlPullParser parser, Policy policy) 495 throws XmlPullParserException, IOException { 496 while (true) { 497 int type = parser.nextTag(); 498 if (type == XmlPullParser.END_TAG && parser.getName().equals("wap-provisioningdoc")) { 499 break; 500 } else if (type == XmlPullParser.START_TAG) { 501 String name = parser.getName(); 502 if (name.equals("characteristic")) { 503 String atype = parser.getAttributeValue(null, "type"); 504 if (atype.equals("SecurityPolicy")) { 505 // If a password isn't required, stop here 506 if (!parseSecurityPolicy(parser, policy)) { 507 return; 508 } 509 } else if (atype.equals("Registry")) { 510 parseRegistry(parser, policy); 511 return; 512 } 513 } 514 } 515 } 516 } 517 518 private void parseProvisionData() throws IOException { 519 while (nextTag(Tags.PROVISION_DATA) != END) { 520 if (tag == Tags.PROVISION_EAS_PROVISION_DOC) { 521 parseProvisionDocWbxml(); 522 } else { 523 skipTag(); 524 } 525 } 526 } 527 528 private void parsePolicy() throws IOException { 529 String policyType = null; 530 while (nextTag(Tags.PROVISION_POLICY) != END) { 531 switch (tag) { 532 case Tags.PROVISION_POLICY_TYPE: 533 policyType = getValue(); 534 mService.userLog("Policy type: ", policyType); 535 break; 536 case Tags.PROVISION_POLICY_KEY: 537 mSecuritySyncKey = getValue(); 538 break; 539 case Tags.PROVISION_STATUS: 540 mService.userLog("Policy status: ", getValue()); 541 break; 542 case Tags.PROVISION_DATA: 543 if (policyType.equalsIgnoreCase(EasSyncService.EAS_2_POLICY_TYPE)) { 544 // Parse the old style XML document 545 parseProvisionDocXml(getValue()); 546 } else { 547 // Parse the newer WBXML data 548 parseProvisionData(); 549 } 550 break; 551 default: 552 skipTag(); 553 } 554 } 555 } 556 557 private void parsePolicies() throws IOException { 558 while (nextTag(Tags.PROVISION_POLICIES) != END) { 559 if (tag == Tags.PROVISION_POLICY) { 560 parsePolicy(); 561 } else { 562 skipTag(); 563 } 564 } 565 } 566 567 private void parseDeviceInformation() throws IOException { 568 while (nextTag(Tags.SETTINGS_DEVICE_INFORMATION) != END) { 569 if (tag == Tags.SETTINGS_STATUS) { 570 mService.userLog("DeviceInformation status: " + getValue()); 571 } else { 572 skipTag(); 573 } 574 } 575 } 576 577 @Override 578 public boolean parse() throws IOException { 579 boolean res = false; 580 if (nextTag(START_DOCUMENT) != Tags.PROVISION_PROVISION) { 581 throw new IOException(); 582 } 583 while (nextTag(START_DOCUMENT) != END_DOCUMENT) { 584 switch (tag) { 585 case Tags.PROVISION_STATUS: 586 int status = getValueInt(); 587 mService.userLog("Provision status: ", status); 588 res = (status == 1); 589 break; 590 case Tags.SETTINGS_DEVICE_INFORMATION: 591 parseDeviceInformation(); 592 break; 593 case Tags.PROVISION_POLICIES: 594 parsePolicies(); 595 break; 596 case Tags.PROVISION_REMOTE_WIPE: 597 // Indicate remote wipe command received 598 mRemoteWipe = true; 599 break; 600 default: 601 skipTag(); 602 } 603 } 604 return res; 605 } 606 607 /** 608 * In order to determine whether the device has removable storage, we need to use the 609 * StorageVolume class, which is hidden (for now) by the framework. Without this, we'd have 610 * to reject all policies that require sd card encryption. 611 * 612 * TODO: Rewrite this when an appropriate API is available from the framework 613 */ 614 private boolean hasRemovableStorage() { 615 try { 616 StorageManager sm = (StorageManager)mService.mContext.getSystemService( 617 Context.STORAGE_SERVICE); 618 Class<?> svClass = Class.forName("android.os.storage.StorageVolume"); 619 Class<?> svManager = Class.forName("android.os.storage.StorageManager"); 620 Method gvl = svManager.getDeclaredMethod("getVolumeList"); 621 Object[] volumeList = (Object[]) gvl.invoke(sm); 622 for (Object volume: volumeList) { 623 Method isRemovable = svClass.getDeclaredMethod("isRemovable"); 624 Method getDescription = svClass.getDeclaredMethod("getDescription"); 625 String desc = (String)getDescription.invoke(volume); 626 if ((Boolean)isRemovable.invoke(volume)) { 627 log("Removable: " + desc); 628 return true; 629 } else { 630 log("Not Removable: " + desc); 631 } 632 } 633 return false; 634 } catch (ClassNotFoundException e) { 635 } catch (NoSuchMethodException e) { 636 } catch (IllegalArgumentException e) { 637 } catch (IllegalAccessException e) { 638 } catch (InvocationTargetException e) { 639 } 640 // To be safe, we'll always indicate that there IS removable storage 641 return true; 642 } 643} 644