Fixes Issue 188

Fixes Issue 394
Fixes Issue 763

Completely new Services architecture providing proper Service
lifecycle management.
  Long running tasks in MailService are executed in a separate thread,
  but the service is not stopped until the tasks are complete.

  SleepService for providing synchronous sleeps with proper WakeLock
  integration.

  Mail polling logic moved from MailService to PollService

  PushService to keep application from being killed while push is
  running.

Improved logging of IMAP protocol, including logging folder name,
thread and connection hashcode.

Don't put a failed connection back into the IMAP mConnections
storage. 

IMAP IDLE uses new SleepService for delaying between failed connection
attempts (allows phone to sleep and provides better reliability)

Use a ThreadLocal for ImapPusher wakelocks

Component enablement/disablement in Email is done only when components
are out of phase with desired state. (Issue 188)
This commit is contained in:
Daniel Applebaum 2009-11-22 17:01:04 +00:00
parent c6b15012e0
commit b551850827
14 changed files with 1107 additions and 493 deletions

View File

@ -203,12 +203,33 @@
<intent-filter> <intent-filter>
<action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" /> <action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="com.android.email.service.BroadcastReceiver.wakeLockRelease"/>
</intent-filter>
<intent-filter>
<action android:name="com.android.email.service.BroadcastReceiver.scheduleIntent"/>
</intent-filter>
</receiver> </receiver>
<service <service
android:name="com.android.email.service.MailService" android:name="com.android.email.service.MailService"
android:enabled="false" android:enabled="true"
> >
</service> </service>
<service
android:name="com.android.email.service.PushService"
android:enabled="true"
>
</service>
<service
android:name="com.android.email.service.PollService"
android:enabled="true"
>
</service>
<service
android:name="com.android.email.service.SleepService"
android:enabled="true"
>
</service>
<provider <provider
android:name="com.android.email.provider.AttachmentProvider" android:name="com.android.email.provider.AttachmentProvider"
android:authorities="com.fsck.k9.attachmentprovider" android:authorities="com.fsck.k9.attachmentprovider"

View File

@ -143,11 +143,13 @@ public class Email extends Application {
public static final int MANUAL_WAKE_LOCK_TIMEOUT = 120000; public static final int MANUAL_WAKE_LOCK_TIMEOUT = 120000;
public static final int PUSH_WAKE_LOCK_TIMEOUT = 40000; public static final int PUSH_WAKE_LOCK_TIMEOUT = 60000;
public static final int MAIL_SERVICE_WAKE_LOCK_TIMEOUT = 30000; public static final int MAIL_SERVICE_WAKE_LOCK_TIMEOUT = 30000;
public static final int BOOT_RECEIVER_WAKE_LOCK_TIMEOUT = 10000; public static final int BOOT_RECEIVER_WAKE_LOCK_TIMEOUT = 60000;
/** /**
* LED color used for the new email notitication * LED color used for the new email notitication
*/ */
@ -401,42 +403,56 @@ public class Email extends Application {
* whether any accounts are configured. * whether any accounts are configured.
*/ */
public static void setServicesEnabled(Context context) { public static void setServicesEnabled(Context context) {
setServicesEnabled(context, Preferences.getPreferences(context).getAccounts().length > 0);
int acctLength = Preferences.getPreferences(context).getAccounts().length;
setServicesEnabled(context, acctLength > 0, null);
}
public static void setServicesEnabled(Context context, Integer wakeLockId) {
setServicesEnabled(context, Preferences.getPreferences(context).getAccounts().length > 0, wakeLockId);
} }
public static void setServicesEnabled(Context context, boolean enabled) { public static void setServicesEnabled(Context context, boolean enabled, Integer wakeLockId) {
PackageManager pm = context.getPackageManager(); PackageManager pm = context.getPackageManager();
if (!enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) == if (!enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
/* /*
* If no accounts now exist but the service is still enabled we're about to disable it * If no accounts now exist but the service is still enabled we're about to disable it
* so we'll reschedule to kill off any existing alarms. * so we'll reschedule to kill off any existing alarms.
*/ */
MailService.actionReschedule(context); MailService.actionReschedule(context, wakeLockId);
} }
pm.setComponentEnabledSetting( Class[] classes = { MessageCompose.class, BootReceiver.class, MailService.class };
new ComponentName(context, MessageCompose.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : for (Class clazz : classes)
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, {
PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting( boolean alreadyEnabled = pm.getComponentEnabledSetting(new ComponentName(context, clazz)) ==
new ComponentName(context, BootReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, if (enabled != alreadyEnabled)
PackageManager.DONT_KILL_APP); {
pm.setComponentEnabledSetting( pm.setComponentEnabledSetting(
new ComponentName(context, MailService.class), new ComponentName(context, clazz),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP); PackageManager.DONT_KILL_APP);
}
}
if (enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) == if (enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
/* /*
* And now if accounts do exist then we've just enabled the service and we want to * And now if accounts do exist then we've just enabled the service and we want to
* schedule alarms for the new accounts. * schedule alarms for the new accounts.
*/ */
MailService.actionReschedule(context); MailService.actionReschedule(context, wakeLockId);
} }
} }
public static void save(SharedPreferences preferences) public static void save(SharedPreferences preferences)
@ -505,7 +521,6 @@ public class Email extends Application {
} }
}); });
MailService.appStarted(this);
} }
public static int getK9Theme() public static int getK9Theme()

View File

