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 
19 import android.content.ContentProviderOperation;
20 import android.content.ContentProviderResult;
21 import android.content.ContentResolver;
22 import android.content.ContentUris;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.OperationApplicationException;
26 import android.database.ContentObserver;
27 import android.database.Cursor;
28 import android.media.RingtoneManager;
29 import android.net.Uri;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 import android.os.RemoteException;
33 
34 import com.android.emailcommon.utility.Utility;
35 import com.android.mail.utils.LogUtils;
36 import com.google.common.annotations.VisibleForTesting;
37 
38 import org.json.JSONException;
39 import org.json.JSONObject;
40 
41 import java.util.ArrayList;
42 
43 public final class Account extends EmailContent implements Parcelable {
44     public static final String TABLE_NAME = "Account";
45 
46     // Define all pseudo account IDs here to avoid conflict with one another.
47     /**
48      * Pseudo account ID to represent a "combined account" that includes messages and mailboxes
49      * from all defined accounts.
50      *
51      * <em>IMPORTANT</em>: This must never be stored to the database.
52      */
53     public static final long ACCOUNT_ID_COMBINED_VIEW = 0x1000000000000000L;
54     /**
55      * Pseudo account ID to represent "no account". This may be used any time the account ID
56      * may not be known or when we want to specifically select "no" account.
57      *
58      * <em>IMPORTANT</em>: This must never be stored to the database.
59      */
60     public static final long NO_ACCOUNT = -1L;
61 
62     /**
63      * Whether or not the user has asked for notifications of new mail in this account
64      *
65      * @deprecated Used only for migration
66      */
67     @Deprecated
68     public final static int FLAGS_NOTIFY_NEW_MAIL = 1<<0;
69     /**
70      * Whether or not the user has asked for vibration notifications with all new mail
71      *
72      * @deprecated Used only for migration
73      */
74     @Deprecated
75     public final static int FLAGS_VIBRATE = 1<<1;
76     // Bit mask for the account's deletion policy (see DELETE_POLICY_x below)
77     public static final int FLAGS_DELETE_POLICY_MASK = 1<<2 | 1<<3;
78     public static final int FLAGS_DELETE_POLICY_SHIFT = 2;
79     // Whether the account is in the process of being created; any account reconciliation code
80     // MUST ignore accounts with this bit set; in addition, ContentObservers for this data
81     // SHOULD consider the state of this flag during operation
82     public static final int FLAGS_INCOMPLETE = 1<<4;
83     // Security hold is used when the device is not in compliance with security policies
84     // required by the server; in this state, the user MUST be alerted to the need to update
85     // security settings.  Sync adapters SHOULD NOT attempt to sync when this flag is set.
86     public static final int FLAGS_SECURITY_HOLD = 1<<5;
87     // Whether the account supports "smart forward" (i.e. the server appends the original
88     // message along with any attachments to the outgoing message)
89     public static final int FLAGS_SUPPORTS_SMART_FORWARD = 1<<7;
90     // Whether the account should try to cache attachments in the background
91     public static final int FLAGS_BACKGROUND_ATTACHMENTS = 1<<8;
92     // Available to sync adapter
93     public static final int FLAGS_SYNC_ADAPTER = 1<<9;
94     // Sync disabled is a status commanded by the server; the sync adapter SHOULD NOT try to
95     // sync mailboxes in this account automatically.  A manual sync request to sync a mailbox
96     // with sync disabled SHOULD try to sync and report any failure result via the UI.
97     public static final int FLAGS_SYNC_DISABLED = 1<<10;
98     // Whether or not server-side search is supported by this account
99     public static final int FLAGS_SUPPORTS_SEARCH = 1<<11;
100     // Whether or not server-side search supports global search (i.e. all mailboxes); only valid
101     // if FLAGS_SUPPORTS_SEARCH is true
102     public static final int FLAGS_SUPPORTS_GLOBAL_SEARCH = 1<<12;
103     // Whether or not the initial folder list has been loaded
104     public static final int FLAGS_INITIAL_FOLDER_LIST_LOADED = 1<<13;
105 
106     // Deletion policy (see FLAGS_DELETE_POLICY_MASK, above)
107     public static final int DELETE_POLICY_NEVER = 0;
108     public static final int DELETE_POLICY_7DAYS = 1<<0;        // not supported
109     public static final int DELETE_POLICY_ON_DELETE = 1<<1;
110 
111     // Sentinel values for the mSyncInterval field of both Account records
112     public static final int CHECK_INTERVAL_NEVER = -1;
113     public static final int CHECK_INTERVAL_PUSH = -2;
114 
115     public static Uri CONTENT_URI;
116     public static Uri RESET_NEW_MESSAGE_COUNT_URI;
117     public static Uri NOTIFIER_URI;
118 
initAccount()119     public static void initAccount() {
120         CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
121         RESET_NEW_MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount");
122         NOTIFIER_URI = Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account");
123     }
124 
125     public String mDisplayName;
126     public String mEmailAddress;
127     public String mSyncKey;
128     public int mSyncLookback;
129     public int mSyncInterval;
130     public long mHostAuthKeyRecv;
131     public long mHostAuthKeySend;
132     public int mFlags;
133     public String mSenderName;
134     /** @deprecated Used only for migration */
135     @Deprecated
136     private String mRingtoneUri;
137     public String mProtocolVersion;
138     public String mSecuritySyncKey;
139     public String mSignature;
140     public long mPolicyKey;
141     public long mPingDuration;
142 
143     @VisibleForTesting
144     static final String JSON_TAG_HOST_AUTH_RECV = "hostAuthRecv";
145     @VisibleForTesting
146     static final String JSON_TAG_HOST_AUTH_SEND = "hostAuthSend";
147 
148     // Convenience for creating/working with an account
149     public transient HostAuth mHostAuthRecv;
150     public transient HostAuth mHostAuthSend;
151     public transient Policy mPolicy;
152 
153     // Marks this account as being a temporary entry, so we know to use it directly and not go
154     // through the database or any caches
155     private transient boolean mTemporary;
156 
157     public static final int CONTENT_ID_COLUMN = 0;
158     public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
159     public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2;
160     public static final int CONTENT_SYNC_KEY_COLUMN = 3;
161     public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4;
162     public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5;
163     public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6;
164     public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7;
165     public static final int CONTENT_FLAGS_COLUMN = 8;
166     public static final int CONTENT_SENDER_NAME_COLUMN = 9;
167     public static final int CONTENT_RINGTONE_URI_COLUMN = 10;
168     public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 11;
169     public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 12;
170     public static final int CONTENT_SIGNATURE_COLUMN = 13;
171     public static final int CONTENT_POLICY_KEY_COLUMN = 14;
172     public static final int CONTENT_PING_DURATION_COLUMN = 15;
173     public static final int CONTENT_MAX_ATTACHMENT_SIZE_COLUMN = 16;
174 
175     public static final String[] CONTENT_PROJECTION = {
176         AttachmentColumns._ID, AccountColumns.DISPLAY_NAME,
177         AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK,
178         AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV,
179         AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS,
180         AccountColumns.SENDER_NAME,
181         AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
182         AccountColumns.SECURITY_SYNC_KEY,
183         AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY, AccountColumns.PING_DURATION,
184         AccountColumns.MAX_ATTACHMENT_SIZE
185     };
186 
187     public static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
188     public static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1;
189     public static final String[] ACCOUNT_FLAGS_PROJECTION = {
190             AccountColumns._ID, AccountColumns.FLAGS};
191 
192     public static final String SECURITY_NONZERO_SELECTION =
193         AccountColumns.POLICY_KEY + " IS NOT NULL AND " + AccountColumns.POLICY_KEY + "!=0";
194 
195     private static final String FIND_INBOX_SELECTION =
196             MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX +
197             " AND " + MailboxColumns.ACCOUNT_KEY + " =?";
198 
Account()199     public Account() {
200         mBaseUri = CONTENT_URI;
201 
202         // other defaults (policy)
203         mRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString();
204         mSyncInterval = -1;
205         mSyncLookback = -1;
206         mFlags = 0;
207     }
208 
restoreAccountWithId(Context context, long id)209     public static Account restoreAccountWithId(Context context, long id) {
210         return restoreAccountWithId(context, id, null);
211     }
212 
restoreAccountWithId(Context context, long id, ContentObserver observer)213     public static Account restoreAccountWithId(Context context, long id, ContentObserver observer) {
214         return EmailContent.restoreContentWithId(context, Account.class,
215                 Account.CONTENT_URI, Account.CONTENT_PROJECTION, id, observer);
216     }
217 
restoreAccountWithAddress(Context context, String emailAddress)218     public static Account restoreAccountWithAddress(Context context, String emailAddress) {
219         return restoreAccountWithAddress(context, emailAddress, null);
220     }
221 
restoreAccountWithAddress(Context context, String emailAddress, ContentObserver observer)222     public static Account restoreAccountWithAddress(Context context, String emailAddress,
223             ContentObserver observer) {
224         final Cursor c = context.getContentResolver().query(CONTENT_URI,
225                 new String[] {AccountColumns._ID},
226                 AccountColumns.EMAIL_ADDRESS + "=?", new String[] {emailAddress},
227                 null);
228         try {
229             if (c == null || !c.moveToFirst()) {
230                 return null;
231             }
232             final long id = c.getLong(c.getColumnIndex(AccountColumns._ID));
233             return restoreAccountWithId(context, id, observer);
234         } finally {
235             if (c != null) {
236                 c.close();
237             }
238         }
239     }
240 
241     @Override
getContentNotificationUri()242     protected Uri getContentNotificationUri() {
243         return Account.CONTENT_URI;
244     }
245 
246     /**
247      * Refresh an account that has already been loaded.  This is slightly less expensive
248      * that generating a brand-new account object.
249      */
refresh(Context context)250     public void refresh(Context context) {
251         Cursor c = context.getContentResolver().query(getUri(), Account.CONTENT_PROJECTION,
252                 null, null, null);
253         try {
254             c.moveToFirst();
255             restore(c);
256         } finally {
257             if (c != null) {
258                 c.close();
259             }
260         }
261     }
262 
263     @Override
restore(Cursor cursor)264     public void restore(Cursor cursor) {
265         mId = cursor.getLong(CONTENT_ID_COLUMN);
266         mBaseUri = CONTENT_URI;
267         mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
268         mEmailAddress = cursor.getString(CONTENT_EMAIL_ADDRESS_COLUMN);
269         mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN);
270         mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN);
271         mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN);
272         mHostAuthKeyRecv = cursor.getLong(CONTENT_HOST_AUTH_KEY_RECV_COLUMN);
273         mHostAuthKeySend = cursor.getLong(CONTENT_HOST_AUTH_KEY_SEND_COLUMN);
274         mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
275         mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN);
276         mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN);
277         mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN);
278         mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN);
279         mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN);
280         mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY_COLUMN);
281         mPingDuration = cursor.getLong(CONTENT_PING_DURATION_COLUMN);
282     }
283 
isTemporary()284     public boolean isTemporary() {
285         return mTemporary;
286     }
287 
setTemporary(boolean temporary)288     public void setTemporary(boolean temporary) {
289         mTemporary = temporary;
290     }
291 
getId(Uri u)292     private static long getId(Uri u) {
293         return Long.parseLong(u.getPathSegments().get(1));
294     }
295 
getId()296     public long getId() {
297         return mId;
298     }
299 
300     /**
301      * Returns the user-visible name for the account, eg. "My work address"
302      * or "foo@exemple.com".
303      * @return the user-visible name for the account.
304      */
getDisplayName()305     public String getDisplayName() {
306         return mDisplayName;
307     }
308 
309     /**
310      * Set the description.  Be sure to call save() to commit to database.
311      * @param description the new description
312      */
setDisplayName(String description)313     public void setDisplayName(String description) {
314         mDisplayName = description;
315     }
316 
317     /**
318      * @return the email address for this account
319      */
getEmailAddress()320     public String getEmailAddress() {
321         return mEmailAddress;
322     }
323 
324     /**
325      * Set the Email address for this account.  Be sure to call save() to commit to database.
326      * @param emailAddress the new email address for this account
327      */
setEmailAddress(String emailAddress)328     public void setEmailAddress(String emailAddress) {
329         mEmailAddress = emailAddress;
330     }
331 
332     /**
333      * @return the sender's name for this account
334      */
getSenderName()335     public String getSenderName() {
336         return mSenderName;
337     }
338 
339     /**
340      * Set the sender's name.  Be sure to call save() to commit to database.
341      * @param name the new sender name
342      */
setSenderName(String name)343     public void setSenderName(String name) {
344         mSenderName = name;
345     }
346 
getSignature()347     public String getSignature() {
348         return mSignature;
349     }
350 
351     @VisibleForTesting
setSignature(String signature)352     public void setSignature(String signature) {
353         mSignature = signature;
354     }
355 
356     /**
357      * @return the minutes per check (for polling)
358      * TODO define sentinel values for "never", "push", etc.  See Account.java
359      */
getSyncInterval()360     public int getSyncInterval() {
361         return mSyncInterval;
362     }
363 
364     /**
365      * Set the minutes per check (for polling).  Be sure to call save() to commit to database.
366      * TODO define sentinel values for "never", "push", etc.  See Account.java
367      * @param minutes the number of minutes between polling checks
368      */
setSyncInterval(int minutes)369     public void setSyncInterval(int minutes) {
370         mSyncInterval = minutes;
371     }
372 
373     /**
374      * @return One of the {@code Account.SYNC_WINDOW_*} constants that represents the sync
375      *     lookback window.
376      * TODO define sentinel values for "all", "1 month", etc.  See Account.java
377      */
getSyncLookback()378     public int getSyncLookback() {
379         return mSyncLookback;
380     }
381 
382     /**
383      * Set the sync lookback window.  Be sure to call save() to commit to database.
384      * TODO define sentinel values for "all", "1 month", etc.  See Account.java
385      * @param value One of the {@link com.android.emailcommon.service.SyncWindow} constants
386      */
setSyncLookback(int value)387     public void setSyncLookback(int value) {
388         mSyncLookback = value;
389     }
390 
391     /**
392      * @return the current ping duration.
393      */
getPingDuration()394     public long getPingDuration() {
395         return mPingDuration;
396     }
397 
398     /**
399      * Set the ping duration.  Be sure to call save() to commit to database.
400      */
setPingDuration(long value)401     public void setPingDuration(long value) {
402         mPingDuration = value;
403     }
404 
405     /**
406      * @return the flags for this account
407      */
getFlags()408     public int getFlags() {
409         return mFlags;
410     }
411 
412     /**
413      * Set the flags for this account
414      * @param newFlags the new value for the flags
415      */
setFlags(int newFlags)416     public void setFlags(int newFlags) {
417         mFlags = newFlags;
418     }
419 
420     /**
421      * @return the ringtone Uri for this account
422      * @deprecated Used only for migration
423      */
424     @Deprecated
getRingtone()425     public String getRingtone() {
426         return mRingtoneUri;
427     }
428 
429     /**
430      * Set the "delete policy" as a simple 0,1,2 value set.
431      * @param newPolicy the new delete policy
432      */
setDeletePolicy(int newPolicy)433     public void setDeletePolicy(int newPolicy) {
434         mFlags &= ~FLAGS_DELETE_POLICY_MASK;
435         mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK;
436     }
437 
438     /**
439      * Return the "delete policy" as a simple 0,1,2 value set.
440      * @return the current delete policy
441      */
getDeletePolicy()442     public int getDeletePolicy() {
443         return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT;
444     }
445 
getOrCreateHostAuthSend(Context context)446     public HostAuth getOrCreateHostAuthSend(Context context) {
447         if (mHostAuthSend == null) {
448             if (mHostAuthKeySend != 0) {
449                 mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend);
450             } else {
451                 mHostAuthSend = new HostAuth();
452             }
453         }
454         return mHostAuthSend;
455     }
456 
getOrCreateHostAuthRecv(Context context)457     public HostAuth getOrCreateHostAuthRecv(Context context) {
458         if (mHostAuthRecv == null) {
459             if (mHostAuthKeyRecv != 0) {
460                 mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
461             } else {
462                 mHostAuthRecv = new HostAuth();
463             }
464         }
465         return mHostAuthRecv;
466     }
467 
468     /**
469      * Return the id of the default account. If one hasn't been explicitly specified, return the
470      * first one in the database. If no account exists, returns {@link #NO_ACCOUNT}.
471      *
472      * @param context the caller's context
473      * @param lastUsedAccountId the last used account id, which is the basis of the default account
474      */
getDefaultAccountId(final Context context, final long lastUsedAccountId)475     public static long getDefaultAccountId(final Context context, final long lastUsedAccountId) {
476         final Cursor cursor = context.getContentResolver().query(
477                 CONTENT_URI, ID_PROJECTION, null, null, null);
478 
479         long firstAccount = NO_ACCOUNT;
480 
481         try {
482             if (cursor != null && cursor.moveToFirst()) {
483                 do {
484                     final long accountId = cursor.getLong(Account.ID_PROJECTION_COLUMN);
485 
486                     if (accountId == lastUsedAccountId) {
487                         return accountId;
488                     }
489 
490                     if (firstAccount == NO_ACCOUNT) {
491                         firstAccount = accountId;
492                     }
493                 } while (cursor.moveToNext());
494             }
495         } finally {
496             if (cursor != null) {
497                 cursor.close();
498             }
499         }
500 
501         return firstAccount;
502     }
503 
504     /**
505      * Given an account id, return the account's protocol
506      * @param context the caller's context
507      * @param accountId the id of the account to be examined
508      * @return the account's protocol (or null if the Account or HostAuth do not exist)
509      */
getProtocol(Context context, long accountId)510     public static String getProtocol(Context context, long accountId) {
511         Account account = Account.restoreAccountWithId(context, accountId);
512         if (account != null) {
513             return account.getProtocol(context);
514          }
515         return null;
516     }
517 
518     /**
519      * Return the account's protocol
520      * @param context the caller's context
521      * @return the account's protocol (or null if the HostAuth doesn't not exist)
522      */
getProtocol(Context context)523     public String getProtocol(Context context) {
524         HostAuth hostAuth = getOrCreateHostAuthRecv(context);
525         if (hostAuth != null) {
526             return hostAuth.mProtocol;
527         }
528         return null;
529     }
530 
531     /**
532      * Return a corresponding account manager object using the passed in type
533      *
534      * @param type We can't look up the account type from here, so pass it in
535      * @return system account object
536      */
getAccountManagerAccount(String type)537     public android.accounts.Account getAccountManagerAccount(String type) {
538         return new android.accounts.Account(mEmailAddress, type);
539     }
540 
541     /**
542      * Return the account ID for a message with a given id
543      *
544      * @param context the caller's context
545      * @param messageId the id of the message
546      * @return the account ID, or -1 if the account doesn't exist
547      */
getAccountIdForMessageId(Context context, long messageId)548     public static long getAccountIdForMessageId(Context context, long messageId) {
549         return Message.getKeyColumnLong(context, messageId, MessageColumns.ACCOUNT_KEY);
550     }
551 
552     /**
553      * Return the account for a message with a given id
554      * @param context the caller's context
555      * @param messageId the id of the message
556      * @return the account, or null if the account doesn't exist
557      */
getAccountForMessageId(Context context, long messageId)558     public static Account getAccountForMessageId(Context context, long messageId) {
559         long accountId = getAccountIdForMessageId(context, messageId);
560         if (accountId != -1) {
561             return Account.restoreAccountWithId(context, accountId);
562         }
563         return null;
564     }
565 
566     /**
567      * @return true if an {@code accountId} is assigned to any existing account.
568      */
isValidId(Context context, long accountId)569     public static boolean isValidId(Context context, long accountId) {
570         return null != Utility.getFirstRowLong(context, CONTENT_URI, ID_PROJECTION,
571                 ID_SELECTION, new String[] {Long.toString(accountId)}, null,
572                 ID_PROJECTION_COLUMN);
573     }
574 
575     /**
576      * Check a single account for security hold status.
577      */
isSecurityHold(Context context, long accountId)578     public static boolean isSecurityHold(Context context, long accountId) {
579         return (Utility.getFirstRowLong(context,
580                 ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
581                 ACCOUNT_FLAGS_PROJECTION, null, null, null, ACCOUNT_FLAGS_COLUMN_FLAGS, 0L)
582                 & Account.FLAGS_SECURITY_HOLD) != 0;
583     }
584 
585     /**
586      * @return id of the "inbox" mailbox, or -1 if not found.
587      */
getInboxId(Context context, long accountId)588     public static long getInboxId(Context context, long accountId) {
589         return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION,
590                 FIND_INBOX_SELECTION, new String[] {Long.toString(accountId)}, null,
591                 ID_PROJECTION_COLUMN, -1L);
592     }
593 
594     /**
595      * Clear all account hold flags that are set.
596      *
597      * (This will trigger watchers, and in particular will cause EAS to try and resync the
598      * account(s).)
599      */
clearSecurityHoldOnAllAccounts(Context context)600     public static void clearSecurityHoldOnAllAccounts(Context context) {
601         ContentResolver resolver = context.getContentResolver();
602         Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
603                 SECURITY_NONZERO_SELECTION, null, null);
604         try {
605             while (c.moveToNext()) {
606                 int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
607 
608                 if (0 != (flags & FLAGS_SECURITY_HOLD)) {
609                     ContentValues cv = new ContentValues();
610                     cv.put(AccountColumns.FLAGS, flags & ~FLAGS_SECURITY_HOLD);
611                     long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
612                     Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
613                     resolver.update(uri, cv, null, null);
614                 }
615             }
616         } finally {
617             c.close();
618         }
619     }
620 
621     /*
622      * Override this so that we can store the HostAuth's first and link them to the Account
623      * (non-Javadoc)
624      * @see com.android.email.provider.EmailContent#save(android.content.Context)
625      */
626     @Override
save(Context context)627     public Uri save(Context context) {
628         if (isSaved()) {
629             throw new UnsupportedOperationException();
630         }
631         // This logic is in place so I can (a) short circuit the expensive stuff when
632         // possible, and (b) override (and throw) if anyone tries to call save() or update()
633         // directly for Account, which are unsupported.
634         if (mHostAuthRecv == null && mHostAuthSend == null && mPolicy != null) {
635             return super.save(context);
636         }
637 
638         int index = 0;
639         int recvIndex = -1;
640         int recvCredentialsIndex = -1;
641         int sendIndex = -1;
642         int sendCredentialsIndex = -1;
643 
644         // Create operations for saving the send and recv hostAuths, and their credentials.
645         // Also, remember which operation in the array they represent
646         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
647         if (mHostAuthRecv != null) {
648             if (mHostAuthRecv.mCredential != null) {
649                 recvCredentialsIndex = index++;
650                 ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mCredential.mBaseUri)
651                         .withValues(mHostAuthRecv.mCredential.toContentValues())
652                     .build());
653             }
654             recvIndex = index++;
655             final ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(
656                     mHostAuthRecv.mBaseUri);
657             b.withValues(mHostAuthRecv.toContentValues());
658             if (recvCredentialsIndex >= 0) {
659                 final ContentValues cv = new ContentValues();
660                 cv.put(HostAuthColumns.CREDENTIAL_KEY, recvCredentialsIndex);
661                 b.withValueBackReferences(cv);
662             }
663             ops.add(b.build());
664         }
665         if (mHostAuthSend != null) {
666             if (mHostAuthSend.mCredential != null) {
667                 if (mHostAuthRecv.mCredential != null &&
668                         mHostAuthRecv.mCredential.equals(mHostAuthSend.mCredential)) {
669                     // These two credentials are identical, use the same row.
670                     sendCredentialsIndex = recvCredentialsIndex;
671                 } else {
672                     sendCredentialsIndex = index++;
673                     ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mCredential.mBaseUri)
674                             .withValues(mHostAuthSend.mCredential.toContentValues())
675                             .build());
676                 }
677             }
678             sendIndex = index++;
679             final ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(
680                     mHostAuthSend.mBaseUri);
681             b.withValues(mHostAuthSend.toContentValues());
682             if (sendCredentialsIndex >= 0) {
683                 final ContentValues cv = new ContentValues();
684                 cv.put(HostAuthColumns.CREDENTIAL_KEY, sendCredentialsIndex);
685                 b.withValueBackReferences(cv);
686             }
687             ops.add(b.build());
688         }
689 
690         // Now do the Account
691         ContentValues cv = null;
692         if (recvIndex >= 0 || sendIndex >= 0) {
693             cv = new ContentValues();
694             if (recvIndex >= 0) {
695                 cv.put(AccountColumns.HOST_AUTH_KEY_RECV, recvIndex);
696             }
697             if (sendIndex >= 0) {
698                 cv.put(AccountColumns.HOST_AUTH_KEY_SEND, sendIndex);
699             }
700         }
701 
702         ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri);
703         b.withValues(toContentValues());
704         if (cv != null) {
705             b.withValueBackReferences(cv);
706         }
707         ops.add(b.build());
708 
709         try {
710             ContentProviderResult[] results =
711                 context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
712             // If saving, set the mId's of the various saved objects
713             if (recvIndex >= 0) {
714                 long newId = getId(results[recvIndex].uri);
715                 mHostAuthKeyRecv = newId;
716                 mHostAuthRecv.mId = newId;
717             }
718             if (sendIndex >= 0) {
719                 long newId = getId(results[sendIndex].uri);
720                 mHostAuthKeySend = newId;
721                 mHostAuthSend.mId = newId;
722             }
723             Uri u = results[index].uri;
724             mId = getId(u);
725             return u;
726         } catch (RemoteException e) {
727             // There is nothing to be done here; fail by returning null
728         } catch (OperationApplicationException e) {
729             // There is nothing to be done here; fail by returning null
730         }
731         return null;
732     }
733 
734     @Override
toContentValues()735     public ContentValues toContentValues() {
736         ContentValues values = new ContentValues();
737         values.put(AccountColumns.DISPLAY_NAME, mDisplayName);
738         values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress);
739         values.put(AccountColumns.SYNC_KEY, mSyncKey);
740         values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback);
741         values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval);
742         values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv);
743         values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend);
744         values.put(AccountColumns.FLAGS, mFlags);
745         values.put(AccountColumns.SENDER_NAME, mSenderName);
746         values.put(AccountColumns.RINGTONE_URI, mRingtoneUri);
747         values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
748         values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey);
749         values.put(AccountColumns.SIGNATURE, mSignature);
750         values.put(AccountColumns.POLICY_KEY, mPolicyKey);
751         values.put(AccountColumns.PING_DURATION, mPingDuration);
752         return values;
753     }
754 
toJsonString(final Context context)755     public String toJsonString(final Context context) {
756         ensureLoaded(context);
757         final JSONObject json = toJson();
758         if (json != null) {
759             return json.toString();
760         }
761         return null;
762     }
763 
toJson()764     protected JSONObject toJson() {
765         try {
766             final JSONObject json = new JSONObject();
767             json.putOpt(AccountColumns.DISPLAY_NAME, mDisplayName);
768             json.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress);
769             json.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback);
770             json.put(AccountColumns.SYNC_INTERVAL, mSyncInterval);
771             final JSONObject recvJson = mHostAuthRecv.toJson();
772             json.put(JSON_TAG_HOST_AUTH_RECV, recvJson);
773             if (mHostAuthSend != null) {
774                 final JSONObject sendJson = mHostAuthSend.toJson();
775                 json.put(JSON_TAG_HOST_AUTH_SEND, sendJson);
776             }
777             json.put(AccountColumns.FLAGS, mFlags);
778             json.putOpt(AccountColumns.SENDER_NAME, mSenderName);
779             json.putOpt(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
780             json.putOpt(AccountColumns.SIGNATURE, mSignature);
781             json.put(AccountColumns.PING_DURATION, mPingDuration);
782             return json;
783         } catch (final JSONException e) {
784             LogUtils.d(LogUtils.TAG, e, "Exception while serializing Account");
785         }
786         return null;
787     }
788 
fromJsonString(final String jsonString)789     public static Account fromJsonString(final String jsonString) {
790         try {
791             final JSONObject json = new JSONObject(jsonString);
792             return fromJson(json);
793         } catch (final JSONException e) {
794             LogUtils.d(LogUtils.TAG, e, "Could not parse json for account");
795         }
796         return null;
797     }
798 
fromJson(final JSONObject json)799     protected static Account fromJson(final JSONObject json) {
800         try {
801             final Account a = new Account();
802             a.mDisplayName = json.optString(AccountColumns.DISPLAY_NAME);
803             a.mEmailAddress = json.getString(AccountColumns.EMAIL_ADDRESS);
804             // SYNC_KEY is not stored
805             a.mSyncLookback = json.getInt(AccountColumns.SYNC_LOOKBACK);
806             a.mSyncInterval = json.getInt(AccountColumns.SYNC_INTERVAL);
807             final JSONObject recvJson = json.getJSONObject(JSON_TAG_HOST_AUTH_RECV);
808             a.mHostAuthRecv = HostAuth.fromJson(recvJson);
809             final JSONObject sendJson = json.optJSONObject(JSON_TAG_HOST_AUTH_SEND);
810             if (sendJson != null) {
811                 a.mHostAuthSend = HostAuth.fromJson(sendJson);
812             }
813             a.mFlags = json.getInt(AccountColumns.FLAGS);
814             a.mSenderName = json.optString(AccountColumns.SENDER_NAME);
815             a.mProtocolVersion = json.optString(AccountColumns.PROTOCOL_VERSION);
816             // SECURITY_SYNC_KEY is not stored
817             a.mSignature = json.optString(AccountColumns.SIGNATURE);
818             // POLICY_KEY is not stored
819             a.mPingDuration = json.optInt(AccountColumns.PING_DURATION, 0);
820             return a;
821         } catch (final JSONException e) {
822             LogUtils.d(LogUtils.TAG, e, "Exception while deserializing Account");
823         }
824         return null;
825     }
826 
827     /**
828      * Ensure that all optionally-loaded fields are populated from the provider.
829      * @param context for provider loads
830      */
ensureLoaded(final Context context)831     public void ensureLoaded(final Context context) {
832         if (mHostAuthKeyRecv == 0 && mHostAuthRecv == null) {
833             throw new IllegalStateException("Trying to load incomplete Account object");
834         }
835         getOrCreateHostAuthRecv(context).ensureLoaded(context);
836 
837         if (mHostAuthKeySend != 0) {
838             getOrCreateHostAuthSend(context);
839             if (mHostAuthSend != null) {
840                 mHostAuthSend.ensureLoaded(context);
841             }
842         }
843     }
844 
845     /**
846      * Supports Parcelable
847      */
848     @Override
describeContents()849     public int describeContents() {
850         return 0;
851     }
852 
853     /**
854      * Supports Parcelable
855      */
856     public static final Parcelable.Creator<Account> CREATOR
857             = new Parcelable.Creator<Account>() {
858         @Override
859         public Account createFromParcel(Parcel in) {
860             return new Account(in);
861         }
862 
863         @Override
864         public Account[] newArray(int size) {
865             return new Account[size];
866         }
867     };
868 
869     /**
870      * Supports Parcelable
871      */
872     @Override
writeToParcel(Parcel dest, int flags)873     public void writeToParcel(Parcel dest, int flags) {
874         // mBaseUri is not parceled
875         dest.writeLong(mId);
876         dest.writeString(mDisplayName);
877         dest.writeString(mEmailAddress);
878         dest.writeString(mSyncKey);
879         dest.writeInt(mSyncLookback);
880         dest.writeInt(mSyncInterval);
881         dest.writeLong(mHostAuthKeyRecv);
882         dest.writeLong(mHostAuthKeySend);
883         dest.writeInt(mFlags);
884         dest.writeString("" /* mCompatibilityUuid */);
885         dest.writeString(mSenderName);
886         dest.writeString(mRingtoneUri);
887         dest.writeString(mProtocolVersion);
888         dest.writeInt(0 /* mNewMessageCount */);
889         dest.writeString(mSecuritySyncKey);
890         dest.writeString(mSignature);
891         dest.writeLong(mPolicyKey);
892 
893         if (mHostAuthRecv != null) {
894             dest.writeByte((byte)1);
895             mHostAuthRecv.writeToParcel(dest, flags);
896         } else {
897             dest.writeByte((byte)0);
898         }
899 
900         if (mHostAuthSend != null) {
901             dest.writeByte((byte)1);
902             mHostAuthSend.writeToParcel(dest, flags);
903         } else {
904             dest.writeByte((byte)0);
905         }
906     }
907 
908     /**
909      * Supports Parcelable
910      */
Account(Parcel in)911     public Account(Parcel in) {
912         mBaseUri = Account.CONTENT_URI;
913         mId = in.readLong();
914         mDisplayName = in.readString();
915         mEmailAddress = in.readString();
916         mSyncKey = in.readString();
917         mSyncLookback = in.readInt();
918         mSyncInterval = in.readInt();
919         mHostAuthKeyRecv = in.readLong();
920         mHostAuthKeySend = in.readLong();
921         mFlags = in.readInt();
922         /* mCompatibilityUuid = */ in.readString();
923         mSenderName = in.readString();
924         mRingtoneUri = in.readString();
925         mProtocolVersion = in.readString();
926         /* mNewMessageCount = */ in.readInt();
927         mSecuritySyncKey = in.readString();
928         mSignature = in.readString();
929         mPolicyKey = in.readLong();
930 
931         mHostAuthRecv = null;
932         if (in.readByte() == 1) {
933             mHostAuthRecv = new HostAuth(in);
934         }
935 
936         mHostAuthSend = null;
937         if (in.readByte() == 1) {
938             mHostAuthSend = new HostAuth(in);
939         }
940     }
941 
942     /**
943      * For debugger support only - DO NOT use for code.
944      */
945     @Override
toString()946     public String toString() {
947         StringBuilder sb = new StringBuilder("[");
948         if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) {
949             sb.append(mHostAuthRecv.mProtocol);
950             sb.append(':');
951         }
952         if (mDisplayName != null)   sb.append(mDisplayName);
953         sb.append(':');
954         if (mEmailAddress != null)  sb.append(mEmailAddress);
955         sb.append(':');
956         if (mSenderName != null)    sb.append(mSenderName);
957         sb.append(']');
958         return sb.toString();
959     }
960 }
961