1 /* 2 * Copyright (C) 2013 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 package com.android.photos.data; 17 18 import android.content.ContentProviderOperation; 19 import android.content.ContentResolver; 20 import android.content.ContentUris; 21 import android.content.ContentValues; 22 import android.content.OperationApplicationException; 23 import android.database.Cursor; 24 import android.database.sqlite.SQLiteDatabase; 25 import android.database.sqlite.SQLiteOpenHelper; 26 import android.net.Uri; 27 import android.os.RemoteException; 28 import android.provider.BaseColumns; 29 import android.test.ProviderTestCase2; 30 31 import com.android.photos.data.PhotoProvider.Accounts; 32 import com.android.photos.data.PhotoProvider.Albums; 33 import com.android.photos.data.PhotoProvider.Metadata; 34 import com.android.photos.data.PhotoProvider.Photos; 35 36 import java.util.ArrayList; 37 38 public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> { 39 @SuppressWarnings("unused") 40 private static final String TAG = PhotoProviderTest.class.getSimpleName(); 41 42 private static final String MIME_TYPE = "test/test"; 43 private static final String ALBUM_TITLE = "My Album"; 44 private static final long ALBUM_PARENT_ID = 100; 45 private static final String META_KEY = "mykey"; 46 private static final String META_VALUE = "myvalue"; 47 private static final String ACCOUNT_NAME = "foo (at) bar.com"; 48 49 private static final Uri NO_TABLE_URI = PhotoProvider.BASE_CONTENT_URI; 50 private static final Uri BAD_TABLE_URI = Uri.withAppendedPath(PhotoProvider.BASE_CONTENT_URI, 51 "bad_table"); 52 53 private static final String WHERE_METADATA_PHOTOS_ID = Metadata.PHOTO_ID + " = ?"; 54 private static final String WHERE_METADATA = Metadata.PHOTO_ID + " = ? AND " + Metadata.KEY 55 + " = ?"; 56 57 private long mAlbumId; 58 private long mPhotoId; 59 private long mMetadataId; 60 private long mAccountId; 61 62 private SQLiteOpenHelper mDBHelper; 63 private ContentResolver mResolver; 64 private NotificationWatcher mNotifications = new NotificationWatcher(); 65 66 public PhotoProviderTest() { 67 super(PhotoProvider.class, PhotoProvider.AUTHORITY); 68 } 69 70 @Override 71 protected void setUp() throws Exception { 72 super.setUp(); 73 mResolver = getMockContentResolver(); 74 PhotoProvider provider = (PhotoProvider) getProvider(); 75 provider.setMockNotification(mNotifications); 76 mDBHelper = provider.getDatabaseHelper(); 77 SQLiteDatabase db = mDBHelper.getWritableDatabase(); 78 db.beginTransaction(); 79 try { 80 PhotoDatabaseUtils.insertAccount(db, ACCOUNT_NAME); 81 mAccountId = PhotoDatabaseUtils.queryAccountIdFromName(db, ACCOUNT_NAME); 82 PhotoDatabaseUtils.insertAlbum(db, ALBUM_PARENT_ID, ALBUM_TITLE, 83 Albums.VISIBILITY_PRIVATE, mAccountId); 84 mAlbumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, ALBUM_PARENT_ID); 85 PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), mAlbumId, 86 MIME_TYPE, mAccountId); 87 mPhotoId = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, mAlbumId); 88 PhotoDatabaseUtils.insertMetadata(db, mPhotoId, META_KEY, META_VALUE); 89 String[] projection = { 90 BaseColumns._ID, 91 }; 92 Cursor cursor = db.query(Metadata.TABLE, projection, null, null, null, null, null); 93 cursor.moveToNext(); 94 mMetadataId = cursor.getLong(0); 95 cursor.close(); 96 db.setTransactionSuccessful(); 97 mNotifications.reset(); 98 } finally { 99 db.endTransaction(); 100 } 101 } 102 103 @Override 104 protected void tearDown() throws Exception { 105 mDBHelper.close(); 106 mDBHelper = null; 107 super.tearDown(); 108 getMockContext().deleteDatabase(PhotoProvider.DB_NAME); 109 } 110 111 public void testDelete() { 112 try { 113 mResolver.delete(NO_TABLE_URI, null, null); 114 fail("Exeption should be thrown when no table given"); 115 } catch (Exception e) { 116 // expected exception 117 } 118 try { 119 mResolver.delete(BAD_TABLE_URI, null, null); 120 fail("Exeption should be thrown when deleting from a table that doesn't exist"); 121 } catch (Exception e) { 122 // expected exception 123 } 124 125 String[] selectionArgs = { 126 String.valueOf(mPhotoId) 127 }; 128 // Delete some metadata 129 assertEquals(1, 130 mResolver.delete(Metadata.CONTENT_URI, WHERE_METADATA_PHOTOS_ID, selectionArgs)); 131 Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId); 132 assertEquals(1, mResolver.delete(photoUri, null, null)); 133 Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId); 134 assertEquals(1, mResolver.delete(albumUri, null, null)); 135 // now delete something that isn't there 136 assertEquals(0, mResolver.delete(photoUri, null, null)); 137 } 138 139 public void testDeleteMetadataId() { 140 Uri metadataUri = ContentUris.withAppendedId(Metadata.CONTENT_URI, mMetadataId); 141 assertEquals(1, mResolver.delete(metadataUri, null, null)); 142 Cursor cursor = mResolver.query(Metadata.CONTENT_URI, null, null, null, null); 143 assertEquals(0, cursor.getCount()); 144 cursor.close(); 145 } 146 147 // Delete the album and ensure that the photos referring to the album are 148 // deleted. 149 public void testDeleteAlbumCascade() { 150 Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId); 151 mResolver.delete(albumUri, null, null); 152 assertTrue(mNotifications.isNotified(Photos.CONTENT_URI)); 153 assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI)); 154 assertTrue(mNotifications.isNotified(albumUri)); 155 assertEquals(3, mNotifications.notificationCount()); 156 Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS, 157 null, null, null); 158 assertEquals(0, cursor.getCount()); 159 cursor.close(); 160 } 161 162 // Delete all albums and ensure that photos in any album are deleted. 163 public void testDeleteAlbumCascade2() { 164 mResolver.delete(Albums.CONTENT_URI, null, null); 165 assertTrue(mNotifications.isNotified(Photos.CONTENT_URI)); 166 assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI)); 167 assertTrue(mNotifications.isNotified(Albums.CONTENT_URI)); 168 assertEquals(3, mNotifications.notificationCount()); 169 Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS, 170 null, null, null); 171 assertEquals(0, cursor.getCount()); 172 cursor.close(); 173 } 174 175 // Delete a photo and ensure that the metadata for that photo are deleted. 176 public void testDeletePhotoCascade() { 177 Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId); 178 mResolver.delete(photoUri, null, null); 179 assertTrue(mNotifications.isNotified(photoUri)); 180 assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI)); 181 assertEquals(2, mNotifications.notificationCount()); 182 Cursor cursor = mResolver.query(Metadata.CONTENT_URI, 183 PhotoDatabaseUtils.PROJECTION_METADATA, null, null, null); 184 assertEquals(0, cursor.getCount()); 185 cursor.close(); 186 } 187 188 public void testDeleteAccountCascade() { 189 Uri accountUri = ContentUris.withAppendedId(Accounts.CONTENT_URI, mAccountId); 190 SQLiteDatabase db = mDBHelper.getWritableDatabase(); 191 db.beginTransaction(); 192 PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), null, 193 "image/jpeg", mAccountId); 194 PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), null, 195 "image/jpeg", 0L); 196 PhotoDatabaseUtils.insertAlbum(db, null, "title", Albums.VISIBILITY_PRIVATE, 10630L); 197 db.setTransactionSuccessful(); 198 db.endTransaction(); 199 // ensure all pictures are there: 200 Cursor cursor = mResolver.query(Photos.CONTENT_URI, null, null, null, null); 201 assertEquals(3, cursor.getCount()); 202 cursor.close(); 203 // delete the account 204 assertEquals(1, mResolver.delete(accountUri, null, null)); 205 // now ensure that all associated photos were deleted 206 cursor = mResolver.query(Photos.CONTENT_URI, null, null, null, null); 207 assertEquals(1, cursor.getCount()); 208 cursor.close(); 209 // now ensure all associated albums were deleted. 210 cursor = mResolver.query(Albums.CONTENT_URI, null, null, null, null); 211 assertEquals(1, cursor.getCount()); 212 cursor.close(); 213 } 214 215 public void testGetType() { 216 // We don't return types for albums 217 assertNull(mResolver.getType(Albums.CONTENT_URI)); 218 219 Uri noImage = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1); 220 assertNull(mResolver.getType(noImage)); 221 222 Uri image = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId); 223 assertEquals(MIME_TYPE, mResolver.getType(image)); 224 } 225 226 public void testInsert() { 227 ContentValues values = new ContentValues(); 228 values.put(Albums.TITLE, "add me"); 229 values.put(Albums.VISIBILITY, Albums.VISIBILITY_PRIVATE); 230 values.put(Albums.ACCOUNT_ID, 100L); 231 values.put(Albums.DATE_MODIFIED, 100L); 232 values.put(Albums.DATE_PUBLISHED, 100L); 233 values.put(Albums.LOCATION_STRING, "Home"); 234 values.put(Albums.TITLE, "hello world"); 235 values.putNull(Albums.PARENT_ID); 236 values.put(Albums.SUMMARY, "Nothing much to say about this"); 237 Uri insertedUri = mResolver.insert(Albums.CONTENT_URI, values); 238 assertNotNull(insertedUri); 239 Cursor cursor = mResolver.query(insertedUri, PhotoDatabaseUtils.PROJECTION_ALBUMS, null, 240 null, null); 241 assertNotNull(cursor); 242 assertEquals(1, cursor.getCount()); 243 cursor.close(); 244 } 245 246 public void testUpdate() { 247 ContentValues values = new ContentValues(); 248 // Normal update -- use an album. 249 values.put(Albums.TITLE, "foo"); 250 Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId); 251 assertEquals(1, mResolver.update(albumUri, values, null, null)); 252 String[] projection = { 253 Albums.TITLE, 254 }; 255 Cursor cursor = mResolver.query(albumUri, projection, null, null, null); 256 assertEquals(1, cursor.getCount()); 257 assertTrue(cursor.moveToNext()); 258 assertEquals("foo", cursor.getString(0)); 259 cursor.close(); 260 261 // Update a row that doesn't exist. 262 Uri noAlbumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId + 1); 263 values.put(Albums.TITLE, "bar"); 264 assertEquals(0, mResolver.update(noAlbumUri, values, null, null)); 265 266 // Update a metadata value that exists. 267 ContentValues metadata = new ContentValues(); 268 metadata.put(Metadata.PHOTO_ID, mPhotoId); 269 metadata.put(Metadata.KEY, META_KEY); 270 metadata.put(Metadata.VALUE, "new value"); 271 assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null)); 272 273 projection = new String[] { 274 Metadata.VALUE, 275 }; 276 277 String[] selectionArgs = { 278 String.valueOf(mPhotoId), META_KEY, 279 }; 280 281 cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs, 282 null); 283 assertEquals(1, cursor.getCount()); 284 assertTrue(cursor.moveToNext()); 285 assertEquals("new value", cursor.getString(0)); 286 cursor.close(); 287 288 // Update a metadata value that doesn't exist. 289 metadata.put(Metadata.KEY, "other stuff"); 290 assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null)); 291 292 selectionArgs[1] = "other stuff"; 293 cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs, 294 null); 295 assertEquals(1, cursor.getCount()); 296 assertTrue(cursor.moveToNext()); 297 assertEquals("new value", cursor.getString(0)); 298 cursor.close(); 299 300 // Remove a metadata value using update. 301 metadata.putNull(Metadata.VALUE); 302 assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null)); 303 cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs, 304 null); 305 assertEquals(0, cursor.getCount()); 306 cursor.close(); 307 } 308 309 public void testQuery() { 310 // Query a photo that exists. 311 Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS, 312 null, null, null); 313 assertNotNull(cursor); 314 assertEquals(1, cursor.getCount()); 315 assertTrue(cursor.moveToNext()); 316 assertEquals(mPhotoId, cursor.getLong(0)); 317 cursor.close(); 318 319 // Query a photo that doesn't exist. 320 Uri noPhotoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1); 321 cursor = mResolver.query(noPhotoUri, PhotoDatabaseUtils.PROJECTION_PHOTOS, null, null, 322 null); 323 assertNotNull(cursor); 324 assertEquals(0, cursor.getCount()); 325 cursor.close(); 326 327 // Query a photo that exists using selection arguments. 328 String[] selectionArgs = { 329 String.valueOf(mPhotoId), 330 }; 331 332 cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS, 333 Photos._ID + " = ?", selectionArgs, null); 334 assertNotNull(cursor); 335 assertEquals(1, cursor.getCount()); 336 assertTrue(cursor.moveToNext()); 337 assertEquals(mPhotoId, cursor.getLong(0)); 338 cursor.close(); 339 } 340 341 public void testUpdatePhotoNotification() { 342 Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId); 343 ContentValues values = new ContentValues(); 344 values.put(Photos.MIME_TYPE, "not-a/mime-type"); 345 mResolver.update(photoUri, values, null, null); 346 assertTrue(mNotifications.isNotified(photoUri)); 347 } 348 349 public void testUpdateMetadataNotification() { 350 ContentValues values = new ContentValues(); 351 values.put(Metadata.PHOTO_ID, mPhotoId); 352 values.put(Metadata.KEY, META_KEY); 353 values.put(Metadata.VALUE, "hello world"); 354 mResolver.update(Metadata.CONTENT_URI, values, null, null); 355 assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI)); 356 } 357 358 public void testBatchTransaction() throws RemoteException, OperationApplicationException { 359 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 360 ContentProviderOperation.Builder insert = ContentProviderOperation 361 .newInsert(Photos.CONTENT_URI); 362 insert.withValue(Photos.WIDTH, 200L); 363 insert.withValue(Photos.HEIGHT, 100L); 364 insert.withValue(Photos.DATE_TAKEN, System.currentTimeMillis()); 365 insert.withValue(Photos.ALBUM_ID, 1000L); 366 insert.withValue(Photos.MIME_TYPE, "image/jpg"); 367 insert.withValue(Photos.ACCOUNT_ID, 1L); 368 operations.add(insert.build()); 369 ContentProviderOperation.Builder update = ContentProviderOperation.newUpdate(Photos.CONTENT_URI); 370 update.withValue(Photos.DATE_MODIFIED, System.currentTimeMillis()); 371 String[] whereArgs = { 372 "100", 373 }; 374 String where = Photos.WIDTH + " = ?"; 375 update.withSelection(where, whereArgs); 376 operations.add(update.build()); 377 ContentProviderOperation.Builder delete = ContentProviderOperation 378 .newDelete(Photos.CONTENT_URI); 379 delete.withSelection(where, whereArgs); 380 operations.add(delete.build()); 381 mResolver.applyBatch(PhotoProvider.AUTHORITY, operations); 382 assertEquals(3, mNotifications.notificationCount()); 383 SQLiteDatabase db = mDBHelper.getReadableDatabase(); 384 long id = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, 1000L); 385 Uri uri = ContentUris.withAppendedId(Photos.CONTENT_URI, id); 386 assertTrue(mNotifications.isNotified(uri)); 387 assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI)); 388 assertTrue(mNotifications.isNotified(Photos.CONTENT_URI)); 389 } 390 391 } 392