Allow database to hold oauth credentials

Change-Id: I127297fd78c7676995f1dcfa59fbbcafe4e72e8e
diff --git a/emailcommon/src/com/android/emailcommon/provider/Account.java b/emailcommon/src/com/android/emailcommon/provider/Account.java
index 1f152cf..18e37b2 100755
--- a/emailcommon/src/com/android/emailcommon/provider/Account.java
+++ b/emailcommon/src/com/android/emailcommon/provider/Account.java
@@ -31,11 +31,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.text.TextUtils;
 
 import com.android.emailcommon.provider.EmailContent.AccountColumns;
 import com.android.emailcommon.utility.Utility;
-import com.android.mail.utils.LogUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -207,9 +205,6 @@
             MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX +
             " AND " + MailboxColumns.ACCOUNT_KEY + " =?";
 
-    /**
-     * no public constructor since this is a utility class
-     */
     public Account() {
         mBaseUri = CONTENT_URI;
 
@@ -739,22 +734,55 @@
 
         int index = 0;
         int recvIndex = -1;
+        int recvCredentialsIndex = -1;
         int sendIndex = -1;
+        int sendCredentialsIndex = -1;
 
-        // Create operations for saving the send and recv hostAuths
+        // Create operations for saving the send and recv hostAuths, and their credentials.
         // Also, remember which operation in the array they represent
         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
         if (mHostAuthRecv != null) {
+            if (mHostAuthRecv.mCredential != null) {
+                recvCredentialsIndex = index++;
+                ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mCredential.mBaseUri)
+                        .withValues(mHostAuthRecv.mCredential.toContentValues())
+                        .build());
+            }
+
             recvIndex = index++;
-            ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri)
-                    .withValues(mHostAuthRecv.toContentValues())
-                    .build());
+            final ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(
+                    mHostAuthRecv.mBaseUri);
+            b.withValues(mHostAuthRecv.toContentValues());
+            if (recvCredentialsIndex >= 0) {
+                final ContentValues cv = new ContentValues();
+                cv.put(HostAuth.CREDENTIAL_KEY, recvCredentialsIndex);
+                b.withValueBackReferences(cv);
+            }
+            ops.add(b.build());
         }
         if (mHostAuthSend != null) {
+            if (mHostAuthSend.mCredential != null) {
+                if (mHostAuthRecv.mCredential != null &&
+                        mHostAuthRecv.mCredential.equals(mHostAuthSend.mCredential)) {
+                    // These two credentials are identical, use the same row.
+                    sendCredentialsIndex = recvCredentialsIndex;
+                } else {
+                    sendCredentialsIndex = index++;
+                    ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mCredential.mBaseUri)
+                            .withValues(mHostAuthRecv.mCredential.toContentValues())
+                            .build());
+                }
+            }
             sendIndex = index++;
-            ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mBaseUri)
-                    .withValues(mHostAuthSend.toContentValues())
-                    .build());
+            final ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(
+                    mHostAuthSend.mBaseUri);
+            b.withValues(mHostAuthSend.toContentValues());
+            if (sendCredentialsIndex >= 0) {
+                final ContentValues cv = new ContentValues();
+                cv.put(HostAuth.CREDENTIAL_KEY, sendCredentialsIndex);
+                b.withValueBackReferences(cv);
+            }
+            ops.add(b.build());
         }
 
         // Now do the Account
