1package com.xtremelabs.robolectric.shadows;
2
3import android.accounts.Account;
4import android.content.*;
5import android.database.ContentObserver;
6import android.database.Cursor;
7import android.net.Uri;
8import android.os.Bundle;
9import com.xtremelabs.robolectric.internal.Implementation;
10import com.xtremelabs.robolectric.internal.Implements;
11import com.xtremelabs.robolectric.tester.android.database.TestCursor;
12
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.OutputStream;
16import java.util.*;
17
18@Implements(ContentResolver.class)
19public class ShadowContentResolver {
20    private int nextDatabaseIdForInserts;
21    private int nextDatabaseIdForUpdates;
22
23    private TestCursor cursor;
24    private final List<InsertStatement> insertStatements = new ArrayList<InsertStatement>();
25    private final List<UpdateStatement> updateStatements = new ArrayList<UpdateStatement>();
26    private final List<DeleteStatement> deleteStatements = new ArrayList<DeleteStatement>();
27    private List<NotifiedUri> notifiedUris = new ArrayList<NotifiedUri>();
28    private HashMap<Uri, TestCursor> uriCursorMap = new HashMap<Uri, TestCursor>();
29    private final Map<String, ArrayList<ContentProviderOperation>> contentProviderOperations = new HashMap<String, ArrayList<ContentProviderOperation>>();
30    private ContentProviderResult[] contentProviderResults;
31
32    private static final Map<String, Map<Account, Status>>  syncableAccounts =
33            new HashMap<String, Map<Account, Status>>();
34    private static final Map<String, ContentProvider> providers = new HashMap<String, ContentProvider>();
35    private static boolean masterSyncAutomatically;
36
37    public static void reset() {
38        syncableAccounts.clear();
39        providers.clear();
40        masterSyncAutomatically = false;
41    }
42
43    public static class NotifiedUri {
44        public final Uri uri;
45        public final boolean syncToNetwork;
46        public final ContentObserver observer;
47
48        public NotifiedUri(Uri uri, ContentObserver observer, boolean syncToNetwork) {
49            this.uri = uri;
50            this.syncToNetwork = syncToNetwork;
51            this.observer = observer;
52        }
53    }
54
55    public static class Status {
56        public int syncRequests;
57        public int state = -1;
58        public boolean syncAutomatically;
59        public Bundle syncExtras;
60        public List<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
61    }
62
63    @Implementation
64    public final InputStream openInputStream(final Uri uri) {
65        return new InputStream() {
66            @Override
67            public int read() throws IOException {
68                throw new UnsupportedOperationException();
69            }
70
71            @Override
72            public String toString() {
73                return "stream for " + uri;
74            }
75        };
76    }
77
78    @Implementation
79    public final OutputStream openOutputStream(final Uri uri) {
80        return new OutputStream() {
81
82            @Override
83            public void write(int arg0) throws IOException {
84            }
85
86            @Override
87            public String toString() {
88                return "outputstream for " + uri;
89            }
90        };
91    }
92
93    @Implementation
94    public final Uri insert(Uri url, ContentValues values) {
95        ContentProvider provider = getProvider(url);
96        if (provider != null) {
97            return provider.insert(url, values);
98        } else {
99            InsertStatement insertStatement = new InsertStatement(url, new ContentValues(values));
100            insertStatements.add(insertStatement);
101            return Uri.parse(url.toString() + "/" + nextDatabaseIdForInserts++);
102        }
103    }
104
105    @Implementation
106    public int update(Uri uri, ContentValues values, String where, String[] selectionArgs) {
107        ContentProvider provider = getProvider(uri);
108        if (provider != null) {
109            return provider.update(uri, values, where, selectionArgs);
110        } else {
111            UpdateStatement updateStatement = new UpdateStatement(uri, new ContentValues(values), where, selectionArgs);
112            updateStatements.add(updateStatement);
113            return nextDatabaseIdForUpdates++;
114        }
115    }
116
117    @Implementation
118    public final Cursor query(Uri uri, String[] projection, String selection,
119            String[] selectionArgs, String sortOrder) {
120        ContentProvider provider = getProvider(uri);
121        if (provider != null) {
122            return provider.query(uri, projection, selection, selectionArgs, sortOrder);
123        } else {
124            TestCursor returnCursor = getCursor(uri);
125            if (returnCursor == null) {
126                return null;
127            }
128
129            returnCursor.setQuery(uri, projection, selection, selectionArgs,
130                    sortOrder);
131            return returnCursor;
132        }
133    }
134
135    @Implementation
136    public final int delete(Uri url, String where, String[] selectionArgs) {
137        ContentProvider provider = getProvider(url);
138        if (provider != null) {
139            return provider.delete(url, where, selectionArgs);
140        } else {
141            DeleteStatement deleteStatement = new DeleteStatement(url, where, selectionArgs);
142            deleteStatements.add(deleteStatement);
143            return 1;
144        }
145    }
146
147    @Implementation
148    public final int bulkInsert(Uri url, ContentValues[] values) {
149        ContentProvider provider = getProvider(url);
150        if (provider != null) {
151            return provider.bulkInsert(url, values);
152        } else {
153            return 0;
154        }
155    }
156
157    @Implementation
158    public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
159       notifiedUris.add(new NotifiedUri(uri, observer, syncToNetwork));
160    }
161
162    @Implementation
163    public void notifyChange(Uri uri, ContentObserver observer) {
164        notifyChange(uri, observer, false);
165    }
166
167    @Implementation
168    public ContentProviderResult[] applyBatch(String authority, ArrayList<ContentProviderOperation> operations) {
169        contentProviderOperations.put(authority, operations);
170        return contentProviderResults;
171    }
172
173    @Implementation
174    public static void requestSync(Account account, String authority, Bundle extras) {
175        validateSyncExtrasBundle(extras);
176        Status status = getStatus(account, authority, true);
177        status.syncRequests++;
178        status.syncExtras = extras;
179    }
180
181    @Implementation
182    public static void setIsSyncable(Account account, String authority, int syncable) {
183        getStatus(account, authority, true).state = syncable;
184    }
185
186    @Implementation
187    public static int getIsSyncable(Account account, String authority) {
188        return getStatus(account, authority, true).state;
189    }
190
191    @Implementation
192    public static boolean getSyncAutomatically(Account account, String authority) {
193        return getStatus(account, authority, true).syncAutomatically;
194    }
195
196    @Implementation
197    public static void setSyncAutomatically(Account account, String authority, boolean sync) {
198        getStatus(account, authority, true).syncAutomatically = sync;
199    }
200
201    @Implementation
202    public static void addPeriodicSync(Account account, String authority, Bundle extras,
203                                       long pollFrequency) {
204
205        validateSyncExtrasBundle(extras);
206        getStatus(account, authority, true).syncs.add(new PeriodicSync(account, authority, extras, pollFrequency));
207    }
208
209    @Implementation
210    public static void removePeriodicSync(Account account, String authority, Bundle extras) {
211        validateSyncExtrasBundle(extras);
212        Status status = getStatus(account, authority);
213        if (status != null) status.syncs.clear();
214    }
215
216    @Implementation
217    public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) {
218        return getStatus(account, authority, true).syncs;
219    }
220
221    @Implementation
222    public static void validateSyncExtrasBundle(Bundle extras) {
223        for (String key : extras.keySet()) {
224            Object value = extras.get(key);
225            if (value == null) continue;
226            if (value instanceof Long) continue;
227            if (value instanceof Integer) continue;
228            if (value instanceof Boolean) continue;
229            if (value instanceof Float) continue;
230            if (value instanceof Double) continue;
231            if (value instanceof String) continue;
232            if (value instanceof Account) continue;
233            throw new IllegalArgumentException("unexpected value type: "
234                    + value.getClass().getName());
235        }
236    }
237
238    @Implementation
239    public static void setMasterSyncAutomatically(boolean sync) {
240        masterSyncAutomatically = sync;
241
242    }
243
244    @Implementation
245    public static boolean getMasterSyncAutomatically() {
246        return masterSyncAutomatically;
247    }
248
249
250    public static ContentProvider getProvider(Uri uri) {
251        if (uri == null) {
252            return null;
253        } else if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
254            return null;
255        } else {
256            return providers.get(uri.getAuthority());
257        }
258    }
259
260    public static void registerProvider(String authority, ContentProvider provider) {
261        providers.put(authority, provider);
262    }
263
264    public static Status getStatus(Account account, String authority) {
265        return getStatus(account, authority, false);
266    }
267
268    public static Status getStatus(Account account, String authority, boolean create) {
269        Map<Account, Status> map = syncableAccounts.get(authority);
270        if (map == null) {
271            map = new HashMap<Account, Status>();
272            syncableAccounts.put(authority, map);
273        }
274        Status status = map.get(account);
275        if (status == null && create) {
276            status = new Status();
277            map.put(account, status);
278        }
279        return status;
280    }
281
282    public void setCursor(TestCursor cursor) {
283        this.cursor = cursor;
284    }
285
286    public void setCursor(Uri uri, TestCursor cursorForUri) {
287        this.uriCursorMap.put(uri, cursorForUri);
288    }
289
290    public void setNextDatabaseIdForInserts(int nextId) {
291        nextDatabaseIdForInserts = nextId;
292    }
293
294    public void setNextDatabaseIdForUpdates(int nextId) {
295        nextDatabaseIdForUpdates = nextId;
296    }
297
298    public List<InsertStatement> getInsertStatements() {
299        return insertStatements;
300    }
301
302    public List<UpdateStatement> getUpdateStatements() {
303        return updateStatements;
304    }
305
306    public List<Uri> getDeletedUris() {
307        List<Uri> uris = new ArrayList<Uri>();
308        for (DeleteStatement deleteStatement : deleteStatements) {
309            uris.add(deleteStatement.getUri());
310        }
311        return uris;
312    }
313
314    public List<DeleteStatement> getDeleteStatements() {
315        return deleteStatements;
316    }
317
318    public List<NotifiedUri> getNotifiedUris() {
319        return notifiedUris;
320    }
321
322    public ArrayList<ContentProviderOperation> getContentProviderOperations(String authority) {
323        ArrayList<ContentProviderOperation> operations = contentProviderOperations.get(authority);
324        if (operations == null)
325            return new ArrayList<ContentProviderOperation>();
326        return operations;
327    }
328
329
330    public void setContentProviderResult(ContentProviderResult[] contentProviderResults) {
331        this.contentProviderResults = contentProviderResults;
332    }
333
334    private TestCursor getCursor(Uri uri) {
335        if (uriCursorMap.get(uri) != null) {
336            return uriCursorMap.get(uri);
337        } else if (cursor != null) {
338            return cursor;
339        } else {
340            return null;
341        }
342    }
343
344    public static class InsertStatement {
345        private final Uri uri;
346        private final ContentValues contentValues;
347
348        public InsertStatement(Uri uri, ContentValues contentValues) {
349            this.uri = uri;
350            this.contentValues = contentValues;
351        }
352
353        public Uri getUri() {
354            return uri;
355        }
356
357        public ContentValues getContentValues() {
358            return contentValues;
359        }
360    }
361
362    public static class UpdateStatement {
363        private final Uri uri;
364        private final ContentValues values;
365        private final String where;
366        private final String[] selectionArgs;
367
368        public UpdateStatement(Uri uri, ContentValues values, String where, String[] selectionArgs) {
369            this.uri = uri;
370            this.values = values;
371            this.where = where;
372            this.selectionArgs = selectionArgs;
373        }
374
375        public Uri getUri() {
376            return uri;
377        }
378
379        public ContentValues getContentValues() {
380            return values;
381        }
382
383        public String getWhere() {
384            return where;
385        }
386
387        public String[] getSelectionArgs() {
388            return selectionArgs;
389        }
390    }
391
392    public static class DeleteStatement {
393        private final Uri uri;
394        private final String where;
395        private final String[] selectionArgs;
396
397        public DeleteStatement(Uri uri, String where, String[] selectionArgs) {
398            this.uri = uri;
399            this.where = where;
400            this.selectionArgs = selectionArgs;
401        }
402
403        public Uri getUri() {
404            return uri;
405        }
406
407        public String getWhere() {
408            return where;
409        }
410
411        public String[] getSelectionArgs() {
412            return selectionArgs;
413        }
414    }
415}
416