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