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