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