@ -68,6 +68,7 @@ import com.android.email.mail.store.LocalStore;
import com.android.email.mail.store.LocalStore.LocalFolder; import com.android.email.mail.store.LocalStore.LocalFolder;
import com.android.email.mail.store.LocalStore.LocalMessage; import com.android.email.mail.store.LocalStore.LocalMessage;
import com.android.email.mail.store.LocalStore.PendingCommand; import com.android.email.mail.store.LocalStore.PendingCommand;
import com.android.email.service.SleepService;
/** /**
* Starts a long running (application) Thread that will run through commands * Starts a long running (application) Thread that will run through commands
@ -3514,52 +3515,44 @@ public class MessagingController implements Runnable {
final MessagingController controller = this; final MessagingController controller = this;
PushReceiver receiver = new PushReceiver() PushReceiver receiver = new PushReceiver()
{ {
WakeLock wakeLock = null; ThreadLocal<WakeLock> threadWakeLock = new ThreadLocal<WakeLock>();
int refCount = 0; public void acquireWakeLock()
public synchronized void pushInProgress()
{ {
WakeLock wakeLock = threadWakeLock.get();
if (wakeLock == null) if (wakeLock == null)
{ {
PowerManager pm = (PowerManager) mApplication.getSystemService(Context.POWER_SERVICE); PowerManager pm = (PowerManager) mApplication.getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email"); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
wakeLock.setReferenceCounted(false); wakeLock.setReferenceCounted(false);
threadWakeLock.set(wakeLock);
} }
wakeLock.acquire(Email.PUSH_WAKE_LOCK_TIMEOUT); wakeLock.acquire(Email.PUSH_WAKE_LOCK_TIMEOUT);
refCount++;
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Acquired WakeLock for Pushing"); Log.d(Email.LOG_TAG, "Acquired WakeLock for Pushing for thread " + Thread.currentThread().getName());
} }
} }
public synchronized void pushComplete() public void releaseWakeLock()
{ {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Considering releasing WakeLock for Pushing"); Log.d(Email.LOG_TAG, "Considering releasing WakeLock for Pushing");
} }
WakeLock wakeLock = threadWakeLock.get();
if (wakeLock != null) if (wakeLock != null)
{ {
if (refCount > 0)
if (Email.DEBUG)
{ {
refCount--; Log.d(Email.LOG_TAG, "Releasing WakeLock for Pushing for thread " + Thread.currentThread().getName());
}
if (refCount == 0)
{
try
{
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, "Releasing WakeLock for Pushing");
}
wakeLock.release();
}
catch (Exception e)
{
Log.e(Email.LOG_TAG, "Failed to release WakeLock", e);
}
} }
wakeLock.release();
} }
else
{
Log.e(Email.LOG_TAG, "No WakeLock waiting to be released for thread " + Thread.currentThread().getName());
}
} }
public void messagesFlagsChanged(String folderName, public void messagesFlagsChanged(String folderName,
@ -3572,6 +3565,11 @@ public class MessagingController implements Runnable {
{ {
controller.messagesArrived(account, folderName, messages, true); controller.messagesArrived(account, folderName, messages, true);
} }
public void sleep(long millis)
{
SleepService.sleep(mApplication, millis, threadWakeLock.get(), Email.PUSH_WAKE_LOCK_TIMEOUT);
}
public void pushError(String errorMessage, Exception e) public void pushError(String errorMessage, Exception e)
{ {
@ -3976,6 +3974,8 @@ public class MessagingController implements Runnable {
} }
} }
class MessageContainer class MessageContainer
{ {
Message message; Message message;

View File

@ -17,6 +17,7 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
@ -314,7 +315,6 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
MessagingController.getInstance(getApplication()).getAccountUnreadCount(Accounts.this, account, mListener); MessagingController.getInstance(getApplication()).getAccountUnreadCount(Accounts.this, account, mListener);
} }
} }
private void onAddNewAccount() { private void onAddNewAccount() {

View File

@ -101,7 +101,7 @@ public class Prefs extends K9PreferenceActivity {
Email.setBackgroundOps(newBackgroundOps); Email.setBackgroundOps(newBackgroundOps);
Email.save(preferences); Email.save(preferences);
if (newBackgroundOps.equals(initBackgroundOps) == false) { if (newBackgroundOps.equals(initBackgroundOps) == false) {
MailService.backgroundDataChanged(this); MailService.backgroundDataChanged(this, null);
} }
} }

View File

@ -4,11 +4,12 @@ import java.util.List;
public interface PushReceiver public interface PushReceiver
{ {
public void pushInProgress(); public void acquireWakeLock();
public void pushComplete(); public void releaseWakeLock();
public void messagesArrived(String folderName, List<Message> mess); public void messagesArrived(String folderName, List<Message> mess);
public void messagesFlagsChanged(String folderName, List<Message> mess); public void messagesFlagsChanged(String folderName, List<Message> mess);
public String getPushState(String folderName); public String getPushState(String folderName);
public void pushError(String errorMessage, Exception e); public void pushError(String errorMessage, Exception e);
public void setPushActive(String folderName, boolean enabled); public void setPushActive(String folderName, boolean enabled);
public void sleep(long millis);
} }

View File

@ -58,10 +58,8 @@ public class ImapResponseParser {
response.mTag = parseTaggedResponse(); response.mTag = parseTaggedResponse();
readTokens(response); readTokens(response);
} }
if (Config.LOGD) { if (Email.DEBUG) {
if (Email.DEBUG) { Log.v(Email.LOG_TAG, "<<< " + response.toString());
Log.d(Email.LOG_TAG, "<<< " + response.toString());
}
} }
return response; return response;
} }

View File

@ -34,6 +34,7 @@ import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
@ -281,10 +282,13 @@ public class ImapStore extends Store {
} }
private void releaseConnection(ImapConnection connection) { private void releaseConnection(ImapConnection connection) {
if (connection.isOpen())
{
synchronized(mConnections) synchronized(mConnections)
{ {
mConnections.offer(connection); mConnections.offer(connection);
} }
}
} }
private String encodeFolderName(String name) { private String encodeFolderName(String name) {
@ -431,7 +435,7 @@ public class ImapStore extends Store {
mPathDelimeter = nameResponses.get(0).getString(2); mPathDelimeter = nameResponses.get(0).getString(2);
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Got path delimeter '" + mPathDelimeter + "'"); Log.d(Email.LOG_TAG, "Got path delimeter '" + mPathDelimeter + "' for " + getLogId());
} }
} }
} }
@ -486,6 +490,11 @@ public class ImapStore extends Store {
} catch (IOException ioe) { } catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe); throw ioExceptionHandler(mConnection, ioe);
} }
catch (MessagingException me)
{
Log.e(Email.LOG_TAG, "Unable to open connection for " + getLogId(), me);
throw me;
}
} }
@ -800,10 +809,10 @@ public class ImapStore extends Store {
} }
try { try {
String tag = mConnection.sendCommand(String.format("UID FETCH %s (%s)", mConnection.sendCommand(String.format("UID FETCH %s (%s)",
Utility.combine(uids, ','), Utility.combine(uids, ','),
Utility.combine(fetchFields.toArray(new String[fetchFields.size()]), ' ') Utility.combine(fetchFields.toArray(new String[fetchFields.size()]), ' ')
), false); ), false);
ImapResponse response; ImapResponse response;
int messageNumber = 0; int messageNumber = 0;
do { do {
@ -811,7 +820,7 @@ public class ImapStore extends Store {
handleUntaggedResponse(response); handleUntaggedResponse(response);
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "response for fetch: " + response); Log.v(Email.LOG_TAG, "response for fetch: " + response + " for " + getLogId());
} }
if (response.mTag == null && response.get(1).equals("FETCH")) { if (response.mTag == null && response.get(1).equals("FETCH")) {
ImapList fetchList = (ImapList)response.getKeyedValue("FETCH"); ImapList fetchList = (ImapList)response.getKeyedValue("FETCH");
@ -820,7 +829,7 @@ public class ImapStore extends Store {
Message message = messageMap.get(uid); Message message = messageMap.get(uid);
if (message == null) if (message == null)
{ {
Log.w(Email.LOG_TAG, "Do not have message in messageMap for UID " + uid); Log.w(Email.LOG_TAG, "Do not have message in messageMap for UID " + uid + " for " + getLogId());
continue; continue;
} }
if (listener != null) { if (listener != null) {
@ -867,7 +876,7 @@ public class ImapStore extends Store {
} }
catch (MessagingException e) { catch (MessagingException e) {
if (Email.DEBUG) { if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "Error handling message", e); Log.d(Email.LOG_TAG, "Error handling message for " + getLogId(), e);
} }
message.setBody(null); message.setBody(null);
} }
@ -904,7 +913,7 @@ public class ImapStore extends Store {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "Part is an String: " + bodyString); Log.v(Email.LOG_TAG, "Part is a String: '" + bodyString + "' for " + getLogId());
} }
InputStream bodyStream = new ByteArrayInputStream(bodyString.getBytes()); InputStream bodyStream = new ByteArrayInputStream(bodyString.getBytes());
String contentTransferEncoding = part.getHeader( String contentTransferEncoding = part.getHeader(
@ -958,7 +967,7 @@ public class ImapStore extends Store {
mMessageCount = response.getNumber(0); mMessageCount = response.getNumber(0);
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Got untagged EXISTS with value " + mMessageCount); Log.d(Email.LOG_TAG, "Got untagged EXISTS with value " + mMessageCount + " for " + getLogId());
} }
} }
if (response.get(0).equals("OK") && response.size() > 1) { if (response.get(0).equals("OK") && response.size() > 1) {
@ -978,7 +987,7 @@ public class ImapStore extends Store {
uidNext = bracketed.getNumber(1); uidNext = bracketed.getNumber(1);
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Got UidNext = " + uidNext); Log.d(Email.LOG_TAG, "Got UidNext = " + uidNext + " for " + getLogId());
} }
} }
} }
@ -991,7 +1000,7 @@ public class ImapStore extends Store {
mMessageCount--; mMessageCount--;
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Got untagged EXPUNGE with value " + mMessageCount); Log.d(Email.LOG_TAG, "Got untagged EXPUNGE with value " + mMessageCount + " for " + getLogId());
} }
} }
// if (response.size() > 1) { // if (response.size() > 1) {
@ -1014,7 +1023,7 @@ public class ImapStore extends Store {
// sb.append(' '); // sb.append(' ');
// } // }
// //
// Log.w(Email.LOG_TAG, "ALERT: " + sb.toString()); // Log.w(Email.LOG_TAG, "ALERT: " + sb.toString() + " for " + getLogId());
// } // }
// } // }
// } // }
@ -1023,7 +1032,7 @@ public class ImapStore extends Store {
// } // }
// } // }
} }
//Log.i(Email.LOG_TAG, "mMessageCount = " + mMessageCount); //Log.i(Email.LOG_TAG, "mMessageCount = " + mMessageCount + " for " + getLogId());
} }
private void parseBodyStructure(ImapList bs, Part part, String id) private void parseBodyStructure(ImapList bs, Part part, String id)
@ -1221,7 +1230,7 @@ public class ImapStore extends Store {
String newUid = getUidFromMessageId(message); String newUid = getUidFromMessageId(message);
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Got UID " + newUid + " for message"); Log.d(Email.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId());
} }
if (newUid != null) if (newUid != null)
@ -1250,14 +1259,14 @@ public class ImapStore extends Store {
if (messageIdHeader == null || messageIdHeader.length == 0) { if (messageIdHeader == null || messageIdHeader.length == 0) {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Did not get a message-id in order to search for UID"); Log.d(Email.LOG_TAG, "Did not get a message-id in order to search for UID for " + getLogId());
} }
return null; return null;
} }
String messageId = messageIdHeader[0]; String messageId = messageIdHeader[0];
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Looking for UID for message with message-id " + messageId); Log.d(Email.LOG_TAG, "Looking for UID for message with message-id " + messageId + " for " + getLogId());
} }
List<ImapResponse> responses = List<ImapResponse> responses =
@ -1343,12 +1352,12 @@ public class ImapStore extends Store {
int messageUid = Integer.parseInt(messageUidS); int messageUid = Integer.parseInt(messageUidS);
ImapPushState oldPushState = ImapPushState.parse(oldPushStateS); ImapPushState oldPushState = ImapPushState.parse(oldPushStateS);
// Log.d(Email.LOG_TAG, "getNewPushState comparing oldUidNext " + oldPushState.uidNext // Log.d(Email.LOG_TAG, "getNewPushState comparing oldUidNext " + oldPushState.uidNext
// + " to message uid " + messageUid); // + " to message uid " + messageUid + " for " + getLogId());
if (messageUid >= oldPushState.uidNext) if (messageUid >= oldPushState.uidNext)
{ {
int uidNext = messageUid + 1; int uidNext = messageUid + 1;
ImapPushState newPushState = new ImapPushState(uidNext); ImapPushState newPushState = new ImapPushState(uidNext);
//Log.d(Email.LOG_TAG, "newPushState = " + newPushState); //Log.d(Email.LOG_TAG, "newPushState = " + newPushState + " for " + getLogId());
return newPushState.toString(); return newPushState.toString();
} }
else else
@ -1358,7 +1367,7 @@ public class ImapStore extends Store {
} }
catch (Exception e) catch (Exception e)
{ {
Log.e(Email.LOG_TAG, "Exception while updated push state", e); Log.e(Email.LOG_TAG, "Exception while updated push state for " + getLogId(), e);
return null; return null;
} }
} }
@ -1406,6 +1415,7 @@ public class ImapStore extends Store {
private MessagingException ioExceptionHandler(ImapConnection connection, IOException ioe) private MessagingException ioExceptionHandler(ImapConnection connection, IOException ioe)
throws MessagingException { throws MessagingException {
Log.e(Email.LOG_TAG, "IOException for " + getLogId(), ioe);
connection.close(); connection.close();
close(false); close(false);
return new MessagingException("IO Error", ioe); return new MessagingException("IO Error", ioe);
@ -1423,6 +1433,16 @@ public class ImapStore extends Store {
{ {
return store; return store;
} }
protected String getLogId()
{
String id = getName() + "/" + Thread.currentThread().getName();
if (mConnection != null)
{
id += "/" + mConnection.getLogId();
}
return id;
}
} }
/** /**
@ -1436,6 +1456,11 @@ public class ImapStore extends Store {
private int mNextCommandTag; private int mNextCommandTag;
protected Set<String> capabilities = new HashSet<String>(); protected Set<String> capabilities = new HashSet<String>();
private String getLogId()
{
return "conn" + hashCode();
}
public void open() throws IOException, MessagingException { public void open() throws IOException, MessagingException {
if (isOpen()) { if (isOpen()) {
return; return;
@ -1450,14 +1475,14 @@ public class ImapStore extends Store {
} }
catch (Exception e) catch (Exception e)
{ {
Log.w(Email.LOG_TAG, "Could not set DNS ttl to 0", e); Log.w(Email.LOG_TAG, "Could not set DNS ttl to 0 for " + getLogId(), e);
} }
try { try {
SocketAddress socketAddress = new InetSocketAddress(mHost, mPort); SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
Log.i(Email.LOG_TAG, "Connecting to " + mHost + " @ IP addr " + socketAddress); Log.i(Email.LOG_TAG, "Connection " + getLogId() + " connecting to " + mHost + " @ IP addr " + socketAddress);
if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED || if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED ||
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) { mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
@ -1480,7 +1505,11 @@ public class ImapStore extends Store {
mParser = new ImapResponseParser(mIn); mParser = new ImapResponseParser(mIn);
mOut = mSocket.getOutputStream(); mOut = mSocket.getOutputStream();
mParser.readResponse(); ImapResponse nullResponse = mParser.readResponse();
if (Email.DEBUG)
{
Log.v(Email.LOG_TAG, getLogId() + "<<<" + nullResponse);
}
List<ImapResponse> responses = executeSimpleCommand("CAPABILITY"); List<ImapResponse> responses = executeSimpleCommand("CAPABILITY");
if (responses.size() != 2) { if (responses.size() != 2) {
throw new MessagingException("Invalid CAPABILITY response received"); throw new MessagingException("Invalid CAPABILITY response received");
@ -1498,7 +1527,7 @@ public class ImapStore extends Store {
{ {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "Saving capability '" + capability + "' for connection " + this.hashCode()); Log.v(Email.LOG_TAG, "Saving capability '" + capability + "' for " + getLogId());
} }
capabilities.add((String)capability); capabilities.add((String)capability);
} }
@ -1557,7 +1586,7 @@ public class ImapStore extends Store {
String[] tokens = ceMess.split("-"); String[] tokens = ceMess.split("-");
if (tokens != null && tokens.length > 1 && tokens[1] != null) if (tokens != null && tokens.length > 1 && tokens[1] != null)
{ {
Log.e(Email.LOG_TAG, "Stripping host/port from ConnectionException", ce); Log.e(Email.LOG_TAG, "Stripping host/port from ConnectionException for " + getLogId(), ce);
throw new ConnectException(tokens[1].trim()); throw new ConnectException(tokens[1].trim());
} }
else else
@ -1569,7 +1598,7 @@ public class ImapStore extends Store {
{ {
if (authSuccess == false) if (authSuccess == false)
{ {
Log.e(Email.LOG_TAG, "Failed to login, closing connection"); Log.e(Email.LOG_TAG, "Failed to login, closing connection for " + getLogId());
close(); close();
} }
} }
@ -1584,21 +1613,20 @@ public class ImapStore extends Store {
{ {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "Connection " + this.hashCode() + " has " + capabilities.size() + " capabilities"); Log.v(Email.LOG_TAG, "Connection " + getLogId() + " has " + capabilities.size() + " capabilities");
for (String capability : capabilities) for (String capability : capabilities)
{ {
Log.v(Email.LOG_TAG, "Have capability '" + capability + "'"); Log.v(Email.LOG_TAG, "Have capability '" + capability + "' for " + getLogId());
} }
} }
return capabilities.contains("IDLE"); return capabilities.contains("IDLE");
} }
public boolean isOpen() { private boolean isOpen() {
return (mIn != null && mOut != null && mSocket != null && mSocket.isConnected() && !mSocket return (mIn != null && mOut != null && mSocket != null && mSocket.isConnected() && !mSocket.isClosed());
.isClosed());
} }
public void close() { private void close() {
// if (isOpen()) { // if (isOpen()) {
// try { // try {
// executeSimpleCommand("LOGOUT"); // executeSimpleCommand("LOGOUT");
@ -1626,9 +1654,14 @@ public class ImapStore extends Store {
mSocket = null; mSocket = null;
} }
public ImapResponse readResponse() throws IOException, MessagingException { private ImapResponse readResponse() throws IOException, MessagingException {
try { try {
return mParser.readResponse(); ImapResponse response = mParser.readResponse();
if (Email.DEBUG)
{
Log.v(Email.LOG_TAG, getLogId() + "<<<" + response);
}
return response;
} }
catch (IOException ioe) catch (IOException ioe)
{ {
@ -1648,7 +1681,7 @@ public class ImapStore extends Store {
return out; return out;
} }
public void sendContinuation(String continuation) throws IOException private void sendContinuation(String continuation) throws IOException
{ {
mOut.write(continuation.getBytes()); mOut.write(continuation.getBytes());
mOut.write('\r'); mOut.write('\r');
@ -1656,7 +1689,7 @@ public class ImapStore extends Store {
mOut.flush(); mOut.flush();
if (Email.DEBUG) { if (Email.DEBUG) {
Log.v(Email.LOG_TAG, ">>> " + continuation); Log.v(Email.LOG_TAG, getLogId() + ">>> " + continuation);
} }
} }
@ -1674,10 +1707,10 @@ public class ImapStore extends Store {
if (Email.DEBUG) { if (Email.DEBUG) {
if (sensitive && !Email.DEBUG_SENSITIVE) { if (sensitive && !Email.DEBUG_SENSITIVE) {
Log.v(Email.LOG_TAG, ">>> " Log.v(Email.LOG_TAG, getLogId() + ">>> "
+ "[Command Hidden, Enable Sensitive Debug Logging To Show]"); + "[Command Hidden, Enable Sensitive Debug Logging To Show]");
} else { } else {
Log.v(Email.LOG_TAG, ">>> " + commandToSend); Log.v(Email.LOG_TAG, getLogId() + ">>> " + commandToSend);
} }
} }
@ -1710,7 +1743,7 @@ public class ImapStore extends Store {
return executeSimpleCommand(command, sensitive, null); return executeSimpleCommand(command, sensitive, null);
} }
public List<ImapResponse> executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler) private List<ImapResponse> executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler)
throws IOException, ImapException, MessagingException { throws IOException, ImapException, MessagingException {
String commandToLog = command; String commandToLog = command;
if (sensitive && !Email.DEBUG_SENSITIVE) if (sensitive && !Email.DEBUG_SENSITIVE)
@ -1721,12 +1754,12 @@ public class ImapStore extends Store {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "Sending IMAP command " + commandToLog + " on connection " + this.hashCode()); Log.v(Email.LOG_TAG, "Sending IMAP command " + commandToLog + " on connection " + getLogId());
} }
String tag = sendCommand(command, sensitive); String tag = sendCommand(command, sensitive);
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "Sent IMAP command " + commandToLog + " with tag " + tag); Log.v(Email.LOG_TAG, "Sent IMAP command " + commandToLog + " with tag " + tag + " for " + getLogId());
} }
ArrayList<ImapResponse> responses = new ArrayList<ImapResponse>(); ArrayList<ImapResponse> responses = new ArrayList<ImapResponse>();
ImapResponse response; ImapResponse response;
@ -1734,11 +1767,11 @@ public class ImapStore extends Store {
response = mParser.readResponse(); response = mParser.readResponse();
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "Got IMAP response " + response); Log.v(Email.LOG_TAG, getLogId() + "<<<" + response);
} }
if (response.mTag != null && response.mTag.equals(tag) == false) if (response.mTag != null && response.mTag.equals(tag) == false)
{ {
Log.w(Email.LOG_TAG, "After sending tag " + tag + ", got tag response from previous command " + response); Log.w(Email.LOG_TAG, "After sending tag " + tag + ", got tag response from previous command " + response + " for " + getLogId());
Iterator<ImapResponse> iter = responses.iterator(); Iterator<ImapResponse> iter = responses.iterator();
while (iter.hasNext()) while (iter.hasNext())
{ {
@ -1810,14 +1843,14 @@ public class ImapStore extends Store {
/* /*
* If the remote trash folder doesn't exist we try to create it. * If the remote trash folder doesn't exist we try to create it.
*/ */
Log.i(Email.LOG_TAG, "IMAPMessage.delete: attempting to create remote " + trashFolderName + " folder"); Log.i(Email.LOG_TAG, "IMAPMessage.delete: attempting to create remote '" + trashFolderName + "' folder for " + iFolder.getLogId());
remoteTrashFolder.create(FolderType.HOLDS_MESSAGES); remoteTrashFolder.create(FolderType.HOLDS_MESSAGES);
} }
if (remoteTrashFolder.exists()) { if (remoteTrashFolder.exists()) {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "IMAPMessage.delete: copying remote message to " + trashFolderName); Log.d(Email.LOG_TAG, "IMAPMessage.delete: copying remote message to '" + trashFolderName + "' for " + iFolder.getLogId());
} }
iFolder.copyMessages(new Message[] { this }, remoteTrashFolder); iFolder.copyMessages(new Message[] { this }, remoteTrashFolder);
setFlag(Flag.DELETED, true); setFlag(Flag.DELETED, true);
@ -1825,7 +1858,7 @@ public class ImapStore extends Store {
} }
else else
{ {
throw new MessagingException("IMAPMessage.delete: remote Trash folder " + trashFolderName + " does not exist and could not be created" throw new MessagingException("IMAPMessage.delete: remote Trash folder " + trashFolderName + " does not exist and could not be created for " + iFolder.getLogId()
, true); , true);
} }
} }
@ -1865,17 +1898,17 @@ public class ImapStore extends Store {
} }
} }
public static int MAX_DELAY_TIME = 240000; public static int MAX_DELAY_TIME = 50000;
public static int NORMAL_DELAY_TIME = 10000; public static int NORMAL_DELAY_TIME = 2500;
public class ImapFolderPusher extends ImapFolder implements UntaggedHandler public class ImapFolderPusher extends ImapFolder implements UntaggedHandler
{ {
PushReceiver receiver = null; final PushReceiver receiver;
Thread listeningThread = null; Thread listeningThread = null;
AtomicBoolean stop = new AtomicBoolean(false); final AtomicBoolean stop = new AtomicBoolean(false);
AtomicBoolean idling = new AtomicBoolean(false); final AtomicBoolean idling = new AtomicBoolean(false);
AtomicBoolean doneSent = new AtomicBoolean(false); final AtomicBoolean doneSent = new AtomicBoolean(false);
private int delayTime = NORMAL_DELAY_TIME; final AtomicInteger delayTime = new AtomicInteger(NORMAL_DELAY_TIME);
public ImapFolderPusher(ImapStore store, String name, PushReceiver nReceiver) public ImapFolderPusher(ImapStore store, String name, PushReceiver nReceiver)
{ {
@ -1886,7 +1919,7 @@ public class ImapStore extends Store {
{ {
if (idling.get()) if (idling.get())
{ {
receiver.pushInProgress(); receiver.acquireWakeLock();
sendDone(); sendDone();
} }
} }
@ -1910,12 +1943,12 @@ public class ImapStore extends Store {
public void start() throws MessagingException public void start() throws MessagingException
{ {
receiver.pushInProgress();
Runnable runner = new Runnable() Runnable runner = new Runnable()
{ {
public void run() public void run()
{ {
Log.i(Email.LOG_TAG, "Pusher for " + getName() + " is running"); receiver.acquireWakeLock();
Log.i(Email.LOG_TAG, "Pusher starting for " + getLogId());
while (stop.get() != true) while (stop.get() != true)
{ {
try try
@ -1926,11 +1959,11 @@ public class ImapStore extends Store {
String pushStateS = receiver.getPushState(getName()); String pushStateS = receiver.getPushState(getName());
ImapPushState pushState = ImapPushState.parse(pushStateS); ImapPushState pushState = ImapPushState.parse(pushStateS);
oldUidNext = pushState.uidNext; oldUidNext = pushState.uidNext;
Log.i(Email.LOG_TAG, "Got oldUidNext " + oldUidNext); Log.i(Email.LOG_TAG, "Got oldUidNext " + oldUidNext + " for " + getLogId());
} }
catch (Exception e) catch (Exception e)
{ {
Log.e(Email.LOG_TAG, "Unable to get oldUidNext", e); Log.e(Email.LOG_TAG, "Unable to get oldUidNext for " + getLogId(), e);
} }
List<ImapResponse> responses = internalOpen(OpenMode.READ_WRITE); List<ImapResponse> responses = internalOpen(OpenMode.READ_WRITE);
@ -1964,7 +1997,7 @@ public class ImapStore extends Store {
startUid = 1; startUid = 1;
} }
Log.i(Email.LOG_TAG, "Needs sync from uid " + startUid + " to " + uidNext); Log.i(Email.LOG_TAG, "Needs sync from uid " + startUid + " to " + uidNext + " for " + getLogId());
List<Message> messages = new ArrayList<Message>(); List<Message> messages = new ArrayList<Message>();
for (int uid = startUid; uid < uidNext; uid++ ) for (int uid = startUid; uid < uidNext; uid++ )
{ {
@ -1979,7 +2012,7 @@ public class ImapStore extends Store {
} }
else else
{ {
Log.i(Email.LOG_TAG, "About to IDLE " + getName()); Log.i(Email.LOG_TAG, "About to IDLE for " + getLogId());
receiver.setPushActive(getName(), true); receiver.setPushActive(getName(), true);
idling.set(true); idling.set(true);
@ -1987,11 +2020,12 @@ public class ImapStore extends Store {
executeSimpleCommand("IDLE", false, ImapFolderPusher.this); executeSimpleCommand("IDLE", false, ImapFolderPusher.this);
idling.set(false); idling.set(false);
receiver.setPushActive(getName(), false); receiver.setPushActive(getName(), false);
delayTime = NORMAL_DELAY_TIME; delayTime.set(NORMAL_DELAY_TIME);
} }
} }
catch (Exception e) catch (Exception e)
{ {
receiver.acquireWakeLock();
idling.set(false); idling.set(false);
receiver.setPushActive(getName(), false); receiver.setPushActive(getName(), false);
try try
@ -2000,40 +2034,39 @@ public class ImapStore extends Store {
} }
catch (Exception me) catch (Exception me)
{ {
Log.e(Email.LOG_TAG, "Got exception while closing for exception", me); Log.e(Email.LOG_TAG, "Got exception while closing for exception for " + getLogId(), me);
} }
if (stop.get() == true) if (stop.get() == true)
{ {
Log.i(Email.LOG_TAG, "Got exception while idling, but stop is set"); Log.i(Email.LOG_TAG, "Got exception while idling, but stop is set for " + getLogId());
} }
else else
{ {
Log.e(Email.LOG_TAG, "Got exception while idling", e); Log.e(Email.LOG_TAG, "Got exception while idling for " + getLogId(), e);
try int delayTimeInt = delayTime.get();
receiver.sleep(delayTimeInt);
delayTimeInt *= 2;
if (delayTimeInt > MAX_DELAY_TIME)
{ {
Thread.sleep(delayTime); delayTimeInt = MAX_DELAY_TIME;
delayTime *= 2;
if (delayTime > MAX_DELAY_TIME)
{
delayTime = MAX_DELAY_TIME;
}
}
catch (Exception ie)
{
Log.e(Email.LOG_TAG, "Got exception while delaying after push exception", ie);
} }
delayTime.set(delayTimeInt);
} }
} }
} }
try try
{ {
Log.i(Email.LOG_TAG, "Pusher for " + getLogId() + " is exiting");
close(false); close(false);
receiver.pushComplete();
Log.i(Email.LOG_TAG, "Pusher for " + getName() + " is exiting");
} }
catch (Exception me) catch (Exception me)
{ {
Log.e(Email.LOG_TAG, "Got exception while closing", me); Log.e(Email.LOG_TAG, "Got exception while closing for " + getLogId(), me);
}
finally
{
receiver.releaseWakeLock();
} }
} }
}; };
@ -2055,7 +2088,8 @@ public class ImapStore extends Store {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "oldMessageCount = " + oldMessageCount + ", new mMessageCount = " + mMessageCount); Log.d(Email.LOG_TAG, "oldMessageCount = " + oldMessageCount + ", new mMessageCount = " + mMessageCount
+ " for " + getLogId());
} }
if (oldMessageCount > 0 && mMessageCount > oldMessageCount) if (oldMessageCount > 0 && mMessageCount > oldMessageCount)
{ {
@ -2063,7 +2097,7 @@ public class ImapStore extends Store {
} }
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "There are " + flagSyncMsgSeqsCopy + " messages needing flag sync"); Log.d(Email.LOG_TAG, "There are " + flagSyncMsgSeqsCopy + " messages needing flag sync for " + getLogId());
} }
// TODO: Identify ranges and call syncMessages on said identified ranges // TODO: Identify ranges and call syncMessages on said identified ranges
for (Integer msgSeq : flagSyncMsgSeqsCopy) for (Integer msgSeq : flagSyncMsgSeqsCopy)
@ -2108,14 +2142,14 @@ public class ImapStore extends Store {
int msgSeq = response.getNumber(0); int msgSeq = response.getNumber(0);
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Got untagged FETCH for msgseq " + msgSeq); Log.d(Email.LOG_TAG, "Got untagged FETCH for msgseq " + msgSeq + " for " + getLogId());
} }
flagSyncMsgSeqs.add(msgSeq); flagSyncMsgSeqs.add(msgSeq);
} }
} }
catch (Exception e) catch (Exception e)
{ {
Log.e(Email.LOG_TAG, "Could not handle untagged FETCH", e); Log.e(Email.LOG_TAG, "Could not handle untagged FETCH for " + getLogId(), e);
} }
} }
} }
@ -2154,13 +2188,13 @@ public class ImapStore extends Store {
{ {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "Closing mConnection to stop pushing"); Log.v(Email.LOG_TAG, "Closing mConnection to stop pushing for " + getLogId());
} }
mConnection.close(); mConnection.close();
} }
else else
{ {
Log.w(Email.LOG_TAG, "Attempt to interrupt null mConnection to stop pushing" + " on folderPusher " + getName()); Log.w(Email.LOG_TAG, "Attempt to interrupt null mConnection to stop pushing on folderPusher for " + getLogId());
} }
} }
@ -2181,12 +2215,12 @@ public class ImapStore extends Store {
{ {
if (started == false) if (started == false)
{ {
receiver.pushInProgress(); receiver.acquireWakeLock();
started = true; started = true;
} }
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Got useful async untagged response: " + response); Log.d(Email.LOG_TAG, "Got useful async untagged response: " + response + " for " + getLogId());
} }
try try
{ {
@ -2194,7 +2228,7 @@ public class ImapStore extends Store {
} }
catch (Exception e) catch (Exception e)
{ {
Log.e(Email.LOG_TAG, "Exception while sending DONE", e); Log.e(Email.LOG_TAG, "Exception while sending DONE for " + getLogId(), e);
} }
} }
} }
@ -2204,9 +2238,9 @@ public class ImapStore extends Store {
{ {
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "Idling"); Log.d(Email.LOG_TAG, "Idling " + getLogId());
} }
receiver.pushComplete(); receiver.releaseWakeLock();
} }
} }
} }
@ -2236,7 +2270,7 @@ public class ImapStore extends Store {
} }
catch (Exception e) catch (Exception e)
{ {
Log.e(Email.LOG_TAG, "Got exception while refreshing"); Log.e(Email.LOG_TAG, "Got exception while refreshing for " + folderPusher.getName(), e);
} }
} }
@ -2252,7 +2286,7 @@ public class ImapStore extends Store {
} }
catch (Exception e) catch (Exception e)
{ {
Log.e(Email.LOG_TAG, "Got exception while starting"); Log.e(Email.LOG_TAG, "Got exception while starting " + folderPusher.getName(), e);
} }
} }
} }
@ -2269,7 +2303,7 @@ public class ImapStore extends Store {
} }
catch (Exception e) catch (Exception e)
{ {
Log.e(Email.LOG_TAG, "Got exception while stopping", e); Log.e(Email.LOG_TAG, "Got exception while stopping " + folderPusher.getName(), e);
} }
} }
folderPushers.clear(); folderPushers.clear();

