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