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