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 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