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 android.content;
18
19import com.android.internal.os.AtomicFile;
20
21import android.test.AndroidTestCase;
22import android.test.RenamingDelegatingContext;
23import android.test.suitebuilder.annotation.SmallTest;
24import android.test.mock.MockContext;
25import android.test.mock.MockContentResolver;
26import android.accounts.Account;
27import android.os.Bundle;
28
29import java.util.List;
30import java.io.File;
31import java.io.FileOutputStream;
32
33public class SyncStorageEngineTest extends AndroidTestCase {
34
35    /**
36     * Test that we handle the case of a history row being old enough to purge before the
37     * correcponding sync is finished. This can happen if the clock changes while we are syncing.
38     *
39     */
40    @SmallTest
41    public void testPurgeActiveSync() throws Exception {
42        final Account account = new Account("a@example.com", "example.type");
43        final String authority = "testprovider";
44
45        MockContentResolver mockResolver = new MockContentResolver();
46
47        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
48                new TestContext(mockResolver, getContext()));
49
50        long time0 = 1000;
51        long historyId = engine.insertStartSyncEvent(
52                account, authority, time0, SyncStorageEngine.SOURCE_LOCAL);
53        long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
54        engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
55    }
56
57    /**
58     * Test that we can create, remove and retrieve periodic syncs
59     */
60    @SmallTest
61    public void testPeriodics() throws Exception {
62        final Account account1 = new Account("a@example.com", "example.type");
63        final Account account2 = new Account("b@example.com", "example.type.2");
64        final String authority = "testprovider";
65        final Bundle extras1 = new Bundle();
66        extras1.putString("a", "1");
67        final Bundle extras2 = new Bundle();
68        extras2.putString("a", "2");
69        final int period1 = 200;
70        final int period2 = 1000;
71
72        PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1);
73        PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1);
74        PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2);
75        PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2);
76
77        MockContentResolver mockResolver = new MockContentResolver();
78
79        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
80                new TestContext(mockResolver, getContext()));
81
82        removePeriodicSyncs(engine, account1, authority);
83        removePeriodicSyncs(engine, account2, authority);
84
85        // this should add two distinct periodic syncs for account1 and one for account2
86        engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
87        engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
88        engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
89        engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
90
91        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority);
92
93        assertEquals(2, syncs.size());
94
95        assertEquals(sync1, syncs.get(0));
96        assertEquals(sync3, syncs.get(1));
97
98        engine.removePeriodicSync(sync1.account, sync1.authority, sync1.extras);
99
100        syncs = engine.getPeriodicSyncs(account1, authority);
101        assertEquals(1, syncs.size());
102        assertEquals(sync3, syncs.get(0));
103
104        syncs = engine.getPeriodicSyncs(account2, authority);
105        assertEquals(1, syncs.size());
106        assertEquals(sync4, syncs.get(0));
107    }
108
109    private void removePeriodicSyncs(SyncStorageEngine engine, Account account, String authority) {
110        engine.setIsSyncable(account, authority, engine.getIsSyncable(account, authority));
111        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority);
112        for (PeriodicSync sync : syncs) {
113            engine.removePeriodicSync(sync.account, sync.authority, sync.extras);
114        }
115    }
116
117    @SmallTest
118    public void testAuthorityPersistence() throws Exception {
119        final Account account1 = new Account("a@example.com", "example.type");
120        final Account account2 = new Account("b@example.com", "example.type.2");
121        final String authority1 = "testprovider1";
122        final String authority2 = "testprovider2";
123        final Bundle extras1 = new Bundle();
124        extras1.putString("a", "1");
125        final Bundle extras2 = new Bundle();
126        extras2.putString("a", "2");
127        extras2.putLong("b", 2);
128        extras2.putInt("c", 1);
129        extras2.putBoolean("d", true);
130        extras2.putDouble("e", 1.2);
131        extras2.putFloat("f", 4.5f);
132        extras2.putParcelable("g", account1);
133        final int period1 = 200;
134        final int period2 = 1000;
135
136        PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1);
137        PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1);
138        PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1);
139        PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2);
140        PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1);
141
142        MockContentResolver mockResolver = new MockContentResolver();
143
144        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
145                new TestContext(mockResolver, getContext()));
146
147        removePeriodicSyncs(engine, account1, authority1);
148        removePeriodicSyncs(engine, account2, authority1);
149        removePeriodicSyncs(engine, account1, authority2);
150        removePeriodicSyncs(engine, account2, authority2);
151
152        engine.setMasterSyncAutomatically(false);
153
154        engine.setIsSyncable(account1, authority1, 1);
155        engine.setSyncAutomatically(account1, authority1, true);
156
157        engine.setIsSyncable(account2, authority1, 1);
158        engine.setSyncAutomatically(account2, authority1, true);
159
160        engine.setIsSyncable(account1, authority2, 1);
161        engine.setSyncAutomatically(account1, authority2, false);
162
163        engine.setIsSyncable(account2, authority2, 0);
164        engine.setSyncAutomatically(account2, authority2, true);
165
166        engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
167        engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
168        engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
169        engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
170        engine.addPeriodicSync(sync5.account, sync5.authority, sync5.extras, sync5.period);
171
172        engine.writeAllState();
173        engine.clearAndReadState();
174
175        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority1);
176        assertEquals(2, syncs.size());
177        assertEquals(sync1, syncs.get(0));
178        assertEquals(sync2, syncs.get(1));
179
180        syncs = engine.getPeriodicSyncs(account1, authority2);
181        assertEquals(2, syncs.size());
182        assertEquals(sync3, syncs.get(0));
183        assertEquals(sync4, syncs.get(1));
184
185        syncs = engine.getPeriodicSyncs(account2, authority1);
186        assertEquals(1, syncs.size());
187        assertEquals(sync5, syncs.get(0));
188
189        assertEquals(true, engine.getSyncAutomatically(account1, authority1));
190        assertEquals(true, engine.getSyncAutomatically(account2, authority1));
191        assertEquals(false, engine.getSyncAutomatically(account1, authority2));
192        assertEquals(true, engine.getSyncAutomatically(account2, authority2));
193
194        assertEquals(1, engine.getIsSyncable(account1, authority1));
195        assertEquals(1, engine.getIsSyncable(account2, authority1));
196        assertEquals(1, engine.getIsSyncable(account1, authority2));
197        assertEquals(0, engine.getIsSyncable(account2, authority2));
198    }
199
200    @SmallTest
201    public void testAuthorityParsing() throws Exception {
202        final Account account = new Account("account1", "type1");
203        final String authority1 = "auth1";
204        final String authority2 = "auth2";
205        final String authority3 = "auth3";
206        final Bundle extras = new Bundle();
207        PeriodicSync sync1 = new PeriodicSync(account, authority1, extras, (long) (60 * 60 * 24));
208        PeriodicSync sync2 = new PeriodicSync(account, authority2, extras, (long) (60 * 60 * 24));
209        PeriodicSync sync3 = new PeriodicSync(account, authority3, extras, (long) (60 * 60 * 24));
210        PeriodicSync sync1s = new PeriodicSync(account, authority1, extras, 1000);
211        PeriodicSync sync2s = new PeriodicSync(account, authority2, extras, 1000);
212        PeriodicSync sync3s = new PeriodicSync(account, authority3, extras, 1000);
213
214        MockContentResolver mockResolver = new MockContentResolver();
215
216        final TestContext testContext = new TestContext(mockResolver, getContext());
217
218        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
219                + "<accounts>\n"
220                + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
221                + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
222                + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
223                + "</accounts>\n").getBytes();
224
225        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
226        syncDir.mkdirs();
227        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
228        FileOutputStream fos = accountInfoFile.startWrite();
229        fos.write(accountsFileData);
230        accountInfoFile.finishWrite(fos);
231
232        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
233
234        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority1);
235        assertEquals(1, syncs.size());
236        assertEquals(sync1, syncs.get(0));
237
238        syncs = engine.getPeriodicSyncs(account, authority2);
239        assertEquals(1, syncs.size());
240        assertEquals(sync2, syncs.get(0));
241
242        syncs = engine.getPeriodicSyncs(account, authority3);
243        assertEquals(1, syncs.size());
244        assertEquals(sync3, syncs.get(0));
245
246        accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
247                + "<accounts version=\"2\">\n"
248                + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
249                + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
250                + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
251                + "</accounts>\n").getBytes();
252
253        accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
254        fos = accountInfoFile.startWrite();
255        fos.write(accountsFileData);
256        accountInfoFile.finishWrite(fos);
257
258        engine.clearAndReadState();
259
260        syncs = engine.getPeriodicSyncs(account, authority1);
261        assertEquals(0, syncs.size());
262
263        syncs = engine.getPeriodicSyncs(account, authority2);
264        assertEquals(0, syncs.size());
265
266        syncs = engine.getPeriodicSyncs(account, authority3);
267        assertEquals(0, syncs.size());
268
269        accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
270                + "<accounts version=\"2\">\n"
271                + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\">\n"
272                + "<periodicSync period=\"1000\" />\n"
273                + "</authority>"
274                + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\">\n"
275                + "<periodicSync period=\"1000\" />\n"
276                + "</authority>"
277                + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\">\n"
278                + "<periodicSync period=\"1000\" />\n"
279                + "</authority>"
280                + "</accounts>\n").getBytes();
281
282        accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
283        fos = accountInfoFile.startWrite();
284        fos.write(accountsFileData);
285        accountInfoFile.finishWrite(fos);
286
287        engine.clearAndReadState();
288
289        syncs = engine.getPeriodicSyncs(account, authority1);
290        assertEquals(1, syncs.size());
291        assertEquals(sync1s, syncs.get(0));
292
293        syncs = engine.getPeriodicSyncs(account, authority2);
294        assertEquals(1, syncs.size());
295        assertEquals(sync2s, syncs.get(0));
296
297        syncs = engine.getPeriodicSyncs(account, authority3);
298        assertEquals(1, syncs.size());
299        assertEquals(sync3s, syncs.get(0));
300    }
301
302    @SmallTest
303    public void testAuthorityRenaming() throws Exception {
304        final Account account1 = new Account("acc1", "type1");
305        final Account account2 = new Account("acc2", "type2");
306        final String authorityContacts = "contacts";
307        final String authorityCalendar = "calendar";
308        final String authorityOther = "other";
309        final String authorityContactsNew = "com.android.contacts";
310        final String authorityCalendarNew = "com.android.calendar";
311
312        MockContentResolver mockResolver = new MockContentResolver();
313
314        final TestContext testContext = new TestContext(mockResolver, getContext());
315
316        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
317                + "<accounts>\n"
318                + "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n"
319                + "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n"
320                + "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n"
321                + "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n"
322                + "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n"
323                + "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n"
324                + "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\""
325                + " authority=\"com.android.calendar\" />\n"
326                + "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\""
327                + " authority=\"com.android.contacts\" />\n"
328                + "</accounts>\n").getBytes();
329
330        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
331        syncDir.mkdirs();
332        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
333        FileOutputStream fos = accountInfoFile.startWrite();
334        fos.write(accountsFileData);
335        accountInfoFile.finishWrite(fos);
336
337        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
338
339        assertEquals(false, engine.getSyncAutomatically(account1, authorityContacts));
340        assertEquals(false, engine.getSyncAutomatically(account1, authorityCalendar));
341        assertEquals(true, engine.getSyncAutomatically(account1, authorityOther));
342        assertEquals(true, engine.getSyncAutomatically(account1, authorityContactsNew));
343        assertEquals(true, engine.getSyncAutomatically(account1, authorityCalendarNew));
344
345        assertEquals(false, engine.getSyncAutomatically(account2, authorityContacts));
346        assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendar));
347        assertEquals(true, engine.getSyncAutomatically(account2, authorityOther));
348        assertEquals(false, engine.getSyncAutomatically(account2, authorityContactsNew));
349        assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendarNew));
350    }
351
352    @SmallTest
353    public void testSyncableMigration() throws Exception {
354        final Account account = new Account("acc", "type");
355
356        MockContentResolver mockResolver = new MockContentResolver();
357
358        final TestContext testContext = new TestContext(mockResolver, getContext());
359
360        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
361                + "<accounts>\n"
362                + "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n"
363                + "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n"
364                + "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\""
365                + " authority=\"other3\" />\n"
366                + "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\""
367                + " authority=\"other4\" />\n"
368                + "</accounts>\n").getBytes();
369
370        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
371        syncDir.mkdirs();
372        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
373        FileOutputStream fos = accountInfoFile.startWrite();
374        fos.write(accountsFileData);
375        accountInfoFile.finishWrite(fos);
376
377        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
378
379        assertEquals(-1, engine.getIsSyncable(account, "other1"));
380        assertEquals(1, engine.getIsSyncable(account, "other2"));
381        assertEquals(0, engine.getIsSyncable(account, "other3"));
382        assertEquals(1, engine.getIsSyncable(account, "other4"));
383    }
384}
385
386class TestContext extends ContextWrapper {
387
388    ContentResolver mResolver;
389
390    private final Context mRealContext;
391
392    public TestContext(ContentResolver resolver, Context realContext) {
393        super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
394        mRealContext = realContext;
395        mResolver = resolver;
396    }
397
398    @Override
399    public File getFilesDir() {
400        return mRealContext.getFilesDir();
401    }
402
403    @Override
404    public void enforceCallingOrSelfPermission(String permission, String message) {
405    }
406
407    @Override
408    public void sendBroadcast(Intent intent) {
409    }
410
411    @Override
412    public ContentResolver getContentResolver() {
413        return mResolver;
414    }
415}
416