Prevent MailService from potential looping due to EAS accounts

* The code assumed that all accounts used the scheduler in MailService
  whereas only those using MessagingController do so (i.e. EAS does
  not)
* Change setupSyncReportsLocked to set the syncInterval for accounts
  that don't use MessagingController to Account.CHECK_INTERVAL_NEVER
* Add unit test for the changed code

Change-Id: I74a3dae21d9ec16f9903bdf2a1c28092ae89cc50
This commit is contained in:
Marc Blank 2010-06-25 12:38:36 -07:00
parent 42e3f10a95
commit 42ff939e3a
2 changed files with 148 additions and 14 deletions

View File

@ -31,6 +31,7 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
@ -77,14 +78,16 @@ public class MailService extends Service {
private static final String[] NEW_MESSAGE_COUNT_PROJECTION =
new String[] {AccountColumns.NEW_MESSAGE_COUNT};
/*package*/ Controller mController;
private final Controller.Result mControllerCallback = new ControllerResults();
private ContentResolver mContentResolver;
private int mStartId;
/**
* Access must be synchronized, because there are accesses from the Controller callback
*/
private static HashMap<Long,AccountSyncReport> mSyncReports =
/*package*/ static HashMap<Long,AccountSyncReport> mSyncReports =
new HashMap<Long,AccountSyncReport>();
/**
@ -161,8 +164,9 @@ public class MailService extends Service {
this.mStartId = startId;
String action = intent.getAction();
Controller controller = Controller.getInstance(getApplication());
controller.addResultCallback(mControllerCallback);
mController = Controller.getInstance(this);
mController.addResultCallback(mControllerCallback);
mContentResolver = getContentResolver();
if (ACTION_CHECK_MAIL.equals(action)) {
// If we have the data, restore the last-sync-times for each account
@ -182,7 +186,7 @@ public class MailService extends Service {
// Start sync if account is given and background data is enabled.
boolean syncStarted = false;
if (checkAccountId != -1 && isBackgroundDataEnabled()) {
syncStarted = syncOneAccount(controller, checkAccountId, startId);
syncStarted = syncOneAccount(mController, checkAccountId, startId);
}
// Reschedule if we didn't start sync.
@ -224,7 +228,7 @@ public class MailService extends Service {
} else if (ACTION_NOTIFY_MAIL.equals(action)) {
long accountId = intent.getLongExtra(EXTRA_CHECK_ACCOUNT, -1);
// Get the current new message count
Cursor c = getContentResolver().query(
Cursor c = mContentResolver.query(
ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
NEW_MESSAGE_COUNT_PROJECTION, null, null, null);
int newMessageCount = 0;
@ -284,7 +288,7 @@ public class MailService extends Service {
// Delete the sync reports to force a refresh from live account db data
mSyncReports.clear();
setupSyncReportsLocked(-1);
setupSyncReportsLocked(-1, mContentResolver);
// Restore prev-sync & next-sync times for any reports in the new list
for (AccountSyncReport newReport : mSyncReports.values()) {
@ -319,9 +323,10 @@ public class MailService extends Service {
long timeNow = SystemClock.elapsedRealtime();
for (AccountSyncReport report : mSyncReports.values()) {
if (report.syncInterval <= 0) { // no timed checks - skip
if (report.syncInterval <= 0) { // no timed checks - skip
continue;
}
long prevSyncTime = report.prevSyncTime;
long nextSyncTime = report.nextSyncTime;
@ -417,7 +422,7 @@ public class MailService extends Service {
/**
* Note: Times are relative to SystemClock.elapsedRealtime()
*/
private static class AccountSyncReport {
/*package*/ static class AccountSyncReport {
long accountId;
long prevSyncTime; // 0 == unknown
long nextSyncTime; // 0 == ASAP -1 == don't sync
@ -448,14 +453,14 @@ public class MailService extends Service {
*/
/* package */ void setupSyncReports(long accountId) {
synchronized (mSyncReports) {
setupSyncReportsLocked(accountId);
setupSyncReportsLocked(accountId, mContentResolver);
}
}
/**
* Handle the work of setupSyncReports. Must be synchronized on mSyncReports.
*/
private void setupSyncReportsLocked(long accountId) {
/*package*/ void setupSyncReportsLocked(long accountId, ContentResolver resolver) {
if (accountId == -1) {
// -1 == reload the list if empty, otherwise exit immediately
if (mSyncReports.size() > 0) {
@ -477,17 +482,19 @@ public class MailService extends Service {
}
// TODO use a narrower projection here
Cursor c = getContentResolver().query(uri, Account.CONTENT_PROJECTION,
null, null, null);
Cursor c = resolver.query(uri, Account.CONTENT_PROJECTION, null, null, null);
try {
while (c.moveToNext()) {
AccountSyncReport report = new AccountSyncReport();
int syncInterval = c.getInt(Account.CONTENT_SYNC_INTERVAL_COLUMN);
int flags = c.getInt(Account.CONTENT_FLAGS_COLUMN);
long id = c.getInt(Account.CONTENT_ID_COLUMN);
String ringtoneString = c.getString(Account.CONTENT_RINGTONE_URI_COLUMN);
// For debugging only
if (DEBUG_FORCE_QUICK_REFRESH && syncInterval >= 0) {
// If we're not using MessagingController (EAS at this point), don't schedule syncs
if (!mController.isMessagingController(id)) {
syncInterval = Account.CHECK_INTERVAL_NEVER;
} else if (DEBUG_FORCE_QUICK_REFRESH && syncInterval >= 0) {
syncInterval = 1;
}

View File

@ -0,0 +1,127 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.service;
import com.android.email.Controller;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.HostAuth;
import com.android.email.service.MailService.AccountSyncReport;
import android.content.ContentValues;
import android.content.Context;
import android.test.ProviderTestCase2;
import java.util.HashMap;
/**
* Tests of the Email provider.
*
* You can run this entire test case with:
* runtest -c com.android.email.service.MailServiceTests email
*/
public class MailServiceTests extends ProviderTestCase2<EmailProvider> {
EmailProvider mProvider;
Context mMockContext;
public MailServiceTests() {
super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
}
@Override
public void setUp() throws Exception {
super.setUp();
mMockContext = getMockContext();
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
/**
* Lightweight subclass of the Controller class allows injection of mock context
*/
public static class TestController extends Controller {
protected TestController(Context providerContext, Context systemContext) {
super(systemContext);
setProviderContext(providerContext);
}
}
/**
* Create a simple HostAuth with protocol
*/
private HostAuth setupSimpleHostAuth(String protocol) {
HostAuth hostAuth = new HostAuth();
hostAuth.mProtocol = protocol;
return hostAuth;
}
/**
* Initial testing on setupSyncReportsLocked, making sure that EAS accounts aren't scheduled
*/
public void testSetupSyncReportsLocked() {
// TODO Test other functionality within setupSyncReportsLocked
// Setup accounts of each type, all with manual sync at different intervals
Account easAccount = ProviderTestUtils.setupAccount("account1", false, mMockContext);
easAccount.mHostAuthRecv = setupSimpleHostAuth("eas");
easAccount.mSyncInterval = 30;
easAccount.save(mMockContext);
Account imapAccount = ProviderTestUtils.setupAccount("account2", false, mMockContext);
imapAccount.mHostAuthRecv = setupSimpleHostAuth("imap");
imapAccount.mSyncInterval = 60;
imapAccount.save(mMockContext);
Account pop3Account = ProviderTestUtils.setupAccount("account3", false, mMockContext);
pop3Account.mHostAuthRecv = setupSimpleHostAuth("pop3");
pop3Account.mSyncInterval = 90;
pop3Account.save(mMockContext);
// Setup the SyncReport's for these Accounts
MailService mailService = new MailService();
mailService.mController = new TestController(mMockContext, getContext());
mailService.setupSyncReportsLocked(-1, mMockContext.getContentResolver());
// Get back the map created by MailService
HashMap<Long, AccountSyncReport> syncReportMap = MailService.mSyncReports;
// Check the SyncReport's for correctness of sync interval
AccountSyncReport syncReport = syncReportMap.get(easAccount.mId);
assertNotNull(syncReport);
// EAS sync interval should have been changed to "never"
assertEquals(Account.CHECK_INTERVAL_NEVER, syncReport.syncInterval);
syncReport = syncReportMap.get(imapAccount.mId);
assertNotNull(syncReport);
assertEquals(60, syncReport.syncInterval);
syncReport = syncReportMap.get(pop3Account.mId);
assertNotNull(syncReport);
assertEquals(90, syncReport.syncInterval);
// Change the EAS account to push
ContentValues cv = new ContentValues();
cv.put(Account.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
easAccount.update(mMockContext, cv);
syncReportMap.clear();
mailService.setupSyncReportsLocked(easAccount.mId, mMockContext.getContentResolver());
syncReport = syncReportMap.get(easAccount.mId);
assertNotNull(syncReport);
// EAS sync interval should be "never" in this case as well
assertEquals(Account.CHECK_INTERVAL_NEVER, syncReport.syncInterval);
}
}