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 17 package com.android.exchange; 18 19 import android.accounts.Account; 20 import android.accounts.AccountManager; 21 import android.accounts.AccountManagerFuture; 22 import android.accounts.AuthenticatorException; 23 import android.accounts.OperationCanceledException; 24 import android.app.NotificationManager; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.database.Cursor; 28 import android.provider.CalendarContract; 29 import android.test.MoreAsserts; 30 import android.test.suitebuilder.annotation.MediumTest; 31 import android.text.TextUtils; 32 import android.util.Log; 33 34 import com.android.emailcommon.AccountManagerTypes; 35 import com.android.emailcommon.Logging; 36 import com.android.exchange.utility.ExchangeTestCase; 37 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 @MediumTest 44 public 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 setUp()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 tearDown()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 testEnableEasCalendarSync()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 checkNotificationEmailAddresses(String actual, Account[] baseAccounts, String... addedAddresses)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 */ verifyCalendarSyncState()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 testEnableEasCalendarSyncWithNoExchangeAccounts()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 testShowNotification()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 getExchangeAccounts()209 protected Account[] getExchangeAccounts() { 210 return AccountManager.get(getContext()).getAccountsByType( 211 AccountManagerTypes.TYPE_EXCHANGE); 212 } 213 makeAccountManagerAccount(String username)214 protected Account makeAccountManagerAccount(String username) { 215 return new Account(username, AccountManagerTypes.TYPE_EXCHANGE); 216 } 217 createAccountManagerAccount(String username)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 setupProviderAndAccountManagerAccount(String username)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 makeExchangeServiceAccountList()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 deleteAccountManagerAccount(Account account)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 deleteTemporaryAccountManagerAccounts()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 getTestAccountName(String name)270 protected String getTestAccountName(String name) { 271 return TEST_ACCOUNT_PREFIX + name; 272 } 273 getTestAccountEmailAddress(String name)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 */ getAccountManagerAccounts(Account[] baseline)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