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