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