View File

@ -1,50 +1,183 @@
package com.android.email.service; package com.android.email.service;
import com.android.email.Email; import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.util.Log; import android.util.Log;
import android.content.BroadcastReceiver; import com.android.email.Email;
import android.content.Context;
import android.content.Intent;
public class BootReceiver extends BroadcastReceiver { public class BootReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
public static String WAKE_LOCK_RELEASE = "com.android.email.service.BroadcastReceiver.wakeLockRelease";
public static String FIRE_INTENT = "com.android.email.service.BroadcastReceiver.fireIntent";
public static String SCHEDULE_INTENT = "com.android.email.service.BroadcastReceiver.scheduleIntent";
public static String CANCEL_INTENT = "com.android.email.service.BroadcastReceiver.cancelIntent";
public static String WAKE_LOCK_ID = "com.android.email.service.BroadcastReceiver.wakeLockId";
public static String ALARMED_INTENT = "com.android.email.service.BroadcastReceiver.pendingIntent";
public static String AT_TIME = "com.android.email.service.BroadcastReceiver.atTime";
private static ConcurrentHashMap<Integer, WakeLock> wakeLocks = new ConcurrentHashMap<Integer, WakeLock>();
private static AtomicInteger wakeLockSeq = new AtomicInteger(0);
private Integer getWakeLock(Context context)
{
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email"); WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
wakeLock.setReferenceCounted(false); wakeLock.setReferenceCounted(false);
wakeLock.acquire(Email.BOOT_RECEIVER_WAKE_LOCK_TIMEOUT); wakeLock.acquire(Email.BOOT_RECEIVER_WAKE_LOCK_TIMEOUT);
// TODO: For now, let the wakeLock expire on its own, don't release it at the end of this method, Integer tmpWakeLockId = wakeLockSeq.getAndIncrement();
// otherwise there's no point to it. We're trying to give the MailService some time to start. wakeLocks.put(tmpWakeLockId, wakeLock);
return tmpWakeLockId;
Log.v(Email.LOG_TAG, "BootReceiver.onReceive" + intent); }
private void releaseWakeLock(Integer wakeLockId)
{
if (wakeLockId != null)
{
WakeLock wl = wakeLocks.remove(wakeLockId);
if (wl != null)
{
wl.release();
}
else
{
Log.w(Email.LOG_TAG, "BootReceiver WakeLock " + wakeLockId + " doesn't exist");
}
}
}
public void onReceive(Context context, Intent intent) {
Integer tmpWakeLockId = getWakeLock(context);
try
{
Log.i(Email.LOG_TAG, "BootReceiver.onReceive" + intent);
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Email.setServicesEnabled(context, tmpWakeLockId);
tmpWakeLockId = null;
}
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
MailService.actionCancel(context, tmpWakeLockId);
tmpWakeLockId = null;
}
else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
MailService.actionReschedule(context, tmpWakeLockId);
tmpWakeLockId = null;
}
else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
MailService.connectivityChange(context, !noConnectivity, tmpWakeLockId);
tmpWakeLockId = null;
}
else if (ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED.equals(intent.getAction())) {
MailService.backgroundDataChanged(context, tmpWakeLockId);
tmpWakeLockId = null;
}
else if (FIRE_INTENT.equals(intent.getAction()))
{
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
String alarmedAction = alarmedIntent.getAction();
Log.i(Email.LOG_TAG, "BootReceiver Got alarm to fire alarmedIntent " + alarmedAction);
alarmedIntent.putExtra(WAKE_LOCK_ID, tmpWakeLockId);
tmpWakeLockId = null;
if (alarmedIntent != null)
{
context.startService(alarmedIntent);
}
}
else if (SCHEDULE_INTENT.equals(intent.getAction()))
{
long atTime = intent.getLongExtra(AT_TIME, -1);
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
Log.i(Email.LOG_TAG,"BootReceiver Scheduling intent " + alarmedIntent + " for " + new Date(atTime));
PendingIntent pi = buildPendingIntent(context, intent);
AlarmManager alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarmMgr.set(AlarmManager.RTC_WAKEUP, atTime, pi);
}
else if (CANCEL_INTENT.equals(intent.getAction()))
{
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
Log.i(Email.LOG_TAG, "BootReceiver Canceling alarmedIntent " + alarmedIntent);
PendingIntent pi = buildPendingIntent(context, intent);
AlarmManager alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarmMgr.cancel(pi);
}
else if (BootReceiver.WAKE_LOCK_RELEASE.equals(intent.getAction()))
{
Integer wakeLockId = intent.getIntExtra(WAKE_LOCK_ID, -1);
if (wakeLockId != -1)
{
Log.i(Email.LOG_TAG, "BootReceiver Release wakeLock " + wakeLockId);
releaseWakeLock(wakeLockId);
}
}
}
finally
{
releaseWakeLock(tmpWakeLockId);
}
}
private PendingIntent buildPendingIntent(Context context, Intent intent)
{
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
String alarmedAction = alarmedIntent.getAction();
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { Intent i = new Intent(context, BootReceiver.class);
Email.setServicesEnabled(context); i.setAction(FIRE_INTENT);
} i.putExtra(ALARMED_INTENT, alarmedIntent);
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) { Uri uri = Uri.parse("action://" + alarmedAction);
MailService.actionCancel(context); i.setData(uri);
} PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) { return pi;
MailService.actionReschedule(context); }
}
else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { public static void scheduleIntent(Context context, long atTime, Intent alarmedIntent)
boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); {
MailService.connectivityChange(context, !noConnectivity); Log.i(Email.LOG_TAG, "BootReceiver Got request to schedule alarmedIntent " + alarmedIntent.getAction());
} Intent i = new Intent();
else if (ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED.equals(intent.getAction())) { i.setClass(context, BootReceiver.class);
MailService.backgroundDataChanged(context); i.setAction(SCHEDULE_INTENT);
} i.putExtra(ALARMED_INTENT, alarmedIntent);
// else if (Intent.ACTION_BATTERY_LOW.equals(intent.getAction())) { i.putExtra(AT_TIME, atTime);
// MailService.batteryStatusChange(context, true); context.sendBroadcast(i);
// } }
// else if (Intent.ACTION_BATTERY_OKAY.equals(intent.getAction())) {
// MailService.batteryStatusChange(context, false); public static void cancelIntent(Context context, Intent alarmedIntent)
// } {
Log.i(Email.LOG_TAG, "BootReceiver Got request to cancel alarmedIntent " + alarmedIntent.getAction());
Intent i = new Intent();
i.setClass(context, BootReceiver.class);
i.setAction(CANCEL_INTENT);
i.putExtra(ALARMED_INTENT, alarmedIntent);
context.sendBroadcast(i);
}
public static void releaseWakeLock(Context context, int wakeLockId)
{
Log.i(Email.LOG_TAG, "BootReceiver Got request to release wakeLock " + wakeLockId);
Intent i = new Intent();
i.setClass(context, BootReceiver.class);
i.setAction(WAKE_LOCK_RELEASE);
i.putExtra(WAKE_LOCK_ID, wakeLockId);
context.sendBroadcast(i);
} }
} }

