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>
<action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />
</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>
<service
android:name="com.android.email.service.MailService"
android:enabled="false"
android:enabled="true"
>
</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
android:name="com.android.email.provider.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 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 BOOT_RECEIVER_WAKE_LOCK_TIMEOUT = 10000;
public static final int BOOT_RECEIVER_WAKE_LOCK_TIMEOUT = 60000;
/**
* LED color used for the new email notitication
*/
@ -401,42 +403,56 @@ public class Email extends Application {
* whether any accounts are configured.
*/
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();
if (!enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
/*
* 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.
*/
MailService.actionReschedule(context);
MailService.actionReschedule(context, wakeLockId);
}
pm.setComponentEnabledSetting(
new ComponentName(context, MessageCompose.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(
new ComponentName(context, BootReceiver.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(
new ComponentName(context, MailService.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
Class[] classes = { MessageCompose.class, BootReceiver.class, MailService.class };
for (Class clazz : classes)
{
boolean alreadyEnabled = pm.getComponentEnabledSetting(new ComponentName(context, clazz)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
if (enabled != alreadyEnabled)
{
pm.setComponentEnabledSetting(
new ComponentName(context, clazz),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
}
if (enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
/*
* And now if accounts do exist then we've just enabled the service and we want to
* schedule alarms for the new accounts.
*/
MailService.actionReschedule(context);
MailService.actionReschedule(context, wakeLockId);
}
}
public static void save(SharedPreferences preferences)
@ -505,7 +521,6 @@ public class Email extends Application {
}
});
MailService.appStarted(this);
}
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.LocalMessage;
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
@ -3514,52 +3515,44 @@ public class MessagingController implements Runnable {
final MessagingController controller = this;
PushReceiver receiver = new PushReceiver()
{
WakeLock wakeLock = null;
int refCount = 0;
public synchronized void pushInProgress()
ThreadLocal<WakeLock> threadWakeLock = new ThreadLocal<WakeLock>();
public void acquireWakeLock()
{
WakeLock wakeLock = threadWakeLock.get();
if (wakeLock == null)
{
PowerManager pm = (PowerManager) mApplication.getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
wakeLock.setReferenceCounted(false);
threadWakeLock.set(wakeLock);
}
wakeLock.acquire(Email.PUSH_WAKE_LOCK_TIMEOUT);
refCount++;
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)
{
Log.d(Email.LOG_TAG, "Considering releasing WakeLock for Pushing");
}
WakeLock wakeLock = threadWakeLock.get();
if (wakeLock != null)
{
if (refCount > 0)
if (Email.DEBUG)
{
refCount--;
}
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);
}
Log.d(Email.LOG_TAG, "Releasing WakeLock for Pushing for thread " + Thread.currentThread().getName());
}
wakeLock.release();
}
else
{
Log.e(Email.LOG_TAG, "No WakeLock waiting to be released for thread " + Thread.currentThread().getName());
}
}
public void messagesFlagsChanged(String folderName,
@ -3572,6 +3565,11 @@ public class MessagingController implements Runnable {
{
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)
{
@ -3976,6 +3974,8 @@ public class MessagingController implements Runnable {
}
}
class MessageContainer
{
Message message;

View File

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

View File

@ -101,7 +101,7 @@ public class Prefs extends K9PreferenceActivity {
Email.setBackgroundOps(newBackgroundOps);
Email.save(preferences);
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 void pushInProgress();
public void pushComplete();
public void acquireWakeLock();
public void releaseWakeLock();
public void messagesArrived(String folderName, List<Message> mess);
public void messagesFlagsChanged(String folderName, List<Message> mess);
public String getPushState(String folderName);
public void pushError(String errorMessage, Exception e);
public void setPushActive(String folderName, boolean enabled);
public void sleep(long millis);
}

View File

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

View File

@ -34,6 +34,7 @@ import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
@ -281,10 +282,13 @@ public class ImapStore extends Store {
}
private void releaseConnection(ImapConnection connection) {
if (connection.isOpen())
{
synchronized(mConnections)
{
mConnections.offer(connection);
}
}
}
private String encodeFolderName(String name) {
@ -431,7 +435,7 @@ public class ImapStore extends Store {
mPathDelimeter = nameResponses.get(0).getString(2);
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) {
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 {
String tag = mConnection.sendCommand(String.format("UID FETCH %s (%s)",
Utility.combine(uids, ','),
Utility.combine(fetchFields.toArray(new String[fetchFields.size()]), ' ')
), false);
mConnection.sendCommand(String.format("UID FETCH %s (%s)",
Utility.combine(uids, ','),
Utility.combine(fetchFields.toArray(new String[fetchFields.size()]), ' ')
), false);
ImapResponse response;
int messageNumber = 0;
do {
@ -811,7 +820,7 @@ public class ImapStore extends Store {
handleUntaggedResponse(response);
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")) {
ImapList fetchList = (ImapList)response.getKeyedValue("FETCH");
@ -820,7 +829,7 @@ public class ImapStore extends Store {
Message message = messageMap.get(uid);
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;
}
if (listener != null) {
@ -867,7 +876,7 @@ public class ImapStore extends Store {
}
catch (MessagingException e) {
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);
}
@ -904,7 +913,7 @@ public class ImapStore extends Store {
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());
String contentTransferEncoding = part.getHeader(
@ -958,7 +967,7 @@ public class ImapStore extends Store {
mMessageCount = response.getNumber(0);
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) {
@ -978,7 +987,7 @@ public class ImapStore extends Store {
uidNext = bracketed.getNumber(1);
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--;
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) {
@ -1014,7 +1023,7 @@ public class ImapStore extends Store {
// 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)
@ -1221,7 +1230,7 @@ public class ImapStore extends Store {
String newUid = getUidFromMessageId(message);
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)
@ -1250,14 +1259,14 @@ public class ImapStore extends Store {
if (messageIdHeader == null || messageIdHeader.length == 0) {
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;
}
String messageId = messageIdHeader[0];
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 =
@ -1343,12 +1352,12 @@ public class ImapStore extends Store {
int messageUid = Integer.parseInt(messageUidS);
ImapPushState oldPushState = ImapPushState.parse(oldPushStateS);
// Log.d(Email.LOG_TAG, "getNewPushState comparing oldUidNext " + oldPushState.uidNext
// + " to message uid " + messageUid);
// + " to message uid " + messageUid + " for " + getLogId());
if (messageUid >= oldPushState.uidNext)
{
int uidNext = messageUid + 1;
ImapPushState newPushState = new ImapPushState(uidNext);
//Log.d(Email.LOG_TAG, "newPushState = " + newPushState);
//Log.d(Email.LOG_TAG, "newPushState = " + newPushState + " for " + getLogId());
return newPushState.toString();
}
else
@ -1358,7 +1367,7 @@ public class ImapStore extends Store {
}
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;
}
}
@ -1406,6 +1415,7 @@ public class ImapStore extends Store {
private MessagingException ioExceptionHandler(ImapConnection connection, IOException ioe)
throws MessagingException {
Log.e(Email.LOG_TAG, "IOException for " + getLogId(), ioe);
connection.close();
close(false);
return new MessagingException("IO Error", ioe);
@ -1423,6 +1433,16 @@ public class ImapStore extends 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;
protected Set<String> capabilities = new HashSet<String>();
private String getLogId()
{
return "conn" + hashCode();
}
public void open() throws IOException, MessagingException {
if (isOpen()) {
return;
@ -1450,14 +1475,14 @@ public class ImapStore extends Store {
}
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 {
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 ||
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
@ -1480,7 +1505,11 @@ public class ImapStore extends Store {
mParser = new ImapResponseParser(mIn);
mOut = mSocket.getOutputStream();
mParser.readResponse();
ImapResponse nullResponse = mParser.readResponse();
if (Email.DEBUG)
{
Log.v(Email.LOG_TAG, getLogId() + "<<<" + nullResponse);
}
List<ImapResponse> responses = executeSimpleCommand("CAPABILITY");
if (responses.size() != 2) {
throw new MessagingException("Invalid CAPABILITY response received");
@ -1498,7 +1527,7 @@ public class ImapStore extends Store {
{
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);
}
@ -1557,7 +1586,7 @@ public class ImapStore extends Store {
String[] tokens = ceMess.split("-");
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());
}
else
@ -1569,7 +1598,7 @@ public class ImapStore extends Store {
{
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();
}
}
@ -1584,21 +1613,20 @@ public class ImapStore extends Store {
{
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)
{
Log.v(Email.LOG_TAG, "Have capability '" + capability + "'");
Log.v(Email.LOG_TAG, "Have capability '" + capability + "' for " + getLogId());
}
}
return capabilities.contains("IDLE");
}
public boolean isOpen() {
return (mIn != null && mOut != null && mSocket != null && mSocket.isConnected() && !mSocket
.isClosed());
private boolean isOpen() {
return (mIn != null && mOut != null && mSocket != null && mSocket.isConnected() && !mSocket.isClosed());
}
public void close() {
private void close() {
// if (isOpen()) {
// try {
// executeSimpleCommand("LOGOUT");
@ -1626,9 +1654,14 @@ public class ImapStore extends Store {
mSocket = null;
}
public ImapResponse readResponse() throws IOException, MessagingException {
private ImapResponse readResponse() throws IOException, MessagingException {
try {
return mParser.readResponse();
ImapResponse response = mParser.readResponse();
if (Email.DEBUG)
{
Log.v(Email.LOG_TAG, getLogId() + "<<<" + response);
}
return response;
}
catch (IOException ioe)
{
@ -1648,7 +1681,7 @@ public class ImapStore extends Store {
return out;
}
public void sendContinuation(String continuation) throws IOException
private void sendContinuation(String continuation) throws IOException
{
mOut.write(continuation.getBytes());
mOut.write('\r');
@ -1656,7 +1689,7 @@ public class ImapStore extends Store {
mOut.flush();
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 (sensitive && !Email.DEBUG_SENSITIVE) {
Log.v(Email.LOG_TAG, ">>> "
Log.v(Email.LOG_TAG, getLogId() + ">>> "
+ "[Command Hidden, Enable Sensitive Debug Logging To Show]");
} 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);
}
public List<ImapResponse> executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler)
private List<ImapResponse> executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler)
throws IOException, ImapException, MessagingException {
String commandToLog = command;
if (sensitive && !Email.DEBUG_SENSITIVE)
@ -1721,12 +1754,12 @@ public class ImapStore extends Store {
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);
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>();
ImapResponse response;
@ -1734,11 +1767,11 @@ public class ImapStore extends Store {
response = mParser.readResponse();
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)
{
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();
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.
*/
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);
}
if (remoteTrashFolder.exists()) {
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);
setFlag(Flag.DELETED, true);
@ -1825,7 +1858,7 @@ public class ImapStore extends Store {
}
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);
}
}
@ -1865,17 +1898,17 @@ public class ImapStore extends Store {
}
}
public static int MAX_DELAY_TIME = 240000;
public static int NORMAL_DELAY_TIME = 10000;
public static int MAX_DELAY_TIME = 50000;
public static int NORMAL_DELAY_TIME = 2500;
public class ImapFolderPusher extends ImapFolder implements UntaggedHandler
{
PushReceiver receiver = null;
final PushReceiver receiver;
Thread listeningThread = null;
AtomicBoolean stop = new AtomicBoolean(false);
AtomicBoolean idling = new AtomicBoolean(false);
AtomicBoolean doneSent = new AtomicBoolean(false);
private int delayTime = NORMAL_DELAY_TIME;
final AtomicBoolean stop = new AtomicBoolean(false);
final AtomicBoolean idling = new AtomicBoolean(false);
final AtomicBoolean doneSent = new AtomicBoolean(false);
final AtomicInteger delayTime = new AtomicInteger(NORMAL_DELAY_TIME);
public ImapFolderPusher(ImapStore store, String name, PushReceiver nReceiver)
{
@ -1886,7 +1919,7 @@ public class ImapStore extends Store {
{
if (idling.get())
{
receiver.pushInProgress();
receiver.acquireWakeLock();
sendDone();
}
}
@ -1910,12 +1943,12 @@ public class ImapStore extends Store {
public void start() throws MessagingException
{
receiver.pushInProgress();
Runnable runner = new Runnable()
{
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)
{
try
@ -1926,11 +1959,11 @@ public class ImapStore extends Store {
String pushStateS = receiver.getPushState(getName());
ImapPushState pushState = ImapPushState.parse(pushStateS);
oldUidNext = pushState.uidNext;
Log.i(Email.LOG_TAG, "Got oldUidNext " + oldUidNext);
Log.i(Email.LOG_TAG, "Got oldUidNext " + oldUidNext + " for " + getLogId());
}
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);
@ -1964,7 +1997,7 @@ public class ImapStore extends Store {
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>();
for (int uid = startUid; uid < uidNext; uid++ )
{
@ -1979,7 +2012,7 @@ public class ImapStore extends Store {
}
else
{
Log.i(Email.LOG_TAG, "About to IDLE " + getName());
Log.i(Email.LOG_TAG, "About to IDLE for " + getLogId());
receiver.setPushActive(getName(), true);
idling.set(true);
@ -1987,11 +2020,12 @@ public class ImapStore extends Store {
executeSimpleCommand("IDLE", false, ImapFolderPusher.this);
idling.set(false);
receiver.setPushActive(getName(), false);
delayTime = NORMAL_DELAY_TIME;
delayTime.set(NORMAL_DELAY_TIME);
}
}
catch (Exception e)
{
receiver.acquireWakeLock();
idling.set(false);
receiver.setPushActive(getName(), false);
try
@ -2000,40 +2034,39 @@ public class ImapStore extends Store {
}
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)
{
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
{
Log.e(Email.LOG_TAG, "Got exception while idling", e);
try
Log.e(Email.LOG_TAG, "Got exception while idling for " + getLogId(), e);
int delayTimeInt = delayTime.get();
receiver.sleep(delayTimeInt);
delayTimeInt *= 2;
if (delayTimeInt > MAX_DELAY_TIME)
{
Thread.sleep(delayTime);
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);
delayTimeInt = MAX_DELAY_TIME;
}
delayTime.set(delayTimeInt);
}
}
}
try
{
Log.i(Email.LOG_TAG, "Pusher for " + getLogId() + " is exiting");
close(false);
receiver.pushComplete();
Log.i(Email.LOG_TAG, "Pusher for " + getName() + " is exiting");
}
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)
{
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)
{
@ -2063,7 +2097,7 @@ public class ImapStore extends Store {
}
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
for (Integer msgSeq : flagSyncMsgSeqsCopy)
@ -2108,14 +2142,14 @@ public class ImapStore extends Store {
int msgSeq = response.getNumber(0);
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);
}
}
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)
{
Log.v(Email.LOG_TAG, "Closing mConnection to stop pushing");
Log.v(Email.LOG_TAG, "Closing mConnection to stop pushing for " + getLogId());
}
mConnection.close();
}
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)
{
receiver.pushInProgress();
receiver.acquireWakeLock();
started = true;
}
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
{
@ -2194,7 +2228,7 @@ public class ImapStore extends Store {
}
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)
{
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)
{
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)
{
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)
{
Log.e(Email.LOG_TAG, "Got exception while stopping", e);
Log.e(Email.LOG_TAG, "Got exception while stopping " + folderPusher.getName(), e);
}
}
folderPushers.clear();

View File

@ -1,50 +1,183 @@
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.Uri;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.android.email.Email;
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);
WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
wakeLock.setReferenceCounted(false);
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,
// otherwise there's no point to it. We're trying to give the MailService some time to start.
Log.v(Email.LOG_TAG, "BootReceiver.onReceive" + intent);
Integer tmpWakeLockId = wakeLockSeq.getAndIncrement();
wakeLocks.put(tmpWakeLockId, wakeLock);
return tmpWakeLockId;
}
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())) {
Email.setServicesEnabled(context);
}
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
MailService.actionCancel(context);
}
else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
MailService.actionReschedule(context);
}
else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
MailService.connectivityChange(context, !noConnectivity);
}
else if (ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED.equals(intent.getAction())) {
MailService.backgroundDataChanged(context);
}
// else if (Intent.ACTION_BATTERY_LOW.equals(intent.getAction())) {
// MailService.batteryStatusChange(context, true);
// }
// else if (Intent.ACTION_BATTERY_OKAY.equals(intent.getAction())) {
// MailService.batteryStatusChange(context, false);
// }
Intent i = new Intent(context, BootReceiver.class);
i.setAction(FIRE_INTENT);
i.putExtra(ALARMED_INTENT, alarmedIntent);
Uri uri = Uri.parse("action://" + alarmedAction);
i.setData(uri);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
return pi;
}
public static void scheduleIntent(Context context, long atTime, Intent alarmedIntent)
{
Log.i(Email.LOG_TAG, "BootReceiver Got request to schedule alarmedIntent " + alarmedIntent.getAction());
Intent i = new Intent();
i.setClass(context, BootReceiver.class);
i.setAction(SCHEDULE_INTENT);
i.putExtra(ALARMED_INTENT, alarmedIntent);
i.putExtra(AT_TIME, atTime);
context.sendBroadcast(i);
}
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.Date;
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.NotificationManager;
import android.app.PendingIntent;
@ -14,7 +15,6 @@ import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.NetworkInfo.State;
import android.os.IBinder;
import android.os.PowerManager;
@ -28,72 +28,64 @@ import com.android.email.MessagingController;
import com.android.email.MessagingListener;
import com.android.email.Preferences;
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.Pusher;
import com.android.email.EmailReceivedIntent;
/**
*/
public class MailService extends Service {
private static final String ACTION_APP_STARTED = "com.android.email.intent.action.MAIL_SERVICE_APP_STARTED";
public class MailService extends CoreService {
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_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_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 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 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;
private int mStartId;
public static void actionReschedule(Context context) {
public static void actionReschedule(Context context, Integer wakeLockId) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_RESCHEDULE);
addWakeLockId(i, wakeLockId);
context.startService(i);
}
public static void appStarted(Context context) {
public static void rescheduleCheck(Context context, Integer wakeLockId) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_APP_STARTED);
i.setAction(MailService.ACTION_RESCHEDULE_CHECK);
addWakeLockId(i, wakeLockId);
context.startService(i);
}
// private static void checkMail(Context context) {
// Intent i = new Intent();
// i.setClass(context, MailService.class);
// i.setAction(MailService.ACTION_CHECK_MAIL);
// context.startService(i);
// }
public static void actionCancel(Context context) {
public static void actionCancel(Context context, Integer wakeLockId) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_CANCEL);
addWakeLockId(i, wakeLockId);
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();
i.setClass(context, MailService.class);
i.setAction(MailService.CONNECTIVITY_CHANGE);
i.putExtra(HAS_CONNECTIVITY, hasConnectivity);
addWakeLockId(i, wakeLockId);
context.startService(i);
}
public static void backgroundDataChanged(Context context) {
public static void backgroundDataChanged(Context context, Integer wakeLockId) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.BACKGROUND_DATA_CHANGED);
addWakeLockId(i, wakeLockId);
context.startService(i);
}
@ -104,26 +96,22 @@ public class MailService extends Service {
}
@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);
public void startService(Intent intent, int startId) {
Integer startIdObj = startId;
long startTime = System.currentTimeMillis();
try
{
ConnectivityManager connectivityManager = (ConnectivityManager)getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
boolean doBackground = true;
state = State.DISCONNECTED;
boolean hasConnectivity = false;
if (connectivityManager != null)
{
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
if (netInfo != null)
{
state = netInfo.getState();
State state = netInfo.getState();
hasConnectivity = state == State.CONNECTED;
}
boolean backgroundData = connectivityManager.getBackgroundDataSetting();
@ -135,38 +123,19 @@ public class MailService extends Service {
setForeground(true); // if it gets killed once, it'll never restart
Log.i(Email.LOG_TAG, "MailService.onStart(" + intent + ", " + startId
+ "), state = " + state + ", doBackground = " + doBackground);
super.onStart(intent, startId);
this.mStartId = startId;
+ "), hasConnectivity = " + hasConnectivity + ", doBackground = " + doBackground);
// MessagingController.getInstance(getApplication()).addListener(mListener);
if (ACTION_CHECK_MAIL.equals(intent.getAction())) {
//if (Config.LOGV) {
MessagingController.getInstance(getApplication()).log("***** MailService *****: checking mail");
Log.v(Email.LOG_TAG, "***** MailService *****: checking mail");
//}
if (state == State.CONNECTED && doBackground)
Log.i(Email.LOG_TAG, "***** MailService *****: checking mail");
if (hasConnectivity && doBackground)
{
MessagingController controller = MessagingController.getInstance(getApplication());
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();
}
PollService.startService(this);
}
reschedule();
// stopSelf(startId);
reschedule(startIdObj);
startIdObj = null;
}
else if (ACTION_CANCEL.equals(intent.getAction())) {
if (Config.LOGV) {
@ -175,57 +144,39 @@ public class MailService extends Service {
MessagingController.getInstance(getApplication()).log("***** MailService *****: cancel");
cancel();
// stopSelf(startId);
}
else if (ACTION_RESCHEDULE.equals(intent.getAction())) {
if (Config.LOGV) {
Log.v(Email.LOG_TAG, "***** MailService *****: reschedule");
}
rescheduleAll(hasConnectivity, doBackground, startIdObj);
startIdObj = null;
MessagingController.getInstance(getApplication()).log("***** MailService *****: reschedule");
boolean polling = false;
boolean pushing = false;
if (state == State.CONNECTED && doBackground)
{
polling = reschedule();
pushing = reschedulePushers();
}
else if (ACTION_RESCHEDULE_CHECK.equals(intent.getAction())) {
if (Config.LOGV) {
Log.v(Email.LOG_TAG, "***** MailService *****: reschedule check");
}
else
{
stopPushers();
}
if (polling == false && pushing == false)
{
Log.i(Email.LOG_TAG, "Neither pushing nor polling, so stopping");
stopSelf(startId);
}
// stopSelf(startId);
reschedule(startIdObj);
startIdObj = null;
MessagingController.getInstance(getApplication()).log("***** MailService *****: reschedule");
}
else if (ACTION_REFRESH_PUSHERS.equals(intent.getAction()))
{
schedulePushers();
try
if (hasConnectivity && doBackground)
{
if (state == State.CONNECTED && doBackground)
{
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);
schedulePushers(null);
refreshPushers(startIdObj);
startIdObj = null;
}
}
else if (CONNECTIVITY_CHANGE.equals(intent.getAction()) ||
BACKGROUND_DATA_CHANGED.equals(intent.getAction()))
{
actionReschedule(this);
boolean hasConnectivity = intent.getBooleanExtra(HAS_CONNECTIVITY, true);
rescheduleAll(hasConnectivity, doBackground, startIdObj);
startIdObj = null;
Log.i(Email.LOG_TAG, "Got connectivity action with hasConnectivity = " + hasConnectivity + ", doBackground = " + doBackground);
notifyConnectionStatus(hasConnectivity);
@ -234,20 +185,31 @@ public class MailService extends Service {
{
notifyConnectionStatus(true);
}
else if (ACTION_APP_STARTED.equals(intent.getAction()))
{
// Not needed for now, but might be useful later
}
}
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)
{
NotificationManager notifMgr =
@ -286,238 +248,206 @@ public class MailService extends Service {
}
private void cancel() {
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent();
i.setClassName(getApplication().getPackageName(), "com.android.email.service.MailService");
i.setAction(ACTION_CHECK_MAIL);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
alarmMgr.cancel(pi);
BootReceiver.cancelIntent(this, i);
}
private boolean reschedule() {
boolean polling = true;
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent();
i.setClassName(getApplication().getPackageName(), "com.android.email.service.MailService");
i.setAction(ACTION_CHECK_MAIL);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
private void reschedule(Integer startId) {
execute(getApplication(), new Runnable()
{
public void run()
{
int shortestInterval = -1;
for (Account account : Preferences.getPreferences(MailService.this).getAccounts()) {
if (account.getAutomaticCheckIntervalMinutes() != -1
&& (account.getAutomaticCheckIntervalMinutes() < shortestInterval || shortestInterval == -1)) {
shortestInterval = account.getAutomaticCheckIntervalMinutes();
}
}
int shortestInterval = -1;
for (Account account : Preferences.getPreferences(this).getAccounts()) {
if (account.getAutomaticCheckIntervalMinutes() != -1
&& (account.getAutomaticCheckIntervalMinutes() < shortestInterval || shortestInterval == -1)) {
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)
if (shortestInterval == -1) {
Log.v(Email.LOG_TAG, "No next check scheduled for package " + getApplication().getPackageName());
cancel();
}
else
{
pushing = true;
Log.i(Email.LOG_TAG, "Starting configured pusher for account " + account.getDescription());
pusher.start();
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);
}
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();
}
return pushing;
}, Email.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId);
}
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();
for (Pusher pusher : pushers)
Collection<Pusher> pushers = MessagingController.getInstance(getApplication()).getPushers();
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();
if (interval != -1 && (interval < minInterval || minInterval == -1))
public void run()
{
minInterval = interval;
}
}
if (Email.DEBUG)
{
Log.v(Email.LOG_TAG, "Pusher refresh interval = " + minInterval);
}
if (minInterval != -1)
{
try
{
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
long nextTime = System.currentTimeMillis() + minInterval;
String checkString = "Next pusher refresh scheduled for " + new Date(nextTime);
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, checkString);
Log.i(Email.LOG_TAG, "MailService running Runnable " + runner.hashCode() + " with startId " + startId);
runner.run();
}
finally
{
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);
alarmMgr.set(AlarmManager.RTC_WAKEUP, nextTime, pi);
}
};
threadPool.execute(myRunner);
}
public IBinder onBind(Intent intent) {
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;
}
}