From 178b04f4cea6dcbe0f84f0f189e9280d4c0d1442 Mon Sep 17 00:00:00 2001 From: George Mount Date: Wed, 20 Feb 2013 17:01:54 -0800 Subject: Add initial implementation of PhotosProvider. Change-Id: I98694cf54bd0fb549703a7184e1816e9590a05ff --- .../com/android/photos/data/DataTestRunner.java | 36 ++ .../com/android/photos/data/PhotoDatabaseTest.java | 187 +++++++++ .../android/photos/data/PhotoDatabaseUtils.java | 117 ++++++ .../com/android/photos/data/PhotoProviderTest.java | 463 +++++++++++++++++++++ 4 files changed, 803 insertions(+) create mode 100644 tests/src/com/android/photos/data/DataTestRunner.java create mode 100644 tests/src/com/android/photos/data/PhotoDatabaseTest.java create mode 100644 tests/src/com/android/photos/data/PhotoDatabaseUtils.java create mode 100644 tests/src/com/android/photos/data/PhotoProviderTest.java (limited to 'tests/src') diff --git a/tests/src/com/android/photos/data/DataTestRunner.java b/tests/src/com/android/photos/data/DataTestRunner.java new file mode 100644 index 000000000..432258561 --- /dev/null +++ b/tests/src/com/android/photos/data/DataTestRunner.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.photos.data; + +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; + +import junit.framework.TestSuite; + +public class DataTestRunner extends InstrumentationTestRunner { + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(PhotoDatabaseTest.class); + suite.addTestSuite(PhotoProviderTest.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return DataTestRunner.class.getClassLoader(); + } +} diff --git a/tests/src/com/android/photos/data/PhotoDatabaseTest.java b/tests/src/com/android/photos/data/PhotoDatabaseTest.java new file mode 100644 index 000000000..48e79d4e1 --- /dev/null +++ b/tests/src/com/android/photos/data/PhotoDatabaseTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.photos.data; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.test.InstrumentationTestCase; + +import com.android.photos.data.PhotoProvider.Albums; +import com.android.photos.data.PhotoProvider.Metadata; +import com.android.photos.data.PhotoProvider.Photos; + +import java.io.File; +import java.io.IOException; + +public class PhotoDatabaseTest extends InstrumentationTestCase { + + private PhotoDatabase mDBHelper; + + @Override + protected void setUp() { + Context context = getInstrumentation().getTargetContext(); + mDBHelper = new PhotoDatabase(context); + } + + @Override + protected void tearDown() { + mDBHelper.close(); + } + + public void testCreateDatabase() throws IOException { + Context context = getInstrumentation().getTargetContext(); + File dbFile = context.getDatabasePath(PhotoDatabase.DB_NAME); + if (dbFile.exists()) { + dbFile.delete(); + } + SQLiteDatabase db = getReadableDB(); + db.beginTransaction(); + db.endTransaction(); + assertTrue(dbFile.exists()); + dbFile.delete(); + } + + public void testTables() { + validateTable(Metadata.TABLE, PhotoDatabaseUtils.PROJECTION_METADATA); + validateTable(Albums.TABLE, PhotoDatabaseUtils.PROJECTION_ALBUMS); + validateTable(Photos.TABLE, PhotoDatabaseUtils.PROJECTION_PHOTOS); + } + + public void testAlbumsConstraints() { + SQLiteDatabase db = getWriteableDB(); + db.beginTransaction(); + try { + // Test NOT NULL constraint on name + assertFalse(PhotoDatabaseUtils + .insertAlbum(db, null, null, Albums.VISIBILITY_PRIVATE, null)); + + // test NOT NULL constraint on privacy + assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello", null, null)); + + // Normal insert + assertTrue(PhotoDatabaseUtils.insertAlbum(db, null, "hello", Albums.VISIBILITY_PRIVATE, + 100L)); + + // Test server id uniqueness + assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "world", Albums.VISIBILITY_PRIVATE, + 100L)); + + // Different server id allowed + assertTrue(PhotoDatabaseUtils.insertAlbum(db, null, "world", Albums.VISIBILITY_PRIVATE, + 101L)); + + // Allow null server id + assertTrue(PhotoDatabaseUtils.insertAlbum(db, null, "hello world", + Albums.VISIBILITY_PRIVATE, null)); + + long albumId = PhotoDatabaseUtils.queryAlbumIdFromServerId(db, 100); + + // Assign a valid child + assertTrue(PhotoDatabaseUtils.insertAlbum(db, albumId, "hello", Albums.VISIBILITY_PRIVATE, + null)); + + long otherAlbumId = PhotoDatabaseUtils.queryAlbumIdFromServerId(db, 101); + assertNotSame(albumId, otherAlbumId); + + // This is a valid child of another album. + assertTrue(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello", + Albums.VISIBILITY_PRIVATE, null)); + + // This isn't allowed due to uniqueness constraint (parent_id/name) + assertFalse(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello", + Albums.VISIBILITY_PRIVATE, null)); + } finally { + db.endTransaction(); + } + } + + public void testPhotosConstraints() { + SQLiteDatabase db = getWriteableDB(); + db.beginTransaction(); + try { + int width = 100; + int height = 100; + long dateTaken = System.currentTimeMillis(); + String mimeType = "test/test"; + + // Test NOT NULL mime-type + assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, width, height, dateTaken, null, + null)); + + // Test NOT NULL width + assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, null, height, dateTaken, null, + mimeType)); + + // Test NOT NULL height + assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, width, null, dateTaken, null, + mimeType)); + + // Test NOT NULL dateTaken + assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, width, height, null, null, + mimeType)); + + // Test normal insert + assertTrue(PhotoDatabaseUtils.insertPhoto(db, null, width, height, dateTaken, null, + mimeType)); + } finally { + db.endTransaction(); + } + } + + public void testMetadataConstraints() { + SQLiteDatabase db = getWriteableDB(); + db.beginTransaction(); + try { + final String mimeType = "test/test"; + long photoServerId = 100; + PhotoDatabaseUtils.insertPhoto(db, photoServerId, 100, 100, 100L, null, mimeType); + long photoId = PhotoDatabaseUtils.queryPhotoIdFromServerId(db, photoServerId); + + // Test NOT NULL PHOTO_ID constraint. + assertFalse(PhotoDatabaseUtils.insertMetadata(db, null, "foo", "bar")); + + // Normal insert. + assertTrue(PhotoDatabaseUtils.insertMetadata(db, photoId, "foo", "bar")); + + // Test uniqueness constraint. + assertFalse(PhotoDatabaseUtils.insertMetadata(db, photoId, "foo", "baz")); + } finally { + db.endTransaction(); + } + } + + private SQLiteDatabase getReadableDB() { + return mDBHelper.getReadableDatabase(); + } + + private SQLiteDatabase getWriteableDB() { + return mDBHelper.getWritableDatabase(); + } + + private void validateTable(String table, String[] projection) { + SQLiteDatabase db = getReadableDB(); + Cursor cursor = db.query(table, projection, null, null, null, null, null); + assertNotNull(cursor); + assertEquals(cursor.getCount(), 0); + assertEquals(cursor.getColumnCount(), projection.length); + for (int i = 0; i < projection.length; i++) { + assertEquals(cursor.getColumnName(i), projection[i]); + } + } + + +} diff --git a/tests/src/com/android/photos/data/PhotoDatabaseUtils.java b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java new file mode 100644 index 000000000..6fd73e1b3 --- /dev/null +++ b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.photos.data; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.android.photos.data.PhotoProvider.Albums; +import com.android.photos.data.PhotoProvider.Metadata; +import com.android.photos.data.PhotoProvider.Photos; + +import junit.framework.AssertionFailedError; + +public class PhotoDatabaseUtils { + public static String[] PROJECTION_ALBUMS = { + Albums._ID, + Albums.PARENT_ID, + Albums.VISIBILITY, + Albums.NAME, + Albums.SERVER_ID, + }; + + public static String[] PROJECTION_METADATA = { + Metadata.PHOTO_ID, + Metadata.KEY, + Metadata.VALUE, + }; + + public static String[] PROJECTION_PHOTOS = { + Photos._ID, + Photos.SERVER_ID, + Photos.WIDTH, + Photos.HEIGHT, + Photos.DATE_TAKEN, + Photos.ALBUM_ID, + Photos.MIME_TYPE, + }; + + private static String SELECTION_ALBUM_SERVER_ID = Albums.SERVER_ID + " = ?"; + private static String SELECTION_PHOTO_SERVER_ID = Photos.SERVER_ID + " = ?"; + + public static long queryAlbumIdFromServerId(SQLiteDatabase db, long serverId) { + return queryId(db, Albums.TABLE, PROJECTION_ALBUMS, SELECTION_ALBUM_SERVER_ID, serverId); + } + + public static long queryPhotoIdFromServerId(SQLiteDatabase db, long serverId) { + return queryId(db, Photos.TABLE, PROJECTION_PHOTOS, SELECTION_PHOTO_SERVER_ID, serverId); + } + + public static long queryId(SQLiteDatabase db, String table, String[] projection, + String selection, Object parameter) { + String paramString = parameter == null ? null : parameter.toString(); + String[] selectionArgs = { + paramString, + }; + Cursor cursor = db.query(table, projection, selection, selectionArgs, null, null, null); + try { + if (cursor.getCount() != 1 || !cursor.moveToNext()) { + throw new AssertionFailedError("Couldn't find item in table"); + } + long id = cursor.getLong(0); + return id; + } finally { + cursor.close(); + } + } + + public static boolean insertPhoto(SQLiteDatabase db, Long serverId, Integer width, + Integer height, Long dateTaken, Long albumId, String mimeType) { + ContentValues values = new ContentValues(); + values.put(Photos.SERVER_ID, serverId); + values.put(Photos.WIDTH, width); + values.put(Photos.HEIGHT, height); + values.put(Photos.DATE_TAKEN, dateTaken); + values.put(Photos.ALBUM_ID, albumId); + values.put(Photos.MIME_TYPE, mimeType); + return db.insert(Photos.TABLE, null, values) != -1; + } + + public static boolean insertAlbum(SQLiteDatabase db, Long parentId, String name, + Integer privacy, Long serverId) { + ContentValues values = new ContentValues(); + values.put(Albums.PARENT_ID, parentId); + values.put(Albums.NAME, name); + values.put(Albums.VISIBILITY, privacy); + values.put(Albums.SERVER_ID, serverId); + return db.insert(Albums.TABLE, null, values) != -1; + } + + public static boolean insertMetadata(SQLiteDatabase db, Long photosId, String key, String value) { + ContentValues values = new ContentValues(); + values.put(Metadata.PHOTO_ID, photosId); + values.put(Metadata.KEY, key); + values.put(Metadata.VALUE, value); + return db.insert(Metadata.TABLE, null, values) != -1; + } + + public static void deleteAllContent(SQLiteDatabase db) { + db.delete(Metadata.TABLE, null, null); + db.delete(Photos.TABLE, null, null); + db.delete(Albums.TABLE, null, null); + } +} diff --git a/tests/src/com/android/photos/data/PhotoProviderTest.java b/tests/src/com/android/photos/data/PhotoProviderTest.java new file mode 100644 index 000000000..ad913b0bd --- /dev/null +++ b/tests/src/com/android/photos/data/PhotoProviderTest.java @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.photos.data; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.provider.BaseColumns; +import android.test.InstrumentationTestCase; + +import com.android.photos.data.PhotoProvider.Albums; +import com.android.photos.data.PhotoProvider.Metadata; +import com.android.photos.data.PhotoProvider.Photos; + +public class PhotoProviderTest extends InstrumentationTestCase { + @SuppressWarnings("unused") + private static final String TAG = PhotoProviderTest.class.getSimpleName(); + + private static final String MIME_TYPE = "test/test"; + private static final String ALBUM_NAME = "My Album"; + private static final long ALBUM_SERVER_ID = 100; + private static final long PHOTO_SERVER_ID = 50; + private static final String META_KEY = "mykey"; + private static final String META_VALUE = "myvalue"; + + private static final Uri NO_TABLE_URI = PhotoProvider.BASE_CONTENT_URI; + private static final Uri BAD_TABLE_URI = Uri.withAppendedPath(PhotoProvider.BASE_CONTENT_URI, + "bad_table"); + + private static final String WHERE_METADATA_PHOTOS_ID = Metadata.PHOTO_ID + " = ?"; + private static final String WHERE_METADATA = Metadata.PHOTO_ID + " = ? AND " + Metadata.KEY + + " = ?"; + + private static final long WAIT_FOR_CHANGE_MILLIS = 200; + + private long mAlbumId; + private long mPhotoId; + private long mMetadataId; + + private PhotoDatabase mDBHelper; + private ContentResolver mResolver; + + private static class WatchContentObserverThread extends Thread { + private WatchContentObserver mObserver; + private Looper mLooper; + + @Override + public void run() { + Looper.prepare(); + mLooper = Looper.myLooper(); + WatchContentObserver observer = new WatchContentObserver(); + synchronized (this) { + mObserver = observer; + this.notifyAll(); + } + Looper.loop(); + } + + public void waitForObserver() throws InterruptedException { + synchronized (this) { + while (mObserver == null) { + this.wait(); + } + } + } + + public WatchContentObserver getObserver() { + return mObserver; + } + + public void stopLooper() { + mLooper.quit(); + } + }; + + private static class WatchContentObserver extends ContentObserver { + private boolean mOnChangeReceived = false; + private Uri mUri = null; + + public WatchContentObserver() { + super(new Handler()); + } + + @Override + public synchronized void onChange(boolean selfChange, Uri uri) { + mOnChangeReceived = true; + mUri = uri; + notifyAll(); + } + + @Override + public synchronized void onChange(boolean selfChange) { + mOnChangeReceived = true; + notifyAll(); + } + + public boolean waitForNotification() { + synchronized (this) { + if (!mOnChangeReceived) { + try { + wait(WAIT_FOR_CHANGE_MILLIS); + } catch (InterruptedException e) { + } + } + } + return mOnChangeReceived; + } + }; + + @Override + protected void setUp() { + Context context = getInstrumentation().getTargetContext(); + mDBHelper = new PhotoDatabase(context); + mResolver = context.getContentResolver(); + SQLiteDatabase db = mDBHelper.getWritableDatabase(); + db.beginTransaction(); + try { + PhotoDatabaseUtils.insertAlbum(db, null, ALBUM_NAME, Albums.VISIBILITY_PRIVATE, + ALBUM_SERVER_ID); + mAlbumId = PhotoDatabaseUtils.queryAlbumIdFromServerId(db, ALBUM_SERVER_ID); + PhotoDatabaseUtils.insertPhoto(db, PHOTO_SERVER_ID, 100, 100, + System.currentTimeMillis(), mAlbumId, MIME_TYPE); + mPhotoId = PhotoDatabaseUtils.queryPhotoIdFromServerId(db, PHOTO_SERVER_ID); + PhotoDatabaseUtils.insertMetadata(db, mPhotoId, META_KEY, META_VALUE); + String[] projection = { + BaseColumns._ID, + }; + Cursor cursor = db.query(Metadata.TABLE, projection, null, null, null, null, null); + cursor.moveToNext(); + mMetadataId = cursor.getLong(0); + cursor.close(); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + @Override + protected void tearDown() { + SQLiteDatabase db = mDBHelper.getWritableDatabase(); + db.beginTransaction(); + try { + PhotoDatabaseUtils.deleteAllContent(db); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + mDBHelper.close(); + mDBHelper = null; + } + + public void testDelete() { + try { + mResolver.delete(NO_TABLE_URI, null, null); + fail("Exeption should be thrown when no table given"); + } catch (Exception e) { + // expected exception + } + try { + mResolver.delete(BAD_TABLE_URI, null, null); + fail("Exeption should be thrown when deleting from a table that doesn't exist"); + } catch (Exception e) { + // expected exception + } + + String[] selectionArgs = { + String.valueOf(mPhotoId) + }; + // Delete some metadata + assertEquals(1, + mResolver.delete(Metadata.CONTENT_URI, WHERE_METADATA_PHOTOS_ID, selectionArgs)); + Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId); + assertEquals(1, mResolver.delete(photoUri, null, null)); + Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId); + assertEquals(1, mResolver.delete(albumUri, null, null)); + // now delete something that isn't there + assertEquals(0, mResolver.delete(photoUri, null, null)); + } + + public void testDeleteMetadataId() { + Uri metadataUri = ContentUris.withAppendedId(Metadata.CONTENT_URI, mMetadataId); + assertEquals(1, mResolver.delete(metadataUri, null, null)); + Cursor cursor = mResolver.query(Metadata.CONTENT_URI, null, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } + // Delete the album and ensure that the photos referring to the album are + // deleted. + public void testDeleteAlbumCascade() { + WatchContentObserverThread observerThread = createObserverThread(); + WatchContentObserver observer = observerThread.getObserver(); + mResolver.registerContentObserver(Photos.CONTENT_URI, true, observer); + try { + Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId); + mResolver.delete(albumUri, null, null); + assertTrue(observer.waitForNotification()); + assertEquals(observer.mUri, Photos.CONTENT_URI); + Cursor cursor = mResolver.query(Photos.CONTENT_URI, + PhotoDatabaseUtils.PROJECTION_PHOTOS, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } finally { + mResolver.unregisterContentObserver(observer); + observerThread.stopLooper(); + } + } + + // Delete the album and ensure that the metadata referring to photos in that + // album are deleted. + public void testDeleteAlbumCascade2() { + WatchContentObserverThread observerThread = createObserverThread(); + WatchContentObserver observer = observerThread.getObserver(); + mResolver.registerContentObserver(Metadata.CONTENT_URI, true, observer); + try { + Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId); + mResolver.delete(albumUri, null, null); + assertTrue(observer.waitForNotification()); + assertEquals(observer.mUri, Metadata.CONTENT_URI); + Cursor cursor = mResolver.query(Metadata.CONTENT_URI, + PhotoDatabaseUtils.PROJECTION_METADATA, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } finally { + mResolver.unregisterContentObserver(observer); + observerThread.stopLooper(); + } + } + + // Delete all albums and ensure that photos in any album are deleted. + public void testDeleteAlbumCascade3() { + WatchContentObserverThread observerThread = createObserverThread(); + WatchContentObserver observer = observerThread.getObserver(); + mResolver.registerContentObserver(Photos.CONTENT_URI, true, observer); + try { + mResolver.delete(Albums.CONTENT_URI, null, null); + assertTrue(observer.waitForNotification()); + assertEquals(observer.mUri, Photos.CONTENT_URI); + Cursor cursor = mResolver.query(Photos.CONTENT_URI, + PhotoDatabaseUtils.PROJECTION_PHOTOS, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } finally { + mResolver.unregisterContentObserver(observer); + observerThread.stopLooper(); + } + } + + // Delete a photo and ensure that the metadata for that photo are deleted. + public void testDeletePhotoCascade() { + WatchContentObserverThread observerThread = createObserverThread(); + WatchContentObserver observer = observerThread.getObserver(); + mResolver.registerContentObserver(Metadata.CONTENT_URI, true, observer); + try { + Uri albumUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId); + mResolver.delete(albumUri, null, null); + assertTrue(observer.waitForNotification()); + assertEquals(observer.mUri, Metadata.CONTENT_URI); + Cursor cursor = mResolver.query(Metadata.CONTENT_URI, + PhotoDatabaseUtils.PROJECTION_METADATA, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } finally { + mResolver.unregisterContentObserver(observer); + observerThread.stopLooper(); + } + } + + public void testGetType() { + // We don't return types for albums + assertNull(mResolver.getType(Albums.CONTENT_URI)); + + Uri noImage = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1); + assertNull(mResolver.getType(noImage)); + + Uri image = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId); + assertEquals(MIME_TYPE, mResolver.getType(image)); + } + + public void testInsert() { + ContentValues values = new ContentValues(); + values.put(Albums.NAME, "don't add me"); + values.put(Albums.VISIBILITY, Albums.VISIBILITY_PRIVATE); + assertNull(mResolver.insert(Albums.CONTENT_URI, values)); + } + + public void testUpdate() { + ContentValues values = new ContentValues(); + // Normal update -- use an album. + values.put(Albums.NAME, "foo"); + Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId); + assertEquals(1, mResolver.update(albumUri, values, null, null)); + String[] projection = { + Albums.NAME, + }; + Cursor cursor = mResolver.query(albumUri, projection, null, null, null); + assertEquals(1, cursor.getCount()); + assertTrue(cursor.moveToNext()); + assertEquals("foo", cursor.getString(0)); + cursor.close(); + + // Update a row that doesn't exist. + Uri noAlbumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId + 1); + values.put(Albums.NAME, "bar"); + assertEquals(0, mResolver.update(noAlbumUri, values, null, null)); + + // Update a metadata value that exists. + ContentValues metadata = new ContentValues(); + metadata.put(Metadata.PHOTO_ID, mPhotoId); + metadata.put(Metadata.KEY, META_KEY); + metadata.put(Metadata.VALUE, "new value"); + assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null)); + + projection = new String[] { + Metadata.VALUE, + }; + + String[] selectionArgs = { + String.valueOf(mPhotoId), META_KEY, + }; + + cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs, + null); + assertEquals(1, cursor.getCount()); + assertTrue(cursor.moveToNext()); + assertEquals("new value", cursor.getString(0)); + cursor.close(); + + // Update a metadata value that doesn't exist. + metadata.put(Metadata.KEY, "other stuff"); + assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null)); + + selectionArgs[1] = "other stuff"; + cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs, + null); + assertEquals(1, cursor.getCount()); + assertTrue(cursor.moveToNext()); + assertEquals("new value", cursor.getString(0)); + cursor.close(); + + // Remove a metadata value using update. + metadata.putNull(Metadata.VALUE); + assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null)); + cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs, + null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } + + public void testQuery() { + // Query a photo that exists. + Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS, + null, null, null); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + assertTrue(cursor.moveToNext()); + assertEquals(mPhotoId, cursor.getLong(0)); + cursor.close(); + + // Query a photo that doesn't exist. + Uri noPhotoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1); + cursor = mResolver.query(noPhotoUri, PhotoDatabaseUtils.PROJECTION_PHOTOS, null, null, + null); + assertNotNull(cursor); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // Query a photo that exists using selection arguments. + String[] selectionArgs = { + String.valueOf(mPhotoId), + }; + + cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS, + Photos._ID + " = ?", selectionArgs, null); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + assertTrue(cursor.moveToNext()); + assertEquals(mPhotoId, cursor.getLong(0)); + cursor.close(); + } + + public void testUpdatePhotoNotification() { + WatchContentObserverThread observerThread = createObserverThread(); + WatchContentObserver observer = observerThread.getObserver(); + mResolver.registerContentObserver(Photos.CONTENT_URI, true, observer); + try { + Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId); + ContentValues values = new ContentValues(); + values.put(Photos.MIME_TYPE, "not-a/mime-type"); + mResolver.update(photoUri, values, null, null); + assertTrue(observer.waitForNotification()); + assertEquals(observer.mUri, photoUri); + } finally { + mResolver.unregisterContentObserver(observer); + observerThread.stopLooper(); + } + } + + public void testUpdateMetadataNotification() { + WatchContentObserverThread observerThread = createObserverThread(); + WatchContentObserver observer = observerThread.getObserver(); + mResolver.registerContentObserver(Metadata.CONTENT_URI, true, observer); + try { + ContentValues values = new ContentValues(); + values.put(Metadata.PHOTO_ID, mPhotoId); + values.put(Metadata.KEY, META_KEY); + values.put(Metadata.VALUE, "hello world"); + mResolver.update(Metadata.CONTENT_URI, values, null, null); + assertTrue(observer.waitForNotification()); + assertEquals(observer.mUri, Metadata.CONTENT_URI); + } finally { + mResolver.unregisterContentObserver(observer); + observerThread.stopLooper(); + } + } + + public void testDeletePhotoNotification() { + WatchContentObserverThread observerThread = createObserverThread(); + WatchContentObserver observer = observerThread.getObserver(); + mResolver.registerContentObserver(Photos.CONTENT_URI, true, observer); + try { + Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId); + mResolver.delete(photoUri, null, null); + assertTrue(observer.waitForNotification()); + assertEquals(observer.mUri, photoUri); + } finally { + mResolver.unregisterContentObserver(observer); + observerThread.stopLooper(); + } + } + + private WatchContentObserverThread createObserverThread() { + WatchContentObserverThread thread = new WatchContentObserverThread(); + thread.start(); + try { + thread.waitForObserver(); + return thread; + } catch (InterruptedException e) { + thread.stopLooper(); + fail("Interruption while waiting for observer being created."); + return null; + } + } +} -- cgit v1.2.3