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