diff --git a/emailcommon/src/com/android/emailcommon/provider/Credential.java b/emailcommon/src/com/android/emailcommon/provider/Credential.java
new file mode 100644
index 0000000..dbb5932
--- /dev/null
+++ b/emailcommon/src/com/android/emailcommon/provider/Credential.java
@@ -0,0 +1,158 @@
+package com.android.emailcommon.provider;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.utility.Utility;
+import com.google.common.base.Objects;
+
+public class Credential extends EmailContent implements Parcelable {
+
+    public static final String TABLE_NAME = "Credential";
+    public static Uri CONTENT_URI;
+
+    public static final Credential EMPTY = new Credential(-1, "", "", 0);
+
+    public static void initCredential() {
+        CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/credential");
+    }
+
+    public static final String TYPE_OAUTH = "oauth";
+
+    public String mAccessToken;
+    public String mRefreshToken;
+    public long mExpiration;
+
+    // Name of the authentication provider.
+    public static final String PROVIDER_COLUMN = "provider";
+    // Access token.
+    public static final String ACCESS_TOKEN_COLUMN = "accessToken";
+    // Refresh token.
+    public static final String REFRESH_TOKEN_COLUMN = "refreshToken";
+    // Expiration date for these credentials.
+    public static final String EXPIRATION_COLUMN = "expiration";
+
+
+    public interface CredentialQuery {
+        public static final int ID_COLUMN_INDEX = 0;
+        public static final int PROVIDER_COLUMN_INDEX = 1;
+        public static final int ACCESS_TOKEN_COLUMN_INDEX = 2;
+        public static final int REFRESH_TOKEN_COLUMN_INDEX = 3;
+        public static final int EXPIRATION_COLUMN_INDEX = 4;
+
+        public static final String[] PROJECTION = new String[] {
+            RECORD_ID,
+            PROVIDER_COLUMN,
+            ACCESS_TOKEN_COLUMN,
+            REFRESH_TOKEN_COLUMN,
+            EXPIRATION_COLUMN
+        };
+    }
+
+    public Credential() {
+        mBaseUri = CONTENT_URI;
+    }
+
+    public Credential(long id, String accessToken, String refreshToken, long expiration) {
+        mBaseUri = CONTENT_URI;
+        mId = id;
+        mAccessToken = accessToken;
+        mRefreshToken = refreshToken;
+        mExpiration = expiration;
+    }
+
+    /**
+     * Restore a Credential from the database, given its unique id
+     * @param context
+     * @param id
+     * @return the instantiated Credential
+     */
+   public static Credential restoreCredentialsWithId(Context context, long id) {
+       return EmailContent.restoreContentWithId(context, Credential.class,
+               Credential.CONTENT_URI, CredentialQuery.PROJECTION, id);
+   }
+
+   @Override
+   public void restore(Cursor cursor) {
+       mBaseUri = CONTENT_URI;
+       mId = cursor.getLong(CredentialQuery.ID_COLUMN_INDEX);
+       mAccessToken = cursor.getString(CredentialQuery.ACCESS_TOKEN_COLUMN_INDEX);
+       mRefreshToken = cursor.getString(CredentialQuery.REFRESH_TOKEN_COLUMN_INDEX);
+       mExpiration = cursor.getInt(CredentialQuery.EXPIRATION_COLUMN_INDEX);
+   }
+
+   /**
+    * Supports Parcelable
+    */
+   @Override
+   public int describeContents() {
+       return 0;
+   }
+
+   /**
+    * Supports Parcelable
+    */
+   public static final Parcelable.Creator<Credential> CREATOR
+           = new Parcelable.Creator<Credential>() {
+       @Override
+       public Credential createFromParcel(Parcel in) {
+           return new Credential(in);
+       }
+
+       @Override
+       public Credential[] newArray(int size) {
+           return new Credential[size];
+       }
+   };
+
+   @Override
+   public void writeToParcel(Parcel dest, int flags) {
+       // mBaseUri is not parceled
+       dest.writeLong(mId);
+       dest.writeString(mAccessToken);
+       dest.writeString(mRefreshToken);
+       dest.writeLong(mExpiration);
+   }
+
+   /**
+    * Supports Parcelable
+    */
+   public Credential(Parcel in) {
+       mBaseUri = CONTENT_URI;
+       mId = in.readLong();
+       mAccessToken = in.readString();
+       mRefreshToken = in.readString();
+       mExpiration = in.readLong();
+   }
+
+   @Override
+   public boolean equals(Object o) {
+       if (!(o instanceof Credential)) {
+           return false;
+       }
+       Credential that = (Credential)o;
+       return Utility.areStringsEqual(mAccessToken, that.mAccessToken)
+               && Utility.areStringsEqual(mRefreshToken, that.mRefreshToken)
+               && mExpiration == that.mExpiration;
+   }
+
+   @Override
+   public int hashCode() {
+       return Objects.hashCode(mAccessToken, mRefreshToken, mExpiration);
+   }
+
+   @Override
+   public ContentValues toContentValues() {
+       ContentValues values = new ContentValues();
+       values.put(ACCESS_TOKEN_COLUMN, mAccessToken);
+       values.put(REFRESH_TOKEN_COLUMN, mRefreshToken);
+       values.put(EXPIRATION_COLUMN, mExpiration);
+       return values;
+   }
+
+}
diff --git a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
index 0abaa79..6e13f04 100755
--- a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
+++ b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
@@ -27,12 +27,14 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Environment;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
 
 import com.android.emailcommon.utility.TextUtilities;
 import com.android.emailcommon.utility.Utility;
