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