1/*
2 * Copyright (C) 2010 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 android.accounts.AccountManager;
20import android.content.ComponentName;
21import android.content.ContentResolver;
22import android.content.ContentUris;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.pm.PackageManager;
26
27import com.android.email.AccountTestCase;
28import com.android.email.Controller;
29import com.android.email.provider.AccountReconciler;
30import com.android.email.provider.EmailProvider;
31import com.android.email.provider.ProviderTestUtils;
32import com.android.email.service.MailService.AccountSyncReport;
33import com.android.emailcommon.provider.Account;
34import com.android.emailcommon.provider.EmailContent;
35import com.android.emailcommon.provider.HostAuth;
36
37import java.util.ArrayList;
38import java.util.HashMap;
39import java.util.List;
40
41/**
42 * Tests of the Email provider.
43 *
44 * You can run this entire test case with:
45 *   runtest -c com.android.email.service.MailServiceTests email
46 */
47public class MailServiceTests extends AccountTestCase {
48
49    EmailProvider mProvider;
50    Context mMockContext;
51
52    public MailServiceTests() {
53        super();
54    }
55
56    @Override
57    public void setUp() throws Exception {
58        super.setUp();
59        PackageManager pm = getContext().getPackageManager();
60        pm.setComponentEnabledSetting(
61                new ComponentName(getContext(), EasTestAuthenticatorService.class),
62                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
63                PackageManager.DONT_KILL_APP);
64        mMockContext = getMockContext();
65        // Delete any test accounts we might have created earlier
66        deleteTemporaryAccountManagerAccounts();
67    }
68
69    @Override
70    public void tearDown() throws Exception {
71        super.tearDown();
72        // Delete any test accounts we might have created earlier
73        deleteTemporaryAccountManagerAccounts();
74    }
75
76    /**
77     * Confirm that the test below is functional (and non-destructive) when there are
78     * prexisting (non-test) accounts in the account manager.
79     */
80    public void testTestReconcileAccounts() {
81        Account firstAccount = null;
82        final String TEST_USER_ACCOUNT = "__user_account_test_1";
83        Context context = getContext();
84        try {
85            // Note:  Unlike calls to setupProviderAndAccountManagerAccount(), we are creating
86            // *real* accounts here (not in the mock provider)
87            createAccountManagerAccount(TEST_USER_ACCOUNT + TEST_ACCOUNT_SUFFIX);
88            firstAccount = ProviderTestUtils.setupAccount(TEST_USER_ACCOUNT, true, context);
89            // Now run the test with the "user" accounts in place
90            testReconcileAccounts();
91        } finally {
92            if (firstAccount != null) {
93                boolean firstAccountFound = false;
94                // delete the provider account
95                context.getContentResolver().delete(firstAccount.getUri(), null, null);
96                // delete the account manager account
97                android.accounts.Account[] accountManagerAccounts = AccountManager.get(context)
98                        .getAccountsByType(TEST_ACCOUNT_TYPE);
99                for (android.accounts.Account accountManagerAccount: accountManagerAccounts) {
100                    if ((TEST_USER_ACCOUNT + TEST_ACCOUNT_SUFFIX)
101                            .equals(accountManagerAccount.name)) {
102                        deleteAccountManagerAccount(accountManagerAccount);
103                        firstAccountFound = true;
104                    }
105                }
106                assertTrue(firstAccountFound);
107            }
108        }
109    }
110
111    /**
112     * Note, there is some inherent risk in this test, as it creates *real* accounts in the
113     * system (it cannot use the mock context with the Account Manager).
114     */
115    public void testReconcileAccounts() {
116        // Note that we can't use mMockContext for AccountManager interactions, as it isn't a fully
117        // functional Context.
118        Context context = getContext();
119
120        // Capture the baseline (account manager accounts) so we can measure the changes
121        // we're making, irrespective of the number of actual accounts, and not destroy them
122        android.accounts.Account[] baselineAccounts =
123            AccountManager.get(context).getAccountsByType(TEST_ACCOUNT_TYPE);
124
125        // Set up three accounts, both in AccountManager and in EmailProvider
126        Account firstAccount = setupProviderAndAccountManagerAccount(getTestAccountName("1"));
127        setupProviderAndAccountManagerAccount(getTestAccountName("2"));
128        setupProviderAndAccountManagerAccount(getTestAccountName("3"));
129
130        // Check that they're set up properly
131        assertEquals(3, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null));
132        android.accounts.Account[] accountManagerAccounts =
133                getAccountManagerAccounts(baselineAccounts);
134        assertEquals(3, accountManagerAccounts.length);
135
136        // Delete account "2" from AccountManager
137        android.accounts.Account removedAccount =
138            makeAccountManagerAccount(getTestAccountEmailAddress("2"));
139        deleteAccountManagerAccount(removedAccount);
140
141        // Confirm it's deleted
142        accountManagerAccounts = getAccountManagerAccounts(baselineAccounts);
143        assertEquals(2, accountManagerAccounts.length);
144
145        // Run the reconciler
146        ContentResolver resolver = mMockContext.getContentResolver();
147        MailService.reconcileAccountsWithAccountManager(context,
148                makeExchangeServiceAccountList(), accountManagerAccounts, mMockContext);
149
150        // There should now be only two EmailProvider accounts
151        assertEquals(2, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null));
152
153        // Ok, now we've got two of each; let's delete a provider account
154        resolver.delete(ContentUris.withAppendedId(Account.CONTENT_URI, firstAccount.mId),
155                null, null);
156        // ...and then there was one
157        assertEquals(1, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null));
158
159        // Run the reconciler
160        MailService.reconcileAccountsWithAccountManager(context,
161                makeExchangeServiceAccountList(), accountManagerAccounts, mMockContext);
162
163        // There should now be only one AccountManager account
164        accountManagerAccounts = getAccountManagerAccounts(baselineAccounts);
165        assertEquals(1, accountManagerAccounts.length);
166        // ... and it should be account "3"
167        assertEquals(getTestAccountEmailAddress("3"), accountManagerAccounts[0].name);
168    }
169
170    /**
171     * Lightweight subclass of the Controller class allows injection of mock context
172     */
173    public static class TestController extends Controller {
174
175        protected TestController(Context providerContext, Context systemContext) {
176            super(systemContext);
177            setProviderContext(providerContext);
178        }
179    }
180
181    /**
182     * Create a simple HostAuth with protocol
183     */
184    private HostAuth setupSimpleHostAuth(String protocol) {
185        HostAuth hostAuth = new HostAuth();
186        hostAuth.mProtocol = protocol;
187        return hostAuth;
188    }
189
190    /**
191     * Initial testing on setupSyncReportsLocked, making sure that EAS accounts aren't scheduled
192     */
193    public void testSetupSyncReportsLocked() {
194        // TODO Test other functionality within setupSyncReportsLocked
195        // Setup accounts of each type, all with manual sync at different intervals
196        Account easAccount = ProviderTestUtils.setupAccount("account1", false, mMockContext);
197        easAccount.mHostAuthRecv = setupSimpleHostAuth("eas");
198        easAccount.mHostAuthSend = easAccount.mHostAuthRecv;
199        easAccount.mSyncInterval = 30;
200        easAccount.save(mMockContext);
201        Account imapAccount = ProviderTestUtils.setupAccount("account2", false, mMockContext);
202        imapAccount.mHostAuthRecv = setupSimpleHostAuth("imap");
203        imapAccount.mHostAuthSend = setupSimpleHostAuth("smtp");
204        imapAccount.mSyncInterval = 60;
205        imapAccount.save(mMockContext);
206        Account pop3Account = ProviderTestUtils.setupAccount("account3", false, mMockContext);
207        pop3Account.mHostAuthRecv = setupSimpleHostAuth("pop3");
208        pop3Account.mHostAuthSend = setupSimpleHostAuth("smtp");
209        pop3Account.mSyncInterval = 90;
210        pop3Account.save(mMockContext);
211
212        // Setup the SyncReport's for these Accounts
213        MailService mailService = new MailService();
214        mailService.mController = new TestController(mMockContext, getContext());
215        try {
216            mailService.setupSyncReportsLocked(MailService.SYNC_REPORTS_RESET, mMockContext);
217
218            // Get back the map created by MailService
219            HashMap<Long, AccountSyncReport> syncReportMap = MailService.mSyncReports;
220            synchronized (syncReportMap) {
221                // Check the SyncReport's for correctness of sync interval
222                AccountSyncReport syncReport = syncReportMap.get(easAccount.mId);
223                assertNotNull(syncReport);
224                // EAS sync interval should have been changed to "never"
225                assertEquals(Account.CHECK_INTERVAL_NEVER, syncReport.syncInterval);
226                syncReport = syncReportMap.get(imapAccount.mId);
227                assertNotNull(syncReport);
228                assertEquals(60, syncReport.syncInterval);
229                syncReport = syncReportMap.get(pop3Account.mId);
230                assertNotNull(syncReport);
231                assertEquals(90, syncReport.syncInterval);
232                // Change the EAS account to push
233                ContentValues cv = new ContentValues();
234                cv.put(Account.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
235                easAccount.update(mMockContext, cv);
236                syncReportMap.clear();
237                mailService.setupSyncReportsLocked(easAccount.mId, mMockContext);
238                syncReport = syncReportMap.get(easAccount.mId);
239                assertNotNull(syncReport);
240                // EAS sync interval should be "never" in this case as well
241                assertEquals(Account.CHECK_INTERVAL_NEVER, syncReport.syncInterval);
242            }
243        } finally {
244            mailService.mController.cleanupForTest();
245        }
246    }
247
248    /**
249     * Test that setupSyncReports will skip over poorly-formed accounts which can be left
250     * over after unit tests.
251     */
252    public void testSetupSyncReportsWithBadAccounts() {
253        // Setup accounts that trigger each skip-over case
254        // 1: no email address
255        Account account1 = ProviderTestUtils.setupAccount("account1", false, mMockContext);
256        account1.mHostAuthRecv = setupSimpleHostAuth("imap");
257        account1.mHostAuthSend = setupSimpleHostAuth("smtp");
258        account1.mSyncInterval = 30;
259        account1.mEmailAddress = null;
260        account1.save(mMockContext);
261        // 2: no receiver hostauth
262        Account account2 = ProviderTestUtils.setupAccount("account2", false, mMockContext);
263        account2.mHostAuthRecv = null;
264        account2.mHostAuthSend = setupSimpleHostAuth("smtp");
265        account2.mSyncInterval = 30;
266        account2.save(mMockContext);
267        // 3: no sender hostauth
268        Account account3 = ProviderTestUtils.setupAccount("account3", false, mMockContext);
269        account3.mHostAuthRecv = setupSimpleHostAuth("imap");
270        account3.mHostAuthSend = null;
271        account3.mSyncInterval = 30;
272        account3.save(mMockContext);
273
274        // Setup the SyncReport's for these Accounts
275        MailService mailService = new MailService();
276        mailService.mController = new TestController(mMockContext, getContext());
277        try {
278            mailService.setupSyncReportsLocked(MailService.SYNC_REPORTS_RESET, mMockContext);
279            // Get back the map created by MailService - it should be empty
280            HashMap<Long, AccountSyncReport> syncReportMap = MailService.mSyncReports;
281            assertEquals(0, syncReportMap.size());
282        } finally {
283            mailService.mController.cleanupForTest();
284        }
285
286    }
287}
288