+import com.android.emailcommon.Logging;
 import com.android.emailcommon.R;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogUtils;
@@ -160,6 +162,7 @@
             Mailbox.initMailbox();
             QuickResponse.initQuickResponse();
             HostAuth.initHostAuth();
+            Credential.initCredential();
             Policy.initPolicy();
             Message.initMessage();
             MessageMove.init();
@@ -169,6 +172,14 @@
         }
     }
 
+
+    private static void warnIfUiThread() {
+        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
+            LogUtils.w(Logging.LOG_TAG, "Method called on the UI thread",
+                    new Throwable());
+        }
+    }
+
     public static boolean isInitialSyncKey(final String syncKey) {
         return syncKey == null || syncKey.isEmpty() || syncKey.equals("0");
     }
@@ -197,6 +208,7 @@
      */
     public static <T extends EmailContent> T restoreContentWithId(Context context,
             Class<T> klass, Uri contentUri, String[] contentProjection, long id) {
+        warnIfUiThread();
         Uri u = ContentUris.withAppendedId(contentUri, id);
         Cursor c = context.getContentResolver().query(u, contentProjection, null, null, null);
         if (c == null) throw new ProviderUnavailableException();
@@ -1718,6 +1730,8 @@
         static final String ACCOUNT_KEY = "accountKey";
         // A blob containing an X509 server certificate
         static final String SERVER_CERT = "serverCert";
+        // The credentials row this hostAuth should use. Currently only set if using OAuth.
+        static final String CREDENTIAL_KEY = "credentialKey";
     }
 
     public interface PolicyColumns {
diff --git a/emailcommon/src/com/android/emailcommon/provider/HostAuth.java b/emailcommon/src/com/android/emailcommon/provider/HostAuth.java
index 5e17a51..8c822a3 100644
--- a/emailcommon/src/com/android/emailcommon/provider/HostAuth.java
+++ b/emailcommon/src/com/android/emailcommon/provider/HostAuth.java
@@ -52,8 +52,9 @@
     public static final int FLAG_TLS          = 0x02;    // Use TLS
     public static final int FLAG_AUTHENTICATE = 0x04;    // Use name/password for authentication
     public static final int FLAG_TRUST_ALL    = 0x08;    // Trust all certificates
+    public static final int FLAG_OAUTH        = 0x10;    // Use OAuth for authentication
     // Mask of settings directly configurable by the user
-    public static final int USER_CONFIG_MASK  = 0x0b;
+    public static final int USER_CONFIG_MASK  = 0x1b;
 
     public String mProtocol;
     public String mAddress;
@@ -65,6 +66,9 @@
     public String mClientCertAlias = null;
     // NOTE: The server certificate is NEVER automatically retrieved from EmailProvider
     public byte[] mServerCert = null;
+    public long mCredentialKey;
+
+    public transient Credential mCredential;
 
     public static final int CONTENT_ID_COLUMN = 0;
     public static final int CONTENT_PROTOCOL_COLUMN = 1;
@@ -75,16 +79,15 @@
     public static final int CONTENT_PASSWORD_COLUMN = 6;
     public static final int CONTENT_DOMAIN_COLUMN = 7;
     public static final int CONTENT_CLIENT_CERT_ALIAS_COLUMN = 8;
+    public static final int CONTENT_CREDENTIAL_KEY_COLUMN = 9;
 
     public static final String[] CONTENT_PROJECTION = new String[] {
         RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT,
         HostAuthColumns.FLAGS, HostAuthColumns.LOGIN,
-        HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN, HostAuthColumns.CLIENT_CERT_ALIAS
+        HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN, HostAuthColumns.CLIENT_CERT_ALIAS,
+        HostAuthColumns.CREDENTIAL_KEY
     };
 
-    /**
-     * no public constructor since this is a utility class
-     */
     public HostAuth() {
         mBaseUri = CONTENT_URI;
 
@@ -92,6 +95,41 @@
         mPort = PORT_UNKNOWN;
     }
 
+    /**
+     * getOrCreateCredentials
+     * Return the credential object for this HostAuth, creating it if it does not yet exist.
+     * This should not be called on the main thread.
+     * @param context
+     * @return the credential object for this HostAuth
+     */
+    public Credential getOrCreateCredentials(Context context) {
+
+        if (mCredential == null) {
+            if (mCredentialKey >= 0) {
+                mCredential = Credential.restoreCredentialsWithId(context, mCredentialKey);
+            } else {
+                mCredential = new Credential();
+            }
+        }
+        return mCredential;
+    }
+
+    /**
+     * getCredentials
+     * Return the credential object for this HostAuth, or null if it does not exist.
+     * This should not be called on the main thread.
+     * @param context
+     * @return
+     */
+    public Credential getCredentials(Context context) {
+        if (mCredential == null) {
+            if (mCredentialKey >= 0) {
+                mCredential = Credential.restoreCredentialsWithId(context, mCredentialKey);
+            }
+        }
+        return mCredential;
+    }
+
      /**
      * Restore a HostAuth from the database, given its unique id
      * @param context
@@ -181,6 +219,7 @@
         mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN);
         mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN);
         mClientCertAlias = cursor.getString(CONTENT_CLIENT_CERT_ALIAS_COLUMN);
+        mCredentialKey = cursor.getLong(CONTENT_CREDENTIAL_KEY_COLUMN);
     }
 
     @Override
@@ -194,6 +233,7 @@
         values.put(HostAuthColumns.PASSWORD, mPassword);
         values.put(HostAuthColumns.DOMAIN, mDomain);
         values.put(HostAuthColumns.CLIENT_CERT_ALIAS, mClientCertAlias);
+        values.put(HostAuthColumns.CREDENTIAL_KEY, mCredentialKey);
         values.put(HostAuthColumns.ACCOUNT_KEY, 0); // Need something to satisfy the DB
         return values;
     }
@@ -330,6 +370,12 @@
         dest.writeString(mPassword);
         dest.writeString(mDomain);
         dest.writeString(mClientCertAlias);
+        dest.writeLong(mCredentialKey);
+        if (mCredential == null) {
+            Credential.EMPTY.writeToParcel(dest, flags);
+        } else {
+            mCredential.writeToParcel(dest, flags);
+        }
     }
 
     /**
@@ -346,6 +392,11 @@
         mPassword = in.readString();
         mDomain = in.readString();
         mClientCertAlias = in.readString();
+        mCredentialKey = in.readLong();
+        mCredential = new Credential(in);
+        if (mCredential.equals(Credential.EMPTY)) {
+            mCredential = null;
+        }
     }
 
     @Override
@@ -362,7 +413,8 @@
                 && Utility.areStringsEqual(mLogin, that.mLogin)
                 && Utility.areStringsEqual(mPassword, that.mPassword)
                 && Utility.areStringsEqual(mDomain, that.mDomain)
-                && Utility.areStringsEqual(mClientCertAlias, that.mClientCertAlias);
+                && Utility.areStringsEqual(mClientCertAlias, that.mClientCertAlias)
+                && mCredentialKey == that.mCredentialKey;
                 // We don't care about the server certificate for equals
     }
 
diff --git a/src/com/android/email/provider/DBHelper.java b/src/com/android/email/provider/DBHelper.java
index 3a29c4a..d066bd6 100644
--- a/src/com/android/email/provider/DBHelper.java
+++ b/src/com/android/email/provider/DBHelper.java
@@ -32,6 +32,7 @@
 import com.android.email2.ui.MailActivityEmail;
 import com.android.emailcommon.mail.Address;
 import com.android.emailcommon.provider.Account;
+import com.android.emailcommon.provider.Credential;
 import com.android.emailcommon.provider.EmailContent;
 import com.android.emailcommon.provider.EmailContent.AccountColumns;
 import com.android.emailcommon.provider.EmailContent.Attachment;
@@ -94,6 +95,15 @@
         " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.POLICY_KEY +
         "; end";
 
+    private static final String TRIGGER_HOST_AUTH_DELETE =
+            "create trigger host_auth_delete after delete on " + HostAuth.TABLE_NAME +
+            " begin delete from " + Credential.TABLE_NAME +
+            " where " + Credential.RECORD_ID + "=old." + HostAuth.CREDENTIAL_KEY +
+            " and (select count(*) from " + HostAuth.TABLE_NAME + " where " +
+            HostAuth.CREDENTIAL_KEY + "=old." + HostAuth.CREDENTIAL_KEY + ")=0" +
+            "; end";
+
+
     // Any changes to the database format *must* include update-in-place code.
     // Original version: 3
     // Version 4: Database wipe required; changing AccountManager interface w/Exchange
@@ -163,7 +173,8 @@
     // Version 122: Need to update Message_Updates and Message_Deletes to match previous.
     // Version 123: Changed the duplicateMesage deletion trigger to ignore accounts that aren't
     //              exchange accounts.
-    public static final int DATABASE_VERSION = 123;
+    // Version 124: Add credentials table for OAuth.
+    public static final int DATABASE_VERSION = 124;
 
     // Any changes to the database format *must* include update-in-place code.
     // Original version: 2
@@ -216,6 +227,17 @@
                 "; end");
     }
 
+    static void createCredentialsTable(SQLiteDatabase db) {
+        String s = " (" + Credential.RECORD_ID + " integer primary key autoincrement, "
+                + Credential.PROVIDER_COLUMN + " text,"
+                + Credential.ACCESS_TOKEN_COLUMN + " text,"
+                + Credential.REFRESH_TOKEN_COLUMN + " text,"
+                + Credential.EXPIRATION_COLUMN + " integer"
+                + ");";
+        db.execSQL("create table " + Credential.TABLE_NAME + s);
+        db.execSQL(TRIGGER_HOST_AUTH_DELETE);
+    }
+
     static void dropDeleteDuplicateMessagesTrigger(final SQLiteDatabase db) {
         db.execSQL("drop trigger message_delete_duplicates_on_insert");
     }
@@ -535,7 +557,8 @@
             + HostAuthColumns.DOMAIN + " text, "
             + HostAuthColumns.ACCOUNT_KEY + " integer,"
             + HostAuthColumns.CLIENT_CERT_ALIAS + " text,"
-            + HostAuthColumns.SERVER_CERT + " blob"
+            + HostAuthColumns.SERVER_CERT + " blob,"
+            + HostAuthColumns.CREDENTIAL_KEY + " integer"
             + ");";
         db.execSQL("create table " + HostAuth.TABLE_NAME + s);
     }
@@ -733,6 +756,7 @@
             createMessageStateChangeTable(db);
             createPolicyTable(db);
             createQuickResponseTable(db);
+            createCredentialsTable(db);
         }
 
         @Override
@@ -1317,6 +1341,15 @@
                 }
                 createDeleteDuplicateMessagesTrigger(mContext, db);
             }
+
+            if (oldVersion <= 123) {
+                createCredentialsTable(db);
+                // Add the credentialKey column, and set it to -1 for all pre-existing hostAuths.
+                db.execSQL("alter table " + HostAuth.TABLE_NAME
+                        + " add " + HostAuthColumns.CREDENTIAL_KEY + " integer");
+                db.execSQL("update table " + HostAuth.TABLE_NAME + " set "
+                        + HostAuthColumns.CREDENTIAL_KEY + "=-1");
+            }
         }
 
         @Override
diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java
index d1be0b2..af924a6 100644
--- a/src/com/android/email/provider/EmailProvider.java
+++ b/src/com/android/email/provider/EmailProvider.java
@@ -72,6 +72,7 @@
 import com.android.emailcommon.Logging;
 import com.android.emailcommon.mail.Address;
 import com.android.emailcommon.provider.Account;
+import com.android.emailcommon.provider.Credential;
 import com.android.emailcommon.provider.EmailContent;
 import com.android.emailcommon.provider.EmailContent.AccountColumns;
 import com.android.emailcommon.provider.EmailContent.Attachment;
@@ -261,6 +262,10 @@
     private static final int BODY = BODY_BASE;
     private static final int BODY_ID = BODY_BASE + 1;
 
+    private static final int CREDENTIAL_BASE = 0xB000;
+    private static final int CREDENTIAL = CREDENTIAL_BASE;
+    private static final int CREDENTIAL_ID = CREDENTIAL_BASE + 1;
+
     private static final int BASE_SHIFT = 12;  // 12 bits to the base type: 0, 0x1000, 0x2000, etc.
 
     private static final SparseArray<String> TABLE_NAMES;
@@ -277,6 +282,7 @@
         array.put(QUICK_RESPONSE_BASE >> BASE_SHIFT, QuickResponse.TABLE_NAME);
         array.put(UI_BASE >> BASE_SHIFT, null);
         array.put(BODY_BASE >> BASE_SHIFT, Body.TABLE_NAME);
+        array.put(CREDENTIAL_BASE >> BASE_SHIFT, Credential.TABLE_NAME);
         TABLE_NAMES = array;
     }
 
@@ -645,6 +651,7 @@
                 case HOSTAUTH_ID:
                 case POLICY_ID:
                 case QUICK_RESPONSE_ID:
+                case CREDENTIAL_ID:
                     id = uri.getPathSegments().get(1);
                     if (match == SYNCED_MESSAGE_ID) {
                         // For synced messages, first copy the old message to the deleted table and
@@ -822,6 +829,7 @@
                 case MAILBOX:
                 case ACCOUNT:
                 case HOSTAUTH:
+                case CREDENTIAL:
                 case POLICY:
                 case QUICK_RESPONSE:
                     longId = db.insert(TABLE_NAMES.valueAt(table), "foo", values);
@@ -1041,6 +1049,11 @@
             // A specific hostauth
             sURIMatcher.addURI(EmailContent.AUTHORITY, "hostauth/*", HOSTAUTH_ID);
 
+            // All credential records
+            sURIMatcher.addURI(EmailContent.AUTHORITY, "credential", CREDENTIAL);
+            // A specific credential
+            sURIMatcher.addURI(EmailContent.AUTHORITY, "credential/*", CREDENTIAL_ID);
+
             /**
              * THIS URI HAS SPECIAL SEMANTICS
              * ITS USE IS INTENDED FOR THE UI TO MARK CHANGES THAT NEED TO BE SYNCED BACK
@@ -1878,6 +1891,7 @@
                 case MAILBOX:
                 case ACCOUNT:
                 case HOSTAUTH:
+                case CREDENTIAL:
                 case POLICY:
                     if (match == ATTACHMENT) {
                         if (values.containsKey(AttachmentColumns.LOCATION) &&