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