• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.exchange.Eas;
26 import com.android.exchange.R;
27 import com.android.exchange.eas.EasProvision;
28 import com.android.mail.utils.LogUtils;
29 
30 import org.xmlpull.v1.XmlPullParser;
31 import org.xmlpull.v1.XmlPullParserException;
32 import org.xmlpull.v1.XmlPullParserFactory;
33 
34 import java.io.ByteArrayInputStream;
35 import java.io.File;
36 import java.io.IOException;
37 import java.io.InputStream;
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 
ProvisionParser(final Context context, final InputStream in)54     public ProvisionParser(final Context context, final InputStream in) throws IOException {
55         super(in);
56         mContext = context;
57         mResources = context.getResources();
58     }
59 
getPolicy()60     public Policy getPolicy() {
61         return mPolicy;
62     }
63 
getSecuritySyncKey()64     public String getSecuritySyncKey() {
65         return mSecuritySyncKey;
66     }
67 
setSecuritySyncKey(String securitySyncKey)68     public void setSecuritySyncKey(String securitySyncKey) {
69         mSecuritySyncKey = securitySyncKey;
70     }
71 
getRemoteWipe()72     public boolean getRemoteWipe() {
73         return mRemoteWipe;
74     }
75 
hasSupportablePolicySet()76     public boolean hasSupportablePolicySet() {
77         return (mPolicy != null) && mIsSupportable;
78     }
79 
clearUnsupportablePolicies()80     public void clearUnsupportablePolicies() {
81         mIsSupportable = true;
82         mPolicy.mProtocolPoliciesUnsupported = null;
83     }
84 
addPolicyString(StringBuilder sb, int res)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      */
setPolicy(Policy policy)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 
deviceSupportsEncryption()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 
parseProvisionDocWbxml()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      */
specifiesApplications(int endTag)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 
parseProvisionDocXml(String doc)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      */
parseSecurityPolicy(XmlPullParser parser)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 
parseCharacteristic(XmlPullParser parser, Policy policy)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 
parseRegistry(XmlPullParser parser, Policy policy)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 
parseWapProvisioningDoc(XmlPullParser parser, Policy policy)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 
parseProvisionData()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 
parsePolicy()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 
parsePolicies()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 
parseDeviceInformation()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
parse()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      */
hasRemovableStorage()618     private boolean hasRemovableStorage() {
619         final File[] cacheDirs = ContextCompat.getExternalCacheDirs(mContext);
620         return Environment.isExternalStorageRemovable()
621                 || (cacheDirs != null && cacheDirs.length > 1);
622     }
623 }
624