1/* 2 * Copyright (C) 2007 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.server.content; 18 19import android.accounts.Account; 20import android.content.ComponentName; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.content.ContextWrapper; 24import android.content.Intent; 25import android.content.PeriodicSync; 26import android.content.res.Resources; 27import android.os.Bundle; 28import android.test.AndroidTestCase; 29import android.test.RenamingDelegatingContext; 30import android.test.mock.MockContentResolver; 31import android.test.mock.MockContext; 32import android.test.suitebuilder.annotation.LargeTest; 33import android.test.suitebuilder.annotation.MediumTest; 34import android.test.suitebuilder.annotation.SmallTest; 35 36import com.android.server.content.SyncStorageEngine.EndPoint; 37 38import com.android.internal.os.AtomicFile; 39 40import java.io.File; 41import java.io.FileOutputStream; 42import java.util.List; 43 44public class SyncStorageEngineTest extends AndroidTestCase { 45 46 protected Account account1; 47 protected Account account2; 48 protected ComponentName syncService1; 49 protected String authority1 = "testprovider"; 50 protected Bundle defaultBundle; 51 protected final int DEFAULT_USER = 0; 52 53 /* Some default poll frequencies. */ 54 final long dayPoll = (60 * 60 * 24); 55 final long dayFuzz = 60; 56 final long thousandSecs = 1000; 57 final long thousandSecsFuzz = 100; 58 59 MockContentResolver mockResolver; 60 SyncStorageEngine engine; 61 62 private File getSyncDir() { 63 return new File(new File(getContext().getFilesDir(), "system"), "sync"); 64 } 65 66 @Override 67 public void setUp() { 68 account1 = new Account("a@example.com", "example.type"); 69 account2 = new Account("b@example.com", "example.type"); 70 syncService1 = new ComponentName("com.example", "SyncService"); 71 // Default bundle. 72 defaultBundle = new Bundle(); 73 defaultBundle.putInt("int_key", 0); 74 defaultBundle.putString("string_key", "hello"); 75 // Set up storage engine. 76 mockResolver = new MockContentResolver(); 77 engine = SyncStorageEngine.newTestInstance( 78 new TestContext(mockResolver, getContext())); 79 } 80 81 /** 82 * Test that we handle the case of a history row being old enough to purge before the 83 * corresponding sync is finished. This can happen if the clock changes while we are syncing. 84 * 85 */ 86 // TODO: this test causes AidlTest to fail. Omit for now 87 // @SmallTest 88 public void testPurgeActiveSync() throws Exception { 89 final Account account = new Account("a@example.com", "example.type"); 90 final String authority = "testprovider"; 91 92 MockContentResolver mockResolver = new MockContentResolver(); 93 94 SyncStorageEngine engine = SyncStorageEngine.newTestInstance( 95 new TestContext(mockResolver, getContext())); 96 long time0 = 1000; 97 SyncOperation op = new SyncOperation(account, 0, 0, "foo", 98 SyncOperation.REASON_PERIODIC, 99 SyncStorageEngine.SOURCE_LOCAL, 100 authority, 101 Bundle.EMPTY, true); 102 long historyId = engine.insertStartSyncEvent(op, time0); 103 long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2; 104 engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0); 105 } 106 107 @LargeTest 108 public void testAuthorityPersistence() throws Exception { 109 final Account account1 = new Account("a@example.com", "example.type"); 110 final Account account2 = new Account("b@example.com", "example.type.2"); 111 final String authority1 = "testprovider1"; 112 final String authority2 = "testprovider2"; 113 114 engine.setMasterSyncAutomatically(false, 0); 115 116 engine.setIsSyncable(account1, 0, authority1, 1); 117 engine.setSyncAutomatically(account1, 0, authority1, true); 118 119 engine.setIsSyncable(account2, 0, authority1, 1); 120 engine.setSyncAutomatically(account2, 0, authority1, true); 121 122 engine.setIsSyncable(account1, 0, authority2, 1); 123 engine.setSyncAutomatically(account1, 0, authority2, false); 124 125 engine.setIsSyncable(account2, 0, authority2, 0); 126 engine.setSyncAutomatically(account2, 0, authority2, true); 127 128 engine.writeAllState(); 129 engine.clearAndReadState(); 130 131 assertEquals(true, engine.getSyncAutomatically(account1, 0, authority1)); 132 assertEquals(true, engine.getSyncAutomatically(account2, 0, authority1)); 133 assertEquals(false, engine.getSyncAutomatically(account1, 0, authority2)); 134 assertEquals(true, engine.getSyncAutomatically(account2, 0, authority2)); 135 136 assertEquals(1, engine.getIsSyncable(account1, 0, authority1)); 137 assertEquals(1, engine.getIsSyncable(account2, 0, authority1)); 138 assertEquals(1, engine.getIsSyncable(account1, 0, authority2)); 139 assertEquals(0, engine.getIsSyncable(account2, 0, authority2)); 140 } 141 142 @MediumTest 143 public void testListenForTicklesParsing() throws Exception { 144 byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" 145 + "<accounts>\n" 146 + "<listenForTickles user=\"0\" enabled=\"false\" />" 147 + "<listenForTickles user=\"1\" enabled=\"true\" />" 148 + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n" 149 + "<authority id=\"1\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n" 150 + "</accounts>\n").getBytes(); 151 152 MockContentResolver mockResolver = new MockContentResolver(); 153 final TestContext testContext = new TestContext(mockResolver, getContext()); 154 155 File syncDir = getSyncDir(); 156 syncDir.mkdirs(); 157 AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); 158 FileOutputStream fos = accountInfoFile.startWrite(); 159 fos.write(accountsFileData); 160 accountInfoFile.finishWrite(fos); 161 162 SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext); 163 164 assertEquals(false, engine.getMasterSyncAutomatically(0)); 165 assertEquals(true, engine.getMasterSyncAutomatically(1)); 166 assertEquals(true, engine.getMasterSyncAutomatically(2)); 167 168 } 169 170 @MediumTest 171 public void testAuthorityRenaming() throws Exception { 172 final Account account1 = new Account("acc1", "type1"); 173 final Account account2 = new Account("acc2", "type2"); 174 final String authorityContacts = "contacts"; 175 final String authorityCalendar = "calendar"; 176 final String authorityOther = "other"; 177 final String authorityContactsNew = "com.android.contacts"; 178 final String authorityCalendarNew = "com.android.calendar"; 179 180 MockContentResolver mockResolver = new MockContentResolver(); 181 182 final TestContext testContext = new TestContext(mockResolver, getContext()); 183 184 byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" 185 + "<accounts>\n" 186 + "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n" 187 + "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n" 188 + "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n" 189 + "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n" 190 + "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n" 191 + "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n" 192 + "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\"" 193 + " authority=\"com.android.calendar\" />\n" 194 + "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\"" 195 + " authority=\"com.android.contacts\" />\n" 196 + "</accounts>\n").getBytes(); 197 198 File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync"); 199 syncDir.mkdirs(); 200 AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); 201 FileOutputStream fos = accountInfoFile.startWrite(); 202 fos.write(accountsFileData); 203 accountInfoFile.finishWrite(fos); 204 205 SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext); 206 207 assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityContacts)); 208 assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityCalendar)); 209 assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityOther)); 210 assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityContactsNew)); 211 assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityCalendarNew)); 212 213 assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContacts)); 214 assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendar)); 215 assertEquals(true, engine.getSyncAutomatically(account2, 0, authorityOther)); 216 assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContactsNew)); 217 assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendarNew)); 218 } 219 220 @SmallTest 221 public void testSyncableMigration() throws Exception { 222 final Account account = new Account("acc", "type"); 223 224 MockContentResolver mockResolver = new MockContentResolver(); 225 226 final TestContext testContext = new TestContext(mockResolver, getContext()); 227 228 byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" 229 + "<accounts>\n" 230 + "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n" 231 + "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n" 232 + "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\"" 233 + " authority=\"other3\" />\n" 234 + "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\"" 235 + " authority=\"other4\" />\n" 236 + "</accounts>\n").getBytes(); 237 238 File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync"); 239 syncDir.mkdirs(); 240 AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); 241 FileOutputStream fos = accountInfoFile.startWrite(); 242 fos.write(accountsFileData); 243 accountInfoFile.finishWrite(fos); 244 245 SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext); 246 247 assertEquals(-1, engine.getIsSyncable(account, 0, "other1")); 248 assertEquals(1, engine.getIsSyncable(account, 0, "other2")); 249 assertEquals(0, engine.getIsSyncable(account, 0, "other3")); 250 assertEquals(1, engine.getIsSyncable(account, 0, "other4")); 251 } 252 253 /** 254 * Verify that the API cannot cause a run-time reboot by passing in the empty string as an 255 * authority. The problem here is that 256 * {@link SyncStorageEngine#getOrCreateAuthorityLocked(account, provider)} would register 257 * an empty authority which causes a RTE in {@link SyncManager#scheduleReadyPeriodicSyncs()}. 258 * This is not strictly a SSE test, but it does depend on the SSE data structures. 259 */ 260 @SmallTest 261 public void testExpectedIllegalArguments() throws Exception { 262 try { 263 ContentResolver.setSyncAutomatically(account1, "", true); 264 fail("empty provider string should throw IllegalArgumentException"); 265 } catch (IllegalArgumentException expected) {} 266 267 try { 268 ContentResolver.addPeriodicSync(account1, "", Bundle.EMPTY, 84000L); 269 fail("empty provider string should throw IllegalArgumentException"); 270 } catch (IllegalArgumentException expected) {} 271 272 try { 273 ContentResolver.removePeriodicSync(account1, "", Bundle.EMPTY); 274 fail("empty provider string should throw IllegalArgumentException"); 275 } catch (IllegalArgumentException expected) {} 276 277 try { 278 ContentResolver.cancelSync(account1, ""); 279 fail("empty provider string should throw IllegalArgumentException"); 280 } catch (IllegalArgumentException expected) {} 281 282 try { 283 ContentResolver.setIsSyncable(account1, "", 0); 284 fail("empty provider string should throw IllegalArgumentException"); 285 } catch (IllegalArgumentException expected) {} 286 287 try { 288 ContentResolver.cancelSync(account1, ""); 289 fail("empty provider string should throw IllegalArgumentException"); 290 } catch (IllegalArgumentException expected) {} 291 292 try { 293 ContentResolver.requestSync(account1, "", Bundle.EMPTY); 294 fail("empty provider string should throw IllegalArgumentException"); 295 } catch (IllegalArgumentException expected) {} 296 297 try { 298 ContentResolver.getSyncStatus(account1, ""); 299 fail("empty provider string should throw IllegalArgumentException"); 300 } catch (IllegalArgumentException expected) {} 301 302 // Make sure we aren't blocking null account/provider for those functions that use it 303 // to specify ALL accounts/providers. 304 ContentResolver.requestSync(null, null, Bundle.EMPTY); 305 ContentResolver.cancelSync(null, null); 306 } 307} 308 309class TestContext extends ContextWrapper { 310 311 ContentResolver mResolver; 312 313 private final Context mRealContext; 314 315 public TestContext(ContentResolver resolver, Context realContext) { 316 super(new RenamingDelegatingContext(new MockContext(), realContext, "test.")); 317 mRealContext = realContext; 318 mResolver = resolver; 319 } 320 321 @Override 322 public Resources getResources() { 323 return mRealContext.getResources(); 324 } 325 326 @Override 327 public File getFilesDir() { 328 return mRealContext.getFilesDir(); 329 } 330 331 @Override 332 public void enforceCallingOrSelfPermission(String permission, String message) { 333 } 334 335 @Override 336 public void sendBroadcast(Intent intent) { 337 } 338 339 @Override 340 public ContentResolver getContentResolver() { 341 return mResolver; 342 } 343} 344