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;
32import android.util.Log;
33
34import com.android.emailcommon.AccountManagerTypes;
35import com.android.emailcommon.Logging;
36import com.android.exchange.utility.ExchangeTestCase;
37
38import java.io.IOException;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.HashMap;
42import java.util.HashSet;
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 = AccountManagerTypes.TYPE_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            Log.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(
211                AccountManagerTypes.TYPE_EXCHANGE);
212    }
213
214    protected Account makeAccountManagerAccount(String username) {
215        return new Account(username, AccountManagerTypes.TYPE_EXCHANGE);
216    }
217
218    protected void createAccountManagerAccount(String username) {
219        final Account account = makeAccountManagerAccount(username);
220        AccountManager.get(getContext()).addAccountExplicitly(account, "password", null);
221    }
222
223    protected com.android.emailcommon.provider.Account
224        setupProviderAndAccountManagerAccount(String username) {
225        // Note that setupAccount creates the email address username@android.com, so that's what
226        // we need to use for the account manager
227        createAccountManagerAccount(username + TEST_ACCOUNT_SUFFIX);
228        return setupTestAccount(username, true);
229    }
230
231    protected ArrayList<com.android.emailcommon.provider.Account> makeExchangeServiceAccountList() {
232        ArrayList<com.android.emailcommon.provider.Account> accountList =
233            new ArrayList<com.android.emailcommon.provider.Account>();
234        Cursor c = mProviderContext.getContentResolver().query(
235                com.android.emailcommon.provider.Account.CONTENT_URI,
236                com.android.emailcommon.provider.Account.CONTENT_PROJECTION, null, null, null);
237        try {
238            while (c.moveToNext()) {
239                com.android.emailcommon.provider.Account account =
240                    new com.android.emailcommon.provider.Account();
241                account.restore(c);
242                accountList.add(account);
243            }
244        } finally {
245            c.close();
246        }
247        return accountList;
248    }
249
250    protected void deleteAccountManagerAccount(Account account) {
251        AccountManagerFuture<Boolean> future =
252            AccountManager.get(getContext()).removeAccount(account, null, null);
253        try {
254            future.getResult();
255        } catch (OperationCanceledException e) {
256        } catch (AuthenticatorException e) {
257        } catch (IOException e) {
258        }
259    }
260
261    protected void deleteTemporaryAccountManagerAccounts() {
262        for (Account accountManagerAccount: getExchangeAccounts()) {
263            if (accountManagerAccount.name.startsWith(TEST_ACCOUNT_PREFIX) &&
264                    accountManagerAccount.name.endsWith(TEST_ACCOUNT_SUFFIX)) {
265                deleteAccountManagerAccount(accountManagerAccount);
266            }
267        }
268    }
269
270    protected String getTestAccountName(String name) {
271        return TEST_ACCOUNT_PREFIX + name;
272    }
273
274    protected String getTestAccountEmailAddress(String name) {
275        return TEST_ACCOUNT_PREFIX + name + TEST_ACCOUNT_SUFFIX;
276    }
277
278
279    /**
280     * Helper to retrieve account manager accounts *and* remove any preexisting accounts
281     * from the list, to "hide" them from the reconciler.
282     */
283    protected Account[] getAccountManagerAccounts(Account[] baseline) {
284        Account[] rawList = getExchangeAccounts();
285        if (baseline.length == 0) {
286            return rawList;
287        }
288        HashSet<Account> set = new HashSet<Account>();
289        for (Account addAccount : rawList) {
290            set.add(addAccount);
291        }
292        for (Account removeAccount : baseline) {
293            set.remove(removeAccount);
294        }
295        return set.toArray(new Account[0]);
296    }
297}
298