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