1 package com.xtremelabs.robolectric.shadows;
2 
3 import android.accounts.Account;
4 import android.content.*;
5 import android.database.ContentObserver;
6 import android.database.Cursor;
7 import android.net.Uri;
8 import android.os.Bundle;
9 import com.xtremelabs.robolectric.internal.Implementation;
10 import com.xtremelabs.robolectric.internal.Implements;
11 import com.xtremelabs.robolectric.tester.android.database.TestCursor;
12 
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.OutputStream;
16 import java.util.*;
17 
18 @Implements(ContentResolver.class)
19 public 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 
reset()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 
NotifiedUri(Uri uri, ContentObserver observer, boolean syncToNetwork)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
openInputStream(final Uri uri)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