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
44import com.android.server.content.SyncStorageEngine.EndPoint;
45
46public class SyncStorageEngineTest extends AndroidTestCase {
47
48    protected Account account1;
49    protected Account account2;
50    protected ComponentName syncService1;
51    protected String authority1 = "testprovider";
52    protected Bundle defaultBundle;
53    protected final int DEFAULT_USER = 0;
54
55    /* Some default poll frequencies. */
56    final long dayPoll = (60 * 60 * 24);
57    final long dayFuzz = 60;
58    final long thousandSecs = 1000;
59    final long thousandSecsFuzz = 100;
60
61    MockContentResolver mockResolver;
62    SyncStorageEngine engine;
63
64    private File getSyncDir() {
65        return new File(new File(getContext().getFilesDir(), "system"), "sync");
66    }
67
68    @Override
69    public void setUp() {
70        account1 = new Account("a@example.com", "example.type");
71        account2 = new Account("b@example.com", "example.type");
72        syncService1 = new ComponentName("com.example", "SyncService");
73        // Default bundle.
74        defaultBundle = new Bundle();
75        defaultBundle.putInt("int_key", 0);
76        defaultBundle.putString("string_key", "hello");
77        // Set up storage engine.
78        mockResolver = new MockContentResolver();
79        engine = SyncStorageEngine.newTestInstance(
80                new TestContext(mockResolver, getContext()));
81    }
82
83    /**
84     * Test that we handle the case of a history row being old enough to purge before the
85     * corresponding sync is finished. This can happen if the clock changes while we are syncing.
86     *
87     */
88    // TODO: this test causes AidlTest to fail. Omit for now
89    // @SmallTest
90    public void testPurgeActiveSync() throws Exception {
91        final Account account = new Account("a@example.com", "example.type");
92        final String authority = "testprovider";
93
94        MockContentResolver mockResolver = new MockContentResolver();
95
96        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
97                new TestContext(mockResolver, getContext()));
98        long time0 = 1000;
99        SyncOperation op = new SyncOperation(account, 0,
100                SyncOperation.REASON_PERIODIC,
101                SyncStorageEngine.SOURCE_LOCAL,
102                authority,
103                Bundle.EMPTY, time0, 0 /* flex*/, 0, 0, true);
104        long historyId = engine.insertStartSyncEvent(op, time0);
105        long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
106        engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
107    }
108
109    /**
110     * Test persistence of pending operations.
111     */
112    @MediumTest
113    public void testAppendPending() throws Exception {
114        SyncOperation sop = new SyncOperation(account1,
115                DEFAULT_USER,
116                SyncOperation.REASON_PERIODIC,
117                SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
118                0 /* runtime */, 0 /* flex */, 0 /* backoff */, 0 /* delayuntil */,
119                true /* expedited */);
120        engine.insertIntoPending(sop);
121
122        // Force engine to read from disk.
123        engine.clearAndReadState();
124
125        assertTrue(engine.getPendingOperationCount() == 1);
126        List<SyncStorageEngine.PendingOperation> pops = engine.getPendingOperations();
127        SyncStorageEngine.PendingOperation popRetrieved = pops.get(0);
128        assertEquals(sop.target.account, popRetrieved.target.account);
129        assertEquals(sop.target.provider, popRetrieved.target.provider);
130        assertEquals(sop.target.service, popRetrieved.target.service);
131        assertEquals(sop.target.userId, popRetrieved.target.userId);
132        assertEquals(sop.reason, popRetrieved.reason);
133        assertEquals(sop.syncSource, popRetrieved.syncSource);
134        assertEquals(sop.isExpedited(), popRetrieved.expedited);
135        assert(android.content.PeriodicSync.syncExtrasEquals(sop.extras, popRetrieved.extras));
136    }
137
138    /**
139     * Verify {@link com.android.server.content.SyncStorageEngine#writePendingOperationsLocked()}
140     */
141    public void testWritePendingOperationsLocked() throws Exception {
142        SyncOperation sop = new SyncOperation(account1,
143                DEFAULT_USER,
144                SyncOperation.REASON_IS_SYNCABLE,
145                SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
146                1000L /* runtime */, 57L /* flex */, 0 /* backoff */, 0 /* delayuntil */,
147                true /* expedited */);
148        SyncOperation sop1 = new SyncOperation(account2,
149                DEFAULT_USER,
150                SyncOperation.REASON_PERIODIC,
151                SyncStorageEngine.SOURCE_LOCAL, authority1, defaultBundle,
152                0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
153                false /* expedited */);
154        SyncOperation deleted = new SyncOperation(account2,
155                DEFAULT_USER,
156                SyncOperation.REASON_SYNC_AUTO,
157                SyncStorageEngine.SOURCE_LOCAL, authority1, Bundle.EMPTY,
158                0 /* runtime */, 0 /* flex */, 20L /* backoff */, 100L /* delayuntil */,
159                false /* expedited */);
160        engine.insertIntoPending(sop);
161        engine.insertIntoPending(sop1);
162        engine.insertIntoPending(deleted);
163
164        SyncStorageEngine.PendingOperation popDeleted = engine.getPendingOperations().get(2);
165        // Free verifying, going to delete it anyway.
166        assertEquals(deleted.target.account, popDeleted.target.account);
167        assertEquals(deleted.target.provider, popDeleted.target.provider);
168        assertEquals(deleted.target.service, popDeleted.target.service);
169        assertEquals(deleted.target.userId, popDeleted.target.userId);
170        assertEquals(deleted.reason, popDeleted.reason);
171        assertEquals(deleted.syncSource, popDeleted.syncSource);
172        assertEquals(deleted.isExpedited(), popDeleted.expedited);
173        assert(android.content.PeriodicSync.syncExtrasEquals(deleted.extras, popDeleted.extras));
174        // Delete one to force write-all
175        engine.deleteFromPending(popDeleted);
176        assertEquals("Delete of pending op failed.", 2, engine.getPendingOperationCount());
177        // If there's dirty pending data (which there is because we deleted a pending op) this
178        // re-writes the entire file.
179        engine.writeAllState();
180
181        engine.clearAndReadState();
182
183        // Validate state read back out.
184        assertEquals("Delete of pending op failed.", 2, engine.getPendingOperationCount());
185
186        List<SyncStorageEngine.PendingOperation> pops = engine.getPendingOperations();
187
188        SyncStorageEngine.PendingOperation popRetrieved = pops.get(0);
189        assertEquals(sop.target.account, popRetrieved.target.account);
190        assertEquals(sop.target.provider, popRetrieved.target.provider);
191        assertEquals(sop.target.service, popRetrieved.target.service);
192        assertEquals(sop.target.userId, popRetrieved.target.userId);
193        assertEquals(sop.reason, popRetrieved.reason);
194        assertEquals(sop.syncSource, popRetrieved.syncSource);
195        assertEquals(sop.isExpedited(), popRetrieved.expedited);
196        assert(android.content.PeriodicSync.syncExtrasEquals(sop.extras, popRetrieved.extras));
197
198        popRetrieved = pops.get(1);
199        assertEquals(sop1.target.account, popRetrieved.target.account);
200        assertEquals(sop1.target.provider, popRetrieved.target.provider);
201        assertEquals(sop1.target.service, popRetrieved.target.service);
202        assertEquals(sop1.target.userId, popRetrieved.target.userId);
203        assertEquals(sop1.reason, popRetrieved.reason);
204        assertEquals(sop1.syncSource, popRetrieved.syncSource);
205        assertEquals(sop1.isExpedited(), popRetrieved.expedited);
206        assert(android.content.PeriodicSync.syncExtrasEquals(sop1.extras, popRetrieved.extras));
207    }
208
209    /**
210     * Test that we can create, remove and retrieve periodic syncs. Backwards compatibility -
211     * periodic syncs with no flex time are no longer used.
212     */
213    @MediumTest
214    public void testPeriodics() throws Exception {
215        final Account account1 = new Account("a@example.com", "example.type");
216        final Account account2 = new Account("b@example.com", "example.type.2");
217        final String authority = "testprovider";
218        final Bundle extras1 = new Bundle();
219        extras1.putString("a", "1");
220        final Bundle extras2 = new Bundle();
221        extras2.putString("a", "2");
222        final int period1 = 200;
223        final int period2 = 1000;
224
225        PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1);
226        EndPoint end1 = new EndPoint(account1, authority, 0);
227
228        PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1);
229        PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2);
230        PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2);
231
232
233
234        removePeriodicSyncs(engine, account1, 0, authority);
235        removePeriodicSyncs(engine, account2, 0, authority);
236        removePeriodicSyncs(engine, account1, 1, authority);
237
238        // this should add two distinct periodic syncs for account1 and one for account2
239        engine.updateOrAddPeriodicSync(new EndPoint(account1, authority, 0), period1, 0, extras1);
240        engine.updateOrAddPeriodicSync(new EndPoint(account1, authority, 0), period1, 0, extras2);
241        engine.updateOrAddPeriodicSync(new EndPoint(account1, authority, 0), period2, 0, extras2);
242        engine.updateOrAddPeriodicSync(new EndPoint(account2, authority, 0), period2, 0, extras2);
243        // add a second user
244        engine.updateOrAddPeriodicSync(new EndPoint(account1, authority, 1), period1, 0, extras2);
245
246        List<PeriodicSync> syncs = engine.getPeriodicSyncs(new EndPoint(account1, authority, 0));
247
248        assertEquals(2, syncs.size());
249
250        assertEquals(sync1, syncs.get(0));
251        assertEquals(sync3, syncs.get(1));
252
253        engine.removePeriodicSync(new EndPoint(account1, authority, 0), extras1);
254
255        syncs = engine.getPeriodicSyncs(new EndPoint(account1, authority, 0));
256        assertEquals(1, syncs.size());
257        assertEquals(sync3, syncs.get(0));
258
259        syncs = engine.getPeriodicSyncs(new EndPoint(account2, authority, 0));
260        assertEquals(1, syncs.size());
261        assertEquals(sync4, syncs.get(0));
262
263        syncs = engine.getPeriodicSyncs(new EndPoint(sync2.account, sync2.authority, 1));
264        assertEquals(1, syncs.size());
265        assertEquals(sync2, syncs.get(0));
266    }
267
268    /**
269     * Test that we can create, remove and retrieve periodic syncs with a provided flex time.
270     */
271    @MediumTest
272    public void testPeriodicsV2() throws Exception {
273        final Account account1 = new Account("a@example.com", "example.type");
274        final Account account2 = new Account("b@example.com", "example.type.2");
275        final String authority = "testprovider";
276        final Bundle extras1 = new Bundle();
277        extras1.putString("a", "1");
278        final Bundle extras2 = new Bundle();
279        extras2.putString("a", "2");
280        final int period1 = 200;
281        final int period2 = 1000;
282        final int flex1 = 10;
283        final int flex2 = 100;
284        EndPoint point1 = new EndPoint(account1, authority, 0);
285        EndPoint point2 = new EndPoint(account2, authority, 0);
286        EndPoint point1User2 = new EndPoint(account1, authority, 1);
287
288        PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1, flex1);
289        PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1, flex1);
290        PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2, flex2);
291        PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2, flex2);
292
293        EndPoint target1 = new EndPoint(account1, authority, 0);
294        EndPoint target2 = new EndPoint(account2, authority, 0);
295        EndPoint target1UserB = new EndPoint(account1, authority, 1);
296
297        MockContentResolver mockResolver = new MockContentResolver();
298
299        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
300                new TestContext(mockResolver, getContext()));
301
302        removePeriodicSyncs(engine, account1, 0, authority);
303        removePeriodicSyncs(engine, account2, 0, authority);
304        removePeriodicSyncs(engine, account1, 1, authority);
305
306        // This should add two distinct periodic syncs for account1 and one for account2
307        engine.updateOrAddPeriodicSync(target1, period1, flex1, extras1);
308        engine.updateOrAddPeriodicSync(target1, period1, flex1, extras2);
309        // Edit existing sync and update the period and flex.
310        engine.updateOrAddPeriodicSync(target1, period2, flex2, extras2);
311        engine.updateOrAddPeriodicSync(target2, period2, flex2, extras2);
312        // add a target for a second user.
313        engine.updateOrAddPeriodicSync(target1UserB, period1, flex1, extras2);
314
315        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target1);
316
317        assertEquals(2, syncs.size());
318
319        assertEquals(sync1, syncs.get(0));
320        assertEquals(sync3, syncs.get(1));
321
322        engine.removePeriodicSync(target1, extras1);
323
324        syncs = engine.getPeriodicSyncs(target1);
325        assertEquals(1, syncs.size());
326        assertEquals(sync3, syncs.get(0));
327
328        syncs = engine.getPeriodicSyncs(target2);
329        assertEquals(1, syncs.size());
330        assertEquals(sync4, syncs.get(0));
331
332        syncs = engine.getPeriodicSyncs(target1UserB);
333        assertEquals(1, syncs.size());
334        assertEquals(sync2, syncs.get(0));
335    }
336
337    private void removePeriodicSyncs(SyncStorageEngine engine, Account account, int userId, String authority) {
338        EndPoint target = new EndPoint(account, authority, userId);
339        engine.setIsSyncable(account, userId, authority, engine.getIsSyncable(account, userId, authority));
340        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target);
341        for (PeriodicSync sync : syncs) {
342            engine.removePeriodicSync(target, sync.extras);
343        }
344    }
345
346    @LargeTest
347    public void testAuthorityPersistence() throws Exception {
348        final Account account1 = new Account("a@example.com", "example.type");
349        final Account account2 = new Account("b@example.com", "example.type.2");
350        final String authority1 = "testprovider1";
351        final String authority2 = "testprovider2";
352        final Bundle extras1 = new Bundle();
353        extras1.putString("a", "1");
354        final Bundle extras2 = new Bundle();
355        extras2.putString("a", "2");
356        extras2.putLong("b", 2);
357        extras2.putInt("c", 1);
358        extras2.putBoolean("d", true);
359        extras2.putDouble("e", 1.2);
360        extras2.putFloat("f", 4.5f);
361        extras2.putParcelable("g", account1);
362        final int period1 = 200;
363        final int period2 = 1000;
364        final int flex1 = 10;
365        final int flex2 = 100;
366
367        EndPoint point1 = new EndPoint(account1, authority1, 0);
368        EndPoint point2 = new EndPoint(account1, authority2, 0);
369        EndPoint point3 = new EndPoint(account2, authority1, 0);
370
371        PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1, flex1);
372        PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1, flex1);
373        PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1, flex1);
374        PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2, flex2);
375        PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1, flex1);
376
377        EndPoint target1 = new EndPoint(account1, authority1, 0);
378        EndPoint target2 = new EndPoint(account1, authority2, 0);
379        EndPoint target3 = new EndPoint(account2, authority1, 0);
380
381        removePeriodicSyncs(engine, account1, 0, authority1);
382        removePeriodicSyncs(engine, account2, 0, authority1);
383        removePeriodicSyncs(engine, account1, 0, authority2);
384        removePeriodicSyncs(engine, account2, 0, authority2);
385
386        engine.setMasterSyncAutomatically(false, 0);
387
388        engine.setIsSyncable(account1, 0, authority1, 1);
389        engine.setSyncAutomatically(account1, 0, authority1, true);
390
391        engine.setIsSyncable(account2, 0, authority1, 1);
392        engine.setSyncAutomatically(account2, 0, authority1, true);
393
394        engine.setIsSyncable(account1, 0, authority2, 1);
395        engine.setSyncAutomatically(account1, 0, authority2, false);
396
397        engine.setIsSyncable(account2, 0, authority2, 0);
398        engine.setSyncAutomatically(account2, 0, authority2, true);
399
400        engine.updateOrAddPeriodicSync(target1, period1, flex1, extras1);
401        engine.updateOrAddPeriodicSync(target1, period1, flex1, extras2);
402        engine.updateOrAddPeriodicSync(target2, period1, flex1, extras1);
403        engine.updateOrAddPeriodicSync(target2, period2, flex2, extras2);
404        engine.updateOrAddPeriodicSync(target3, period1, flex1, extras1);
405
406        engine.writeAllState();
407        engine.clearAndReadState();
408
409        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target1);
410        assertEquals(2, syncs.size());
411        assertEquals(sync1, syncs.get(0));
412        assertEquals(sync2, syncs.get(1));
413
414        syncs = engine.getPeriodicSyncs(target2);
415        assertEquals(2, syncs.size());
416        assertEquals(sync3, syncs.get(0));
417        assertEquals(sync4, syncs.get(1));
418
419        syncs = engine.getPeriodicSyncs(target3);
420        assertEquals(1, syncs.size());
421        assertEquals(sync5, syncs.get(0));
422
423        assertEquals(true, engine.getSyncAutomatically(account1, 0, authority1));
424        assertEquals(true, engine.getSyncAutomatically(account2, 0, authority1));
425        assertEquals(false, engine.getSyncAutomatically(account1, 0, authority2));
426        assertEquals(true, engine.getSyncAutomatically(account2, 0, authority2));
427
428        assertEquals(1, engine.getIsSyncable(account1, 0, authority1));
429        assertEquals(1, engine.getIsSyncable(account2, 0, authority1));
430        assertEquals(1, engine.getIsSyncable(account1, 0, authority2));
431        assertEquals(0, engine.getIsSyncable(account2, 0, authority2));
432    }
433
434    @SmallTest
435    public void testComponentParsing() throws Exception {
436
437        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
438                + "<accounts version=\"2\" >\n"
439                + "<authority id=\"0\" user=\"0\" package=\"" + syncService1.getPackageName() + "\""
440                + " class=\"" + syncService1.getClassName() + "\" syncable=\"true\">"
441                + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
442                + "\n</authority>"
443                + "</accounts>").getBytes();
444
445        File syncDir = getSyncDir();
446        syncDir.mkdirs();
447        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
448        FileOutputStream fos = accountInfoFile.startWrite();
449        fos.write(accountsFileData);
450        accountInfoFile.finishWrite(fos);
451
452        engine.clearAndReadState();
453
454        SyncStorageEngine.AuthorityInfo aInfo = engine.getAuthority(0);
455        assertNotNull(aInfo);
456
457        // Test service component read
458        List<PeriodicSync> syncs = engine.getPeriodicSyncs(
459                new SyncStorageEngine.EndPoint(syncService1, 0));
460        assertEquals(1, syncs.size());
461        assertEquals(true, engine.getIsTargetServiceActive(syncService1, 0));
462    }
463
464    @SmallTest
465    public void testComponentSettings() throws Exception {
466        EndPoint target1 = new EndPoint(syncService1, 0);
467        engine.updateOrAddPeriodicSync(target1, dayPoll, dayFuzz, Bundle.EMPTY);
468
469        engine.setIsTargetServiceActive(target1.service, 0, true);
470        boolean active = engine.getIsTargetServiceActive(target1.service, 0);
471        assert(active);
472
473        engine.setIsTargetServiceActive(target1.service, 1, false);
474        active = engine.getIsTargetServiceActive(target1.service, 1);
475        assert(!active);
476    }
477
478    @MediumTest
479    /**
480     * V2 introduces flex time as well as service components.
481     * @throws Exception
482     */
483    public void testAuthorityParsingV2() throws Exception {
484        final Account account = new Account("account1", "type1");
485        final String authority1 = "auth1";
486        final String authority2 = "auth2";
487        final String authority3 = "auth3";
488
489        EndPoint target1 = new EndPoint(account, authority1, 0);
490        EndPoint target2 = new EndPoint(account, authority2, 0);
491        EndPoint target3 = new EndPoint(account, authority3, 0);
492        EndPoint target4 = new EndPoint(account, authority3, 1);
493
494        PeriodicSync sync1 = new PeriodicSync(account, authority1, Bundle.EMPTY, dayPoll, dayFuzz);
495        PeriodicSync sync2 = new PeriodicSync(account, authority2, Bundle.EMPTY, dayPoll, dayFuzz);
496        PeriodicSync sync3 = new PeriodicSync(account, authority3, Bundle.EMPTY, dayPoll, dayFuzz);
497        PeriodicSync sync1s = new PeriodicSync(account, authority1, Bundle.EMPTY, thousandSecs,
498                thousandSecsFuzz);
499        PeriodicSync sync2s = new PeriodicSync(account, authority2, Bundle.EMPTY, thousandSecs,
500                thousandSecsFuzz);
501        PeriodicSync sync3s = new PeriodicSync(account, authority3, Bundle.EMPTY, thousandSecs,
502                thousandSecsFuzz);
503
504        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
505                + "<accounts version=\"2\" >\n"
506                + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" >"
507                + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
508                + "\n</authority>"
509                + "<authority id=\"1\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth2\" >"
510                + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
511                + "\n</authority>"
512                // No user defaults to user 0 - all users.
513                + "<authority id=\"2\"            account=\"account1\" type=\"type1\" authority=\"auth3\" >"
514                + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
515                + "\n</authority>"
516                + "<authority id=\"3\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth3\" >"
517                + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
518                + "\n</authority>"
519                + "</accounts>").getBytes();
520
521        File syncDir = getSyncDir();
522        syncDir.mkdirs();
523        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
524        FileOutputStream fos = accountInfoFile.startWrite();
525        fos.write(accountsFileData);
526        accountInfoFile.finishWrite(fos);
527
528        engine.clearAndReadState();
529
530        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target1);
531        assertEquals("Got incorrect # of syncs", 1, syncs.size());
532        assertEquals(sync1, syncs.get(0));
533
534        syncs = engine.getPeriodicSyncs(target2);
535        assertEquals(1, syncs.size());
536        assertEquals(sync2, syncs.get(0));
537
538        syncs = engine.getPeriodicSyncs(target3);
539        assertEquals(1, syncs.size());
540        assertEquals(sync3, syncs.get(0));
541
542        syncs = engine.getPeriodicSyncs(target4);
543
544        assertEquals(1, syncs.size());
545        assertEquals(sync3, syncs.get(0));
546
547        // Test empty periodic data.
548        accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
549                + "<accounts version=\"2\">\n"
550                + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
551                + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
552                + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
553                + "</accounts>\n").getBytes();
554
555        accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
556        fos = accountInfoFile.startWrite();
557        fos.write(accountsFileData);
558        accountInfoFile.finishWrite(fos);
559
560        engine.clearAndReadState();
561
562        syncs = engine.getPeriodicSyncs(target1);
563        assertEquals(0, syncs.size());
564
565        syncs = engine.getPeriodicSyncs(target2);
566        assertEquals(0, syncs.size());
567
568        syncs = engine.getPeriodicSyncs(target3);
569        assertEquals(0, syncs.size());
570
571        accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
572                + "<accounts version=\"2\">\n"
573                + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\">\n"
574                + "<periodicSync period=\"1000\" />\n"
575                + "</authority>"
576                + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\">\n"
577                + "<periodicSync period=\"1000\" />\n"
578                + "</authority>"
579                + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\">\n"
580                + "<periodicSync period=\"1000\" />\n"
581                + "</authority>"
582                + "</accounts>\n").getBytes();
583
584        accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
585        fos = accountInfoFile.startWrite();
586        fos.write(accountsFileData);
587        accountInfoFile.finishWrite(fos);
588
589        engine.clearAndReadState();
590
591        syncs = engine.getPeriodicSyncs(target1);
592        assertEquals(1, syncs.size());
593        assertEquals(sync1s, syncs.get(0));
594
595        syncs = engine.getPeriodicSyncs(target2);
596        assertEquals(1, syncs.size());
597        assertEquals(sync2s, syncs.get(0));
598
599        syncs = engine.getPeriodicSyncs(target3);
600        assertEquals(1, syncs.size());
601        assertEquals(sync3s, syncs.get(0));
602    }
603
604    @MediumTest
605    public void testAuthorityParsing() throws Exception {
606        final Account account = new Account("account1", "type1");
607        final String authority1 = "auth1";
608        final String authority2 = "auth2";
609        final String authority3 = "auth3";
610        final Bundle extras = new Bundle();
611
612        EndPoint target1 = new EndPoint(account, authority1, 0);
613        EndPoint target2 = new EndPoint(account, authority2, 0);
614        EndPoint target3 = new EndPoint(account, authority3, 0);
615        EndPoint target4 = new EndPoint(account, authority3, 1);
616
617        PeriodicSync sync1 = new PeriodicSync(account, authority1, extras, (long) (60 * 60 * 24));
618        PeriodicSync sync2 = new PeriodicSync(account, authority2, extras, (long) (60 * 60 * 24));
619        PeriodicSync sync3 = new PeriodicSync(account, authority3, extras, (long) (60 * 60 * 24));
620        PeriodicSync sync1s = new PeriodicSync(account, authority1, extras, 1000);
621        PeriodicSync sync2s = new PeriodicSync(account, authority2, extras, 1000);
622        PeriodicSync sync3s = new PeriodicSync(account, authority3, extras, 1000);
623
624        MockContentResolver mockResolver = new MockContentResolver();
625
626        final TestContext testContext = new TestContext(mockResolver, getContext());
627
628        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
629                + "<accounts>\n"
630                + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
631                + "<authority id=\"1\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
632                + "<authority id=\"2\"            account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
633                + "<authority id=\"3\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
634                + "</accounts>\n").getBytes();
635
636        File syncDir = getSyncDir();
637        syncDir.mkdirs();
638        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
639        FileOutputStream fos = accountInfoFile.startWrite();
640        fos.write(accountsFileData);
641        accountInfoFile.finishWrite(fos);
642
643        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
644
645        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target1);
646        assertEquals(1, syncs.size());
647        assertEquals("expected sync1: " + sync1.toString() + " == sync 2" + syncs.get(0).toString(), sync1, syncs.get(0));
648
649        syncs = engine.getPeriodicSyncs(target2);
650        assertEquals(1, syncs.size());
651        assertEquals(sync2, syncs.get(0));
652
653        syncs = engine.getPeriodicSyncs(target3);
654        assertEquals(1, syncs.size());
655        assertEquals(sync3, syncs.get(0));
656        syncs = engine.getPeriodicSyncs(target4);
657
658
659        assertEquals(1, syncs.size());
660        assertEquals(sync3, syncs.get(0));
661
662        accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
663                + "<accounts version=\"2\">\n"
664                + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
665                + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
666                + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
667                + "</accounts>\n").getBytes();
668
669        accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
670        fos = accountInfoFile.startWrite();
671        fos.write(accountsFileData);
672        accountInfoFile.finishWrite(fos);
673
674        engine.clearAndReadState();
675
676        syncs = engine.getPeriodicSyncs(target1);
677        assertEquals(0, syncs.size());
678
679        syncs = engine.getPeriodicSyncs(target2);
680        assertEquals(0, syncs.size());
681
682        syncs = engine.getPeriodicSyncs(target3);
683        assertEquals(0, syncs.size());
684
685        accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
686                + "<accounts version=\"2\">\n"
687                + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\">\n"
688                + "<periodicSync period=\"1000\" />\n"
689                + "</authority>"
690                + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\">\n"
691                + "<periodicSync period=\"1000\" />\n"
692                + "</authority>"
693                + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\">\n"
694                + "<periodicSync period=\"1000\" />\n"
695                + "</authority>"
696                + "</accounts>\n").getBytes();
697
698        accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
699        fos = accountInfoFile.startWrite();
700        fos.write(accountsFileData);
701        accountInfoFile.finishWrite(fos);
702
703        engine.clearAndReadState();
704
705        syncs = engine.getPeriodicSyncs(target1);
706        assertEquals(1, syncs.size());
707        assertEquals(sync1s, syncs.get(0));
708
709        syncs = engine.getPeriodicSyncs(target2);
710        assertEquals(1, syncs.size());
711        assertEquals(sync2s, syncs.get(0));
712
713        syncs = engine.getPeriodicSyncs(target3);
714        assertEquals(1, syncs.size());
715        assertEquals(sync3s, syncs.get(0));
716    }
717
718    @MediumTest
719    public void testListenForTicklesParsing() throws Exception {
720        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
721                + "<accounts>\n"
722                + "<listenForTickles user=\"0\" enabled=\"false\" />"
723                + "<listenForTickles user=\"1\" enabled=\"true\" />"
724                + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
725                + "<authority id=\"1\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
726                + "</accounts>\n").getBytes();
727
728        MockContentResolver mockResolver = new MockContentResolver();
729        final TestContext testContext = new TestContext(mockResolver, getContext());
730
731        File syncDir = getSyncDir();
732        syncDir.mkdirs();
733        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
734        FileOutputStream fos = accountInfoFile.startWrite();
735        fos.write(accountsFileData);
736        accountInfoFile.finishWrite(fos);
737
738        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
739
740        assertEquals(false, engine.getMasterSyncAutomatically(0));
741        assertEquals(true, engine.getMasterSyncAutomatically(1));
742        assertEquals(true, engine.getMasterSyncAutomatically(2));
743
744    }
745
746    @MediumTest
747    public void testAuthorityRenaming() throws Exception {
748        final Account account1 = new Account("acc1", "type1");
749        final Account account2 = new Account("acc2", "type2");
750        final String authorityContacts = "contacts";
751        final String authorityCalendar = "calendar";
752        final String authorityOther = "other";
753        final String authorityContactsNew = "com.android.contacts";
754        final String authorityCalendarNew = "com.android.calendar";
755
756        MockContentResolver mockResolver = new MockContentResolver();
757
758        final TestContext testContext = new TestContext(mockResolver, getContext());
759
760        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
761                + "<accounts>\n"
762                + "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n"
763                + "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n"
764                + "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n"
765                + "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n"
766                + "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n"
767                + "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n"
768                + "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\""
769                + " authority=\"com.android.calendar\" />\n"
770                + "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\""
771                + " authority=\"com.android.contacts\" />\n"
772                + "</accounts>\n").getBytes();
773
774        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
775        syncDir.mkdirs();
776        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
777        FileOutputStream fos = accountInfoFile.startWrite();
778        fos.write(accountsFileData);
779        accountInfoFile.finishWrite(fos);
780
781        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
782
783        assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityContacts));
784        assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityCalendar));
785        assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityOther));
786        assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityContactsNew));
787        assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityCalendarNew));
788
789        assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContacts));
790        assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendar));
791        assertEquals(true, engine.getSyncAutomatically(account2, 0, authorityOther));
792        assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContactsNew));
793        assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendarNew));
794    }
795
796    @SmallTest
797    public void testSyncableMigration() throws Exception {
798        final Account account = new Account("acc", "type");
799
800        MockContentResolver mockResolver = new MockContentResolver();
801
802        final TestContext testContext = new TestContext(mockResolver, getContext());
803
804        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
805                + "<accounts>\n"
806                + "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n"
807                + "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n"
808                + "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\""
809                + " authority=\"other3\" />\n"
810                + "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\""
811                + " authority=\"other4\" />\n"
812                + "</accounts>\n").getBytes();
813
814        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
815        syncDir.mkdirs();
816        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
817        FileOutputStream fos = accountInfoFile.startWrite();
818        fos.write(accountsFileData);
819        accountInfoFile.finishWrite(fos);
820
821        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
822
823        assertEquals(-1, engine.getIsSyncable(account, 0, "other1"));
824        assertEquals(1, engine.getIsSyncable(account, 0, "other2"));
825        assertEquals(0, engine.getIsSyncable(account, 0, "other3"));
826        assertEquals(1, engine.getIsSyncable(account, 0, "other4"));
827    }
828
829    /**
830     * Verify that the API cannot cause a run-time reboot by passing in the empty string as an
831     * authority. The problem here is that
832     * {@link SyncStorageEngine#getOrCreateAuthorityLocked(account, provider)} would register
833     * an empty authority which causes a RTE in {@link SyncManager#scheduleReadyPeriodicSyncs()}.
834     * This is not strictly a SSE test, but it does depend on the SSE data structures.
835     */
836    @SmallTest
837    public void testExpectedIllegalArguments() throws Exception {
838        try {
839            ContentResolver.setSyncAutomatically(account1, "", true);
840            fail("empty provider string should throw IllegalArgumentException");
841        } catch (IllegalArgumentException expected) {}
842
843        try {
844            ContentResolver.addPeriodicSync(account1, "", Bundle.EMPTY, 84000L);
845            fail("empty provider string should throw IllegalArgumentException");
846        } catch (IllegalArgumentException expected) {}
847
848        try {
849            ContentResolver.removePeriodicSync(account1, "", Bundle.EMPTY);
850            fail("empty provider string should throw IllegalArgumentException");
851        } catch (IllegalArgumentException expected) {}
852
853        try {
854            ContentResolver.cancelSync(account1, "");
855            fail("empty provider string should throw IllegalArgumentException");
856        } catch (IllegalArgumentException expected) {}
857
858        try {
859            ContentResolver.setIsSyncable(account1, "", 0);
860            fail("empty provider string should throw IllegalArgumentException");
861        } catch (IllegalArgumentException expected) {}
862
863        try {
864            ContentResolver.cancelSync(account1, "");
865            fail("empty provider string should throw IllegalArgumentException");
866        } catch (IllegalArgumentException expected) {}
867
868        try {
869            ContentResolver.requestSync(account1, "", Bundle.EMPTY);
870            fail("empty provider string should throw IllegalArgumentException");
871        } catch (IllegalArgumentException expected) {}
872
873        try {
874            ContentResolver.getSyncStatus(account1, "");
875            fail("empty provider string should throw IllegalArgumentException");
876        } catch (IllegalArgumentException expected) {}
877
878        // Make sure we aren't blocking null account/provider for those functions that use it
879        // to specify ALL accounts/providers.
880        ContentResolver.requestSync(null, null, Bundle.EMPTY);
881        ContentResolver.cancelSync(null, null);
882    }
883}
884
885class TestContext extends ContextWrapper {
886
887    ContentResolver mResolver;
888
889    private final Context mRealContext;
890
891    public TestContext(ContentResolver resolver, Context realContext) {
892        super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
893        mRealContext = realContext;
894        mResolver = resolver;
895    }
896
897    @Override
898    public Resources getResources() {
899        return mRealContext.getResources();
900    }
901
902    @Override
903    public File getFilesDir() {
904        return mRealContext.getFilesDir();
905    }
906
907    @Override
908    public void enforceCallingOrSelfPermission(String permission, String message) {
909    }
910
911    @Override
912    public void sendBroadcast(Intent intent) {
913    }
914
915    @Override
916    public ContentResolver getContentResolver() {
917        return mResolver;
918    }
919}
920