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.exchange;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.accounts.AccountManagerFuture;
22import android.accounts.AuthenticatorException;
23import android.accounts.OperationCanceledException;
24import android.app.NotificationManager;
25import android.content.ContentResolver;
26import android.content.Context;
27import android.database.Cursor;
28import android.provider.CalendarContract;
29import android.test.MoreAsserts;
30import android.test.suitebuilder.annotation.MediumTest;
31import android.text.TextUtils;
32
33import com.android.emailcommon.Logging;
34import com.android.exchange.utility.ExchangeTestCase;
35import com.android.mail.utils.LogUtils;
36
37import java.io.IOException;
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.HashMap;
41import java.util.HashSet;
42
43@MediumTest
44public class CalendarSyncEnablerTest extends ExchangeTestCase {
45
46    protected static final String TEST_ACCOUNT_PREFIX = "__test";
47    protected static final String TEST_ACCOUNT_SUFFIX = "@android.com";
48
49    private HashMap<Account, Boolean> origCalendarSyncStates = new HashMap<Account, Boolean>();
50
51    // To make the rest of the code shorter thus more readable...
52    private static final String EAT = "com.android.exchange";
53
54    @Override
55    public void setUp() throws Exception {
56        super.setUp();
57        // Delete any test accounts we might have created earlier
58        deleteTemporaryAccountManagerAccounts();
59
60        // Save the original calendar sync states.
61        for (Account account : AccountManager.get(getContext()).getAccounts()) {
62            origCalendarSyncStates.put(account,
63                    ContentResolver.getSyncAutomatically(account, CalendarContract.AUTHORITY));
64        }
65    }
66
67    @Override
68    public void tearDown() throws Exception {
69        super.tearDown();
70        // Delete any test accounts we might have created earlier
71        deleteTemporaryAccountManagerAccounts();
72
73        // Restore the original calendar sync states.
74        // Note we restore only for Exchange accounts.
75        // Other accounts should remain intact throughout the tests.  Plus we don't know if the
76        // Calendar.AUTHORITY is supported by other types of accounts.
77        for (Account account : getExchangeAccounts()) {
78            Boolean state = origCalendarSyncStates.get(account);
79            if (state == null) continue; // Shouldn't happen, but just in case.
80
81            ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, state);
82        }
83    }
84
85    public void testEnableEasCalendarSync() {
86        final Account[] baseAccounts = getExchangeAccounts();
87
88        String a1 = getTestAccountEmailAddress("1");
89        String a2 = getTestAccountEmailAddress("2");
90
91        // 1. Test with 1 account
92
93        CalendarSyncEnabler enabler = new CalendarSyncEnabler(getContext());
94
95        // Add exchange accounts
96        createAccountManagerAccount(a1);
97
98        String emailAddresses = enabler.enableEasCalendarSyncInternalForTest();
99
100        // Verify
101        verifyCalendarSyncState();
102
103        // There seems to be no good way to examine the contents of Notification, so let's verify
104        // we at least (tried to) show the correct email addresses.
105        checkNotificationEmailAddresses(emailAddresses, baseAccounts, a1);
106
107        // Delete added account.
108        deleteTemporaryAccountManagerAccounts();
109
110        // 2. Test with 2 accounts
111        enabler = new CalendarSyncEnabler(getContext());
112
113        // Add exchange accounts
114        createAccountManagerAccount(a1);
115        createAccountManagerAccount(a2);
116
117        emailAddresses = enabler.enableEasCalendarSyncInternalForTest();
118
119        // Verify
120        verifyCalendarSyncState();
121
122        // Check
123        checkNotificationEmailAddresses(emailAddresses, baseAccounts, a1, a2);
124    }
125
126    private static void checkNotificationEmailAddresses(String actual, Account[] baseAccounts,
127            String... addedAddresses) {
128        // Build and sort actual string array.
129        final String[] actualArray = TextUtils.split(actual, " ");
130        Arrays.sort(actualArray);
131
132        // Build and sort expected string array.
133        ArrayList<String> expected = new ArrayList<String>();
134        for (Account account : baseAccounts) {
135            expected.add(account.name);
136        }
137        for (String address : addedAddresses) {
138            expected.add(address);
139        }
140        final String[] expectedArray = new String[expected.size()];
141        expected.toArray(expectedArray);
142        Arrays.sort(expectedArray);
143
144        // Check!
145        MoreAsserts.assertEquals(expectedArray, actualArray);
146    }
147
148    /**
149     * For all {@link Account}, confirm that:
150     * <ol>
151     *   <li>Calendar sync is enabled if it's an Exchange account.<br>
152     *       Unfortunately setSyncAutomatically() doesn't take effect immediately, so we skip this
153     *       check for now.
154             TODO Find a stable way to check this.
155     *   <li>Otherwise, calendar sync state isn't changed.
156     * </ol>
157     */
158    private void verifyCalendarSyncState() {
159        // It's very unfortunate that setSyncAutomatically doesn't take effect immediately.
160        for (Account account : AccountManager.get(getContext()).getAccounts()) {
161            String message = "account=" + account.name + "(" + account.type + ")";
162            boolean enabled = ContentResolver.getSyncAutomatically(account,
163                    CalendarContract.AUTHORITY);
164            int syncable = ContentResolver.getIsSyncable(account, CalendarContract.AUTHORITY);
165
166            if (EAT.equals(account.type)) {
167                // Should be enabled.
168                // assertEquals(message, Boolean.TRUE, (Boolean) enabled);
169                // assertEquals(message, 1, syncable);
170            } else {
171                // Shouldn't change.
172                assertEquals(message, origCalendarSyncStates.get(account), (Boolean) enabled);
173            }
174        }
175    }
176
177    public void testEnableEasCalendarSyncWithNoExchangeAccounts() {
178        // This test can only meaningfully run when there's no exchange accounts
179        // set up on the device.  Otherwise there'll be no difference from
180        // testEnableEasCalendarSync.
181        if (AccountManager.get(getContext()).getAccountsByType(EAT).length > 0) {
182            LogUtils.w(Logging.LOG_TAG, "testEnableEasCalendarSyncWithNoExchangeAccounts skipped:"
183                    + " It only runs when there's no Exchange account on the device.");
184            return;
185        }
186        CalendarSyncEnabler enabler = new CalendarSyncEnabler(getContext());
187        String emailAddresses = enabler.enableEasCalendarSyncInternalForTest();
188
189        // Verify (nothing should change)
190        verifyCalendarSyncState();
191
192        // No exchange accounts found.
193        assertEquals(0, emailAddresses.length());
194    }
195
196    public void testShowNotification() {
197        CalendarSyncEnabler enabler = new CalendarSyncEnabler(getContext());
198
199        // We can't really check the result, but at least we can make sure it won't crash....
200        enabler.showNotificationForTest("a@b.com");
201
202        // Remove the notification.  Comment it out when you want to know how it looks like.
203        // TODO If NotificationController supports this notification, we can just mock it out
204        // and remove this code.
205        ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE))
206                .cancel(CalendarSyncEnabler.NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED);
207    }
208
209    protected Account[] getExchangeAccounts() {
210        return AccountManager.get(getContext()).getAccountsByType(EAT);
211    }
212
213    protected Account makeAccountManagerAccount(String username) {
214        return new Account(username, EAT);
215    }
216
217    protected void createAccountManagerAccount(String username) {
218        final Account account = makeAccountManagerAccount(username);
219        AccountManager.get(getContext()).addAccountExplicitly(account, "password", null);
220    }
221
222    protected com.android.emailcommon.provider.Account
223        setupProviderAndAccountManagerAccount(String username) {
224        // Note that setupAccount creates the email address username@android.com, so that's what
225        // we need to use for the account manager
226        createAccountManagerAccount(username + TEST_ACCOUNT_SUFFIX);
227        return setupTestAccount(username, true);
228    }
229
230    protected ArrayList<com.android.emailcommon.provider.Account> makeExchangeServiceAccountList() {
231        ArrayList<com.android.emailcommon.provider.Account> accountList =
232            new ArrayList<com.android.emailcommon.provider.Account>();
233        Cursor c = mProviderContext.getContentResolver().query(
234                com.android.emailcommon.provider.Account.CONTENT_URI,
235                com.android.emailcommon.provider.Account.CONTENT_PROJECTION, null, null, null);
236        try {
237            while (c.moveToNext()) {
238                com.android.emailcommon.provider.Account account =
239                    new com.android.emailcommon.provider.Account();
240                account.restore(c);
241                accountList.add(account);
242            }
243        } finally {
244            c.close();
245        }
246        return accountList;
247    }
248
249    protected void deleteAccountManagerAccount(Account account) {
250        AccountManagerFuture<Boolean> future =
251            AccountManager.get(getContext()).removeAccount(account, null, null);
252        try {
253            future.getResult();
254        } catch (OperationCanceledException e) {
255        } catch (AuthenticatorException e) {
256        } catch (IOException e) {
257        }
258    }
259
260    protected void deleteTemporaryAccountManagerAccounts() {
261        for (Account accountManagerAccount: getExchangeAccounts()) {
262            if (accountManagerAccount.name.startsWith(TEST_ACCOUNT_PREFIX) &&
263                    accountManagerAccount.name.endsWith(TEST_ACCOUNT_SUFFIX)) {
264                deleteAccountManagerAccount(accountManagerAccount);
265            }
266        }
267    }
268
269    protected String getTestAccountName(String name) {
270        return TEST_ACCOUNT_PREFIX + name;
271    }
272
273    protected String getTestAccountEmailAddress(String name) {
274        return TEST_ACCOUNT_PREFIX + name + TEST_ACCOUNT_SUFFIX;
275    }
276
277
278    /**
279     * Helper to retrieve account manager accounts *and* remove any preexisting accounts
280     * from the list, to "hide" them from the reconciler.
281     */
282    protected Account[] getAccountManagerAccounts(Account[] baseline) {
283        Account[] rawList = getExchangeAccounts();
284        if (baseline.length == 0) {
285            return rawList;
286        }
287        HashSet<Account> set = new HashSet<Account>();
288        for (Account addAccount : rawList) {
289            set.add(addAccount);
290        }
291        for (Account removeAccount : baseline) {
292            set.remove(removeAccount);
293        }
294        return set.toArray(new Account[0]);
295    }
296}
297