Home | History | Annotate | Download | only in provider
      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 
     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 
    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 
    138     public static Policy restorePolicyWithId(Context context, long id) {
    139         return restorePolicyWithId(context, id, null);
    140     }
    141 
    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
    148     protected Uri getContentNotificationUri() {
    149         return Policy.CONTENT_URI;
    150     }
    151 
    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 
    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
    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      */
    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      */
    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
    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
    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
    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
    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      */
    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      */
    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 
    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
    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
    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
    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      */
    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 }