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