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.ContentProviderOperation;
     20 import android.content.ContentResolver;
     21 import android.content.ContentUris;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.content.OperationApplicationException;
     25 import android.database.Cursor;
     26 import android.net.Uri;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 
     32 import com.android.emailcommon.utility.Utility;
     33 import com.google.common.annotations.VisibleForTesting;
     34 
     35 import 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  */
     42 public 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 }