View File

@ -0,0 +1,71 @@
package com.android.email.service;
import com.android.email.Email;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
public abstract class CoreService extends Service
{
protected static void addWakeLockId(Intent i, Integer wakeLockId)
{
if (wakeLockId != null)
{
i.putExtra(BootReceiver.WAKE_LOCK_ID, wakeLockId);
}
}
@Override
public void onStart(Intent intent, int startId) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
wakeLock.setReferenceCounted(false);
wakeLock.acquire(Email.MAIL_SERVICE_WAKE_LOCK_TIMEOUT);
Log.i(Email.LOG_TAG, "CoreService: " + this.getClass().getName() + ".onStart(" + intent + ", " + startId);
int wakeLockId = intent.getIntExtra(BootReceiver.WAKE_LOCK_ID, -1);
if (wakeLockId != -1)
{
BootReceiver.releaseWakeLock(this, wakeLockId);
}
try
{
super.onStart(intent, startId);
startService(intent, startId);
}
finally
{
if (wakeLock != null)
{
wakeLock.release();
}
}
}
public abstract void startService(Intent intent, int startId);
@Override
public IBinder onBind(Intent arg0)
{
// TODO Auto-generated method stub
return null;
}
@Override
public void onDestroy() {
Log.i(Email.LOG_TAG, "CoreService: " + this.getClass().getName() + ".onDestroy()");
super.onDestroy();
// MessagingController.getInstance(getApplication()).removeListener(mListener);
}
}

