Move notifications to UnifiedEmail

This involves redoing the persistence model, to split general,
account, and folder settings into separate SharedPreference stores.

It also requires some preferences to be moved into UnifiedEmail.

Depends on Ibe2e3f93ec164370535ffc5f5b2409544cc8d36d (UnifiedEmail)

Change-Id: Ie6ec389b5b5d2e7ab1b299d0877811ae716526e2
This commit is contained in:
Scott Kennedy 2012-12-11 10:37:35 -08:00
parent 19b77bfe12
commit b34608228f
31 changed files with 803 additions and 1103 deletions

View File

@ -294,7 +294,7 @@
</activity>
<activity
android:name=".activity.setup.AccountSecurity"
android.label="@string/account_security_title"
android:label="@string/account_security_title"
>
</activity>
@ -339,7 +339,6 @@
<receiver android:name="com.android.mail.providers.protos.boot.AccountReceiver">
<intent-filter>
<action android:name="com.android.email2.providers.protos.boot.intent.ACTION_PROVIDER_CREATED" />
<action android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED" />
</intent-filter>
</receiver>
@ -661,6 +660,30 @@
/>
</service>
<service android:name="com.android.email.EmailIntentService"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED"/>
<action android:name="com.android.mail.action.RESEND_NOTIFICATIONS" />
<action android:name="com.android.mail.action.CLEAR_NEW_MAIL_NOTIFICATIONS" />
</intent-filter>
</service>
<service android:name="com.android.mail.NotificationActionIntentService"
android:exported="false">
<intent-filter>
<action android:name="com.android.mail.action.NOTIF_MARK_READ" />
<action android:name="com.android.mail.action.NOTIF_ARCHIVE" />
<action android:name="com.android.mail.action.NOTIF_DELETE" />
<action android:name="com.android.mail.action.NOTIF_UNDO" />
<action android:name="com.android.mail.action.NOTIF_DESTRUCT" />
<action android:name="com.android.mail.action.NOTIF_UNDO_TIMEOUT" />
<action android:name="com.android.mail.action.NOTIF_REPLY" />
<action android:name="com.android.mail.action.NOTIF_REPLY_ALL" />
<action android:name="com.android.mail.action.NOTIF_FORWARD" />
</intent-filter>
</service>
</application>
<!-- Legacy permissions, etc. can go here -->

View File

@ -26,6 +26,8 @@ apache_src_dir := ../../UnifiedEmail/src/org
imported_unified_email_files := \
$(unified_email_unified_src_dir)/com/android/mail/utils/LogTag.java \
$(unified_email_src_dir)/com/android/mail/preferences/BasePreferenceMigrator.java \
$(unified_email_unified_src_dir)/com/android/mail/preferences/PreferenceMigrator.java \
$(unified_email_src_dir)/com/android/mail/utils/LogUtils.java \
$(unified_email_src_dir)/com/android/mail/providers/UIProvider.java

View File

