1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.emailcommon.provider;
18 import android.app.admin.DevicePolicyManager;
19 import android.content.ContentResolver;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.database.ContentObserver;
24 import android.database.Cursor;
25 import android.net.Uri;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 
29 import com.android.emailcommon.utility.TextUtilities;
30 import com.android.emailcommon.utility.Utility;
31 
32 import java.util.ArrayList;
33 
34 /**
35  * The Policy class represents a set of security requirements that are associated with an Account.
36  * The requirements may be either device-specific (e.g. password) or application-specific (e.g.
37  * a limit on the sync window for the Account)
38  */
39 public final class Policy extends EmailContent implements EmailContent.PolicyColumns, Parcelable {
40     public static final boolean DEBUG_POLICY = false;  // DO NOT SUBMIT WITH THIS SET TO TRUE
41     public static final String TAG = "Email/Policy";
42 
43     public static final String TABLE_NAME = "Policy";
44     public static Uri CONTENT_URI;
45 
initPolicy()46     public static void initPolicy() {
47         CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/policy");
48     }
49 
50     /* Convert days to mSec (used for password expiration) */
51     private static final long DAYS_TO_MSEC = 24 * 60 * 60 * 1000;
52     /* Small offset (2 minutes) added to policy expiration to make user testing easier. */
53     private static final long EXPIRATION_OFFSET_MSEC = 2 * 60 * 1000;
54 
55     public static final int PASSWORD_MODE_NONE = 0;
56     public static final int PASSWORD_MODE_SIMPLE = 1;
57     public static final int PASSWORD_MODE_STRONG = 2;
58 
59     public static final char POLICY_STRING_DELIMITER = '\1';
60 
61     public int mPasswordMode;
62     public int mPasswordMinLength;
63     public int mPasswordMaxFails;
64     public int mPasswordExpirationDays;
65     public int mPasswordHistory;
66     public int mPasswordComplexChars;
67     public int mMaxScreenLockTime;
68     public boolean mRequireRemoteWipe;
69     public boolean mRequireEncryption;
70     public boolean mRequireEncryptionExternal;
71     public boolean mRequireManualSyncWhenRoaming;
72     public boolean mDontAllowCamera;
73     public boolean mDontAllowAttachments;
74     public boolean mDontAllowHtml;
75     public int mMaxAttachmentSize;
76     public int mMaxTextTruncationSize;
77     public int mMaxHtmlTruncationSize;
78     public int mMaxEmailLookback;
79     public int mMaxCalendarLookback;
80     public boolean mPasswordRecoveryEnabled;
81     public String mProtocolPoliciesEnforced;
82     public String mProtocolPoliciesUnsupported;
83 
84     public static final int CONTENT_ID_COLUMN = 0;
85     public static final int CONTENT_PASSWORD_MODE_COLUMN = 1;
86     public static final int CONTENT_PASSWORD_MIN_LENGTH_COLUMN = 2;
87     public static final int CONTENT_PASSWORD_EXPIRATION_DAYS_COLUMN = 3;
88     public static final int CONTENT_PASSWORD_HISTORY_COLUMN = 4;
89     public static final int CONTENT_PASSWORD_COMPLEX_CHARS_COLUMN = 5;
90     public static final int CONTENT_PASSWORD_MAX_FAILS_COLUMN = 6;
91     public static final int CONTENT_MAX_SCREEN_LOCK_TIME_COLUMN = 7;
92     public static final int CONTENT_REQUIRE_REMOTE_WIPE_COLUMN = 8;
93     public static final int CONTENT_REQUIRE_ENCRYPTION_COLUMN = 9;
94     public static final int CONTENT_REQUIRE_ENCRYPTION_EXTERNAL_COLUMN = 10;
95     public static final int CONTENT_REQUIRE_MANUAL_SYNC_WHEN_ROAMING = 11;
96     public static final int CONTENT_DONT_ALLOW_CAMERA_COLUMN = 12;
97     public static final int CONTENT_DONT_ALLOW_ATTACHMENTS_COLUMN = 13;
98     public static final int CONTENT_DONT_ALLOW_HTML_COLUMN = 14;
99     public static final int CONTENT_MAX_ATTACHMENT_SIZE_COLUMN = 15;
100     public static final int CONTENT_MAX_TEXT_TRUNCATION_SIZE_COLUMN = 16;
101     public static final int CONTENT_MAX_HTML_TRUNCATION_SIZE_COLUMN = 17;
102     public static final int CONTENT_MAX_EMAIL_LOOKBACK_COLUMN = 18;
103     public static final int CONTENT_MAX_CALENDAR_LOOKBACK_COLUMN = 19;
104     public static final int CONTENT_PASSWORD_RECOVERY_ENABLED_COLUMN = 20;
105     public static final int CONTENT_PROTOCOL_POLICIES_ENFORCED_COLUMN = 21;
106     public static final int CONTENT_PROTOCOL_POLICIES_UNSUPPORTED_COLUMN = 22;
107 
108     public static final String[] CONTENT_PROJECTION = new String[] {RECORD_ID,
109         PolicyColumns.PASSWORD_MODE, PolicyColumns.PASSWORD_MIN_LENGTH,
110         PolicyColumns.PASSWORD_EXPIRATION_DAYS, PolicyColumns.PASSWORD_HISTORY,
111         PolicyColumns.PASSWORD_COMPLEX_CHARS, PolicyColumns.PASSWORD_MAX_FAILS,
112         PolicyColumns.MAX_SCREEN_LOCK_TIME, PolicyColumns.REQUIRE_REMOTE_WIPE,
113         PolicyColumns.REQUIRE_ENCRYPTION, PolicyColumns.REQUIRE_ENCRYPTION_EXTERNAL,
114         PolicyColumns.REQUIRE_MANUAL_SYNC_WHEN_ROAMING, PolicyColumns.DONT_ALLOW_CAMERA,
115         PolicyColumns.DONT_ALLOW_ATTACHMENTS, PolicyColumns.DONT_ALLOW_HTML,
116         PolicyColumns.MAX_ATTACHMENT_SIZE, PolicyColumns.MAX_TEXT_TRUNCATION_SIZE,
117         PolicyColumns.MAX_HTML_TRUNCATION_SIZE, PolicyColumns.MAX_EMAIL_LOOKBACK,
118         PolicyColumns.MAX_CALENDAR_LOOKBACK, PolicyColumns.PASSWORD_RECOVERY_ENABLED,
119         PolicyColumns.PROTOCOL_POLICIES_ENFORCED, PolicyColumns.PROTOCOL_POLICIES_UNSUPPORTED
120     };
121 
122     public static final Policy NO_POLICY = new Policy();
123 
124     private static final String[] ATTACHMENT_RESET_PROJECTION =
125         new String[] {EmailContent.RECORD_ID, AttachmentColumns.SIZE, AttachmentColumns.FLAGS};
126     private static final int ATTACHMENT_RESET_PROJECTION_ID = 0;
127     private static final int ATTACHMENT_RESET_PROJECTION_SIZE = 1;
128     private static final int ATTACHMENT_RESET_PROJECTION_FLAGS = 2;
129 
Policy()130     public Policy() {
131         mBaseUri = CONTENT_URI;
132         // By default, the password mode is "none"
133         mPasswordMode = PASSWORD_MODE_NONE;
134         // All server policies require the ability to wipe the device
135         mRequireRemoteWipe = true;
136     }
137 
restorePolicyWithId(Context context, long id)138     public static Policy restorePolicyWithId(Context context, long id) {
139         return restorePolicyWithId(context, id, null);
140     }
141 
restorePolicyWithId(Context context, long id, ContentObserver observer)142     public static Policy restorePolicyWithId(Context context, long id, ContentObserver observer) {
143         return EmailContent.restoreContentWithId(context, Policy.class, Policy.CONTENT_URI,
144                 Policy.CONTENT_PROJECTION, id, observer);
145     }
146 
147     @Override
getContentNotificationUri()148     protected Uri getContentNotificationUri() {
149         return Policy.CONTENT_URI;
150     }
151 
getAccountIdWithPolicyKey(Context context, long id)152     public static long getAccountIdWithPolicyKey(Context context, long id) {
153         return Utility.getFirstRowLong(context, Account.CONTENT_URI, Account.ID_PROJECTION,
154                 AccountColumns.POLICY_KEY + "=?", new String[] {Long.toString(id)}, null,
155                 Account.ID_PROJECTION_COLUMN, Account.NO_ACCOUNT);
156     }
157 
addPolicyStringToList(String policyString, ArrayList<String> policyList)158     public static ArrayList<String> addPolicyStringToList(String policyString,
159             ArrayList<String> policyList) {
160         if (policyString != null) {
161             int start = 0;
162             int len = policyString.length();
163             while(start < len) {
164                 int end = policyString.indexOf(POLICY_STRING_DELIMITER, start);
165                 if (end > start) {
166                     policyList.add(policyString.substring(start, end));
167                     start = end + 1;
168                 } else {
169                     break;
170                 }
171             }
172         }
173         return policyList;
174     }
175 
176     // We override this method to insure that we never write invalid policy data to the provider
177     @Override
save(Context context)178     public Uri save(Context context) {
179         normalize();
180         return super.save(context);
181     }
182 
183     /**
184      * Review all attachment records for this account, and reset the "don't allow download" flag
185      * as required by the account's new security policies
186      * @param context the caller's context
187      * @param account the account whose attachments need to be reviewed
188      * @param policy the new policy for this account
189      */
setAttachmentFlagsForNewPolicy(Context context, Account account, Policy policy)190     public static void setAttachmentFlagsForNewPolicy(Context context, Account account,
191             Policy policy) {
192         // A nasty bit of work; start with all attachments for a given account
193         ContentResolver resolver = context.getContentResolver();
194         Cursor c = resolver.query(Attachment.CONTENT_URI, ATTACHMENT_RESET_PROJECTION,
195                 AttachmentColumns.ACCOUNT_KEY + "=?", new String[] {Long.toString(account.mId)},
196                 null);
197         ContentValues cv = new ContentValues();
198         try {
199             // Get maximum allowed size (0 if we don't allow attachments at all)
200             int policyMax = policy.mDontAllowAttachments ? 0 : (policy.mMaxAttachmentSize > 0) ?
201                     policy.mMaxAttachmentSize : Integer.MAX_VALUE;
202             while (c.moveToNext()) {
203                 int flags = c.getInt(ATTACHMENT_RESET_PROJECTION_FLAGS);
204                 int size = c.getInt(ATTACHMENT_RESET_PROJECTION_SIZE);
205                 boolean wasRestricted = (flags & Attachment.FLAG_POLICY_DISALLOWS_DOWNLOAD) != 0;
206                 boolean isRestricted = size > policyMax;
207                 if (isRestricted != wasRestricted) {
208                     if (isRestricted) {
209                         flags |= Attachment.FLAG_POLICY_DISALLOWS_DOWNLOAD;
210                     } else {
211                         flags &= ~Attachment.FLAG_POLICY_DISALLOWS_DOWNLOAD;
212                     }
213                     long id = c.getLong(ATTACHMENT_RESET_PROJECTION_ID);
214                     cv.put(AttachmentColumns.FLAGS, flags);
215                     resolver.update(ContentUris.withAppendedId(Attachment.CONTENT_URI, id),
216                             cv, null, null);
217                 }
218             }
219         } finally {
220             c.close();
221         }
222     }
223 
224     /**
225      * Normalize the Policy.  If the password mode is "none", zero out all password-related fields;
226      * zero out complex characters for simple passwords.
227      */
normalize()228     public void normalize() {
229         if (mPasswordMode == PASSWORD_MODE_NONE) {
230             mPasswordMaxFails = 0;
231             mMaxScreenLockTime = 0;
232             mPasswordMinLength = 0;
233             mPasswordComplexChars = 0;
234             mPasswordHistory = 0;
235             mPasswordExpirationDays = 0;
236         } else {
237             if ((mPasswordMode != PASSWORD_MODE_SIMPLE) &&
238                     (mPasswordMode != PASSWORD_MODE_STRONG)) {
239                 throw new IllegalArgumentException("password mode");
240             }
241             // If we're only requiring a simple password, set complex chars to zero; note
242             // that EAS can erroneously send non-zero values in this case
243             if (mPasswordMode == PASSWORD_MODE_SIMPLE) {
244                 mPasswordComplexChars = 0;
245             }
246         }
247     }
248 
249     @Override
equals(Object other)250     public boolean equals(Object other) {
251         if (!(other instanceof Policy)) return false;
252         Policy otherPolicy = (Policy)other;
253         // Policies here are enforced by the DPM
254         if (mRequireEncryption != otherPolicy.mRequireEncryption) return false;
255         if (mRequireEncryptionExternal != otherPolicy.mRequireEncryptionExternal) return false;
256         if (mRequireRemoteWipe != otherPolicy.mRequireRemoteWipe) return false;
257         if (mMaxScreenLockTime != otherPolicy.mMaxScreenLockTime) return false;
258         if (mPasswordComplexChars != otherPolicy.mPasswordComplexChars) return false;
259         if (mPasswordExpirationDays != otherPolicy.mPasswordExpirationDays) return false;
260         if (mPasswordHistory != otherPolicy.mPasswordHistory) return false;
261         if (mPasswordMaxFails != otherPolicy.mPasswordMaxFails) return false;
262         if (mPasswordMinLength != otherPolicy.mPasswordMinLength) return false;
263         if (mPasswordMode != otherPolicy.mPasswordMode) return false;
264         if (mDontAllowCamera != otherPolicy.mDontAllowCamera) return false;
265 
266         // Policies here are enforced by the Exchange sync manager
267         // They should eventually be removed from Policy and replaced with some opaque data
268         if (mRequireManualSyncWhenRoaming != otherPolicy.mRequireManualSyncWhenRoaming) {
269             return false;
270         }
271         if (mDontAllowAttachments != otherPolicy.mDontAllowAttachments) return false;
272         if (mDontAllowHtml != otherPolicy.mDontAllowHtml) return false;
273         if (mMaxAttachmentSize != otherPolicy.mMaxAttachmentSize) return false;
274         if (mMaxTextTruncationSize != otherPolicy.mMaxTextTruncationSize) return false;
275         if (mMaxHtmlTruncationSize != otherPolicy.mMaxHtmlTruncationSize) return false;
276         if (mMaxEmailLookback != otherPolicy.mMaxEmailLookback) return false;
277         if (mMaxCalendarLookback != otherPolicy.mMaxCalendarLookback) return false;
278         if (mPasswordRecoveryEnabled != otherPolicy.mPasswordRecoveryEnabled) return false;
279 
280         if (!TextUtilities.stringOrNullEquals(mProtocolPoliciesEnforced,
281                 otherPolicy.mProtocolPoliciesEnforced)) {
282             return false;
283         }
284         if (!TextUtilities.stringOrNullEquals(mProtocolPoliciesUnsupported,
285                 otherPolicy.mProtocolPoliciesUnsupported)) {
286             return false;
287         }
288         return true;
289     }
290 
291     @Override
hashCode()292     public int hashCode() {
293         int code = mRequireEncryption ? 1 : 0;
294         code += (mRequireEncryptionExternal ? 1 : 0) << 1;
295         code += (mRequireRemoteWipe ? 1 : 0) << 2;
296         code += (mMaxScreenLockTime << 3);
297         code += (mPasswordComplexChars << 6);
298         code += (mPasswordExpirationDays << 12);
299         code += (mPasswordHistory << 15);
300         code += (mPasswordMaxFails << 18);
301         code += (mPasswordMinLength << 22);
302         code += (mPasswordMode << 26);
303         // Don't need to include the other fields
304         return code;
305     }
306 
307     @Override
restore(Cursor cursor)308     public void restore(Cursor cursor) {
309         mBaseUri = CONTENT_URI;
310         mId = cursor.getLong(CONTENT_ID_COLUMN);
311         mPasswordMode = cursor.getInt(CONTENT_PASSWORD_MODE_COLUMN);
312         mPasswordMinLength = cursor.getInt(CONTENT_PASSWORD_MIN_LENGTH_COLUMN);
313         mPasswordMaxFails = cursor.getInt(CONTENT_PASSWORD_MAX_FAILS_COLUMN);
314         mPasswordHistory = cursor.getInt(CONTENT_PASSWORD_HISTORY_COLUMN);
315         mPasswordExpirationDays = cursor.getInt(CONTENT_PASSWORD_EXPIRATION_DAYS_COLUMN);
316         mPasswordComplexChars = cursor.getInt(CONTENT_PASSWORD_COMPLEX_CHARS_COLUMN);
317         mMaxScreenLockTime = cursor.getInt(CONTENT_MAX_SCREEN_LOCK_TIME_COLUMN);
318         mRequireRemoteWipe = cursor.getInt(CONTENT_REQUIRE_REMOTE_WIPE_COLUMN) == 1;
319         mRequireEncryption = cursor.getInt(CONTENT_REQUIRE_ENCRYPTION_COLUMN) == 1;
320         mRequireEncryptionExternal =
321             cursor.getInt(CONTENT_REQUIRE_ENCRYPTION_EXTERNAL_COLUMN) == 1;
322         mRequireManualSyncWhenRoaming =
323             cursor.getInt(CONTENT_REQUIRE_MANUAL_SYNC_WHEN_ROAMING) == 1;
324         mDontAllowCamera = cursor.getInt(CONTENT_DONT_ALLOW_CAMERA_COLUMN) == 1;
325         mDontAllowAttachments = cursor.getInt(CONTENT_DONT_ALLOW_ATTACHMENTS_COLUMN) == 1;
326         mDontAllowHtml = cursor.getInt(CONTENT_DONT_ALLOW_HTML_COLUMN) == 1;
327         mMaxAttachmentSize = cursor.getInt(CONTENT_MAX_ATTACHMENT_SIZE_COLUMN);
328         mMaxTextTruncationSize = cursor.getInt(CONTENT_MAX_TEXT_TRUNCATION_SIZE_COLUMN);
329         mMaxHtmlTruncationSize = cursor.getInt(CONTENT_MAX_HTML_TRUNCATION_SIZE_COLUMN);
330         mMaxEmailLookback = cursor.getInt(CONTENT_MAX_EMAIL_LOOKBACK_COLUMN);
331         mMaxCalendarLookback = cursor.getInt(CONTENT_MAX_CALENDAR_LOOKBACK_COLUMN);
332         mPasswordRecoveryEnabled = cursor.getInt(CONTENT_PASSWORD_RECOVERY_ENABLED_COLUMN) == 1;
333         mProtocolPoliciesEnforced = cursor.getString(CONTENT_PROTOCOL_POLICIES_ENFORCED_COLUMN);
334         mProtocolPoliciesUnsupported =
335             cursor.getString(CONTENT_PROTOCOL_POLICIES_UNSUPPORTED_COLUMN);
336     }
337 
338     @Override
toContentValues()339     public ContentValues toContentValues() {
340         ContentValues values = new ContentValues();
341         values.put(PolicyColumns.PASSWORD_MODE, mPasswordMode);
342         values.put(PolicyColumns.PASSWORD_MIN_LENGTH, mPasswordMinLength);
343         values.put(PolicyColumns.PASSWORD_MAX_FAILS, mPasswordMaxFails);
344         values.put(PolicyColumns.PASSWORD_HISTORY, mPasswordHistory);
345         values.put(PolicyColumns.PASSWORD_EXPIRATION_DAYS, mPasswordExpirationDays);
346         values.put(PolicyColumns.PASSWORD_COMPLEX_CHARS, mPasswordComplexChars);
347         values.put(PolicyColumns.MAX_SCREEN_LOCK_TIME, mMaxScreenLockTime);
348         values.put(PolicyColumns.REQUIRE_REMOTE_WIPE, mRequireRemoteWipe);
349         values.put(PolicyColumns.REQUIRE_ENCRYPTION, mRequireEncryption);
350         values.put(PolicyColumns.REQUIRE_ENCRYPTION_EXTERNAL, mRequireEncryptionExternal);
351         values.put(PolicyColumns.REQUIRE_MANUAL_SYNC_WHEN_ROAMING, mRequireManualSyncWhenRoaming);
352         values.put(PolicyColumns.DONT_ALLOW_CAMERA, mDontAllowCamera);
353         values.put(PolicyColumns.DONT_ALLOW_ATTACHMENTS, mDontAllowAttachments);
354         values.put(PolicyColumns.DONT_ALLOW_HTML, mDontAllowHtml);
355         values.put(PolicyColumns.MAX_ATTACHMENT_SIZE, mMaxAttachmentSize);
356         values.put(PolicyColumns.MAX_TEXT_TRUNCATION_SIZE, mMaxTextTruncationSize);
357         values.put(PolicyColumns.MAX_HTML_TRUNCATION_SIZE, mMaxHtmlTruncationSize);
358         values.put(PolicyColumns.MAX_EMAIL_LOOKBACK, mMaxEmailLookback);
359         values.put(PolicyColumns.MAX_CALENDAR_LOOKBACK, mMaxCalendarLookback);
360         values.put(PolicyColumns.PASSWORD_RECOVERY_ENABLED, mPasswordRecoveryEnabled);
361         values.put(PolicyColumns.PROTOCOL_POLICIES_ENFORCED, mProtocolPoliciesEnforced);
362         values.put(PolicyColumns.PROTOCOL_POLICIES_UNSUPPORTED, mProtocolPoliciesUnsupported);
363         return values;
364     }
365 
366     /**
367      * Helper to map our internal encoding to DevicePolicyManager password modes.
368      */
getDPManagerPasswordQuality()369     public int getDPManagerPasswordQuality() {
370         switch (mPasswordMode) {
371             case PASSWORD_MODE_SIMPLE:
372                 return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
373             case PASSWORD_MODE_STRONG:
374                 if (mPasswordComplexChars == 0) {
375                     return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
376                 } else {
377                     return DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
378                 }
379             default:
380                 return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
381         }
382     }
383 
384     /**
385      * Helper to map expiration times to the millisecond values used by DevicePolicyManager.
386      */
getDPManagerPasswordExpirationTimeout()387     public long getDPManagerPasswordExpirationTimeout() {
388         long result = mPasswordExpirationDays * DAYS_TO_MSEC;
389         // Add a small offset to the password expiration.  This makes it easier to test
390         // by changing (for example) 1 day to 1 day + 5 minutes.  If you set an expiration
391         // that is within the warning period, you should get a warning fairly quickly.
392         if (result > 0) {
393             result += EXPIRATION_OFFSET_MSEC;
394         }
395         return result;
396     }
397 
appendPolicy(StringBuilder sb, String code, int value)398     private static void appendPolicy(StringBuilder sb, String code, int value) {
399         sb.append(code);
400         sb.append(":");
401         sb.append(value);
402         sb.append(" ");
403     }
404 
405     @Override
toString()406     public String toString() {
407         StringBuilder sb = new StringBuilder("[");
408         if (equals(NO_POLICY)) {
409             sb.append("No policies]");
410         } else {
411             if (mPasswordMode == PASSWORD_MODE_NONE) {
412                 sb.append("Pwd none ");
413             } else {
414                 appendPolicy(sb, "Pwd strong", mPasswordMode == PASSWORD_MODE_STRONG ? 1 : 0);
415                 appendPolicy(sb, "len", mPasswordMinLength);
416                 appendPolicy(sb, "cmpx", mPasswordComplexChars);
417                 appendPolicy(sb, "expy", mPasswordExpirationDays);
418                 appendPolicy(sb, "hist", mPasswordHistory);
419                 appendPolicy(sb, "fail", mPasswordMaxFails);
420                 appendPolicy(sb, "idle", mMaxScreenLockTime);
421             }
422             if (mRequireEncryption) {
423                 sb.append("encrypt ");
424             }
425             if (mRequireEncryptionExternal) {
426                 sb.append("encryptsd ");
427             }
428             if (mDontAllowCamera) {
429                 sb.append("nocamera ");
430             }
431             if (mDontAllowAttachments) {
432                 sb.append("noatts ");
433             }
434             if (mRequireManualSyncWhenRoaming) {
435                 sb.append("nopushroam ");
436             }
437             if (mMaxAttachmentSize > 0) {
438                 appendPolicy(sb, "attmax", mMaxAttachmentSize);
439             }
440             sb.append("]");
441         }
442         return sb.toString();
443     }
444 
445     /**
446      * Supports Parcelable
447      */
448     @Override
describeContents()449     public int describeContents() {
450         return 0;
451     }
452 
453     /**
454      * Supports Parcelable
455      */
456     public static final Parcelable.Creator<Policy> CREATOR = new Parcelable.Creator<Policy>() {
457         @Override
458         public Policy createFromParcel(Parcel in) {
459             return new Policy(in);
460         }
461 
462         @Override
463         public Policy[] newArray(int size) {
464             return new Policy[size];
465         }
466     };
467 
468     /**
469      * Supports Parcelable
470      */
471     @Override
writeToParcel(Parcel dest, int flags)472     public void writeToParcel(Parcel dest, int flags) {
473         // mBaseUri is not parceled
474         dest.writeLong(mId);
475         dest.writeInt(mPasswordMode);
476         dest.writeInt(mPasswordMinLength);
477         dest.writeInt(mPasswordMaxFails);
478         dest.writeInt(mPasswordHistory);
479         dest.writeInt(mPasswordExpirationDays);
480         dest.writeInt(mPasswordComplexChars);
481         dest.writeInt(mMaxScreenLockTime);
482         dest.writeInt(mRequireRemoteWipe ? 1 : 0);
483         dest.writeInt(mRequireEncryption ? 1 : 0);
484         dest.writeInt(mRequireEncryptionExternal ? 1 : 0);
485         dest.writeInt(mRequireManualSyncWhenRoaming ? 1 : 0);
486         dest.writeInt(mDontAllowCamera ? 1 : 0);
487         dest.writeInt(mDontAllowAttachments ? 1 : 0);
488         dest.writeInt(mDontAllowHtml ? 1 : 0);
489         dest.writeInt(mMaxAttachmentSize);
490         dest.writeInt(mMaxTextTruncationSize);
491         dest.writeInt(mMaxHtmlTruncationSize);
492         dest.writeInt(mMaxEmailLookback);
493         dest.writeInt(mMaxCalendarLookback);
494         dest.writeInt(mPasswordRecoveryEnabled ? 1 : 0);
495         dest.writeString(mProtocolPoliciesEnforced);
496         dest.writeString(mProtocolPoliciesUnsupported);
497     }
498 
499     /**
500      * Supports Parcelable
501      */
Policy(Parcel in)502     public Policy(Parcel in) {
503         mBaseUri = CONTENT_URI;
504         mId = in.readLong();
505         mPasswordMode = in.readInt();
506         mPasswordMinLength = in.readInt();
507         mPasswordMaxFails = in.readInt();
508         mPasswordHistory = in.readInt();
509         mPasswordExpirationDays = in.readInt();
510         mPasswordComplexChars = in.readInt();
511         mMaxScreenLockTime = in.readInt();
512         mRequireRemoteWipe = in.readInt() == 1;
513         mRequireEncryption = in.readInt() == 1;
514         mRequireEncryptionExternal = in.readInt() == 1;
515         mRequireManualSyncWhenRoaming = in.readInt() == 1;
516         mDontAllowCamera = in.readInt() == 1;
517         mDontAllowAttachments = in.readInt() == 1;
518         mDontAllowHtml = in.readInt() == 1;
519         mMaxAttachmentSize = in.readInt();
520         mMaxTextTruncationSize = in.readInt();
521         mMaxHtmlTruncationSize = in.readInt();
522         mMaxEmailLookback = in.readInt();
523         mMaxCalendarLookback = in.readInt();
524         mPasswordRecoveryEnabled = in.readInt() == 1;
525         mProtocolPoliciesEnforced = in.readString();
526         mProtocolPoliciesUnsupported = in.readString();
527     }
528 }