View File

@ -4,8 +4,9 @@ package com.android.email.service;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.app.AlarmManager;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -14,7 +15,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.Uri;
import android.net.NetworkInfo.State; import android.net.NetworkInfo.State;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
@ -28,72 +28,64 @@ import com.android.email.MessagingController;
import com.android.email.MessagingListener; import com.android.email.MessagingListener;
import com.android.email.Preferences; import com.android.email.Preferences;
import com.android.email.R; import com.android.email.R;
import com.android.email.mail.Address;
import com.android.email.mail.Message;
import com.android.email.mail.MessagingException; import com.android.email.mail.MessagingException;
import com.android.email.mail.Pusher; import com.android.email.mail.Pusher;
import com.android.email.EmailReceivedIntent;
/** /**
*/ */
public class MailService extends Service { public class MailService extends CoreService {
private static final String ACTION_APP_STARTED = "com.android.email.intent.action.MAIL_SERVICE_APP_STARTED";
private static final String ACTION_CHECK_MAIL = "com.android.email.intent.action.MAIL_SERVICE_WAKEUP"; private static final String ACTION_CHECK_MAIL = "com.android.email.intent.action.MAIL_SERVICE_WAKEUP";
private static final String ACTION_RESCHEDULE = "com.android.email.intent.action.MAIL_SERVICE_RESCHEDULE"; private static final String ACTION_RESCHEDULE = "com.android.email.intent.action.MAIL_SERVICE_RESCHEDULE";
private static final String ACTION_RESCHEDULE_CHECK = "com.android.email.intent.action.MAIL_SERVICE_RESCHEDULE_CHECK";
private static final String ACTION_CANCEL = "com.android.email.intent.action.MAIL_SERVICE_CANCEL"; private static final String ACTION_CANCEL = "com.android.email.intent.action.MAIL_SERVICE_CANCEL";
private static final String ACTION_REFRESH_PUSHERS = "com.android.email.intent.action.MAIL_SERVICE_REFRESH_PUSHERS"; private static final String ACTION_REFRESH_PUSHERS = "com.android.email.intent.action.MAIL_SERVICE_REFRESH_PUSHERS";
private static final String CONNECTIVITY_CHANGE = "com.android.email.intent.action.MAIL_SERVICE_CONNECTIVITY_CHANGE"; private static final String CONNECTIVITY_CHANGE = "com.android.email.intent.action.MAIL_SERVICE_CONNECTIVITY_CHANGE";
private static final String BACKGROUND_DATA_CHANGED = "com.android.email.intent.action.MAIL_SERVICE_BACKGROUND_DATA_CHANGED"; private static final String BACKGROUND_DATA_CHANGED = "com.android.email.intent.action.MAIL_SERVICE_BACKGROUND_DATA_CHANGED";
private static final String CANCEL_CONNECTIVITY_NOTICE = "com.android.email.intent.action.MAIL_SERVICE_CANCEL_CONNECTIVITY_NOTICE"; private static final String CANCEL_CONNECTIVITY_NOTICE = "com.android.email.intent.action.MAIL_SERVICE_CANCEL_CONNECTIVITY_NOTICE";
private static final String HAS_CONNECTIVITY = "com.android.email.intent.action.MAIL_SERVICE_HAS_CONNECTIVITY"; private static final String HAS_CONNECTIVITY = "com.android.email.intent.action.MAIL_SERVICE_HAS_CONNECTIVITY";
private Listener mListener = new Listener(); private final ExecutorService threadPool = Executors.newFixedThreadPool(1); // Must be single threaded
private State state = null;
public static void actionReschedule(Context context, Integer wakeLockId) {
private int mStartId;
public static void actionReschedule(Context context) {
Intent i = new Intent(); Intent i = new Intent();
i.setClass(context, MailService.class); i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_RESCHEDULE); i.setAction(MailService.ACTION_RESCHEDULE);
addWakeLockId(i, wakeLockId);
context.startService(i); context.startService(i);
} }
public static void appStarted(Context context) { public static void rescheduleCheck(Context context, Integer wakeLockId) {
Intent i = new Intent(); Intent i = new Intent();
i.setClass(context, MailService.class); i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_APP_STARTED); i.setAction(MailService.ACTION_RESCHEDULE_CHECK);
addWakeLockId(i, wakeLockId);
context.startService(i); context.startService(i);
} }
// private static void checkMail(Context context) { public static void actionCancel(Context context, Integer wakeLockId) {
// Intent i = new Intent();
// i.setClass(context, MailService.class);
// i.setAction(MailService.ACTION_CHECK_MAIL);
// context.startService(i);
// }
public static void actionCancel(Context context) {
Intent i = new Intent(); Intent i = new Intent();
i.setClass(context, MailService.class); i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_CANCEL); i.setAction(MailService.ACTION_CANCEL);
addWakeLockId(i, wakeLockId);
context.startService(i); context.startService(i);
} }
public static void connectivityChange(Context context, boolean hasConnectivity) { public static void connectivityChange(Context context, boolean hasConnectivity, Integer wakeLockId) {
Intent i = new Intent(); Intent i = new Intent();
i.setClass(context, MailService.class); i.setClass(context, MailService.class);
i.setAction(MailService.CONNECTIVITY_CHANGE); i.setAction(MailService.CONNECTIVITY_CHANGE);
i.putExtra(HAS_CONNECTIVITY, hasConnectivity); i.putExtra(HAS_CONNECTIVITY, hasConnectivity);
addWakeLockId(i, wakeLockId);
context.startService(i); context.startService(i);
} }
public static void backgroundDataChanged(Context context) { public static void backgroundDataChanged(Context context, Integer wakeLockId) {
Intent i = new Intent(); Intent i = new Intent();
i.setClass(context, MailService.class); i.setClass(context, MailService.class);
i.setAction(MailService.BACKGROUND_DATA_CHANGED); i.setAction(MailService.BACKGROUND_DATA_CHANGED);
addWakeLockId(i, wakeLockId);
context.startService(i); context.startService(i);
} }
@ -104,26 +96,22 @@ public class MailService extends Service {
} }
@Override @Override
public void onStart(Intent intent, int startId) { public void startService(Intent intent, int startId) {
Integer startIdObj = startId;
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); long startTime = System.currentTimeMillis();
WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
wakeLock.setReferenceCounted(false);
wakeLock.acquire(Email.MAIL_SERVICE_WAKE_LOCK_TIMEOUT);
try try
{ {
ConnectivityManager connectivityManager = (ConnectivityManager)getApplication().getSystemService(Context.CONNECTIVITY_SERVICE); ConnectivityManager connectivityManager = (ConnectivityManager)getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
boolean doBackground = true; boolean doBackground = true;
boolean hasConnectivity = false;
state = State.DISCONNECTED;
if (connectivityManager != null) if (connectivityManager != null)
{ {
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
if (netInfo != null) if (netInfo != null)
{ {
state = netInfo.getState(); State state = netInfo.getState();
hasConnectivity = state == State.CONNECTED;
} }
boolean backgroundData = connectivityManager.getBackgroundDataSetting(); boolean backgroundData = connectivityManager.getBackgroundDataSetting();
@ -135,38 +123,19 @@ public class MailService extends Service {
setForeground(true); // if it gets killed once, it'll never restart setForeground(true); // if it gets killed once, it'll never restart
Log.i(Email.LOG_TAG, "MailService.onStart(" + intent + ", " + startId Log.i(Email.LOG_TAG, "MailService.onStart(" + intent + ", " + startId
+ "), state = " + state + ", doBackground = " + doBackground); + "), hasConnectivity = " + hasConnectivity + ", doBackground = " + doBackground);
super.onStart(intent, startId);
this.mStartId = startId;
// MessagingController.getInstance(getApplication()).addListener(mListener); // MessagingController.getInstance(getApplication()).addListener(mListener);
if (ACTION_CHECK_MAIL.equals(intent.getAction())) { if (ACTION_CHECK_MAIL.equals(intent.getAction())) {
//if (Config.LOGV) { Log.i(Email.LOG_TAG, "***** MailService *****: checking mail");
MessagingController.getInstance(getApplication()).log("***** MailService *****: checking mail");
Log.v(Email.LOG_TAG, "***** MailService *****: checking mail"); if (hasConnectivity && doBackground)
//}
if (state == State.CONNECTED && doBackground)
{ {
MessagingController controller = MessagingController.getInstance(getApplication()); PollService.startService(this);
Listener listener = (Listener)controller.getCheckMailListener();
if (listener == null)
{
MessagingController.getInstance(getApplication()).log("***** MailService *****: starting new check");
mListener.wakeLockAcquire();
controller.setCheckMailListener(mListener);
controller.checkMail(this, null, false, false, mListener);
}
else
{
MessagingController.getInstance(getApplication()).log("***** MailService *****: renewing WakeLock");
listener.wakeLockAcquire();
}
} }
reschedule(); reschedule(startIdObj);
// stopSelf(startId); startIdObj = null;
} }
else if (ACTION_CANCEL.equals(intent.getAction())) { else if (ACTION_CANCEL.equals(intent.getAction())) {
if (Config.LOGV) { if (Config.LOGV) {
@ -175,57 +144,39 @@ public class MailService extends Service {
MessagingController.getInstance(getApplication()).log("***** MailService *****: cancel"); MessagingController.getInstance(getApplication()).log("***** MailService *****: cancel");
cancel(); cancel();
// stopSelf(startId);
} }
else if (ACTION_RESCHEDULE.equals(intent.getAction())) { else if (ACTION_RESCHEDULE.equals(intent.getAction())) {
if (Config.LOGV) { if (Config.LOGV) {
Log.v(Email.LOG_TAG, "***** MailService *****: reschedule"); Log.v(Email.LOG_TAG, "***** MailService *****: reschedule");
} }
rescheduleAll(hasConnectivity, doBackground, startIdObj);
startIdObj = null;
MessagingController.getInstance(getApplication()).log("***** MailService *****: reschedule"); MessagingController.getInstance(getApplication()).log("***** MailService *****: reschedule");
boolean polling = false;
boolean pushing = false; }
if (state == State.CONNECTED && doBackground) else if (ACTION_RESCHEDULE_CHECK.equals(intent.getAction())) {
{ if (Config.LOGV) {
polling = reschedule(); Log.v(Email.LOG_TAG, "***** MailService *****: reschedule check");
pushing = reschedulePushers();
} }
else reschedule(startIdObj);
{ startIdObj = null;
stopPushers(); MessagingController.getInstance(getApplication()).log("***** MailService *****: reschedule");
}
if (polling == false && pushing == false)
{
Log.i(Email.LOG_TAG, "Neither pushing nor polling, so stopping");
stopSelf(startId);
}
// stopSelf(startId);
} }
else if (ACTION_REFRESH_PUSHERS.equals(intent.getAction())) else if (ACTION_REFRESH_PUSHERS.equals(intent.getAction()))
{ {
schedulePushers(); if (hasConnectivity && doBackground)
try
{ {
if (state == State.CONNECTED && doBackground) schedulePushers(null);
{ refreshPushers(startIdObj);
Log.i(Email.LOG_TAG, "Refreshing pushers"); startIdObj = null;
Collection<Pusher> pushers = MessagingController.getInstance(getApplication()).getPushers();
for (Pusher pusher : pushers)
{
pusher.refresh();
}
}
}
catch (Exception e)
{
Log.e(Email.LOG_TAG, "Exception while refreshing pushers", e);
} }
} }
else if (CONNECTIVITY_CHANGE.equals(intent.getAction()) || else if (CONNECTIVITY_CHANGE.equals(intent.getAction()) ||
BACKGROUND_DATA_CHANGED.equals(intent.getAction())) BACKGROUND_DATA_CHANGED.equals(intent.getAction()))
{ {
actionReschedule(this); rescheduleAll(hasConnectivity, doBackground, startIdObj);
boolean hasConnectivity = intent.getBooleanExtra(HAS_CONNECTIVITY, true); startIdObj = null;
Log.i(Email.LOG_TAG, "Got connectivity action with hasConnectivity = " + hasConnectivity + ", doBackground = " + doBackground); Log.i(Email.LOG_TAG, "Got connectivity action with hasConnectivity = " + hasConnectivity + ", doBackground = " + doBackground);
notifyConnectionStatus(hasConnectivity); notifyConnectionStatus(hasConnectivity);
@ -234,20 +185,31 @@ public class MailService extends Service {
{ {
notifyConnectionStatus(true); notifyConnectionStatus(true);
} }
else if (ACTION_APP_STARTED.equals(intent.getAction()))
{
// Not needed for now, but might be useful later
}
} }
finally finally
{ {
if (wakeLock != null) if (startIdObj != null)
{ {
wakeLock.release(); stopSelf(startId);
} }
} }
long endTime = System.currentTimeMillis();
Log.i(Email.LOG_TAG, "MailService.onStart took " + (endTime - startTime) + "ms");
} }
private void rescheduleAll(final boolean hasConnectivity, final boolean doBackground, final Integer startId)
{
if (hasConnectivity && doBackground)
{
reschedule(null);
reschedulePushers(startId);
}
else
{
stopPushers(startId);
}
}
private void notifyConnectionStatus(boolean hasConnectivity) private void notifyConnectionStatus(boolean hasConnectivity)
{ {
NotificationManager notifMgr = NotificationManager notifMgr =
@ -286,238 +248,206 @@ public class MailService extends Service {
} }
private void cancel() { private void cancel() {
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(); Intent i = new Intent();
i.setClassName(getApplication().getPackageName(), "com.android.email.service.MailService"); i.setClassName(getApplication().getPackageName(), "com.android.email.service.MailService");
i.setAction(ACTION_CHECK_MAIL); i.setAction(ACTION_CHECK_MAIL);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0); BootReceiver.cancelIntent(this, i);
alarmMgr.cancel(pi);
} }
private boolean reschedule() { private void reschedule(Integer startId) {
boolean polling = true; execute(getApplication(), new Runnable()
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE); {
Intent i = new Intent(); public void run()
i.setClassName(getApplication().getPackageName(), "com.android.email.service.MailService"); {
i.setAction(ACTION_CHECK_MAIL); int shortestInterval = -1;
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
for (Account account : Preferences.getPreferences(MailService.this).getAccounts()) {
if (account.getAutomaticCheckIntervalMinutes() != -1
&& (account.getAutomaticCheckIntervalMinutes() < shortestInterval || shortestInterval == -1)) {
shortestInterval = account.getAutomaticCheckIntervalMinutes();
}
}
int shortestInterval = -1; if (shortestInterval == -1) {
Log.v(Email.LOG_TAG, "No next check scheduled for package " + getApplication().getPackageName());
for (Account account : Preferences.getPreferences(this).getAccounts()) { cancel();
if (account.getAutomaticCheckIntervalMinutes() != -1 }
&& (account.getAutomaticCheckIntervalMinutes() < shortestInterval || shortestInterval == -1)) { else
shortestInterval = account.getAutomaticCheckIntervalMinutes();
}
}
if (shortestInterval == -1) {
Log.v(Email.LOG_TAG, "No next check scheduled for package " + getApplication().getPackageName());
alarmMgr.cancel(pi);
polling = false;
}
else
{
long delay = (shortestInterval * (60 * 1000));
long nextTime = System.currentTimeMillis() + delay;
try
{
String checkString = "Next check for package " + getApplication().getPackageName() + " scheduled for " + new Date(nextTime);
Log.i(Email.LOG_TAG, checkString);
MessagingController.getInstance(getApplication()).log(checkString);
}
catch (Exception e)
{
// I once got a NullPointerException deep in new Date();
Log.e(Email.LOG_TAG, "Exception while logging", e);
}
alarmMgr.set(AlarmManager.RTC_WAKEUP, nextTime, pi);
}
return polling;
}
private void stopPushers()
{
MessagingController.getInstance(getApplication()).stopAllPushing();
}
private boolean reschedulePushers()
{
boolean pushing = false;
Log.i(Email.LOG_TAG, "Rescheduling pushers");
stopPushers();
if (state == State.CONNECTED)
{
for (Account account : Preferences.getPreferences(this).getAccounts()) {
Log.i(Email.LOG_TAG, "Setting up pushers for account " + account.getDescription());
Pusher pusher = MessagingController.getInstance(getApplication()).setupPushing(account);
if (pusher != null)
{ {
pushing = true; long delay = (shortestInterval * (60 * 1000));
Log.i(Email.LOG_TAG, "Starting configured pusher for account " + account.getDescription());
pusher.start(); long nextTime = System.currentTimeMillis() + delay;
try
{
String checkString = "Next check for package " + getApplication().getPackageName() + " scheduled for " + new Date(nextTime);
Log.i(Email.LOG_TAG, checkString);
MessagingController.getInstance(getApplication()).log(checkString);
}
catch (Exception e)
{
// I once got a NullPointerException deep in new Date();
Log.e(Email.LOG_TAG, "Exception while logging", e);
}
Intent i = new Intent();
i.setClassName(getApplication().getPackageName(), "com.android.email.service.MailService");
i.setAction(ACTION_CHECK_MAIL);
BootReceiver.scheduleIntent(MailService.this, nextTime, i);
} }
} }
schedulePushers(); }, Email.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId);
}
return pushing;
} }
private void schedulePushers() private void stopPushers(final Integer startId)
{ {
int minInterval = -1; execute(getApplication(), new Runnable()
{
public void run()
{
MessagingController.getInstance(getApplication()).stopAllPushing();
PushService.stopService(MailService.this);
}
}, Email.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId );
}
private void reschedulePushers(final Integer startId)
{
execute(getApplication(), new Runnable()
{
public void run()
{
Log.i(Email.LOG_TAG, "Rescheduling pushers");
stopPushers(null);
setupPushers(null);
schedulePushers(startId);
}
}, Email.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, null );
}
private void setupPushers(final Integer startId)
{
execute(getApplication(), new Runnable()
{
public void run()
{
boolean pushing = false;
for (Account account : Preferences.getPreferences(MailService.this).getAccounts()) {
Log.i(Email.LOG_TAG, "Setting up pushers for account " + account.getDescription());
Pusher pusher = MessagingController.getInstance(getApplication()).setupPushing(account);
if (pusher != null)
{
pushing = true;
Log.i(Email.LOG_TAG, "Starting configured pusher for account " + account.getDescription());
pusher.start();
}
}
if (pushing)
{
PushService.startService(MailService.this);
}
}
}, Email.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId);
}
private void refreshPushers(final Integer startId)
{
execute(getApplication(), new Runnable()
{
public void run()
{
try
{
Log.i(Email.LOG_TAG, "Refreshing pushers");
Collection<Pusher> pushers = MessagingController.getInstance(getApplication()).getPushers();
for (Pusher pusher : pushers)
{
pusher.refresh();
}
}
catch (Exception e)
{
Log.e(Email.LOG_TAG, "Exception while refreshing pushers", e);
}
}
}, Email.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId );
}
private void schedulePushers(final Integer startId)
{
execute(getApplication(), new Runnable()
{
public void run()
{
int minInterval = -1;
Collection<Pusher> pushers = MessagingController.getInstance(getApplication()).getPushers(); Collection<Pusher> pushers = MessagingController.getInstance(getApplication()).getPushers();
for (Pusher pusher : pushers) for (Pusher pusher : pushers)
{
int interval = pusher.getRefreshInterval();
if (interval != -1 && (interval < minInterval || minInterval == -1))
{
minInterval = interval;
}
}
if (Email.DEBUG)
{
Log.v(Email.LOG_TAG, "Pusher refresh interval = " + minInterval);
}
if (minInterval != -1)
{
long nextTime = System.currentTimeMillis() + minInterval;
String checkString = "Next pusher refresh scheduled for " + new Date(nextTime);
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, checkString);
}
Intent i = new Intent();
i.setClassName(getApplication().getPackageName(), "com.android.email.service.MailService");
i.setAction(ACTION_REFRESH_PUSHERS);
BootReceiver.scheduleIntent(MailService.this, nextTime, i);
}}
}, Email.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId);
}
public void execute(Context context, final Runnable runner, int wakeLockTime, final Integer startId)
{
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
final WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
wakeLock.setReferenceCounted(false);
wakeLock.acquire(wakeLockTime);
Log.i(Email.LOG_TAG, "MailService queueing Runnable " + runner.hashCode() + " with startId " + startId);
Runnable myRunner = new Runnable()
{ {
int interval = pusher.getRefreshInterval(); public void run()
if (interval != -1 && (interval < minInterval || minInterval == -1))
{ {
minInterval = interval; try
} {
}
if (Email.DEBUG)
{
Log.v(Email.LOG_TAG, "Pusher refresh interval = " + minInterval);
}
if (minInterval != -1)
{
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE); Log.i(Email.LOG_TAG, "MailService running Runnable " + runner.hashCode() + " with startId " + startId);
long nextTime = System.currentTimeMillis() + minInterval; runner.run();
String checkString = "Next pusher refresh scheduled for " + new Date(nextTime); }
if (Email.DEBUG) finally
{ {
Log.d(Email.LOG_TAG, checkString); Log.i(Email.LOG_TAG, "MailService completed Runnable " + runner.hashCode() + " with startId " + startId);
wakeLock.release();
if (startId != null)
{
stopSelf(startId);
}
}
} }
Intent i = new Intent();
i.setClassName(getApplication().getPackageName(), "com.android.email.service.MailService"); };
i.setAction(ACTION_REFRESH_PUSHERS);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0); threadPool.execute(myRunner);
alarmMgr.set(AlarmManager.RTC_WAKEUP, nextTime, pi);
}
} }
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return null; return null;
} }
class Listener extends MessagingListener {
HashMap<String, Integer> accountsChecked = new HashMap<String, Integer>();
private WakeLock wakeLock = null;
// wakelock strategy is to be very conservative. If there is any reason to release, then release
// don't want to take the chance of running wild
public synchronized void wakeLockAcquire()
{
WakeLock oldWakeLock = wakeLock;
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
wakeLock.setReferenceCounted(false);
wakeLock.acquire(Email.WAKE_LOCK_TIMEOUT);
if (oldWakeLock != null)
{
oldWakeLock.release();
}
}
public synchronized void wakeLockRelease()
{
if (wakeLock != null)
{
wakeLock.release();
wakeLock = null;
}
}
@Override
public void checkMailStarted(Context context, Account account) {
accountsChecked.clear();
}
@Override
public void checkMailFailed(Context context, Account account, String reason) {
release();
}
@Override
public void synchronizeMailboxFinished(
Account account,
String folder,
int totalMessagesInMailbox,
int numNewMessages) {
if (account.isNotifyNewMail()) {
Integer existingNewMessages = accountsChecked.get(account.getUuid());
if (existingNewMessages == null)
{
existingNewMessages = 0;
}
accountsChecked.put(account.getUuid(), existingNewMessages + numNewMessages);
}
}
private void checkMailDone(Context context, Account doNotUseaccount)
{
if (accountsChecked.isEmpty())
{
return;
}
NotificationManager notifMgr =
(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
for (Account thisAccount : Preferences.getPreferences(context).getAccounts()) {
Integer newMailCount = accountsChecked.get(thisAccount.getUuid());
if (newMailCount != null)
{
try
{
int unreadMessageCount = thisAccount.getUnreadMessageCount(context, getApplication());
if (unreadMessageCount > 0 && newMailCount > 0)
{
MessagingController.getInstance(getApplication()).notifyAccount(context, thisAccount, unreadMessageCount);
}
else if (unreadMessageCount == 0)
{
notifMgr.cancel(thisAccount.getAccountNumber());
}
}
catch (MessagingException me)
{
Log.e(Email.LOG_TAG, "***** MailService *****: couldn't get unread count for account " +
thisAccount.getDescription(), me);
}
}
}//for accounts
}//checkMailDone
private void release()
{
MessagingController controller = MessagingController.getInstance(getApplication());
controller.setCheckMailListener(null);
reschedule();
wakeLockRelease();
// stopSelf(mStartId);
}
@Override
public void checkMailFinished(Context context, Account account) {
Log.v(Email.LOG_TAG, "***** MailService *****: checkMailFinished");
try
{
checkMailDone(context, account);
}
finally
{
release();
}
}
}
} }