@ -57,10 +57,6 @@ public final class Account extends EmailContent implements AccountColumns, Parce
*/
public static final long NO_ACCOUNT = -1L;
// Whether or not the user has asked for notifications of new mail in this account
public final static int FLAGS_NOTIFY_NEW_MAIL = 1<<0;
// Whether or not the user has asked for vibration notifications with all new mail
public final static int FLAGS_VIBRATE = 1<<1;
// Bit mask for the account's deletion policy (see DELETE_POLICY_x below)
public static final int FLAGS_DELETE_POLICY_MASK = 1<<2 | 1<<3;
public static final int FLAGS_DELETE_POLICY_SHIFT = 2;
@ -125,7 +121,6 @@ public final class Account extends EmailContent implements AccountColumns, Parce
public boolean mIsDefault; // note: callers should use getDefaultAccountId()
public String mCompatibilityUuid;
public String mSenderName;
public String mRingtoneUri;
public String mProtocolVersion;
public int mNewMessageCount;
public String mSecuritySyncKey;
@ -151,12 +146,11 @@ public final class Account extends EmailContent implements AccountColumns, Parce
public static final int CONTENT_IS_DEFAULT_COLUMN = 9;
public static final int CONTENT_COMPATIBILITY_UUID_COLUMN = 10;
public static final int CONTENT_SENDER_NAME_COLUMN = 11;
public static final int CONTENT_RINGTONE_URI_COLUMN = 12;
public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 13;
public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 14;
public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 15;
public static final int CONTENT_SIGNATURE_COLUMN = 16;
public static final int CONTENT_POLICY_KEY = 17;
public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 12;
public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 13;
public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 14;
public static final int CONTENT_SIGNATURE_COLUMN = 15;
public static final int CONTENT_POLICY_KEY = 16;
public static final String[] CONTENT_PROJECTION = new String[] {
RECORD_ID, AccountColumns.DISPLAY_NAME,
@ -164,7 +158,7 @@ public final class Account extends EmailContent implements AccountColumns, Parce
AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV,
AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.IS_DEFAULT,
AccountColumns.COMPATIBILITY_UUID, AccountColumns.SENDER_NAME,
AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
AccountColumns.PROTOCOL_VERSION,
AccountColumns.NEW_MESSAGE_COUNT, AccountColumns.SECURITY_SYNC_KEY,
AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY
};
@ -205,10 +199,9 @@ public final class Account extends EmailContent implements AccountColumns, Parce
mBaseUri = CONTENT_URI;
// other defaults (policy)
mRingtoneUri = "content://settings/system/notification_sound";
mSyncInterval = -1;
mSyncLookback = -1;
mFlags = FLAGS_NOTIFY_NEW_MAIL;
mFlags = 0;
mCompatibilityUuid = UUID.randomUUID().toString();
}
@ -258,7 +251,6 @@ public final class Account extends EmailContent implements AccountColumns, Parce
mIsDefault = cursor.getInt(CONTENT_IS_DEFAULT_COLUMN) == 1;
mCompatibilityUuid = cursor.getString(CONTENT_COMPATIBILITY_UUID_COLUMN);
mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN);
mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN);
mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN);
mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN);
mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN);
@ -360,8 +352,6 @@ public final class Account extends EmailContent implements AccountColumns, Parce
/**
* @return the flags for this account
* @see #FLAGS_NOTIFY_NEW_MAIL
* @see #FLAGS_VIBRATE
*/
public int getFlags() {
return mFlags;
@ -369,29 +359,12 @@ public final class Account extends EmailContent implements AccountColumns, Parce
/**
* Set the flags for this account
* @see #FLAGS_NOTIFY_NEW_MAIL
* @see #FLAGS_VIBRATE
* @param newFlags the new value for the flags
*/
public void setFlags(int newFlags) {
mFlags = newFlags;
}
/**
* @return the ringtone Uri for this account
*/
public String getRingtone() {
return mRingtoneUri;
}
/**
* Set the ringtone Uri for this account
* @param newUri the new URI string for the ringtone for this account
*/
public void setRingtone(String newUri) {
mRingtoneUri = newUri;
}
/**
* Set the "delete policy" as a simple 0,1,2 value set.
* @param newPolicy the new delete policy
@ -819,7 +792,6 @@ public final class Account extends EmailContent implements AccountColumns, Parce
values.put(AccountColumns.IS_DEFAULT, mIsDefault);
values.put(AccountColumns.COMPATIBILITY_UUID, mCompatibilityUuid);
values.put(AccountColumns.SENDER_NAME, mSenderName);
values.put(AccountColumns.RINGTONE_URI, mRingtoneUri);
values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount);
values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey);
@ -870,7 +842,6 @@ public final class Account extends EmailContent implements AccountColumns, Parce
dest.writeByte(mIsDefault ? (byte)1 : (byte)0);
dest.writeString(mCompatibilityUuid);
dest.writeString(mSenderName);
dest.writeString(mRingtoneUri);
dest.writeString(mProtocolVersion);
dest.writeInt(mNewMessageCount);
dest.writeString(mSecuritySyncKey);
@ -909,7 +880,6 @@ public final class Account extends EmailContent implements AccountColumns, Parce
mIsDefault = in.readByte() == 1;
mCompatibilityUuid = in.readString();
mSenderName = in.readString();
mRingtoneUri = in.readString();
mProtocolVersion = in.readString();
mNewMessageCount = in.readInt();
mSecuritySyncKey = in.readString();

View File

@ -60,11 +60,9 @@ import java.util.ArrayList;
*
*/
public abstract class EmailContent {
public static final String[] NOTIFICATION_PROJECTION =
new String[] {MailboxColumns.ID, MailboxColumns.UNREAD_COUNT, MailboxColumns.MESSAGE_COUNT};
public static final int NOTIFICATION_MAILBOX_ID_COLUMN = 0;
public static final int NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN = 1;
public static final int NOTIFICATION_MAILBOX_MESSAGE_COUNT_COLUMN = 2;
public static final int NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN = 2;
// All classes share this
public static final String RECORD_ID = "_id";
@ -520,11 +518,6 @@ public abstract class EmailContent {
mIntroText = cursor.getString(CONTENT_INTRO_TEXT_COLUMN);
mQuotedTextStartPos = cursor.getInt(CONTENT_QUOTED_TEXT_START_POS_COLUMN);
}
public boolean update() {
// TODO Auto-generated method stub
return false;
}
}
public interface MessageColumns {
@ -578,6 +571,9 @@ public abstract class EmailContent {
public static final String THREAD_TOPIC = "threadTopic";
// For sync adapter use
public static final String SYNC_DATA = "syncData";
/** Boolean, unseen = 0, seen = 1 [INDEX] */
public static final String FLAG_SEEN = "flagSeen";
}
public static final class Message extends EmailContent implements SyncColumns, MessageColumns {
@ -636,6 +632,7 @@ public abstract class EmailContent {
public static final int CONTENT_PROTOCOL_SEARCH_INFO_COLUMN = 22;
public static final int CONTENT_THREAD_TOPIC_COLUMN = 23;
public static final int CONTENT_SYNC_DATA_COLUMN = 24;
public static final int CONTENT_FLAG_SEEN_COLUMN = 25;
public static final String[] CONTENT_PROJECTION = new String[] {
RECORD_ID,
@ -650,7 +647,7 @@ public abstract class EmailContent {
MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO,
MessageColumns.THREAD_TOPIC, MessageColumns.SYNC_DATA
MessageColumns.THREAD_TOPIC, MessageColumns.SYNC_DATA, MessageColumns.FLAG_SEEN
};
public static final int LIST_ID_COLUMN = 0;
@ -759,6 +756,7 @@ public abstract class EmailContent {
public long mTimeStamp;
public String mSubject;
public boolean mFlagRead = false;
public boolean mFlagSeen = false;
public int mFlagLoaded = FLAG_LOADED_UNLOADED;
public boolean mFlagFavorite = false;
public boolean mFlagAttachment = false;
@ -878,6 +876,7 @@ public abstract class EmailContent {
values.put(MessageColumns.TIMESTAMP, mTimeStamp);
values.put(MessageColumns.SUBJECT, mSubject);
values.put(MessageColumns.FLAG_READ, mFlagRead);
values.put(MessageColumns.FLAG_SEEN, mFlagSeen);
values.put(MessageColumns.FLAG_LOADED, mFlagLoaded);
values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite);
values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment);
@ -914,6 +913,7 @@ public abstract class EmailContent {
mTimeStamp = cursor.getLong(CONTENT_TIMESTAMP_COLUMN);
mSubject = cursor.getString(CONTENT_SUBJECT_COLUMN);
mFlagRead = cursor.getInt(CONTENT_FLAG_READ_COLUMN) == 1;
mFlagSeen = cursor.getInt(CONTENT_FLAG_SEEN_COLUMN) == 1;
mFlagLoaded = cursor.getInt(CONTENT_FLAG_LOADED_COLUMN);
mFlagFavorite = cursor.getInt(CONTENT_FLAG_FAVORITE_COLUMN) == 1;
mFlagAttachment = cursor.getInt(CONTENT_FLAG_ATTACHMENT_COLUMN) == 1;
@ -936,11 +936,6 @@ public abstract class EmailContent {
mSyncData = cursor.getString(CONTENT_SYNC_DATA_COLUMN);
}
public boolean update() {
// TODO Auto-generated method stub
return false;
}
/*
* Override this so that we can store the Body first and link it to the Message
* Also, attachments when we get there...
@ -1518,7 +1513,12 @@ public abstract class EmailContent {
public static final String COMPATIBILITY_UUID = "compatibilityUuid";
// User name (for outgoing messages)
public static final String SENDER_NAME = "senderName";
// Ringtone
/**
* Ringtone
*
* @deprecated This is no longer used by anything except for creating the database.
*/
@Deprecated
public static final String RINGTONE_URI = "ringtoneUri";
// Protocol version (arbitrary string, used by EAS currently)
public static final String PROTOCOL_VERSION = "protocolVersion";
@ -1585,9 +1585,19 @@ public abstract class EmailContent {
public static final String UI_SYNC_STATUS = "uiSyncStatus";
// The UIProvider last sync result
public static final String UI_LAST_SYNC_RESULT = "uiLastSyncResult";
// The UIProvider sync status
/**
* The UIProvider sync status
*
* @deprecated This is no longer used by anything except for creating the database.
*/
@Deprecated
public static final String LAST_NOTIFIED_MESSAGE_KEY = "lastNotifiedMessageKey";
// The UIProvider last sync result
/**
* The UIProvider last sync result
*
* @deprecated This is no longer used by anything except for creating the database.
*/
@Deprecated
public static final String LAST_NOTIFIED_MESSAGE_COUNT = "lastNotifiedMessageCount";
// The total number of messages in the remote mailbox
public static final String TOTAL_COUNT = "totalCount";

View File

@ -63,8 +63,6 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
public long mLastTouchedTime;
public int mUiSyncStatus;
public int mUiLastSyncResult;
public long mLastNotifiedMessageKey;
public int mLastNotifiedMessageCount;
public int mTotalCount;
public String mHierarchicalName;
@ -87,10 +85,8 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
public static final int CONTENT_LAST_TOUCHED_TIME_COLUMN = 16;
public static final int CONTENT_UI_SYNC_STATUS_COLUMN = 17;
public static final int CONTENT_UI_LAST_SYNC_RESULT_COLUMN = 18;
public static final int CONTENT_LAST_NOTIFIED_MESSAGE_KEY_COLUMN = 19;
public static final int CONTENT_LAST_NOTIFIED_MESSAGE_COUNT_COLUMN = 20;
public static final int CONTENT_TOTAL_COUNT_COLUMN = 21;
public static final int CONTENT_HIERARCHICAL_NAME_COLUMN = 22;
public static final int CONTENT_TOTAL_COUNT_COLUMN = 19;
public static final int CONTENT_HIERARCHICAL_NAME_COLUMN = 20;
/**
* <em>NOTE</em>: If fields are added or removed, the method {@link #getHashes()}
@ -104,7 +100,6 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
MailboxColumns.FLAG_VISIBLE, MailboxColumns.FLAGS, MailboxColumns.VISIBLE_LIMIT,
MailboxColumns.SYNC_STATUS, MailboxColumns.PARENT_KEY, MailboxColumns.LAST_TOUCHED_TIME,
MailboxColumns.UI_SYNC_STATUS, MailboxColumns.UI_LAST_SYNC_RESULT,
MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY, MailboxColumns.LAST_NOTIFIED_MESSAGE_COUNT,
MailboxColumns.TOTAL_COUNT, MailboxColumns.HIERARCHICAL_NAME
};
@ -334,8 +329,6 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
mLastTouchedTime = cursor.getLong(CONTENT_LAST_TOUCHED_TIME_COLUMN);
mUiSyncStatus = cursor.getInt(CONTENT_UI_SYNC_STATUS_COLUMN);
mUiLastSyncResult = cursor.getInt(CONTENT_UI_LAST_SYNC_RESULT_COLUMN);
mLastNotifiedMessageKey = cursor.getLong(CONTENT_LAST_NOTIFIED_MESSAGE_KEY_COLUMN);
mLastNotifiedMessageCount = cursor.getInt(CONTENT_LAST_NOTIFIED_MESSAGE_COUNT_COLUMN);
mTotalCount = cursor.getInt(CONTENT_TOTAL_COUNT_COLUMN);
mHierarchicalName = cursor.getString(CONTENT_HIERARCHICAL_NAME_COLUMN);
}
@ -361,8 +354,6 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
values.put(MailboxColumns.LAST_TOUCHED_TIME, mLastTouchedTime);
values.put(MailboxColumns.UI_SYNC_STATUS, mUiSyncStatus);
values.put(MailboxColumns.UI_LAST_SYNC_RESULT, mUiLastSyncResult);
values.put(MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY, mLastNotifiedMessageKey);
values.put(MailboxColumns.LAST_NOTIFIED_MESSAGE_COUNT, mLastNotifiedMessageCount);
values.put(MailboxColumns.TOTAL_COUNT, mTotalCount);
values.put(MailboxColumns.HIERARCHICAL_NAME, mHierarchicalName);
return values;
@ -554,10 +545,6 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
= mUiSyncStatus;
hash[CONTENT_UI_LAST_SYNC_RESULT_COLUMN]
= mUiLastSyncResult;
hash[CONTENT_LAST_NOTIFIED_MESSAGE_KEY_COLUMN]
= mLastNotifiedMessageKey;
hash[CONTENT_LAST_NOTIFIED_MESSAGE_COUNT_COLUMN]
= mLastNotifiedMessageCount;
hash[CONTENT_TOTAL_COUNT_COLUMN]
= mTotalCount;
hash[CONTENT_HIERARCHICAL_NAME_COLUMN]
@ -594,8 +581,6 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
dest.writeLong(mLastTouchedTime);
dest.writeInt(mUiSyncStatus);
dest.writeInt(mUiLastSyncResult);
dest.writeLong(mLastNotifiedMessageKey);
dest.writeInt(mLastNotifiedMessageCount);
dest.writeInt(mTotalCount);
dest.writeString(mHierarchicalName);
}
@ -621,8 +606,6 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
mLastTouchedTime = in.readLong();
mUiSyncStatus = in.readInt();
mUiLastSyncResult = in.readInt();
mLastNotifiedMessageKey = in.readLong();
mLastNotifiedMessageCount = in.readInt();
mTotalCount = in.readInt();
mHierarchicalName = in.readString();
}

View File

@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Debug;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
@ -181,6 +182,16 @@ public abstract class ServiceProxy {
}
public void waitForCompletion() {
/*
* onServiceConnected() is always called on the main thread, and we block the current thread
* for up to 10 seconds as a timeout. If we're currently on the main thread,
* onServiceConnected() is not called until our timeout elapses (and the UI is frozen for
* the duration).
*/
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new IllegalStateException("This cannot be called on the main thread.");
}
synchronized (mConnection) {
long time = System.currentTimeMillis();
try {

View File

@ -50,10 +50,8 @@ import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.provider.ProviderUnavailableException;
import java.io.ByteArrayInputStream;
@ -903,72 +901,6 @@ public class Utility {
}
}
/**
* Updates the last seen message key in the mailbox data base for the INBOX of the currently
* shown account. If the account is {@link Account#ACCOUNT_ID_COMBINED_VIEW}, the INBOX for
* all accounts are updated.
* @return an {@link EmailAsyncTask} for test only.
*/
public static EmailAsyncTask<Void, Void, Void> updateLastNotifiedMessageKey(
final Context context, final long mailboxId) {
return EmailAsyncTask.runAsyncParallel(new Runnable() {
private void updateLastSeenMessageKeyForMailbox(long mailboxId) {
ContentResolver resolver = context.getContentResolver();
if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
Cursor c = resolver.query(
Mailbox.CONTENT_URI, EmailContent.ID_PROJECTION, Mailbox.TYPE + "=?",
new String[] { Integer.toString(Mailbox.TYPE_INBOX) }, null);
if (c == null) throw new ProviderUnavailableException();
try {
while (c.moveToNext()) {
final long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
updateLastSeenMessageKeyForMailbox(id);
}
} finally {
c.close();
}
} else if (mailboxId > 0L) {
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
// mailbox has been removed
if (mailbox == null) {
return;
}
// We use the highest _id for the account the mailbox table as the "last seen
// message key". We don't care if the message has been read or not. We only
// need a point at which we can compare against in the future. By setting this
// value, we are claiming that every message before this has potentially been
// seen by the user.
long mostRecentMessageId = Utility.getFirstRowLong(context,
ContentUris.withAppendedId(
EmailContent.MAILBOX_MOST_RECENT_MESSAGE_URI, mailboxId),
Message.ID_COLUMN_PROJECTION, null, null, null,
Message.ID_MAILBOX_COLUMN_ID, -1L);
long lastNotifiedMessageId = mailbox.mLastNotifiedMessageKey;
// Only update the db if the value has changed
if (mostRecentMessageId != lastNotifiedMessageId) {
Log.d(Logging.LOG_TAG, "Most recent = " + mostRecentMessageId +
", last notified: " + lastNotifiedMessageId +
"; updating last notified");
ContentValues values = mailbox.toContentValues();
values.put(MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY, mostRecentMessageId);
resolver.update(
Mailbox.CONTENT_URI,
values,
EmailContent.ID_SELECTION,
new String[] { Long.toString(mailbox.mId) });
} else {
Log.d(Logging.LOG_TAG, "Most recent = last notified; no change");
}
}
}
@Override
public void run() {
updateLastSeenMessageKeyForMailbox(mailboxId);
}
});
}
public static long[] toPrimitiveLongArray(Collection<Long> collection) {
// Need to do this manually because we're converting to a primitive long array, not
// a Long array.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 B

View File

@ -94,22 +94,19 @@
android:title="@string/account_settings_notifications">
<CheckBoxPreference
android:key="account_notify"
android:key="notifications-enabled"
android:title="@string/account_settings_notify_label"
android:defaultValue="true"
android:summary="@string/account_settings_notify_summary" />
<RingtonePreference
android:key="account_ringtone"
android:layout="?android:attr/preferenceLayoutChild"
android:dependency="account_notify"
android:title="@string/account_settings_ringtone"
android:ringtoneType="notification"
android:defaultValue="content://settings/system/notification_sound" />
<Preference
android:key="notification-ringtone"
android:dependency="notifications-enabled"
android:title="@string/account_settings_ringtone" />
<CheckBoxPreference
android:key="account_settings_vibrate"
android:dependency="account_notify"
android:key="notification-vibrate"
android:dependency="notifications-enabled"
android:defaultValue="false"
android:title="@string/account_settings_vibrate_when_label" />

View File

@ -25,7 +25,7 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_manager_type_exchange"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:smallIcon="@drawable/stat_notify_email"
android:label="@string/exchange_name_alternate"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -23,7 +23,7 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_manager_type_exchange"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:smallIcon="@drawable/stat_notify_email"
android:label="@string/exchange_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -23,7 +23,7 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_manager_type_imap"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:smallIcon="@drawable/stat_notify_email"
android:label="@string/imap_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -23,7 +23,7 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.android.exchange"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:smallIcon="@drawable/stat_notify_email"
android:label="@string/exchange_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -23,7 +23,7 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.android.email"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:smallIcon="@drawable/stat_notify_email"
android:label="@string/exchange_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -23,7 +23,7 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_manager_type_legacy_imap"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:smallIcon="@drawable/stat_notify_email"
android:label="@string/imap_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -23,7 +23,7 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_manager_type_pop3"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:smallIcon="@drawable/stat_notify_email"
android:label="@string/pop3_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -56,8 +56,8 @@
<!-- This may be removed in GeneralPreferences.java -->
<CheckBoxPreference
android:key="reply_all"
android:persistent="true"
android:key="default-reply-all"
android:persistent="false"
android:defaultValue="false"
android:title="@string/general_preference_reply_all_label"
android:summary="@string/general_preference_reply_all_summary" />

View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email;
import android.content.Intent;
import com.android.mail.MailIntentService;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
/**
* A service to handle various intents asynchronously.
*/
public class EmailIntentService extends MailIntentService {
private static final String LOG_TAG = LogTag.getLogTag();
public EmailIntentService() {
super("EmailIntentService");
}
@Override
protected void onHandleIntent(final Intent intent) {
super.onHandleIntent(intent);
LogUtils.v(LOG_TAG, "Handling intent %s", intent);
}
}

View File

@ -22,54 +22,45 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.text.SpannableString;
import android.provider.Settings;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import com.android.email.activity.ContactStatusLoader;
import com.android.email.activity.setup.AccountSecurity;
import com.android.email.activity.setup.AccountSettings;
import com.android.email.provider.EmailProvider;
import com.android.email.service.EmailBroadcastProcessorService;
import com.android.email2.ui.MailActivityEmail;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.Address;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.utility.EmailAsyncTask;
import com.android.emailcommon.utility.Utility;
import com.android.mail.providers.Conversation;
import com.android.mail.preferences.FolderPreferences;
import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.Utils;
import com.google.common.annotations.VisibleForTesting;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.NotificationUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Class that manages notifications.
*/
public class NotificationController {
private static final String TAG = "NotificationController";
private static final String LOG_TAG = LogTag.getLogTag();
/** Reserved for {@link com.android.exchange.CalendarSyncEnabler} */
@SuppressWarnings("unused")
@ -84,56 +75,24 @@ public class NotificationController {
private static final int NOTIFICATION_ID_BASE_SECURITY_NEEDED = 0x30000000;
private static final int NOTIFICATION_ID_BASE_SECURITY_CHANGED = 0x40000000;
/** Selection to retrieve accounts that should we notify user for changes */
private final static String NOTIFIED_ACCOUNT_SELECTION =
Account.FLAGS + "&" + Account.FLAGS_NOTIFY_NEW_MAIL + " != 0";
private static final String NEW_MAIL_MAILBOX_ID = "com.android.email.new_mail.mailboxId";
private static final String NEW_MAIL_MESSAGE_ID = "com.android.email.new_mail.messageId";
private static final String NEW_MAIL_MESSAGE_COUNT = "com.android.email.new_mail.messageCount";
private static final String NEW_MAIL_UNREAD_COUNT = "com.android.email.new_mail.unreadCount";
private static NotificationThread sNotificationThread;
private static Handler sNotificationHandler;
private static NotificationController sInstance;
private final Context mContext;
private final NotificationManager mNotificationManager;
private final AudioManager mAudioManager;
private final Bitmap mGenericSenderIcon;
private final Bitmap mGenericMultipleSenderIcon;
private final Clock mClock;
/** Maps account id to its observer */
private final HashMap<Long, ContentObserver> mNotificationMap;
private final Map<Long, ContentObserver> mNotificationMap =
new HashMap<Long, ContentObserver>();
private ContentObserver mAccountObserver;
/**
* Timestamp indicating when the last message notification sound was played.
* Used for throttling.
*/
private long mLastMessageNotifyTime;
/**
* Minimum interval between notification sounds.
* Since a long sync (either on account setup or after a long period of being offline) can cause
* several notifications consecutively, it can be pretty overwhelming to get a barrage of
* notification sounds. Throttle them using this value.
*/
private static final long MIN_SOUND_INTERVAL_MS = 15 * 1000; // 15 seconds
/** Constructor */
@VisibleForTesting
NotificationController(Context context, Clock clock) {
private NotificationController(Context context, Clock clock) {
mContext = context.getApplicationContext();
EmailContent.init(context);
mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mGenericSenderIcon = BitmapFactory.decodeResource(mContext.getResources(),
R.drawable.ic_contact_picture);
mGenericMultipleSenderIcon = BitmapFactory.decodeResource(mContext.getResources(),
R.drawable.ic_notification_multiple_mail_holo_dark);
mClock = clock;
mNotificationMap = new HashMap<Long, ContentObserver>();
}
/** Singleton access */
@ -149,16 +108,16 @@ public class NotificationController {
* @param notificationId the notification id to check
* @return whether or not the notification must be "ongoing"
*/
private boolean needsOngoingNotification(int notificationId) {
private static boolean needsOngoingNotification(int notificationId) {
// "Security needed" must be ongoing so that the user doesn't close it; otherwise, sync will
// be prevented until a reboot. Consider also doing this for password expired.
return (notificationId & NOTIFICATION_ID_BASE_MASK) == NOTIFICATION_ID_BASE_SECURITY_NEEDED;
}
/**
* Returns a {@link Notification.Builder}} for an event with the given account. The account
* contains specific rules on ring tone usage and these will be used to modify the notification
* behaviour.
* Returns a {@link android.support.v4.app.NotificationCompat.Builder} for an event with the
* given account. The account contains specific rules on ring tone usage and these will be used
* to modify the notification behaviour.
*
* @param accountId The id of the account this notification is being built for.
* @param ticker Text displayed when the notification is first shown. May be {@code null}.
@ -166,14 +125,13 @@ public class NotificationController {
* @param contentText The second line of text. May NOT be {@code null}.
* @param intent The intent to start if the user clicks on the notification.
* @param largeIcon A large icon. May be {@code null}
* @param number A number to display using {@link Builder#setNumber(int)}. May
* be {@code null}.
* @param number A number to display using {@link Builder#setNumber(int)}. May be {@code null}.
* @param enableAudio If {@code false}, do not play any sound. Otherwise, play sound according
* to the settings for the given account.
* @return A {@link Notification} that can be sent to the notification service.
*/
private Notification.Builder createBaseAccountNotificationBuilder(long accountId, String ticker,
CharSequence title, String contentText, Intent intent, Bitmap largeIcon,
private NotificationCompat.Builder createBaseAccountNotificationBuilder(long accountId,
String ticker, CharSequence title, String contentText, Intent intent, Bitmap largeIcon,
Integer number, boolean enableAudio, boolean ongoing) {
// Pending Intent
PendingIntent pending = null;
@ -183,13 +141,13 @@ public class NotificationController {
}
// NOTE: the ticker is not shown for notifications in the Holo UX
final Notification.Builder builder = new Notification.Builder(mContext)
final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
.setContentTitle(title)
.setContentText(contentText)
.setContentIntent(pending)
.setLargeIcon(largeIcon)
.setNumber(number == null ? 0 : number)
.setSmallIcon(R.drawable.stat_notify_email_generic)
.setSmallIcon(R.drawable.stat_notify_email)
.setWhen(mClock.getTime())
.setTicker(ticker)
.setOngoing(ongoing);
@ -214,16 +172,16 @@ public class NotificationController {
*/
private void showNotification(long accountId, String ticker, String title,
String contentText, Intent intent, int notificationId) {
final Notification.Builder builder = createBaseAccountNotificationBuilder(accountId, ticker,
title, contentText, intent, null, null, true,
final NotificationCompat.Builder builder = createBaseAccountNotificationBuilder(accountId,
ticker, title, contentText, intent, null, null, true,
needsOngoingNotification(notificationId));
mNotificationManager.notify(notificationId, builder.getNotification());
mNotificationManager.notify(notificationId, builder.build());
}
/**
* Returns a notification ID for new message notifications for the given account.
*/
private int getNewMessageNotificationId(long mailboxId) {
private static int getNewMessageNotificationId(long mailboxId) {
// We assume accountId will always be less than 0x0FFFFFFF; is there a better way?
return (int) (NOTIFICATION_ID_BASE_NEW_MESSAGES + mailboxId);
}
@ -234,42 +192,20 @@ public class NotificationController {
* database changes, we save the state [e.g. message ID and count] of the most recent
* notification shown to the user. And, when we start observing database changes, we restore
* the saved state.
* @param watch If {@code true}, we register observers for all accounts whose settings have
* notifications enabled. Otherwise, all observers are unregistered.
*/
public void watchForMessages(final boolean watch) {
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, "Notifications being toggled: " + watch);
}
// Don't create the thread if we're only going to stop watching
if (!watch && sNotificationThread == null) return;
public void watchForMessages() {
ensureHandlerExists();
// Run this on the message notification handler
sNotificationHandler.post(new Runnable() {
@Override
public void run() {
ContentResolver resolver = mContext.getContentResolver();
if (!watch) {
unregisterMessageNotification(Account.ACCOUNT_ID_COMBINED_VIEW);
if (mAccountObserver != null) {
resolver.unregisterContentObserver(mAccountObserver);
mAccountObserver = null;
}
// tear down the event loop
sNotificationThread.quit();
sNotificationThread = null;
return;
}
// otherwise, start new observers for all notified accounts
registerMessageNotification(Account.ACCOUNT_ID_COMBINED_VIEW);
// If we're already observing account changes, don't do anything else
if (mAccountObserver == null) {
if (MailActivityEmail.DEBUG) {
Log.i(Logging.LOG_TAG, "Observing account changes for notifications");
}
LogUtils.i(LOG_TAG, "Observing account changes for notifications");
mAccountObserver = new AccountContentObserver(sNotificationHandler, mContext);
resolver.registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver);
}
@ -294,12 +230,12 @@ public class NotificationController {
* {@link Account#ACCOUNT_ID_COMBINED_VIEW} to register observers for all
* accounts that allow for user notification.
*/
private void registerMessageNotification(long accountId) {
private void registerMessageNotification(final long accountId) {
ContentResolver resolver = mContext.getContentResolver();
if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
Cursor c = resolver.query(
Account.CONTENT_URI, EmailContent.ID_PROJECTION,
NOTIFIED_ACCOUNT_SELECTION, null, null);
null, null, null);
try {
while (c.moveToNext()) {
long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
@ -311,9 +247,7 @@ public class NotificationController {
} else {
ContentObserver obs = mNotificationMap.get(accountId);
if (obs != null) return; // we're already observing; nothing to do
if (MailActivityEmail.DEBUG) {
Log.i(Logging.LOG_TAG, "Registering for notifications for account " + accountId);
}
LogUtils.i(LOG_TAG, "Registering for notifications for account " + accountId);
ContentObserver observer = new MessageContentObserver(
sNotificationHandler, mContext, accountId);
resolver.registerContentObserver(Message.NOTIFIER_URI, true, observer);
@ -331,21 +265,17 @@ public class NotificationController {
* @param accountId The ID of the account to unregister from. To unregister all accounts that
* have observers, specify an ID of {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
*/
private void unregisterMessageNotification(long accountId) {
private void unregisterMessageNotification(final long accountId) {
ContentResolver resolver = mContext.getContentResolver();
if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
if (MailActivityEmail.DEBUG) {
Log.i(Logging.LOG_TAG, "Unregistering notifications for all accounts");
}
LogUtils.i(LOG_TAG, "Unregistering notifications for all accounts");
// cancel all existing message observers
for (ContentObserver observer : mNotificationMap.values()) {
resolver.unregisterContentObserver(observer);
}
mNotificationMap.clear();
} else {
if (MailActivityEmail.DEBUG) {
Log.i(Logging.LOG_TAG, "Unregistering notifications for account " + accountId);
}
LogUtils.i(LOG_TAG, "Unregistering notifications for account " + accountId);
ContentObserver observer = mNotificationMap.remove(accountId);
if (observer != null) {
resolver.unregisterContentObserver(observer);
@ -353,411 +283,60 @@ public class NotificationController {
}
}
/**
* Returns a picture of the sender of the given message. If no picture is available, returns
* {@code null}.
*
* NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
*/
private Bitmap getSenderPhoto(Message message) {
Address sender = Address.unpackFirst(message.mFrom);
if (sender == null) {
return null;
}
String email = sender.getAddress();
if (TextUtils.isEmpty(email)) {
return null;
}
Bitmap photo = ContactStatusLoader.getContactInfo(mContext, email).mPhoto;
if (photo != null) {
final Resources res = mContext.getResources();
final int idealIconHeight =
res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
final int idealIconWidth =
res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
if (photo.getHeight() < idealIconHeight) {
// We should scale this image to fit the intended size
photo = Bitmap.createScaledBitmap(
photo, idealIconWidth, idealIconHeight, true);
}
}
return photo;
}
public static final String EXTRA_ACCOUNT = "account";
public static final String EXTRA_CONVERSATION = "conversationUri";
public static final String EXTRA_FOLDER = "folder";
private Intent createViewConversationIntent(Conversation conversation, Folder folder,
com.android.mail.providers.Account account) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(EXTRA_ACCOUNT, account.serialize());
if (folder != null) {
intent.setDataAndType(folder.uri, account.mimeType);
intent.putExtra(EXTRA_FOLDER, Folder.toString(folder));
}
intent.putExtra(EXTRA_CONVERSATION, conversation);
return intent;
}
private Intent createViewMailboxIntent(com.android.mail.providers.Account account,
Folder folder) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(EXTRA_ACCOUNT, account.serialize());
if (folder != null) {
intent.setDataAndType(folder.uri, account.mimeType);
intent.putExtra(EXTRA_FOLDER, Folder.toString(folder));
}
return intent;
}
private Cursor getUiCursor(Uri uri, String[] projection) {
Cursor c = mContext.getContentResolver().query(uri, projection, null, null, null);
if (c == null) return null;
if (c.moveToFirst()) {
return c;
} else {
c.close();
return null;
}
}
private Intent createViewConversationIntent(Message message) {
Cursor c = getUiCursor(EmailProvider.uiUri("uiaccount", message.mAccountKey),
UIProvider.ACCOUNTS_PROJECTION);
if (c == null) {
Log.w(TAG, "Can't find account for message " + message.mId);
return null;
}
com.android.mail.providers.Account acct = new com.android.mail.providers.Account(c);
c.close();
c = getUiCursor(EmailProvider.uiUri("uifolder", message.mMailboxKey),
UIProvider.FOLDERS_PROJECTION);
if (c == null) {
Log.w(TAG, "Can't find folder for message " + message.mId + ", folder " +
message.mMailboxKey);
return null;
}
Folder folder = new Folder(c);
c.close();
c = getUiCursor(EmailProvider.uiUri("uiconversation", message.mId),
UIProvider.CONVERSATION_PROJECTION);
if (c == null) {
Log.w(TAG, "Can't find conversation for message " + message.mId);
return null;
}
Conversation conv = new Conversation(c);
c.close();
return createViewConversationIntent(conv, folder, acct);
}
private Intent createViewMailboxIntentForMessage(Message message) {
Cursor c = null;
com.android.mail.providers.Account acct = null;
try {
c = getUiCursor(EmailProvider.uiUri("uiaccount", message.mAccountKey),
UIProvider.ACCOUNTS_PROJECTION);
if (c == null) {
Log.w(TAG, "Can't find account for message " + message.mId);
return null;
}
acct = new com.android.mail.providers.Account(c);
} finally {
if (c != null) {
c.close();
c = null;
}
}
Folder folder = null;
try {
c = getUiCursor(EmailProvider.uiUri("uifolder", message.mMailboxKey),
UIProvider.FOLDERS_PROJECTION);
if (c == null) {
Log.w(TAG, "Can't find folder for message " + message.mId + ", folder " +
message.mMailboxKey);
return null;
}
folder = new Folder(c);
} finally {
if (c != null) {
c.close();
c = null;
}
}
return createViewMailboxIntent(acct, folder);
}
/**
* Returns a "new message" notification for the given account.
*
* NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
*/
@VisibleForTesting
Notification createNewMessageNotification(long mailboxId, long newMessageId,
int unseenMessageCount, int unreadCount) {
final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
if (mailbox == null) {
return null;
}
final Account account = Account.restoreAccountWithId(mContext, mailbox.mAccountKey);
if (account == null) {
return null;
}
// Get the latest message
final Message message = Message.restoreMessageWithId(mContext, newMessageId);
if (message == null) {
return null; // no message found???
}
String senderName = Address.toFriendly(Address.unpack(message.mFrom));
if (senderName == null) {
senderName = ""; // Happens when a message has no from.
}
final boolean multipleUnseen = unseenMessageCount > 1;
final Bitmap senderPhoto = multipleUnseen
? mGenericMultipleSenderIcon
: getSenderPhoto(message);
final SpannableString title = getNewMessageTitle(senderName, unseenMessageCount);
// TODO: add in display name on the second line for the text, once framework supports
// multiline texts.
// Show account name if an inbox; otherwise mailbox name
final String text = multipleUnseen
? ((mailbox.mType == Mailbox.TYPE_INBOX) ? account.mDisplayName :
mailbox.mDisplayName)
: message.mSubject;
final Bitmap largeIcon = senderPhoto != null ? senderPhoto : mGenericSenderIcon;
final Integer number = unreadCount > 1 ? unreadCount : null;
final Intent intent;
if (multipleUnseen) {
intent = createViewMailboxIntentForMessage(message);
} else {
intent = createViewConversationIntent(message);
}
if (intent == null) {
return null;
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
Intent.FLAG_ACTIVITY_TASK_ON_HOME);
long now = mClock.getTime();
boolean enableAudio = (now - mLastMessageNotifyTime) > MIN_SOUND_INTERVAL_MS;
final Notification.Builder builder = createBaseAccountNotificationBuilder(
mailbox.mAccountKey, title.toString(), title, text,
intent, largeIcon, number, enableAudio, false);
if (Utils.isRunningJellybeanOrLater()) {
// For a new-style notification
if (multipleUnseen) {
final Cursor messageCursor =
mContext.getContentResolver().query(ContentUris.withAppendedId(
EmailContent.MAILBOX_NOTIFICATION_URI, mailbox.mAccountKey),
EmailContent.NOTIFICATION_PROJECTION, null, null, null);
try {
if (messageCursor != null && messageCursor.getCount() > 0) {
final int maxNumDigestItems = mContext.getResources().getInteger(
R.integer.max_num_notification_digest_items);
// The body of the notification is the account name, or the label name.
builder.setSubText(text);
Notification.InboxStyle digest = new Notification.InboxStyle(builder);
digest.setBigContentTitle(title);
int numDigestItems = 0;
// We can assume that the current position of the cursor is on the
// newest message
messageCursor.moveToFirst();
do {
final long messageId =
messageCursor.getLong(EmailContent.ID_PROJECTION_COLUMN);
// Get the latest message
final Message digestMessage =
Message.restoreMessageWithId(mContext, messageId);
if (digestMessage != null) {
final CharSequence digestLine =
getSingleMessageInboxLine(mContext, digestMessage);
digest.addLine(digestLine);
numDigestItems++;
}
} while (numDigestItems <= maxNumDigestItems && messageCursor.moveToNext());
// We want to clear the content text in this case. The content text would
// have been set in createBaseAccountNotificationBuilder, but since the
// same string was set in as the subtext, we don't want to show a
// duplicate string.
builder.setContentText(null);
}
} finally {
if (messageCursor != null) {
messageCursor.close();
}
}
} else {
// The notification content will be the subject of the conversation.
builder.setContentText(getSingleMessageLittleText(mContext, message.mSubject));
// The notification subtext will be the subject of the conversation for inbox
// notifications, or will based on the the label name for user label notifications.
builder.setSubText(account.mDisplayName);
final Notification.BigTextStyle bigText = new Notification.BigTextStyle(builder);
bigText.bigText(getSingleMessageBigText(mContext, message));
}
}
mLastMessageNotifyTime = now;
return builder.getNotification();
}
/**
* Sets the bigtext for a notification for a single new conversation
* @param context
* @param message New message that triggered the notification.
* @return a {@link CharSequence} suitable for use in {@link Notification.BigTextStyle}
*/
private static CharSequence getSingleMessageInboxLine(Context context, Message message) {
final String subject = message.mSubject;
final String snippet = message.mSnippet;
final String senders = Address.toFriendly(Address.unpack(message.mFrom));
final String subjectSnippet = !TextUtils.isEmpty(subject) ? subject : snippet;
final TextAppearanceSpan notificationPrimarySpan =
new TextAppearanceSpan(context, R.style.NotificationPrimaryText);
if (TextUtils.isEmpty(senders)) {
// If the senders are empty, just use the subject/snippet.
return subjectSnippet;
}
else if (TextUtils.isEmpty(subjectSnippet)) {
// If the subject/snippet is empty, just use the senders.
final SpannableString spannableString = new SpannableString(senders);
spannableString.setSpan(notificationPrimarySpan, 0, senders.length(), 0);
return spannableString;
} else {
final String formatString = context.getResources().getString(
R.string.multiple_new_message_notification_item);
final TextAppearanceSpan notificationSecondarySpan =
new TextAppearanceSpan(context, R.style.NotificationSecondaryText);
final String instantiatedString = String.format(formatString, senders, subjectSnippet);
final SpannableString spannableString = new SpannableString(instantiatedString);
final boolean isOrderReversed = formatString.indexOf("%2$s") <
formatString.indexOf("%1$s");
final int primaryOffset =
(isOrderReversed ? instantiatedString.lastIndexOf(senders) :
instantiatedString.indexOf(senders));
final int secondaryOffset =
(isOrderReversed ? instantiatedString.lastIndexOf(subjectSnippet) :
instantiatedString.indexOf(subjectSnippet));
spannableString.setSpan(notificationPrimarySpan,
primaryOffset, primaryOffset + senders.length(), 0);
spannableString.setSpan(notificationSecondarySpan,
secondaryOffset, secondaryOffset + subjectSnippet.length(), 0);
return spannableString;
}
}
/**
* Sets the bigtext for a notification for a single new conversation
* @param context
* @param subject Subject of the new message that triggered the notification
* @return a {@link CharSequence} suitable for use in {@link Notification.ContentText}
*/
private static CharSequence getSingleMessageLittleText(Context context, String subject) {
if (subject == null) {
return null;
}
final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan(
context, R.style.NotificationPrimaryText);
final SpannableString spannableString = new SpannableString(subject);
spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0);
return spannableString;
}
/**
* Sets the bigtext for a notification for a single new conversation
* @param context
* @param message New message that triggered the notification
* @return a {@link CharSequence} suitable for use in {@link Notification.BigTextStyle}
*/
private static CharSequence getSingleMessageBigText(Context context, Message message) {
final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan(
context, R.style.NotificationPrimaryText);
final String subject = message.mSubject;
final String snippet = message.mSnippet;
if (TextUtils.isEmpty(subject)) {
// If the subject is empty, just use the snippet.
return snippet;
}
else if (TextUtils.isEmpty(snippet)) {
// If the snippet is empty, just use the subject.
final SpannableString spannableString = new SpannableString(subject);
spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0);
return spannableString;
} else {
final String notificationBigTextFormat = context.getResources().getString(
R.string.single_new_message_notification_big_text);
// Localizers may change the order of the parameters, look at how the format
// string is structured.
final boolean isSubjectFirst = notificationBigTextFormat.indexOf("%2$s") >
notificationBigTextFormat.indexOf("%1$s");
final String bigText = String.format(notificationBigTextFormat, subject, snippet);
final SpannableString spannableString = new SpannableString(bigText);
final int subjectOffset =
(isSubjectFirst ? bigText.indexOf(subject) : bigText.lastIndexOf(subject));
spannableString.setSpan(notificationSubjectSpan,
subjectOffset, subjectOffset + subject.length(), 0);
return spannableString;
}
}
/**
* Creates a notification title for a new message. If there is only a single message,
* show the sender name. Otherwise, show "X new messages".
*/
@VisibleForTesting
SpannableString getNewMessageTitle(String sender, int unseenCount) {
String title;
if (unseenCount > 1) {
title = String.format(
mContext.getString(R.string.notification_multiple_new_messages_fmt),
unseenCount);
} else {
title = sender;
}
return new SpannableString(title);
}
/** Returns the system's current ringer mode */
@VisibleForTesting
int getRingerMode() {
return mAudioManager.getRingerMode();
}
/** Sets up the notification's sound and vibration based upon account details. */
@VisibleForTesting
void setupSoundAndVibration(Notification.Builder builder, Account account) {
final int flags = account.mFlags;
final String ringtoneUri = account.mRingtoneUri;
final boolean vibrate = (flags & Account.FLAGS_VIBRATE) != 0;
private void setupSoundAndVibration(
NotificationCompat.Builder builder, Account account) {
String ringtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
boolean vibrate = false;
// Use the Inbox notification preferences
final Cursor accountCursor = mContext.getContentResolver().query(EmailProvider.uiUri(
"uiaccount", account.mId), UIProvider.ACCOUNTS_PROJECTION, null, null, null);
com.android.mail.providers.Account uiAccount = null;
try {
if (accountCursor.moveToFirst()) {
uiAccount = new com.android.mail.providers.Account(accountCursor);
}
} finally {
accountCursor.close();
}
if (uiAccount != null) {
final Cursor folderCursor =
mContext.getContentResolver().query(uiAccount.settings.defaultInbox,
UIProvider.FOLDERS_PROJECTION, null, null, null);
if (folderCursor == null) {
LogUtils.e(LOG_TAG, "Null folder cursor for mailbox %s",
uiAccount.settings.defaultInbox);
}
Folder folder = null;
try {
if (folderCursor.moveToFirst()) {
folder = new Folder(folderCursor);
}
} finally {
folderCursor.close();
}
if (folder != null) {
final FolderPreferences folderPreferences =
new FolderPreferences(mContext, uiAccount.name, folder, true /* inbox */);
ringtoneUri = folderPreferences.getNotificationRingtoneUri();
vibrate = folderPreferences.isNotificationVibrateEnabled();
} else {
LogUtils.e(LOG_TAG, "Null folder for mailbox %s", uiAccount.settings.defaultInbox);
}
} else {
LogUtils.e(LOG_TAG, "Null uiAccount for account id %d", account.mId);
}
int defaults = Notification.DEFAULT_LIGHTS;
if (vibrate) {
@ -790,7 +369,7 @@ public class NotificationController {
/**
* Returns a notification ID for login failed notifications for the given account account.
*/
private int getLoginFailedNotificationId(long accountId) {
private static int getLoginFailedNotificationId(long accountId) {
return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int)accountId;
}
@ -953,109 +532,100 @@ public class NotificationController {
private final long mAccountId;
public MessageContentObserver(
Handler handler, Context context, long accountId) {
final Handler handler, final Context context, final long accountId) {
super(handler);
mContext = context;
mAccountId = accountId;
}
@Override
public void onChange(boolean selfChange) {
ContentObserver observer = sInstance.mNotificationMap.get(mAccountId);
Account account = Account.restoreAccountWithId(mContext, mAccountId);
if (observer == null || account == null) {
Log.w(Logging.LOG_TAG, "Couldn't find account for changed message notification");
public void onChange(final boolean selfChange) {
final ContentResolver contentResolver = mContext.getContentResolver();
final Cursor accountCursor = contentResolver.query(
EmailProvider.uiUri("uiaccount", mAccountId), UIProvider.ACCOUNTS_PROJECTION,
null, null, null);
if (accountCursor == null) {
LogUtils.e(LOG_TAG, "Null account cursor for mAccountId %d", mAccountId);
return;
}
ContentResolver resolver = mContext.getContentResolver();
Cursor c = resolver.query(ContentUris.withAppendedId(
EmailContent.MAILBOX_NOTIFICATION_URI, mAccountId),
EmailContent.NOTIFICATION_PROJECTION, null, null, null);
com.android.mail.providers.Account account = null;
try {
while (c.moveToNext()) {
long mailboxId = c.getLong(EmailContent.NOTIFICATION_MAILBOX_ID_COLUMN);
if (mailboxId == 0) continue;
int messageCount =
c.getInt(EmailContent.NOTIFICATION_MAILBOX_MESSAGE_COUNT_COLUMN);
int unreadCount =
c.getInt(EmailContent.NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN);
Mailbox m = Mailbox.restoreMailboxWithId(mContext, mailboxId);
long newMessageId = Utility.getFirstRowLong(mContext,
ContentUris.withAppendedId(
EmailContent.MAILBOX_MOST_RECENT_MESSAGE_URI, mailboxId),
Message.ID_COLUMN_PROJECTION, null, null, null,
Message.ID_MAILBOX_COLUMN_ID, -1L);
Log.d(Logging.LOG_TAG, "Changes to " + account.mDisplayName + "/" +
m.mDisplayName + ", count: " + messageCount + ", lastNotified: " +
m.mLastNotifiedMessageKey + ", mostRecent: " + newMessageId);
// Broadcast intent here
Intent i = new Intent(EmailBroadcastProcessorService.ACTION_NOTIFY_NEW_MAIL);
// Required by UIProvider
i.setType(EmailProvider.EMAIL_APP_MIME_TYPE);
i.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_FOLDER,
Uri.parse(EmailProvider.uiUriString("uifolder", mailboxId)));
i.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_ACCOUNT,
Uri.parse(EmailProvider.uiUriString("uiaccount", m.mAccountKey)));
i.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNREAD_COUNT,
unreadCount);
// Required by our notification controller
i.putExtra(NEW_MAIL_MAILBOX_ID, mailboxId);
i.putExtra(NEW_MAIL_MESSAGE_ID, newMessageId);
i.putExtra(NEW_MAIL_MESSAGE_COUNT, messageCount);
i.putExtra(NEW_MAIL_UNREAD_COUNT, unreadCount);
mContext.sendOrderedBroadcast(i, null);
if (accountCursor.moveToFirst()) {
account = new com.android.mail.providers.Account(accountCursor);
}
} finally {
c.close();
accountCursor.close();
}
final Cursor mailboxCursor = contentResolver.query(
ContentUris.withAppendedId(EmailContent.MAILBOX_NOTIFICATION_URI, mAccountId),
null, null, null, null);
try {
while (mailboxCursor.moveToNext()) {
final long mailboxId =
mailboxCursor.getLong(EmailContent.NOTIFICATION_MAILBOX_ID_COLUMN);
if (mailboxId == 0) continue;
final int unreadCount = mailboxCursor.getInt(
EmailContent.NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN);
final int unseenCount = mailboxCursor.getInt(
EmailContent.NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN);
final Cursor folderCursor = contentResolver.query(
EmailProvider.uiUri("uifolder", mailboxId),
UIProvider.FOLDERS_PROJECTION, null, null, null);
if (folderCursor == null) {
LogUtils.e(LOG_TAG, "Null folder cursor for mMailboxId %d", mailboxId);
return;
}
Folder folder = null;
try {
if (folderCursor.moveToFirst()) {
folder = new Folder(folderCursor);
}
} finally {
folderCursor.close();
}
LogUtils.d(LOG_TAG, "Changes to account " + account.name + ", folder: "
+ folder.name + ", unreadCount: " + unreadCount + ", unseenCount: "
+ unseenCount);
NotificationUtils.setNewEmailIndicator(mContext, unreadCount, unseenCount,
account, folder, true);
}
} finally {
mailboxCursor.close();
}
}
}
public static void notifyNewMail(Context context, Intent i) {
Log.d(Logging.LOG_TAG, "Sending notification to system...");
NotificationController nc = NotificationController.getInstance(context);
ContentResolver resolver = context.getContentResolver();
long mailboxId = i.getLongExtra(NEW_MAIL_MAILBOX_ID, -1);
long newMessageId = i.getLongExtra(NEW_MAIL_MESSAGE_ID, -1);
int messageCount = i.getIntExtra(NEW_MAIL_MESSAGE_COUNT, 0);
int unreadCount = i.getIntExtra(NEW_MAIL_UNREAD_COUNT, 0);
Notification n = nc.createNewMessageNotification(mailboxId, newMessageId,
messageCount, unreadCount);
if (n != null) {
// Make the notification visible
nc.mNotificationManager.notify(nc.getNewMessageNotificationId(mailboxId), n);
}
// Save away the new values
ContentValues cv = new ContentValues();
cv.put(MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY, newMessageId);
cv.put(MailboxColumns.LAST_NOTIFIED_MESSAGE_COUNT, messageCount);
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), cv,
null, null);
}
/**
* Observer invoked whenever an account is modified. This could mean the user changed the
* notification settings.
*/
private static class AccountContentObserver extends ContentObserver {
private final Context mContext;
public AccountContentObserver(Handler handler, Context context) {
public AccountContentObserver(final Handler handler, final Context context) {
super(handler);
mContext = context;
}
@Override
public void onChange(boolean selfChange) {
public void onChange(final boolean selfChange) {
final ContentResolver resolver = mContext.getContentResolver();
final Cursor c = resolver.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
NOTIFIED_ACCOUNT_SELECTION, null, null);
final HashSet<Long> newAccountList = new HashSet<Long>();
final HashSet<Long> removedAccountList = new HashSet<Long>();
null, null, null);
final Set<Long> newAccountList = new HashSet<Long>();
final Set<Long> removedAccountList = new HashSet<Long>();
if (c == null) {
// Suspender time ... theoretically, this will never happen
Log.wtf(Logging.LOG_TAG, "#onChange(); NULL response for account id query");
LogUtils.wtf(LOG_TAG, "#onChange(); NULL response for account id query");
return;
}
try {
@ -1071,22 +641,22 @@ public class NotificationController {
// NOTE: Looping over three lists is not necessarily the most efficient. However, the
// account lists are going to be very small, so, this will not be necessarily bad.
// Cycle through existing notification list and adjust as necessary
for (long accountId : sInstance.mNotificationMap.keySet()) {
for (final long accountId : sInstance.mNotificationMap.keySet()) {
if (!newAccountList.remove(accountId)) {
// account id not in the current set of notifiable accounts
removedAccountList.add(accountId);
}
}
// A new account was added to the notification list
for (long accountId : newAccountList) {
for (final long accountId : newAccountList) {
sInstance.registerMessageNotification(accountId);
}
// An account was removed from the notification list
for (long accountId : removedAccountList) {
for (final long accountId : removedAccountList) {
sInstance.unregisterMessageNotification(accountId);
int notificationId = sInstance.getNewMessageNotificationId(accountId);
sInstance.mNotificationManager.cancel(notificationId);
}
NotificationUtils.resendNotifications(mContext, false);
}
}
@ -1099,7 +669,7 @@ public class NotificationController {
/** The {@link Looper} that handles messages for this thread */
private Looper mLooper;
NotificationThread() {
public NotificationThread() {
new Thread(null, this, "EmailNotification").start();
synchronized (mLock) {
while (mLooper == null) {
@ -1121,10 +691,8 @@ public class NotificationController {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Looper.loop();
}
void quit() {
mLooper.quit();
}
Looper getLooper() {
public Looper getLooper() {
return mLooper;
}
}

View File

@ -75,13 +75,6 @@ public class Preferences {
// "normal" will be the default
public static final int TEXT_ZOOM_DEFAULT = TEXT_ZOOM_NORMAL;
// Starting something new here:
// REPLY_ALL is saved by the framework (CheckBoxPreference's parent, Preference).
// i.e. android:persistent=true in general_preferences.xml
public static final String REPLY_ALL = "reply_all";
// Reply All Default - when changing this, be sure to update general_preferences.xml
public static final boolean REPLY_ALL_DEFAULT = false;
private static Preferences sPreferences;
private final SharedPreferences mSharedPreferences;

View File

@ -21,8 +21,10 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@ -32,14 +34,16 @@ import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.RingtonePreference;
import android.preference.Preference.OnPreferenceClickListener;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import com.android.email.R;
import com.android.email.SecurityPolicy;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.FolderPickerActivity;
import com.android.email.service.EmailServiceUtils;
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
@ -51,6 +55,11 @@ import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.utility.Utility;
import com.android.mail.preferences.AccountPreferences;
import com.android.mail.preferences.FolderPreferences;
import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.NotificationUtils;
import java.util.ArrayList;
@ -79,10 +88,6 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
private static final String PREFERENCE_DEFAULT = "account_default";
private static final String PREFERENCE_CATEGORY_DATA_USAGE = "data_usage";
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "account_notifications";
private static final String PREFERENCE_NOTIFY = "account_notify";
private static final String PREFERENCE_VIBRATE = "account_settings_vibrate";
private static final String PREFERENCE_VIBRATE_OLD = "account_settings_vibrate_when";
private static final String PREFERENCE_RINGTONE = "account_ringtone";
private static final String PREFERENCE_CATEGORY_SERVER = "account_servers";
private static final String PREFERENCE_CATEGORY_POLICIES = "account_policies";
private static final String PREFERENCE_POLICIES_ENFORCED = "policies_enforced";
@ -98,6 +103,9 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
private static final String PREFERENCE_SYSTEM_FOLDERS_TRASH = "system_folders_trash";
private static final String PREFERENCE_SYSTEM_FOLDERS_SENT = "system_folders_sent";
// Request code to start different activities.
private static final int RINGTONE_REQUEST_CODE = 0;
private EditTextPreference mAccountDescription;
private EditTextPreference mAccountName;
private EditTextPreference mAccountSignature;
@ -105,9 +113,9 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
private ListPreference mSyncWindow;
private CheckBoxPreference mAccountBackgroundAttachments;
private CheckBoxPreference mAccountDefault;
private CheckBoxPreference mAccountNotify;
private CheckBoxPreference mAccountVibrate;
private RingtonePreference mAccountRingtone;
private CheckBoxPreference mInboxNotify;
private CheckBoxPreference mInboxVibrate;
private Preference mInboxRingtone;
private CheckBoxPreference mSyncContacts;
private CheckBoxPreference mSyncCalendar;
private CheckBoxPreference mSyncEmail;
@ -121,6 +129,11 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
private boolean mLoaded;
private boolean mSaveOnExit;
private Ringtone mRingtone;
private AccountPreferences mAccountPreferences;
private FolderPreferences mInboxFolderPreferences;
/** The e-mail of the account being edited. */
private String mAccountEmail;
@ -178,8 +191,6 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
}
super.onCreate(savedInstanceState);
upgradeVibrateSetting();
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.account_settings_preferences);
@ -197,20 +208,6 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
mAccountDirty = false;
}
/**
* Upgrades the old tri-state vibrate setting to the new boolean value.
*/
private void upgradeVibrateSetting() {
final SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
if (!sharedPreferences.contains(PREFERENCE_VIBRATE)) {
// Try to migrate the old one
final boolean vibrate =
"always".equals(sharedPreferences.getString(PREFERENCE_VIBRATE_OLD, ""));
sharedPreferences.edit().putBoolean(PREFERENCE_VIBRATE, vibrate);
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
@ -293,6 +290,41 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
mStarted = false;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case RINGTONE_REQUEST_CODE:
if (resultCode == Activity.RESULT_OK && data != null) {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
setRingtone(uri);
}
break;
}
}
/**
* Sets the current ringtone.
*/
private void setRingtone(Uri ringtone) {
if (ringtone != null) {
mInboxFolderPreferences.setNotificationRingtoneUri(ringtone.toString());
mRingtone = RingtoneManager.getRingtone(getActivity(), ringtone);
} else {
// Null means silent was selected.
mInboxFolderPreferences.setNotificationRingtoneUri("");
mRingtone = null;
}
setRingtoneSummary();
}
private void setRingtoneSummary() {
final String summary = mRingtone != null ? mRingtone.getTitle(mContext)
: mContext.getString(R.string.silent_ringtone);
mInboxRingtone.setSummary(summary);
}
/**
* Listen to all preference changes in this class.
* @param preference
@ -338,11 +370,16 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
preferenceChanged(PREFERENCE_NAME, summary);
}
return false;
} else if (key.equals(PREFERENCE_VIBRATE)) {
} else if (FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE.equals(key)) {
final boolean vibrateSetting = (Boolean) newValue;
mAccountVibrate.setChecked(vibrateSetting);
preferenceChanged(PREFERENCE_VIBRATE, newValue);
return false;
mInboxVibrate.setChecked(vibrateSetting);
mInboxFolderPreferences.setNotificationVibrateEnabled(vibrateSetting);
preferenceChanged(FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE, newValue);
return true;
} else if (FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED.equals(key)) {
mInboxFolderPreferences.setNotificationsEnabled((Boolean) newValue);
preferenceChanged(FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED, newValue);
return true;
} else {
// Default behavior, just indicate that the preferences were written
preferenceChanged(key, newValue);
@ -474,7 +511,72 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
}
/**
* Load account data into preference UI
* Loads settings that are dependent on a {@link com.android.mail.providers.Account}, which
* must be obtained off the main thread. This will also call {@link #loadMainThreadSettings()}.
*/
private void loadSettingsOffMainThread() {
new Thread(new Runnable() {
@Override
public void run() {
final Cursor accountCursor = mContext.getContentResolver().query(EmailProvider
.uiUri("uiaccount", mAccount.mId), UIProvider.ACCOUNTS_PROJECTION, null,
null, null);
com.android.mail.providers.Account account = null;
try {
if (accountCursor.moveToFirst()) {
account = new com.android.mail.providers.Account(accountCursor);
}
} finally {
accountCursor.close();
}
final Cursor folderCursor = mContext.getContentResolver().query(
account.settings.defaultInbox, UIProvider.FOLDERS_PROJECTION, null, null,
null);
Folder folder = null;
try {
if (folderCursor.moveToFirst()) {
folder = new Folder(folderCursor);
}
} finally {
folderCursor.close();
}
mAccountPreferences = new AccountPreferences(mContext, account.name);
mInboxFolderPreferences =
new FolderPreferences(mContext, account.name, folder, true);
NotificationUtils.moveNotificationSetting(
mAccountPreferences, mInboxFolderPreferences);
final String ringtoneUri = mInboxFolderPreferences.getNotificationRingtoneUri();
if (!TextUtils.isEmpty(ringtoneUri)) {
mRingtone = RingtoneManager.getRingtone(getActivity(), Uri.parse(ringtoneUri));
}
final Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
mInboxNotify.setChecked(
mInboxFolderPreferences.areNotificationsEnabled());
mInboxVibrate.setChecked(
mInboxFolderPreferences.isNotificationVibrateEnabled());
setRingtoneSummary();
}
});
}
}
}).start();
}
/**
* Load account data into preference UI. This must be called on the main thread.
*/
private void loadSettings() {
// We can only do this once, so prevent repeat
@ -482,6 +584,8 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
// Once loaded the data is ready to be saved, as well
mSaveOnExit = false;
loadSettingsOffMainThread();
mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION);
mAccountDescription.setSummary(mAccount.getDisplayName());
mAccountDescription.setText(mAccount.getDisplayName());
@ -549,8 +653,7 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
PreferenceCategory folderPrefs =
(PreferenceCategory) findPreference(PREFERENCE_SYSTEM_FOLDERS);
if (info.requiresSetup) {
Preference trashPreference =
(Preference) findPreference(PREFERENCE_SYSTEM_FOLDERS_TRASH);
Preference trashPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_TRASH);
Intent i = new Intent(mContext, FolderPickerActivity.class);
Uri uri = EmailContent.CONTENT_URI.buildUpon().appendQueryParameter(
"account", Long.toString(mAccount.mId)).build();
@ -558,8 +661,7 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_TRASH);
trashPreference.setIntent(i);
Preference sentPreference =
(Preference) findPreference(PREFERENCE_SYSTEM_FOLDERS_SENT);
Preference sentPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_SENT);
i = new Intent(mContext, FolderPickerActivity.class);
i.setData(uri);
i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_SENT);
@ -582,33 +684,35 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
mAccountDefault.setChecked(mAccount.mId == mDefaultAccountId);
mAccountDefault.setOnPreferenceChangeListener(this);
mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY);
mAccountNotify.setChecked(0 != (mAccount.getFlags() & Account.FLAGS_NOTIFY_NEW_MAIL));
mAccountNotify.setOnPreferenceChangeListener(this);
mInboxNotify = (CheckBoxPreference) findPreference(
FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED);
mInboxNotify.setOnPreferenceChangeListener(this);
mAccountRingtone = (RingtonePreference) findPreference(PREFERENCE_RINGTONE);
mAccountRingtone.setOnPreferenceChangeListener(this);
mInboxRingtone = findPreference(FolderPreferences.PreferenceKeys.NOTIFICATION_RINGTONE);
mInboxRingtone.setOnPreferenceChangeListener(this);
mInboxRingtone.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference preference) {
showRingtonePicker();
// The following two lines act as a workaround for the RingtonePreference
// which does not let us set/get the value programmatically
SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences();
prefs.edit().putString(PREFERENCE_RINGTONE, mAccount.getRingtone()).apply();
return true;
}
});
// Set the vibrator value, or hide it on devices w/o a vibrator
mAccountVibrate = (CheckBoxPreference) findPreference(PREFERENCE_VIBRATE);
mInboxVibrate = (CheckBoxPreference) findPreference(
FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE);
Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator.hasVibrator()) {
// Calculate the value to set based on the choices, and set the value.
final boolean vibrateSetting = 0 != (mAccount.getFlags() & Account.FLAGS_VIBRATE);
mAccountVibrate.setChecked(vibrateSetting);
// Checked state will be set when we obtain it in #loadSettingsOffMainThread()
// When the value is changed, update the setting.
mAccountVibrate.setOnPreferenceChangeListener(this);
mInboxVibrate.setOnPreferenceChangeListener(this);
} else {
// No vibrator present. Remove the preference altogether.
PreferenceCategory notificationsCategory = (PreferenceCategory)
findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS);
notificationsCategory.removePreference(mAccountVibrate);
notificationsCategory.removePreference(mInboxVibrate);
}
final Preference retryAccount = findPreference(PREFERENCE_POLICIES_RETRY_ACCOUNT);
@ -725,10 +829,7 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
*/
private void saveSettings() {
// Turn off all controlled flags - will turn them back on while checking UI elements
int newFlags = mAccount.getFlags() &
~(Account.FLAGS_NOTIFY_NEW_MAIL |
Account.FLAGS_VIBRATE |
Account.FLAGS_BACKGROUND_ATTACHMENTS);
int newFlags = mAccount.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS);
newFlags |= mAccountBackgroundAttachments.isChecked() ?
Account.FLAGS_BACKGROUND_ATTACHMENTS : 0;
@ -738,16 +839,10 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
// The sender name must never be empty (this is enforced by the preference editor)
mAccount.setSenderName(mAccountName.getText().trim());
mAccount.setSignature(mAccountSignature.getText());
newFlags |= mAccountNotify.isChecked() ? Account.FLAGS_NOTIFY_NEW_MAIL : 0;
mAccount.setSyncInterval(Integer.parseInt(mCheckFrequency.getValue()));
if (mSyncWindow != null) {
mAccount.setSyncLookback(Integer.parseInt(mSyncWindow.getValue()));
}
if (mAccountVibrate.isChecked()) {
newFlags |= Account.FLAGS_VIBRATE;
}
SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences();
mAccount.setRingtone(prefs.getString(PREFERENCE_RINGTONE, null));
mAccount.setFlags(newFlags);
EmailServiceInfo info =
@ -779,4 +874,21 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
// Get the e-mail address of the account being editted, if this is for an existing account.
return mAccountEmail;
}
/**
* Shows the system ringtone picker.
*/
private void showRingtonePicker() {
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
final String ringtoneUri = mInboxFolderPreferences.getNotificationRingtoneUri();
if (!TextUtils.isEmpty(ringtoneUri)) {
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(ringtoneUri));
}
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
Settings.System.DEFAULT_NOTIFICATION_URI);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
startActivityForResult(intent, RINGTONE_REQUEST_CODE);
}
}

View File

@ -92,7 +92,6 @@ public class AccountSettingsUtils {
cv.put(AccountColumns.SENDER_NAME, account.getSenderName());
cv.put(AccountColumns.SIGNATURE, account.getSignature());
cv.put(AccountColumns.SYNC_INTERVAL, account.mSyncInterval);
cv.put(AccountColumns.RINGTONE_URI, account.mRingtoneUri);
cv.put(AccountColumns.FLAGS, account.mFlags);
cv.put(AccountColumns.SYNC_LOOKBACK, account.mSyncLookback);
cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);

View File

@ -28,6 +28,7 @@ import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
@ -40,6 +41,7 @@ import android.widget.Spinner;
import com.android.email.R;
import com.android.email.activity.ActivityHelper;
import com.android.email.activity.UiUtilities;
import com.android.email.provider.EmailProvider;
import com.android.email.service.EmailServiceUtils;
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
import com.android.email.service.MailService;
@ -50,10 +52,17 @@ import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.emailcommon.service.SyncWindow;
import com.android.emailcommon.utility.Utility;
import com.android.mail.preferences.AccountPreferences;
import com.android.mail.preferences.FolderPreferences;
import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import java.io.IOException;
public class AccountSetupOptions extends AccountSetupActivity implements OnClickListener {
private static final String LOG_TAG = LogTag.getLogTag();
private Spinner mCheckFrequencyView;
private Spinner mSyncWindowView;
@ -126,8 +135,8 @@ public class AccountSetupOptions extends AccountSetupActivity implements OnClick
if (account.mIsDefault || SetupData.isDefault()) {
mDefaultView.setChecked(true);
}
mNotifyView.setChecked(
(account.getFlags() & Account.FLAGS_NOTIFY_NEW_MAIL) != 0);
mNotifyView.setChecked(true); // By default, we want notifications on
SpinnerOption.setSpinnerOptionValue(mCheckFrequencyView, account.getSyncInterval());
if (mServiceInfo.syncContacts) {
@ -210,11 +219,7 @@ public class AccountSetupOptions extends AccountSetupActivity implements OnClick
return;
}
account.setDisplayName(account.getEmailAddress());
int newFlags = account.getFlags() &
~(Account.FLAGS_NOTIFY_NEW_MAIL | Account.FLAGS_BACKGROUND_ATTACHMENTS);
if (mNotifyView.isChecked()) {
newFlags |= Account.FLAGS_NOTIFY_NEW_MAIL;
}
int newFlags = account.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS);
if (mServiceInfo.offerAttachmentPreload && mBackgroundAttachmentsView.isChecked()) {
newFlags |= Account.FLAGS_BACKGROUND_ATTACHMENTS;
}
@ -269,6 +274,12 @@ public class AccountSetupOptions extends AccountSetupActivity implements OnClick
AccountSettingsUtils.commitSettings(context, account);
MailService.setupAccountManagerAccount(context, account,
email2, calendar2, contacts2, mAccountManagerCallback);
// We can move the notification setting to the inbox FolderPreferences later, once
// we know what the inbox is
final AccountPreferences accountPreferences =
new AccountPreferences(context, account.mEmailAddress);
accountPreferences.setDefaultInboxNotificationsEnabled(mNotifyView.isChecked());
}
});
}

View File

@ -29,6 +29,7 @@ import com.android.email.Preferences;
import com.android.email.R;
import com.android.email.provider.EmailProvider;
import com.android.mail.preferences.MailPrefs;
import com.android.mail.utils.Utils;
public class GeneralPreferences extends EmailPreferenceFragment implements
@ -41,8 +42,8 @@ public class GeneralPreferences extends EmailPreferenceFragment implements
private static final String PREFERENCE_KEY_SWIPE_DELETE = "swipe_delete";
private static final String PREFERENCE_KEY_SHOW_CHECKBOXES = "show_checkboxes";
private static final String PREFERENCE_KEY_CLEAR_TRUSTED_SENDERS = "clear_trusted_senders";
private static final String PREFERNECE_REPLY_ALL = "reply_all";
private MailPrefs mMailPrefs;
private Preferences mPreferences;
private ListPreference mAutoAdvance;
/**
@ -64,6 +65,7 @@ public class GeneralPreferences extends EmailPreferenceFragment implements
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMailPrefs = MailPrefs.get(getActivity());
getPreferenceManager().setSharedPreferencesName(Preferences.PREFERENCES_FILE);
// Load the preferences from an XML resource
@ -75,7 +77,7 @@ public class GeneralPreferences extends EmailPreferenceFragment implements
// Disabling reply-all on tablets, as this setting is just for phones
if (Utils.useTabletUI(getActivity().getResources())) {
ps.removePreference(findPreference(PREFERNECE_REPLY_ALL));
ps.removePreference(findPreference(MailPrefs.PreferenceKeys.DEFAULT_REPLY_ALL));
}
}
@ -108,6 +110,9 @@ public class GeneralPreferences extends EmailPreferenceFragment implements
mPreferences.setTextZoom(mTextZoom.findIndexOfValue((String) newValue));
reloadDynamicSummaries();
return true;
} else if (MailPrefs.PreferenceKeys.DEFAULT_REPLY_ALL.equals(key)) {
mMailPrefs.setDefaultReplyAll((Boolean) newValue);
return true;
}
return false;
}
@ -158,6 +163,13 @@ public class GeneralPreferences extends EmailPreferenceFragment implements
mShowCheckboxes = (CheckBoxPreference) findPreference(PREFERENCE_KEY_SHOW_CHECKBOXES);
mSwipeDelete = (CheckBoxPreference) findPreference(PREFERENCE_KEY_SWIPE_DELETE);
final Preference replyAllPreference =
findPreference(MailPrefs.PreferenceKeys.DEFAULT_REPLY_ALL);
// This preference is removed on tablets
if (replyAllPreference != null) {
replyAllPreference.setOnPreferenceChangeListener(this);
}
reloadDynamicSummaries();
}

View File

@ -131,8 +131,9 @@ public final class DBHelper {
// Version 102&103: Add hierarchicalName to Mailbox
// Version 104&105: add syncData to Message
// Version 106: Add certificate to HostAuth
// Version 107: Add a SEEN column to the message table
public static final int DATABASE_VERSION = 106;
public static final int DATABASE_VERSION = 107;
// Any changes to the database format *must* include update-in-place code.
// Original version: 2
@ -178,7 +179,8 @@ public final class DBHelper {
+ MessageColumns.SNIPPET + " text, "
+ MessageColumns.PROTOCOL_SEARCH_INFO + " text, "
+ MessageColumns.THREAD_TOPIC + " text, "
+ MessageColumns.SYNC_DATA + " text"
+ MessageColumns.SYNC_DATA + " text, "
+ MessageColumns.FLAG_SEEN + " integer"
+ ");";
// This String and the following String MUST have the same columns, except for the type
@ -1012,6 +1014,20 @@ public final class DBHelper {
}
oldVersion = 106;
}
if (oldVersion == 106) {
try {
db.execSQL("alter table " + Message.TABLE_NAME
+ " add " + MessageColumns.FLAG_SEEN + " integer");
db.execSQL("alter table " + Message.UPDATED_TABLE_NAME
+ " add " + MessageColumns.FLAG_SEEN + " integer");
db.execSQL("alter table " + Message.DELETED_TABLE_NAME
+ " add " + MessageColumns.FLAG_SEEN + " integer");
} catch (SQLException e) {
// Shouldn't be needed unless we're debugging and interrupt the process
Log.w(TAG, "Exception upgrading EmailProvider.db from v106 to v107", e);
}
oldVersion = 107;
}
}
@Override

View File

@ -93,7 +93,6 @@ import com.android.mail.utils.MatrixCursorWithExtra;
import com.android.mail.utils.Utils;
import com.android.mail.widget.BaseWidgetProvider;
import com.android.mail.widget.WidgetProvider;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@ -119,33 +118,29 @@ public class EmailProvider extends ContentProvider {
public static String EMAIL_APP_MIME_TYPE;
protected static final String DATABASE_NAME = "EmailProvider.db";
protected static final String BODY_DATABASE_NAME = "EmailProviderBody.db";
protected static final String BACKUP_DATABASE_NAME = "EmailProviderBackup.db";
public static final String ACTION_ATTACHMENT_UPDATED = "com.android.email.ATTACHMENT_UPDATED";
public static final String ATTACHMENT_UPDATED_EXTRA_FLAGS =
"com.android.email.ATTACHMENT_UPDATED_FLAGS";
private static final String DATABASE_NAME = "EmailProvider.db";
private static final String BODY_DATABASE_NAME = "EmailProviderBody.db";
private static final String BACKUP_DATABASE_NAME = "EmailProviderBackup.db";
/**
* Notifies that changes happened. Certain UI components, e.g., widgets, can register for this
* {@link android.content.Intent} and update accordingly. However, this can be very broad and
* is NOT the preferred way of getting notification.
*/
public static final String ACTION_NOTIFY_MESSAGE_LIST_DATASET_CHANGED =
private static final String ACTION_NOTIFY_MESSAGE_LIST_DATASET_CHANGED =
"com.android.email.MESSAGE_LIST_DATASET_CHANGED";
public static final String EMAIL_MESSAGE_MIME_TYPE =
private static final String EMAIL_MESSAGE_MIME_TYPE =
"vnd.android.cursor.item/email-message";
public static final String EMAIL_ATTACHMENT_MIME_TYPE =
private static final String EMAIL_ATTACHMENT_MIME_TYPE =
"vnd.android.cursor.item/email-attachment";
/** Appended to the notification URI for delete operations */
public static final String NOTIFICATION_OP_DELETE = "delete";
private static final String NOTIFICATION_OP_DELETE = "delete";
/** Appended to the notification URI for insert operations */
public static final String NOTIFICATION_OP_INSERT = "insert";
private static final String NOTIFICATION_OP_INSERT = "insert";
/** Appended to the notification URI for update operations */
public static final String NOTIFICATION_OP_UPDATE = "update";
private static final String NOTIFICATION_OP_UPDATE = "update";
// Definitions for our queries looking for orphaned messages
private static final String[] ORPHANS_PROJECTION
@ -339,7 +334,7 @@ public class EmailProvider extends ContentProvider {
private static ContentValues CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
private static final ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
public static final String MESSAGE_URI_PARAMETER_MAILBOX_ID = "mailboxId";
private static final String MESSAGE_URI_PARAMETER_MAILBOX_ID = "mailboxId";
// For undo handling
private int mLastSequence = -1;
@ -368,10 +363,9 @@ public class EmailProvider extends ContentProvider {
return match;
}
public static Uri INTEGRITY_CHECK_URI;
private static Uri INTEGRITY_CHECK_URI;
public static Uri ACCOUNT_BACKUP_URI;
public static Uri FOLDER_STATUS_URI;
public static Uri FOLDER_REFRESH_URI;
private static Uri FOLDER_STATUS_URI;
private SQLiteDatabase mDatabase;
private SQLiteDatabase mBodyDatabase;
@ -399,9 +393,8 @@ public class EmailProvider extends ContentProvider {
* @param foreignColumn the column in the foreign table whose absence will trigger the deletion
* @param foreignTable the foreign table
*/
@VisibleForTesting
void deleteUnlinked(SQLiteDatabase db, String table, String column, String foreignColumn,
String foreignTable) {
private static void deleteUnlinked(SQLiteDatabase db, String table, String column,
String foreignColumn, String foreignTable) {
int count = db.delete(table, column + " not in (select " + foreignColumn + " from " +
foreignTable + ")", null);
if (count > 0) {
@ -409,8 +402,7 @@ public class EmailProvider extends ContentProvider {
}
}
@VisibleForTesting
synchronized SQLiteDatabase getDatabase(Context context) {
private synchronized SQLiteDatabase getDatabase(Context context) {
// Always return the cached database, if we've got one
if (mDatabase != null) {
return mDatabase;
@ -542,15 +534,10 @@ public class EmailProvider extends ContentProvider {
}
}
/*package*/ static SQLiteDatabase getReadableDatabase(Context context) {
DBHelper.DatabaseHelper helper = new DBHelper.DatabaseHelper(context, DATABASE_NAME);
return helper.getReadableDatabase();
}
/**
* Restore user Account and HostAuth data from our backup database
*/
public static void restoreIfNeeded(Context context, SQLiteDatabase mainDatabase) {
private static void restoreIfNeeded(Context context, SQLiteDatabase mainDatabase) {
if (MailActivityEmail.DEBUG) {
Log.w(TAG, "restoreIfNeeded...");
}
@ -596,7 +583,7 @@ public class EmailProvider extends ContentProvider {
}
}
/*package*/ static void deleteMessageOrphans(SQLiteDatabase database, String tableName) {
private static void deleteMessageOrphans(SQLiteDatabase database, String tableName) {
if (database != null) {
// We'll look at all of the items in the table; there won't be many typically
Cursor c = database.query(tableName, ORPHANS_PROJECTION, null, null, null, null, null);
@ -1061,8 +1048,6 @@ public class EmailProvider extends ContentProvider {
Uri.parse("content://" + EmailContent.AUTHORITY + "/accountBackup");
FOLDER_STATUS_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/status");
FOLDER_REFRESH_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/refresh");
EMAIL_APP_MIME_TYPE = context.getString(R.string.application_mime_type);
}
checkDatabases();
@ -1303,7 +1288,16 @@ public class EmailProvider extends ContentProvider {
if (selection != null || selectionArgs != null) {
throw new IllegalArgumentException("UI queries can't have selection/args");
}
c = uiQuery(match, uri, projection);
final String seenParam = uri.getQueryParameter(UIProvider.SEEN_QUERY_PARAMETER);
final boolean unseenOnly;
if (seenParam != null && Boolean.FALSE.toString().equals(seenParam)) {
unseenOnly = true;
} else {
unseenOnly = false;
}
c = uiQuery(match, uri, projection, unseenOnly);
return c;
case UI_FOLDERS:
c = uiFolders(uri, projection);
@ -1451,7 +1445,7 @@ public class EmailProvider extends ContentProvider {
return c;
}
private String whereWithId(String id, String selection) {
private static String whereWithId(String id, String selection) {
StringBuilder sb = new StringBuilder(256);
sb.append("_id=");
sb.append(id);
@ -1473,7 +1467,7 @@ public class EmailProvider extends ContentProvider {
* @param selection user-provided selection, may be null
* @return a single selection string
*/
private String whereWith(String where, String selection) {
private static String whereWith(String where, String selection) {
if (selection == null) {
return where;
}
@ -1689,7 +1683,7 @@ outer:
case ACCOUNT_PICK_SENT_FOLDER:
return pickSentFolder(uri);
case UI_FOLDER:
return uiUpdateFolder(uri, values);
return uiUpdateFolder(context, uri, values);
case UI_RECENT_FOLDERS:
return uiUpdateRecentFolders(uri, values);
case UI_DEFAULT_RECENT_FOLDERS:
@ -1933,7 +1927,7 @@ outer:
*
* @param match The type of content that was modified.
*/
private Uri getBaseNotificationUri(int match) {
private static Uri getBaseNotificationUri(int match) {
Uri baseUri = null;
switch (match) {
case MESSAGE:
@ -2013,22 +2007,6 @@ outer:
}
}
/**
* For testing purposes, check whether a given row is cached
* @param baseUri the base uri of the EmailContent
* @param id the row id of the EmailContent
* @return whether or not the row is currently cached
*/
@VisibleForTesting
protected boolean isCached(Uri baseUri, long id) {
int match = findMatch(baseUri, "isCached");
int table = match >> BASE_SHIFT;
ContentCache cache = mContentCaches[table];
if (cache == null) return false;
Cursor cc = cache.get(Long.toString(id));
return (cc != null);
}
public static interface AttachmentService {
/**
* Notify the service that an attachment has changed.
@ -2045,44 +2023,31 @@ outer:
};
private AttachmentService mAttachmentService = DEFAULT_ATTACHMENT_SERVICE;
/**
* Injects a custom attachment service handler. If null is specified, will reset to the
* default service.
*/
public void injectAttachmentService(AttachmentService as) {
mAttachmentService = (as == null) ? DEFAULT_ATTACHMENT_SERVICE : as;
private Cursor notificationQuery(final Uri uri) {
final SQLiteDatabase db = getDatabase(getContext());
final String accountId = uri.getLastPathSegment();
final StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("SELECT ");
sqlBuilder.append(MessageColumns.MAILBOX_KEY).append(", ");
sqlBuilder.append("SUM(CASE ")
.append(MessageColumns.FLAG_READ).append(" WHEN 0 THEN 1 ELSE 0 END), ");
sqlBuilder.append("SUM(CASE ")
.append(MessageColumns.FLAG_SEEN).append(" WHEN 0 THEN 1 ELSE 0 END)\n");
sqlBuilder.append("FROM ");
sqlBuilder.append(Message.TABLE_NAME).append('\n');
sqlBuilder.append("WHERE ");
sqlBuilder.append(MessageColumns.ACCOUNT_KEY).append(" = ?\n");
sqlBuilder.append("GROUP BY ");
sqlBuilder.append(MessageColumns.MAILBOX_KEY);
final String sql = sqlBuilder.toString();
final String[] selectionArgs = {accountId};
return db.rawQuery(sql, selectionArgs);
}
// SELECT DISTINCT Boxes._id, Boxes.unreadCount count(Message._id) from Message,
// (SELECT _id, unreadCount, messageCount, lastNotifiedMessageCount, lastNotifiedMessageKey
// FROM Mailbox WHERE accountKey=6 AND ((type = 0) OR (syncInterval!=0 AND syncInterval!=-1)))
// AS Boxes
// WHERE Boxes.messageCount!=Boxes.lastNotifiedMessageCount
// OR (Boxes._id=Message.mailboxKey AND Message._id>Boxes.lastNotifiedMessageKey)
// TODO: This query can be simplified a bit
private static final String NOTIFICATION_QUERY =
"SELECT DISTINCT Boxes." + MailboxColumns.ID + ", Boxes." + MailboxColumns.UNREAD_COUNT +
", count(" + Message.TABLE_NAME + "." + MessageColumns.ID + ")" +
" FROM " +
Message.TABLE_NAME + "," +
"(SELECT " + MailboxColumns.ID + "," + MailboxColumns.UNREAD_COUNT + "," +
MailboxColumns.MESSAGE_COUNT + "," + MailboxColumns.LAST_NOTIFIED_MESSAGE_COUNT +
"," + MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY + " FROM " + Mailbox.TABLE_NAME +
" WHERE " + MailboxColumns.ACCOUNT_KEY + "=?" +
" AND (" + MailboxColumns.TYPE + "=" + Mailbox.TYPE_INBOX + " OR ("
+ MailboxColumns.SYNC_INTERVAL + "!=0 AND " +
MailboxColumns.SYNC_INTERVAL + "!=-1))) AS Boxes " +
"WHERE Boxes." + MailboxColumns.ID + '=' + Message.TABLE_NAME + "." +
MessageColumns.MAILBOX_KEY + " AND " + Message.TABLE_NAME + "." +
MessageColumns.ID + ">Boxes." + MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY +
" AND " + MessageColumns.FLAG_READ + "=0 AND " + MessageColumns.TIMESTAMP + "!=0";
public Cursor notificationQuery(Uri uri) {
SQLiteDatabase db = getDatabase(getContext());
String accountId = uri.getLastPathSegment();
return db.rawQuery(NOTIFICATION_QUERY, new String[] {accountId});
}
public Cursor mostRecentMessageQuery(Uri uri) {
SQLiteDatabase db = getDatabase(getContext());
String mailboxId = uri.getLastPathSegment();
@ -2145,7 +2110,7 @@ outer:
* Mapping of UIProvider columns to EmailProvider columns for the message list (called the
* conversation list in UnifiedEmail)
*/
private ProjectionMap getMessageListMap() {
private static ProjectionMap getMessageListMap() {
if (sMessageListMap == null) {
sMessageListMap = ProjectionMap.builder()
.add(BaseColumns._ID, MessageColumns.ID)
@ -2163,6 +2128,7 @@ outer:
.add(UIProvider.ConversationColumns.PRIORITY,
Integer.toString(ConversationPriority.LOW))
.add(UIProvider.ConversationColumns.READ, MessageColumns.FLAG_READ)
.add(UIProvider.ConversationColumns.SEEN, MessageColumns.FLAG_SEEN)
.add(UIProvider.ConversationColumns.STARRED, MessageColumns.FLAG_FAVORITE)
.add(UIProvider.ConversationColumns.FLAGS, CONVERSATION_FLAGS)
.add(UIProvider.ConversationColumns.ACCOUNT_URI,
@ -2197,7 +2163,7 @@ outer:
* Mapping of UIProvider columns to EmailProvider columns for a detailed message view in
* UnifiedEmail
*/
private ProjectionMap getMessageViewMap() {
private static ProjectionMap getMessageViewMap() {
if (sMessageViewMap == null) {
sMessageViewMap = ProjectionMap.builder()
.add(BaseColumns._ID, Message.TABLE_NAME + "." + EmailContent.MessageColumns.ID)
@ -2233,6 +2199,7 @@ outer:
uriWithColumn("account", MessageColumns.ACCOUNT_KEY))
.add(UIProvider.MessageColumns.STARRED, EmailContent.MessageColumns.FLAG_FAVORITE)
.add(UIProvider.MessageColumns.READ, EmailContent.MessageColumns.FLAG_READ)
.add(UIProvider.MessageColumns.SEEN, EmailContent.MessageColumns.FLAG_SEEN)
.add(UIProvider.MessageColumns.SPAM_WARNING_STRING, null)
.add(UIProvider.MessageColumns.SPAM_WARNING_LEVEL,
Integer.toString(UIProvider.SpamWarningLevel.NO_WARNING))
@ -2274,10 +2241,11 @@ outer:
+ " WHEN " + Mailbox.TYPE_STARRED + " THEN " + R.drawable.ic_menu_star_holo_light
+ " ELSE -1 END";
private ProjectionMap getFolderListMap() {
private static ProjectionMap getFolderListMap() {
if (sFolderListMap == null) {
sFolderListMap = ProjectionMap.builder()
.add(BaseColumns._ID, MailboxColumns.ID)
.add(UIProvider.FolderColumns.PERSISTENT_ID, MailboxColumns.SERVER_ID)
.add(UIProvider.FolderColumns.URI, uriWithId("uifolder"))
.add(UIProvider.FolderColumns.NAME, "displayName")
.add(UIProvider.FolderColumns.HAS_CHILDREN,
@ -2304,7 +2272,7 @@ outer:
* Constructs the map of default entries for accounts. These values can be overridden in
* {@link #genQueryAccount(String[], String)}.
*/
private ProjectionMap getAccountListMap() {
private static ProjectionMap getAccountListMap() {
if (sAccountListMap == null) {
sAccountListMap = ProjectionMap.builder()
.add(BaseColumns._ID, AccountColumns.ID)
@ -2355,7 +2323,7 @@ outer:
/**
* Mapping of UIProvider columns to EmailProvider columns for a message's attachments
*/
private ProjectionMap getAttachmentMap() {
private static ProjectionMap getAttachmentMap() {
if (sAttachmentMap == null) {
sAttachmentMap = ProjectionMap.builder()
.add(UIProvider.AttachmentColumns.NAME, AttachmentColumns.FILENAME)
@ -2377,14 +2345,14 @@ outer:
* Generate the SELECT clause using a specified mapping and the original UI projection
* @param map the ProjectionMap to use for this projection
* @param projection the projection as sent by UnifiedEmail
* @param values ContentValues to be used if the ProjectionMap entry is null
* @return a StringBuilder containing the SELECT expression for a SQLite query
*/
private StringBuilder genSelect(ProjectionMap map, String[] projection) {
private static StringBuilder genSelect(ProjectionMap map, String[] projection) {
return genSelect(map, projection, EMPTY_CONTENT_VALUES);
}
private StringBuilder genSelect(ProjectionMap map, String[] projection, ContentValues values) {
private static StringBuilder genSelect(ProjectionMap map, String[] projection,
ContentValues values) {
StringBuilder sb = new StringBuilder("SELECT ");
boolean first = true;
for (String column: projection) {
@ -2545,12 +2513,16 @@ outer:
* Generate the "message list" SQLite query, given a projection from UnifiedEmail
*
* @param uiProjection as passed from UnifiedEmail
* @param unseenOnly <code>true</code> to only return unseen messages
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryMailboxMessages(String[] uiProjection) {
private static String genQueryMailboxMessages(String[] uiProjection, final boolean unseenOnly) {
StringBuilder sb = genSelect(getMessageListMap(), uiProjection);
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.MAILBOX_KEY + "=? ORDER BY " +
MessageColumns.TIMESTAMP + " DESC");
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.MAILBOX_KEY + "=? ");
if (unseenOnly) {
sb.append("AND ").append(MessageColumns.FLAG_SEEN).append(" = 0 ");
}
sb.append("ORDER BY " + MessageColumns.TIMESTAMP + " DESC");
return sb.toString();
}
@ -2558,11 +2530,12 @@ outer:
* Generate various virtual mailbox SQLite queries, given a projection from UnifiedEmail
*
* @param uiProjection as passed from UnifiedEmail
* @param id the id of the virtual mailbox
* @param mailboxId the id of the virtual mailbox
* @param unseenOnly <code>true</code> to only return unseen messages
* @return the SQLite query to be executed on the EmailProvider database
*/
private Cursor getVirtualMailboxMessagesCursor(SQLiteDatabase db, String[] uiProjection,
long mailboxId) {
private static Cursor getVirtualMailboxMessagesCursor(SQLiteDatabase db, String[] uiProjection,
long mailboxId, final boolean unseenOnly) {
ContentValues values = new ContentValues();
values.put(UIProvider.ConversationColumns.COLOR, CONVERSATION_COLOR);
StringBuilder sb = genSelect(getMessageListMap(), uiProjection, values);
@ -2572,13 +2545,19 @@ outer:
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " +
MessageColumns.MAILBOX_KEY + " IN (SELECT " + MailboxColumns.ID +
" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.TYPE +
"=" + Mailbox.TYPE_INBOX + ") ORDER BY " + MessageColumns.TIMESTAMP +
" DESC");
"=" + Mailbox.TYPE_INBOX + ") ");
if (unseenOnly) {
sb.append("AND ").append(MessageColumns.FLAG_SEEN).append(" = 0 ");
}
sb.append("ORDER BY " + MessageColumns.TIMESTAMP + " DESC");
break;
case Mailbox.TYPE_STARRED:
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " +
MessageColumns.FLAG_FAVORITE + "=1 ORDER BY " +
MessageColumns.TIMESTAMP + " DESC");
MessageColumns.FLAG_FAVORITE + "=1 ");
if (unseenOnly) {
sb.append("AND ").append(MessageColumns.FLAG_SEEN).append(" = 0 ");
}
sb.append("ORDER BY " + MessageColumns.TIMESTAMP + " DESC");
break;
default:
throw new IllegalArgumentException("No virtual mailbox for: " + mailboxId);
@ -2606,7 +2585,7 @@ outer:
* @param uiProjection as passed from UnifiedEmail
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryConversation(String[] uiProjection) {
private static String genQueryConversation(String[] uiProjection) {
StringBuilder sb = genSelect(getMessageListMap(), uiProjection);
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.RECORD_ID + "=?");
return sb.toString();
@ -2618,7 +2597,7 @@ outer:
* @param uiProjection as passed from UnifiedEmail
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAccountMailboxes(String[] uiProjection) {
private static String genQueryAccountMailboxes(String[] uiProjection) {
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
@ -2634,7 +2613,7 @@ outer:
* @param uiProjection as passed from UnifiedEmail
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAccountAllMailboxes(String[] uiProjection) {
private static String genQueryAccountAllMailboxes(String[] uiProjection) {
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
// Use a derived column to choose either hierarchicalName or displayName
sb.append(", case when " + MailboxColumns.HIERARCHICAL_NAME + " is null then " +
@ -2653,7 +2632,7 @@ outer:
* @param uiProjection as passed from UnifiedEmail
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryRecentMailboxes(String[] uiProjection) {
private static String genQueryRecentMailboxes(String[] uiProjection) {
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
@ -2663,7 +2642,8 @@ outer:
return sb.toString();
}
private int getFolderCapabilities(EmailServiceInfo info, int flags, int type, long mailboxId) {
private static int getFolderCapabilities(EmailServiceInfo info, int flags, int type,
long mailboxId) {
// All folders support delete
int caps = UIProvider.FolderCapabilities.DELETE;
if (info != null && info.offerLookback) {
@ -2739,7 +2719,7 @@ outer:
.appendQueryParameter("account", account).build().toString();
}
private String getBits(int bitField) {
private static String getBits(int bitField) {
StringBuilder sb = new StringBuilder(" ");
for (int i = 0; i < 32; i++, bitField >>= 1) {
if ((bitField & 1) != 0) {
@ -2833,7 +2813,7 @@ outer:
values.put(UIProvider.AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE,
textZoomToUiValue(textZoom));
}
// Set default inbox, if we've got an inbox; otherwise, say initial sync needed
// Set default inbox, if we've got an inbox; otherwise, say initial sync needed
long mailboxId = Mailbox.findMailboxOfType(context, accountId, Mailbox.TYPE_INBOX);
if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX) &&
mailboxId != Mailbox.NO_MAILBOX) {
@ -2878,7 +2858,7 @@ outer:
return sb.toString();
}
private int autoAdvanceToUiValue(int autoAdvance) {
private static int autoAdvanceToUiValue(int autoAdvance) {
switch(autoAdvance) {
case Preferences.AUTO_ADVANCE_OLDER:
return UIProvider.AutoAdvance.OLDER;
@ -2890,7 +2870,7 @@ outer:
}
}
private int textZoomToUiValue(int textZoom) {
private static int textZoomToUiValue(int textZoom) {
switch(textZoom) {
case Preferences.TEXT_ZOOM_HUGE:
return UIProvider.MessageTextSize.HUGE;
@ -3130,7 +3110,7 @@ outer:
* or null if there are no query parameters
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAttachments(String[] uiProjection,
private static String genQueryAttachments(String[] uiProjection,
List<String> contentTypeQueryParameters) {
StringBuilder sb = genSelect(getAttachmentMap(), uiProjection);
sb.append(" FROM " + Attachment.TABLE_NAME + " WHERE " + AttachmentColumns.MESSAGE_KEY +
@ -3180,7 +3160,7 @@ outer:
* @param uiProjection as passed from UnifiedEmail
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQuerySubfolders(String[] uiProjection) {
private static String genQuerySubfolders(String[] uiProjection) {
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.PARENT_KEY +
" =? ORDER BY ");
@ -3324,12 +3304,21 @@ outer:
if (params.containsKey(
UIProvider.ConversationCursorCommand.COMMAND_KEY_ENTERED_FOLDER)) {
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mMailboxId);
if (mailbox != null && mailbox.mVisibleLimit > 0) {
ContentValues values = new ContentValues();
values.put(MailboxColumns.VISIBLE_LIMIT, 0);
mContext.getContentResolver().update(
ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailboxId),
values, null, null);
if (mailbox != null) {
if (mailbox.mVisibleLimit > 0) {
ContentValues values = new ContentValues();
values.put(MailboxColumns.VISIBLE_LIMIT, 0);
mContext.getContentResolver().update(ContentUris.withAppendedId(
Mailbox.CONTENT_URI, mMailboxId), values, null, null);
}
// Mark all messages as seen
final ContentValues contentValues = new ContentValues(1);
contentValues.put(MessageColumns.FLAG_SEEN, true);
final Uri uri = EmailContent.Message.CONTENT_URI;
mContext.getContentResolver().update(uri, contentValues,
MessageColumns.MAILBOX_KEY + " = ?",
new String[] {String.valueOf(mailbox.mId)});
}
}
}
@ -3388,7 +3377,7 @@ outer:
* We need to do individual queries for the mailboxes in order to get correct
* folder capabilities.
*/
Cursor getFolderListCursor(SQLiteDatabase db, Cursor c, String[] uiProjection) {
private Cursor getFolderListCursor(SQLiteDatabase db, Cursor c, String[] uiProjection) {
final MatrixCursor mc = new MatrixCursorWithCachedColumns(uiProjection);
Object[] values = new Object[uiProjection.length];
String[] args = new String[1];
@ -3417,9 +3406,10 @@ outer:
* @param match the UriMatcher match for the original uri passed in from UnifiedEmail
* @param uri the original uri passed in from UnifiedEmail
* @param uiProjection the projection passed in from UnifiedEmail
* @param unseenOnly <code>true</code> to only return unseen messages (where supported)
* @return the result Cursor
*/
private Cursor uiQuery(int match, Uri uri, String[] uiProjection) {
private Cursor uiQuery(int match, Uri uri, String[] uiProjection, final boolean unseenOnly) {
Context context = getContext();
ContentResolver resolver = context.getContentResolver();
SQLiteDatabase db = getDatabase(context);
@ -3443,9 +3433,10 @@ outer:
case UI_MESSAGES:
long mailboxId = Long.parseLong(id);
if (isVirtualMailbox(mailboxId)) {
c = getVirtualMailboxMessagesCursor(db, uiProjection, mailboxId);
c = getVirtualMailboxMessagesCursor(db, uiProjection, mailboxId, unseenOnly);
} else {
c = db.rawQuery(genQueryMailboxMessages(uiProjection), new String[] {id});
c = db.rawQuery(
genQueryMailboxMessages(uiProjection, unseenOnly), new String[] {id});
}
notifyUri = UIPROVIDER_CONVERSATION_NOTIFIER.buildUpon().appendPath(id).build();
c = new VisibilityCursor(context, c, mailboxId);
@ -3508,7 +3499,7 @@ outer:
* @param uiAtt the UIProvider attachment to convert
* @return the EmailProvider attachment
*/
private Attachment convertUiAttachmentToAttachment(
private static Attachment convertUiAttachmentToAttachment(
com.android.mail.providers.Attachment uiAtt) {
Attachment att = new Attachment();
att.setContentUri(uiAtt.contentUri.toString());
@ -3572,7 +3563,7 @@ outer:
/**
* Given an account name and a mailbox type, return that mailbox, creating it if necessary
* @param accountName the account name to use
* @param accountId the account id to use
* @param mailboxType the type of mailbox we're trying to find
* @return the mailbox of the given type for the account in the uri, or null if not found
*/
@ -3619,6 +3610,7 @@ outer:
msg.mDisplayName = msg.mTo;
msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
msg.mFlagRead = true;
msg.mFlagSeen = true;
Integer quoteStartPos = values.getAsInteger(UIProvider.MessageColumns.QUOTE_START_POS);
msg.mQuotedTextStartPos = quoteStartPos == null ? 0 : quoteStartPos;
int flags = 0;
@ -3629,7 +3621,7 @@ outer:
break;
case DraftType.REPLY_ALL:
flags |= Message.FLAG_TYPE_REPLY_ALL;
// Fall through
//$FALL-THROUGH$
case DraftType.REPLY:
flags |= Message.FLAG_TYPE_REPLY;
break;
@ -3818,7 +3810,8 @@ outer:
return 1;
}
private void putIntegerLongOrBoolean(ContentValues values, String columnName, Object value) {
private static void putIntegerLongOrBoolean(ContentValues values, String columnName,
Object value) {
if (value instanceof Integer) {
Integer intValue = (Integer)value;
values.put(columnName, intValue);
@ -3836,7 +3829,7 @@ outer:
* @param folders
* @return number of folders updated
*/
private int updateTimestamp(final Context context, String id, Uri[] folders){
private static int updateTimestamp(final Context context, String id, Uri[] folders){
int updated = 0;
final long now = System.currentTimeMillis();
final ContentResolver resolver = context.getContentResolver();
@ -3941,8 +3934,23 @@ outer:
return 0;
}
private int uiUpdateFolder(Uri uri, ContentValues uiValues) {
Uri ourUri = convertToEmailProviderUri(uri, Mailbox.CONTENT_URI, true);
private int uiUpdateFolder(final Context context, Uri uri, ContentValues uiValues) {
// We need to mark seen separately
if (uiValues.containsKey(UIProvider.ConversationColumns.SEEN)) {
final int seenValue = uiValues.getAsInteger(UIProvider.ConversationColumns.SEEN);
if (seenValue == 1) {
final String mailboxId = uri.getLastPathSegment();
final int rows = markAllSeen(context, mailboxId);
if (uiValues.size() == 1) {
// Nothing else to do, so return this value
return rows;
}
}
}
final Uri ourUri = convertToEmailProviderUri(uri, Mailbox.CONTENT_URI, true);
if (ourUri == null) return 0;
ContentValues ourValues = new ContentValues();
// This should only be called via update to "recent folders"
@ -3954,6 +3962,17 @@ outer:
return update(ourUri, ourValues, null, null);
}
private int markAllSeen(final Context context, final String mailboxId) {
final SQLiteDatabase db = getDatabase(context);
final String table = Message.TABLE_NAME;
final ContentValues values = new ContentValues(1);
values.put(MessageColumns.FLAG_SEEN, 1);
final String whereClause = MessageColumns.MAILBOX_KEY + " = ?";
final String[] whereArgs = new String[] {mailboxId};
return db.update(table, values, whereClause, whereArgs);
}
private ContentValues convertUiMessageValues(Message message, ContentValues values) {
ContentValues ourValues = new ContentValues();
for (String columnName : values.keySet()) {
@ -3962,6 +3981,8 @@ outer:
putIntegerLongOrBoolean(ourValues, MessageColumns.FLAG_FAVORITE, val);
} else if (columnName.equals(UIProvider.ConversationColumns.READ)) {
putIntegerLongOrBoolean(ourValues, MessageColumns.FLAG_READ, val);
} else if (columnName.equals(UIProvider.ConversationColumns.SEEN)) {
putIntegerLongOrBoolean(ourValues, MessageColumns.FLAG_SEEN, val);
} else if (columnName.equals(MessageColumns.MAILBOX_KEY)) {
putIntegerLongOrBoolean(ourValues, MessageColumns.MAILBOX_KEY, val);
} else if (columnName.equals(UIProvider.ConversationColumns.RAW_FOLDERS)) {
@ -3994,7 +4015,7 @@ outer:
return ourValues;
}
private Uri convertToEmailProviderUri(Uri uri, Uri newBaseUri, boolean asProvider) {
private static Uri convertToEmailProviderUri(Uri uri, Uri newBaseUri, boolean asProvider) {
String idString = uri.getLastPathSegment();
try {
long id = Long.parseLong(idString);
@ -4034,7 +4055,7 @@ outer:
}
// TODO: This should depend on flags on the mailbox...
private boolean uploadsToServer(Context context, Mailbox m) {
private static boolean uploadsToServer(Context context, Mailbox m) {
if (m.mType == Mailbox.TYPE_DRAFTS || m.mType == Mailbox.TYPE_OUTBOX ||
m.mType == Mailbox.TYPE_SEARCH) {
return false;
@ -4084,6 +4105,8 @@ outer:
undoValues.put(MessageColumns.MAILBOX_KEY, msg.mMailboxKey);
} else if (columnName.equals(MessageColumns.FLAG_READ)) {
undoValues.put(MessageColumns.FLAG_READ, msg.mFlagRead);
} else if (columnName.equals(MessageColumns.FLAG_SEEN)) {
undoValues.put(MessageColumns.FLAG_SEEN, msg.mFlagSeen);
} else if (columnName.equals(MessageColumns.FLAG_FAVORITE)) {
undoValues.put(MessageColumns.FLAG_FAVORITE, msg.mFlagFavorite);
}
@ -4403,7 +4426,7 @@ outer:
// This will look just like a "normal" folder
return uiQuery(UI_FOLDER, ContentUris.withAppendedId(Mailbox.CONTENT_URI,
searchMailbox.mId), projection);
searchMailbox.mId), projection, false);
}
private static final String MAILBOXES_FOR_ACCOUNT_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?";
@ -4454,7 +4477,7 @@ outer:
return 1;
}
private void deleteAccountData(Context context, long accountId) {
private static void deleteAccountData(Context context, long accountId) {
// Delete synced attachments
AttachmentUtilities.deleteAllAccountAttachmentFiles(context, accountId);

View File

@ -16,21 +16,16 @@
package com.android.email.provider;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.net.Uri;
import android.provider.BaseColumns;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.Mailbox;
import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
import com.android.mail.providers.UIProvider.AccountColumns;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import com.android.mail.widget.BaseWidgetProvider;
@ -43,56 +38,6 @@ public class WidgetProvider extends BaseWidgetProvider {
private static final String LOG_TAG = LogTag.getLogTag();
// This projection is needed, as if we were to request the capabilities of the account,
// that provider attempts to bind to the email service to get this information. It is not
// valid to bind to a service in a broadcast receiver, as the bind just blocks, for the amount
// of time specified in the timeout.
// Instead, this projection doesn't include the capabilities column. The cursor wrapper then
// makes sure that the Account objects can find all of the columns it expects.
private static final String[] WIDGET_ACCOUNTS_PROJECTION = {
BaseColumns._ID,
AccountColumns.NAME,
AccountColumns.PROVIDER_VERSION,
AccountColumns.URI,
AccountColumns.FOLDER_LIST_URI,
AccountColumns.FULL_FOLDER_LIST_URI,
AccountColumns.SEARCH_URI,
AccountColumns.ACCOUNT_FROM_ADDRESSES,
AccountColumns.SAVE_DRAFT_URI,
AccountColumns.SEND_MAIL_URI,
AccountColumns.EXPUNGE_MESSAGE_URI,
AccountColumns.UNDO_URI,
AccountColumns.SETTINGS_INTENT_URI,
AccountColumns.SYNC_STATUS,
AccountColumns.HELP_INTENT_URI,
AccountColumns.SEND_FEEDBACK_INTENT_URI,
AccountColumns.REAUTHENTICATION_INTENT_URI,
AccountColumns.COMPOSE_URI,
AccountColumns.MIME_TYPE,
AccountColumns.RECENT_FOLDER_LIST_URI,
AccountColumns.COLOR,
AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI,
AccountColumns.MANUAL_SYNC_URI,
AccountColumns.VIEW_INTENT_PROXY_URI,
AccountColumns.SettingsColumns.SIGNATURE,
AccountColumns.SettingsColumns.AUTO_ADVANCE,
AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE,
AccountColumns.SettingsColumns.SNAP_HEADERS,
AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
AccountColumns.SettingsColumns.SHOW_CHECKBOXES,
AccountColumns.SettingsColumns.CONFIRM_DELETE,
AccountColumns.SettingsColumns.CONFIRM_ARCHIVE,
AccountColumns.SettingsColumns.CONFIRM_SEND,
AccountColumns.SettingsColumns.DEFAULT_INBOX,
AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME,
AccountColumns.SettingsColumns.FORCE_REPLY_FROM_DEFAULT,
AccountColumns.SettingsColumns.MAX_ATTACHMENT_SIZE,
AccountColumns.SettingsColumns.SWIPE,
AccountColumns.SettingsColumns.PRIORITY_ARROWS_ENABLED,
AccountColumns.SettingsColumns.SETUP_INTENT_URI
};
/**
* Remove preferences when deleting widget
*/
@ -115,10 +60,10 @@ public class WidgetProvider extends BaseWidgetProvider {
protected com.android.mail.providers.Account getAccountObject(
Context context, String accountUri) {
final ContentResolver resolver = context.getContentResolver();
final Cursor sparseAccountCursor = resolver.query(Uri.parse(accountUri),
WIDGET_ACCOUNTS_PROJECTION, null, null, null);
final Cursor accountCursor = resolver.query(Uri.parse(accountUri),
UIProvider.ACCOUNTS_PROJECTION_NO_CAPABILITIES, null, null, null);
return getPopulatedAccountObject(sparseAccountCursor);
return getPopulatedAccountObject(accountCursor);
}
@ -126,13 +71,13 @@ public class WidgetProvider extends BaseWidgetProvider {
protected boolean isAccountValid(Context context, com.android.mail.providers.Account account) {
if (account != null) {
final ContentResolver resolver = context.getContentResolver();
final Cursor sparseAccountCursor = resolver.query(account.uri,
WIDGET_ACCOUNTS_PROJECTION, null, null, null);
if (sparseAccountCursor != null) {
final Cursor accountCursor = resolver.query(account.uri,
UIProvider.ACCOUNTS_PROJECTION_NO_CAPABILITIES, null, null, null);
if (accountCursor != null) {
try {
return sparseAccountCursor.getCount() > 0;
return accountCursor.getCount() > 0;
} finally {
sparseAccountCursor.close();
accountCursor.close();
}
}
}
@ -169,24 +114,23 @@ public class WidgetProvider extends BaseWidgetProvider {
editor.apply();
}
private com.android.mail.providers.Account getAccount(Context context, long accountId) {
private static com.android.mail.providers.Account getAccount(Context context, long accountId) {
final ContentResolver resolver = context.getContentResolver();
final Cursor ac = resolver.query(EmailProvider.uiUri("uiaccount", accountId),
WIDGET_ACCOUNTS_PROJECTION, null, null, null);
UIProvider.ACCOUNTS_PROJECTION_NO_CAPABILITIES, null, null, null);
com.android.mail.providers.Account uiAccount = getPopulatedAccountObject(ac);
return uiAccount;
}
private com.android.mail.providers.Account getPopulatedAccountObject(final Cursor ac) {
if (ac == null) {
private static com.android.mail.providers.Account getPopulatedAccountObject(
final Cursor accountCursor) {
if (accountCursor == null) {
LogUtils.e(LOG_TAG, "Null account cursor");
return null;
}
final Cursor accountCursor = new SparseAccountCursorWrapper(ac);
com.android.mail.providers.Account uiAccount = null;
try {
if (accountCursor.moveToFirst()) {
@ -198,7 +142,7 @@ public class WidgetProvider extends BaseWidgetProvider {
return uiAccount;
}
private Folder getFolder(Context context, long mailboxId) {
private static Folder getFolder(Context context, long mailboxId) {
final ContentResolver resolver = context.getContentResolver();
final Cursor fc = resolver.query(EmailProvider.uiUri("uifolder", mailboxId),
UIProvider.FOLDERS_PROJECTION, null, null, null);
@ -240,64 +184,4 @@ public class WidgetProvider extends BaseWidgetProvider {
long mailboxId = prefs.getLong(LEGACY_MAILBOX_ID_PREFIX + appWidgetId, Mailbox.NO_MAILBOX);
return mailboxId;
}
private class SparseAccountCursorWrapper extends CursorWrapper {
public SparseAccountCursorWrapper(Cursor cursor) {
super(cursor);
}
@Override
public int getColumnCount () {
return UIProvider.ACCOUNTS_PROJECTION.length;
}
@Override
public int getColumnIndex (String columnName) {
for (int i = 0; i < UIProvider.ACCOUNTS_PROJECTION.length; i++) {
if (UIProvider.ACCOUNTS_PROJECTION[i].equals(columnName)) {
return i;
}
}
return -1;
}
@Override
public String getColumnName (int columnIndex) {
return UIProvider.ACCOUNTS_PROJECTION[columnIndex];
}
@Override
public String[] getColumnNames () {
return UIProvider.ACCOUNTS_PROJECTION;
}
@Override
public int getInt (int columnIndex) {
if (columnIndex == UIProvider.ACCOUNT_CAPABILITIES_COLUMN) {
return 0;
}
return super.getInt(convertColumnIndex(columnIndex));
}
@Override
public long getLong (int columnIndex) {
return super.getLong(convertColumnIndex(columnIndex));
}
@Override
public String getString (int columnIndex) {
return super.getString(convertColumnIndex(columnIndex));
}
private int convertColumnIndex(int columnIndex) {
// Since this sparse cursor doesn't have the capabilities column,
// we need to adjust all of the column indexes that come after where the
// capabilities column should be
if (columnIndex > UIProvider.ACCOUNT_CAPABILITIES_COLUMN) {
return columnIndex - 1;
}
return columnIndex;
}
}
}

View File

@ -29,7 +29,6 @@ import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import com.android.email.NotificationController;
import com.android.email.Preferences;
import com.android.email.R;
import com.android.email.SecurityPolicy;
@ -65,10 +64,6 @@ public class EmailBroadcastProcessorService extends IntentService {
private static final String ACTION_DEVICE_POLICY_ADMIN = "com.android.email.devicepolicy";
private static final String EXTRA_DEVICE_POLICY_ADMIN = "message_code";
// Broadcast received to initiate new message notification updates
public static final String ACTION_NOTIFY_NEW_MAIL =
"com.android.mail.action.update_notification";
public EmailBroadcastProcessorService() {
// Class name will be the thread name.
super(EmailBroadcastProcessorService.class.getName());
@ -116,8 +111,6 @@ public class EmailBroadcastProcessorService extends IntentService {
AccountSettings.actionSettingsWithDebug(this);
} else if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(broadcastAction)) {
onSystemAccountChanged();
} else if (ACTION_NOTIFY_NEW_MAIL.equals(broadcastAction)) {
NotificationController.notifyNewMail(this, broadcastIntent);
}
} else if (ACTION_DEVICE_POLICY_ADMIN.equals(action)) {
int message = intent.getIntExtra(EXTRA_DEVICE_POLICY_ADMIN, -1);

View File

@ -144,7 +144,7 @@ public class MailActivityEmail extends com.android.mail.ui.MailActivity {
// Start/stop the various services depending on whether there are any accounts
startOrStopService(enabled, context, new Intent(context, AttachmentDownloadService.class));
NotificationController.getInstance(context).watchForMessages(enabled);
NotificationController.getInstance(context).watchForMessages();
}
/**

View File

@ -0,0 +1,121 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mail.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.preference.PreferenceManager;
import com.android.emailcommon.provider.EmailContent;
import com.android.mail.providers.Account;
import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Migrates Email settings to UnifiedEmail
*/
public class PreferenceMigrator extends BasePreferenceMigrator {
private static final String LOG_TAG = LogTag.getLogTag();
@Override
protected void migrate(final Context context, final int oldVersion, final int newVersion) {
final List<Account> accounts = new ArrayList<Account>();
final Cursor accountCursor = context.getContentResolver().query(Uri.parse(
EmailContent.CONTENT_URI + "/uiaccts"),
UIProvider.ACCOUNTS_PROJECTION_NO_CAPABILITIES, null, null, null);
try {
while (accountCursor.moveToNext()) {
accounts.add(new Account(accountCursor));
}
} finally {
accountCursor.close();
}
migrate(context, oldVersion, newVersion, accounts);
}
public static final String REPLY_ALL = "reply_all";
private static final String PREFERENCE_NOTIFY = "account_notify";
private static final String PREFERENCE_VIBRATE = "account_settings_vibrate";
private static final String PREFERENCE_VIBRATE_OLD = "account_settings_vibrate_when";
private static final String PREFERENCE_RINGTONE = "account_ringtone";
protected static void migrate(final Context context, final int oldVersion, final int newVersion,
final List<Account> accounts) {
if (oldVersion < 1) {
// No global settings to move yet
// Move folder notification settings
for (final Account account : accounts) {
// The only setting in AccountPreferences so far is a global notification toggle,
// but we only allow Inbox notifications, so it will remain unused
final Cursor folderCursor =
context.getContentResolver().query(account.settings.defaultInbox,
UIProvider.FOLDERS_PROJECTION, null, null, null);
if (folderCursor == null) {
LogUtils.e(LOG_TAG, "Null folder cursor for mailbox %s",
account.settings.defaultInbox);
continue;
}
Folder folder = null;
try {
if (folderCursor.moveToFirst()) {
folder = new Folder(folderCursor);
}
} finally {
folderCursor.close();
}
final FolderPreferences folderPreferences =
new FolderPreferences(context, account.name, folder, true /* inbox */);
final SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context);
if (sharedPreferences.contains(PREFERENCE_NOTIFY)) {
final boolean notify = sharedPreferences.getBoolean(PREFERENCE_NOTIFY, true);
folderPreferences.setNotificationsEnabled(notify);
}
if (sharedPreferences.contains(PREFERENCE_RINGTONE)) {
final String ringtoneUri =
sharedPreferences.getString(PREFERENCE_RINGTONE, null);
folderPreferences.setNotificationRingtoneUri(ringtoneUri);
}
if (sharedPreferences.contains(PREFERENCE_VIBRATE)) {
final boolean vibrate = sharedPreferences.getBoolean(PREFERENCE_VIBRATE, false);
folderPreferences.setNotificationVibrateEnabled(vibrate);
} else if (sharedPreferences.contains(PREFERENCE_VIBRATE_OLD)) {
final boolean vibrate = "always".equals(
sharedPreferences.getString(PREFERENCE_VIBRATE_OLD, ""));
folderPreferences.setNotificationVibrateEnabled(vibrate);
}
}
}
}
}