MailService.java revision 72dce733900a812cf11a5ac08756362030556849
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.service; 18 19import com.android.email.Account; 20import com.android.email.Email; 21import com.android.email.MessagingController; 22import com.android.email.MessagingListener; 23import com.android.email.R; 24import com.android.email.activity.AccountFolderList; 25import com.android.email.activity.FolderMessageList; 26import com.android.email.mail.MessagingException; 27import com.android.email.mail.Store; 28import com.android.email.mail.store.LocalStore; 29import com.android.email.provider.EmailContent; 30 31import android.app.AlarmManager; 32import android.app.Notification; 33import android.app.NotificationManager; 34import android.app.PendingIntent; 35import android.app.Service; 36import android.content.Context; 37import android.content.Intent; 38import android.database.Cursor; 39import android.net.Uri; 40import android.os.IBinder; 41import android.os.SystemClock; 42import android.text.TextUtils; 43import android.util.Config; 44import android.util.Log; 45 46import java.util.ArrayList; 47import java.util.HashMap; 48 49/** 50 */ 51public class MailService extends Service { 52 private static final String ACTION_CHECK_MAIL = "com.android.email.intent.action.MAIL_SERVICE_WAKEUP"; 53 private static final String ACTION_RESCHEDULE = "com.android.email.intent.action.MAIL_SERVICE_RESCHEDULE"; 54 private static final String ACTION_CANCEL = "com.android.email.intent.action.MAIL_SERVICE_CANCEL"; 55 56 private static final String EXTRA_CHECK_ACCOUNT = "com.android.email.intent.extra.ACCOUNT"; 57 58 private Listener mListener = new Listener(); 59 60 private int mStartId; 61 62 public static void actionReschedule(Context context) { 63 Intent i = new Intent(); 64 i.setClass(context, MailService.class); 65 i.setAction(MailService.ACTION_RESCHEDULE); 66 context.startService(i); 67 } 68 69 public static void actionCancel(Context context) { 70 Intent i = new Intent(); 71 i.setClass(context, MailService.class); 72 i.setAction(MailService.ACTION_CANCEL); 73 context.startService(i); 74 } 75 76 /** 77 * Entry point for asynchronous message services (e.g. push mode) to post notifications of new 78 * messages. Note: Although this is not a blocking call, it will start the MessagingController 79 * which will attempt to load the new messages. So the Store should expect to be opened and 80 * fetched from shortly after making this call. 81 * 82 * @param storeUri the Uri of the store that is reporting new messages 83 */ 84 public static void actionNotifyNewMessages(Context context, String storeUri) { 85 Intent i = new Intent(ACTION_CHECK_MAIL); 86 i.setClass(context, MailService.class); 87 i.putExtra(EXTRA_CHECK_ACCOUNT, storeUri); 88 context.startService(i); 89 } 90 91 @Override 92 public void onStart(Intent intent, int startId) { 93 super.onStart(intent, startId); 94 this.mStartId = startId; 95 96 MessagingController controller = MessagingController.getInstance(getApplication()); 97 controller.addListener(mListener); 98 if (ACTION_CHECK_MAIL.equals(intent.getAction())) { 99 if (Config.LOGD && Email.DEBUG) { 100 Log.d(Email.LOG_TAG, "*** MailService: checking mail"); 101 } 102 // Only check mail for accounts that have enabled automatic checking. There is still 103 // a bug here in that we check every enabled account, on every refresh - irrespective 104 // of that account's refresh frequency - but this fixes the worst case of checking 105 // accounts that should not have been checked at all. 106 // Also note: Due to the organization of this service, you must gather the accounts 107 // and make a single call to controller.checkMail(). 108 109 // TODO: Notification for single push account will fire up checks on all other 110 // accounts. This needs to be cleaned up for better efficiency. 111 String specificStoreUri = intent.getStringExtra(EXTRA_CHECK_ACCOUNT); 112 113 ArrayList<EmailContent.Account> accountsToCheck = new ArrayList<EmailContent.Account>(); 114 115 Cursor c = null; 116 try { 117 c = this.getContentResolver().query( 118 EmailContent.Account.CONTENT_URI, 119 EmailContent.Account.CONTENT_PROJECTION, 120 null, null, null); 121 while (c.moveToNext()) { 122 EmailContent.Account account = EmailContent.getContent(c, 123 EmailContent.Account.class); 124 int interval = account.getAutomaticCheckIntervalMinutes(); 125 String storeUri = account.getStoreUri(this); 126 if (interval > 0 || (storeUri != null && storeUri.equals(specificStoreUri))) { 127 accountsToCheck.add(account); 128 } 129 130 // For each account, switch pushmail on or off 131 enablePushMail(account, interval == EmailContent.Account.CHECK_INTERVAL_PUSH); 132 } 133 } finally { 134 if (c != null) { 135 c.close(); 136 } 137 } 138 139 EmailContent.Account[] accounts = accountsToCheck.toArray( 140 new EmailContent.Account[accountsToCheck.size()]); 141 controller.checkMail(this, accounts, mListener); 142 } 143 else if (ACTION_CANCEL.equals(intent.getAction())) { 144 if (Config.LOGD && Email.DEBUG) { 145 Log.d(Email.LOG_TAG, "*** MailService: cancel"); 146 } 147 cancel(); 148 stopSelf(startId); 149 } 150 else if (ACTION_RESCHEDULE.equals(intent.getAction())) { 151 if (Config.LOGD && Email.DEBUG) { 152 Log.d(Email.LOG_TAG, "*** MailService: reschedule"); 153 } 154 reschedule(); 155 stopSelf(startId); 156 } 157 } 158 159 @Override 160 public void onDestroy() { 161 super.onDestroy(); 162 MessagingController.getInstance(getApplication()).removeListener(mListener); 163 } 164 165 private void cancel() { 166 AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE); 167 Intent i = new Intent(); 168 i.setClassName("com.android.email", "com.android.email.service.MailService"); 169 i.setAction(ACTION_CHECK_MAIL); 170 PendingIntent pi = PendingIntent.getService(this, 0, i, 0); 171 alarmMgr.cancel(pi); 172 } 173 174 private void reschedule() { 175 AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE); 176 Intent i = new Intent(); 177 i.setClassName("com.android.email", "com.android.email.service.MailService"); 178 i.setAction(ACTION_CHECK_MAIL); 179 PendingIntent pi = PendingIntent.getService(this, 0, i, 0); 180 181 int shortestInterval = -1; 182 Cursor c = null; 183 try { 184 c = this.getContentResolver().query( 185 EmailContent.Account.CONTENT_URI, 186 EmailContent.Account.CONTENT_PROJECTION, 187 null, null, null); 188 while (c.moveToNext()) { 189 EmailContent.Account account = EmailContent.getContent(c, 190 EmailContent.Account.class); 191 int interval = account.getAutomaticCheckIntervalMinutes(); 192 if (interval > 0 && (interval < shortestInterval || shortestInterval == -1)) { 193 shortestInterval = interval; 194 } 195 enablePushMail(account, interval == Account.CHECK_INTERVAL_PUSH); 196 } 197 } finally { 198 if (c != null) { 199 c.close(); 200 } 201 } 202 203 if (shortestInterval == -1) { 204 alarmMgr.cancel(pi); 205 } 206 else { 207 alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() 208 + (shortestInterval * (60 * 1000)), pi); 209 } 210 } 211 212 public IBinder onBind(Intent intent) { 213 return null; 214 } 215 216 class Listener extends MessagingListener { 217 HashMap<EmailContent.Account, Integer> accountsWithNewMail = 218 new HashMap<EmailContent.Account, Integer>(); 219 220 // TODO this should be redone because account is usually null, not very interesting. 221 // I think it would make more sense to pass Account[] here in case anyone uses it 222 // In any case, it should be noticed that this is called once per cycle 223 @Override 224 public void checkMailStarted(Context context, EmailContent.Account account) { 225 accountsWithNewMail.clear(); 226 } 227 228 // Called once per checked account 229 @Override 230 public void checkMailFailed(Context context, EmailContent.Account account, String reason) { 231 if (Config.LOGD && Email.DEBUG) { 232 Log.d(Email.LOG_TAG, "*** MailService: checkMailFailed: " + reason); 233 } 234 reschedule(); 235 stopSelf(mStartId); 236 } 237 238 // Called once per checked account 239 @Override 240 public void synchronizeMailboxFinished(EmailContent.Account account, 241 EmailContent.Mailbox folder, int totalMessagesInMailbox, int numNewMessages) { 242 if (Config.LOGD && Email.DEBUG) { 243 Log.d(Email.LOG_TAG, "*** MailService: synchronizeMailboxFinished: total=" + 244 totalMessagesInMailbox + " new=" + numNewMessages); 245 } 246 if (numNewMessages > 0 && 247 ((account.getFlags() & EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL) != 0)) { 248 accountsWithNewMail.put(account, numNewMessages); 249 } 250 } 251 252 // TODO this should be redone because account is usually null, not very interesting. 253 // I think it would make more sense to pass Account[] here in case anyone uses it 254 // In any case, it should be noticed that this is called once per cycle 255 @Override 256 public void checkMailFinished(Context context, EmailContent.Account account) { 257 if (Config.LOGD && Email.DEBUG) { 258 Log.d(Email.LOG_TAG, "*** MailService: checkMailFinished"); 259 } 260 NotificationManager notifMgr = (NotificationManager)context 261 .getSystemService(Context.NOTIFICATION_SERVICE); 262 263 if (accountsWithNewMail.size() > 0) { 264 Notification notif = new Notification(R.drawable.stat_notify_email_generic, 265 getString(R.string.notification_new_title), System.currentTimeMillis()); 266 boolean vibrate = false; 267 String ringtone = null; 268 if (accountsWithNewMail.size() > 1) { 269 for (EmailContent.Account account1 : accountsWithNewMail.keySet()) { 270 if ((account1.getFlags() & EmailContent.Account.FLAGS_VIBRATE) != 0) { 271 vibrate = true; 272 } 273 ringtone = account1.getRingtone(); 274 } 275 Intent i = new Intent(context, AccountFolderList.class); 276 PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0); 277 notif.setLatestEventInfo(context, getString(R.string.notification_new_title), 278 getResources(). 279 getQuantityString(R.plurals.notification_new_multi_account_fmt, 280 accountsWithNewMail.size(), 281 accountsWithNewMail.size()), pi); 282 } else { 283 EmailContent.Account account1 = accountsWithNewMail.keySet().iterator().next(); 284 int totalNewMails = accountsWithNewMail.get(account1); 285 Intent i = FolderMessageList.actionHandleAccountIntent(context, 286 account1.mId, Email.INBOX); 287 PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0); 288 notif.setLatestEventInfo(context, getString(R.string.notification_new_title), 289 getResources(). 290 getQuantityString(R.plurals.notification_new_one_account_fmt, 291 totalNewMails, totalNewMails, 292 account1.getDescription()), pi); 293 vibrate = ((account1.getFlags() & EmailContent.Account.FLAGS_VIBRATE) != 0); 294 ringtone = account1.getRingtone(); 295 } 296 notif.defaults = Notification.DEFAULT_LIGHTS; 297 notif.sound = TextUtils.isEmpty(ringtone) ? null : Uri.parse(ringtone); 298 if (vibrate) { 299 notif.defaults |= Notification.DEFAULT_VIBRATE; 300 } 301 notifMgr.notify(1, notif); 302 } 303 304 reschedule(); 305 stopSelf(mStartId); 306 } 307 } 308 309 /** 310 * For any account that wants push mail, get its Store and start the pushmail service. 311 * This function makes no attempt to optimize, so accounts may have push enabled (or disabled) 312 * repeatedly, and should handle this appropriately. 313 * 314 * @param account the account that needs push delivery enabled 315 */ 316 private void enablePushMail(EmailContent.Account account, boolean enable) { 317 try { 318 String localUri = account.getLocalStoreUri(this); 319 String storeUri = account.getStoreUri(this); 320 if (localUri != null && storeUri != null) { 321 LocalStore localStore = (LocalStore) Store.getInstance( 322 localUri, this.getBaseContext(), null); 323 Store store = Store.getInstance(storeUri, this.getBaseContext(), 324 localStore.getPersistentCallbacks()); 325 if (store != null) { 326 store.enablePushModeDelivery(enable); 327 } 328 } 329 } catch (MessagingException me) { 330 if (Config.LOGD && Email.DEBUG) { 331 Log.d(Email.LOG_TAG, "Failed to enable push mail for account" + account.getName() + 332 " with exception " + me.toString()); 333 } 334 } 335 } 336} 337