View File

@ -0,0 +1,205 @@
package com.android.email.service;
import java.util.HashMap;
import com.android.email.Account;
import com.android.email.Email;
import com.android.email.MessagingController;
import com.android.email.MessagingListener;
import com.android.email.Preferences;
import com.android.email.mail.MessagingException;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
public class PollService extends CoreService
{
private static String START_SERVICE = "com.android.email.service.PollService.startService";
private static String STOP_SERVICE = "com.android.email.service.PollService.stopService";
private Listener mListener = new Listener();
public static void startService(Context context) {
Intent i = new Intent();
i.setClass(context, PollService.class);
i.setAction(PollService.START_SERVICE);
context.startService(i);
}
public static void stopService(Context context) {
Intent i = new Intent();
i.setClass(context, PollService.class);
i.setAction(PollService.STOP_SERVICE);
context.startService(i);
}
@Override
public void startService(Intent intent, int startId)
{
if (START_SERVICE.equals(intent.getAction()))
{
Log.i(Email.LOG_TAG, "PollService started with startId = " + startId);
MessagingController controller = MessagingController.getInstance(getApplication());
Listener listener = (Listener)controller.getCheckMailListener();
if (listener == null)
{
MessagingController.getInstance(getApplication()).log("***** PollService *****: starting new check");
mListener.setStartId(startId);
mListener.wakeLockAcquire();
controller.setCheckMailListener(mListener);
controller.checkMail(this, null, false, false, mListener);
}
else
{
MessagingController.getInstance(getApplication()).log("***** PollService *****: renewing WakeLock");
listener.setStartId(startId);
listener.wakeLockAcquire();
}
}
else if (STOP_SERVICE.equals(intent.getAction()))
{
Log.i(Email.LOG_TAG, "PollService stopping");
stopSelf();
}
}
@Override
public IBinder onBind(Intent arg0)
{
return null;
}
class Listener extends MessagingListener {
HashMap<String, Integer> accountsChecked = new HashMap<String, Integer>();
private WakeLock wakeLock = null;
private int startId = -1;
// wakelock strategy is to be very conservative. If there is any reason to release, then release
// don't want to take the chance of running wild
public synchronized void wakeLockAcquire()
{
WakeLock oldWakeLock = wakeLock;
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
wakeLock.setReferenceCounted(false);
wakeLock.acquire(Email.WAKE_LOCK_TIMEOUT);
if (oldWakeLock != null)
{
oldWakeLock.release();
}
}
public synchronized void wakeLockRelease()
{
if (wakeLock != null)
{
wakeLock.release();
wakeLock = null;
}
}
@Override
public void checkMailStarted(Context context, Account account) {
accountsChecked.clear();
}
@Override
public void checkMailFailed(Context context, Account account, String reason) {
release();
}
@Override
public void synchronizeMailboxFinished(
Account account,
String folder,
int totalMessagesInMailbox,
int numNewMessages) {
if (account.isNotifyNewMail()) {
Integer existingNewMessages = accountsChecked.get(account.getUuid());
if (existingNewMessages == null)
{
existingNewMessages = 0;
}
accountsChecked.put(account.getUuid(), existingNewMessages + numNewMessages);
}
}
private void checkMailDone(Context context, Account doNotUseaccount)
{
if (accountsChecked.isEmpty())
{
return;
}
NotificationManager notifMgr =
(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
for (Account thisAccount : Preferences.getPreferences(context).getAccounts()) {
Integer newMailCount = accountsChecked.get(thisAccount.getUuid());
if (newMailCount != null)
{
try
{
int unreadMessageCount = thisAccount.getUnreadMessageCount(context, getApplication());
if (unreadMessageCount > 0 && newMailCount > 0)
{
MessagingController.getInstance(getApplication()).notifyAccount(context, thisAccount, unreadMessageCount);
}
else if (unreadMessageCount == 0)
{
notifMgr.cancel(thisAccount.getAccountNumber());
}
}
catch (MessagingException me)
{
Log.e(Email.LOG_TAG, "***** PollService *****: couldn't get unread count for account " +
thisAccount.getDescription(), me);
}
}
}//for accounts
}//checkMailDone
private void release()
{
MessagingController controller = MessagingController.getInstance(getApplication());
controller.setCheckMailListener(null);
MailService.rescheduleCheck(PollService.this, null);
wakeLockRelease();
Log.i(Email.LOG_TAG, "PollService stopping with startId = " + startId);
stopSelf(startId);
}
@Override
public void checkMailFinished(Context context, Account account) {
Log.v(Email.LOG_TAG, "***** PollService *****: checkMailFinished");
try
{
checkMailDone(context, account);
}
finally
{
release();
}
}
public int getStartId()
{
return startId;
}
public void setStartId(int startId)
{
this.startId = startId;
}
}
}

View File

@ -0,0 +1,50 @@
package com.android.email.service;
import com.android.email.Email;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class PushService extends CoreService
{
private static String START_SERVICE = "com.android.email.service.PushService.startService";
private static String STOP_SERVICE = "com.android.email.service.PushService.stopService";
public static void startService(Context context) {
Intent i = new Intent();
i.setClass(context, PushService.class);
i.setAction(PushService.START_SERVICE);
context.startService(i);
}
public static void stopService(Context context) {
Intent i = new Intent();
i.setClass(context, PushService.class);
i.setAction(PushService.STOP_SERVICE);
context.startService(i);
}
@Override
public void startService(Intent intent, int startId)
{
if (START_SERVICE.equals(intent.getAction()))
{
Log.i(Email.LOG_TAG, "PushService started with startId = " + startId);
}
else if (STOP_SERVICE.equals(intent.getAction()))
{
Log.i(Email.LOG_TAG, "PushService stopping with startId = " + startId);
stopSelf(startId);
}
}
@Override
public IBinder onBind(Intent arg0)
{
// TODO Auto-generated method stub
return null;
}
}

View File

@ -0,0 +1,156 @@
package com.android.email.service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import com.android.email.Email;
public class SleepService extends CoreService
{
private static String ALARM_FIRED = "com.android.email.service.SleepService.ALARM_FIRED";
private static String LATCH_ID = "com.android.email.service.SleepService.LATCH_ID_EXTRA";
private static ConcurrentHashMap<Integer, SleepDatum> sleepData = new ConcurrentHashMap<Integer, SleepDatum>();
private static AtomicInteger latchId = new AtomicInteger();
public static void sleep(Context context, long sleepTime, WakeLock wakeLock, long wakeLockTimeout)
{
Integer id = latchId.getAndIncrement();
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, "SleepService Preparing CountDownLatch with id = " + id + ", thread " + Thread.currentThread().getName());
}
SleepDatum sleepDatum = new SleepDatum();
CountDownLatch latch = new CountDownLatch(1);
sleepDatum.latch = latch;
sleepData.put(id, sleepDatum);
Intent i = new Intent();
i.setClassName(context.getPackageName(), "com.android.email.service.SleepService");
i.putExtra(LATCH_ID, id);
i.setAction(ALARM_FIRED + "." + id);
long startTime = System.currentTimeMillis();
long nextTime = startTime + sleepTime;
BootReceiver.scheduleIntent(context, nextTime, i);
if (wakeLock != null)
{
sleepDatum.wakeLock = wakeLock;
sleepDatum.timeout = wakeLockTimeout;
wakeLock.release();
}
try
{
boolean timedOut = latch.await(sleepTime, TimeUnit.MILLISECONDS);
if (timedOut == false)
{
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, "SleepService latch timed out for id = " + id + ", thread " + Thread.currentThread().getName());
}
// don't call endSleep here or remove the sleepDatum here, instead of the following block.
// We might not get the wakeLock before
// falling asleep again, so we have to get the wakeLock *first* The alarmed version will
// already be running in a WakeLock due to the nature of AlarmManager
sleepDatum = sleepData.get(id);
if (sleepDatum != null)
{
reacquireWakeLock(sleepDatum);
// OK, we have the wakeLock, now we can remove the sleepDatum
sleepData.remove(id);
}
}
}
catch (InterruptedException ie)
{
Log.e(Email.LOG_TAG, "SleepService Interrupted", ie);
}
long endTime = System.currentTimeMillis();
long actualSleep = endTime - startTime;
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, "SleepService requested sleep time was " + sleepTime + ", actual was " + actualSleep);
}
if (actualSleep < sleepTime)
{
Log.w(Email.LOG_TAG, "SleepService sleep time too short: requested was " + sleepTime + ", actual was " + actualSleep);
}
}
private static void endSleep(Integer id)
{
if (id != -1)
{
SleepDatum sleepDatum = sleepData.remove(id);
if (sleepDatum != null)
{
CountDownLatch latch = sleepDatum.latch;
if (latch == null)
{
Log.e(Email.LOG_TAG, "SleepService No CountDownLatch available with id = " + id);
}
else
{
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, "SleepService Counting down CountDownLatch with id = " + id);
}
latch.countDown();
}
reacquireWakeLock(sleepDatum);
}
else
{
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, "SleepService Sleep for id " + id + " already finished");
}
}
}
}
private static void reacquireWakeLock(SleepDatum sleepDatum)
{
WakeLock wakeLock = sleepDatum.wakeLock;
if (wakeLock != null)
{
synchronized(wakeLock)
{
long timeout = sleepDatum.timeout;
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, "SleepService Acquring wakeLock for id for " + timeout + "ms");
}
wakeLock.acquire(timeout);
}
}
}
@Override
public void startService(Intent intent, int startId)
{
if (intent.getAction().startsWith(ALARM_FIRED)) {
Integer id = intent.getIntExtra(LATCH_ID, -1);
endSleep(id);
}
stopSelf(startId);
}
private static class SleepDatum
{
CountDownLatch latch;
WakeLock wakeLock;
long timeout;
}
}