1 /* 2 * Copyright (C) 2010 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 17 package com.android.gallery3d.data; 18 19 import android.content.ContentProvider; 20 import android.content.ContentResolver; 21 import android.database.Cursor; 22 import android.database.sqlite.SQLiteDatabase; 23 import android.database.sqlite.SQLiteQueryBuilder; 24 import android.net.Uri; 25 import android.os.Looper; 26 import android.test.AndroidTestCase; 27 import android.test.mock.MockContentProvider; 28 import android.test.mock.MockContentResolver; 29 import android.test.suitebuilder.annotation.MediumTest; 30 import android.util.Log; 31 32 import java.util.concurrent.CountDownLatch; 33 import java.util.concurrent.TimeUnit; 34 35 public class LocalDataTest extends AndroidTestCase { 36 @SuppressWarnings("unused") 37 private static final String TAG = "LocalDataTest"; 38 private static final long DEFAULT_TIMEOUT = 1000; // one second 39 40 @MediumTest 41 public void testLocalAlbum() throws Exception { 42 new TestZeroImage().run(); 43 new TestOneImage().run(); 44 new TestMoreImages().run(); 45 new TestZeroVideo().run(); 46 new TestOneVideo().run(); 47 new TestMoreVideos().run(); 48 new TestDeleteOneImage().run(); 49 new TestDeleteOneAlbum().run(); 50 } 51 52 abstract class TestLocalAlbumBase { 53 private boolean mIsImage; 54 protected GalleryAppStub mApp; 55 protected LocalAlbumSet mAlbumSet; 56 57 TestLocalAlbumBase(boolean isImage) { 58 mIsImage = isImage; 59 } 60 61 public void run() throws Exception { 62 SQLiteDatabase db = SQLiteDatabase.create(null); 63 prepareData(db); 64 mApp = newGalleryContext(db, Looper.getMainLooper()); 65 Path.clearAll(); 66 Path path = Path.fromString( 67 mIsImage ? "/local/image" : "/local/video"); 68 mAlbumSet = new LocalAlbumSet(path, mApp); 69 mAlbumSet.reload(); 70 verifyResult(); 71 } 72 73 abstract void prepareData(SQLiteDatabase db); 74 abstract void verifyResult() throws Exception; 75 } 76 77 abstract class TestLocalImageAlbum extends TestLocalAlbumBase { 78 TestLocalImageAlbum() { 79 super(true); 80 } 81 } 82 83 abstract class TestLocalVideoAlbum extends TestLocalAlbumBase { 84 TestLocalVideoAlbum() { 85 super(false); 86 } 87 } 88 89 class TestZeroImage extends TestLocalImageAlbum { 90 @Override 91 public void prepareData(SQLiteDatabase db) { 92 createImageTable(db); 93 } 94 95 @Override 96 public void verifyResult() { 97 assertEquals(0, mAlbumSet.getMediaItemCount()); 98 assertEquals(0, mAlbumSet.getSubMediaSetCount()); 99 assertEquals(0, mAlbumSet.getTotalMediaItemCount()); 100 } 101 } 102 103 class TestOneImage extends TestLocalImageAlbum { 104 @Override 105 public void prepareData(SQLiteDatabase db) { 106 createImageTable(db); 107 insertImageData(db); 108 } 109 110 @Override 111 public void verifyResult() { 112 assertEquals(0, mAlbumSet.getMediaItemCount()); 113 assertEquals(1, mAlbumSet.getSubMediaSetCount()); 114 assertEquals(1, mAlbumSet.getTotalMediaItemCount()); 115 MediaSet sub = mAlbumSet.getSubMediaSet(0); 116 assertEquals(1, sub.getMediaItemCount()); 117 assertEquals(0, sub.getSubMediaSetCount()); 118 LocalMediaItem item = (LocalMediaItem) sub.getMediaItem(0, 1).get(0); 119 assertEquals(1, item.id); 120 assertEquals("IMG_0072", item.caption); 121 assertEquals("image/jpeg", item.mimeType); 122 assertEquals(12.0, item.latitude); 123 assertEquals(34.0, item.longitude); 124 assertEquals(0xD000, item.dateTakenInMs); 125 assertEquals(1280395646L, item.dateAddedInSec); 126 assertEquals(1275934796L, item.dateModifiedInSec); 127 assertEquals("/mnt/sdcard/DCIM/100CANON/IMG_0072.JPG", item.filePath); 128 } 129 } 130 131 class TestMoreImages extends TestLocalImageAlbum { 132 @Override 133 public void prepareData(SQLiteDatabase db) { 134 // Albums are sorted by names, and items are sorted by 135 // dateTimeTaken (descending) 136 createImageTable(db); 137 // bucket 0xB000 138 insertImageData(db, 1000, 0xB000, "second"); // id 1 139 insertImageData(db, 2000, 0xB000, "second"); // id 2 140 // bucket 0xB001 141 insertImageData(db, 3000, 0xB001, "first"); // id 3 142 } 143 144 @Override 145 public void verifyResult() { 146 assertEquals(0, mAlbumSet.getMediaItemCount()); 147 assertEquals(2, mAlbumSet.getSubMediaSetCount()); 148 assertEquals(3, mAlbumSet.getTotalMediaItemCount()); 149 150 MediaSet first = mAlbumSet.getSubMediaSet(0); 151 assertEquals(1, first.getMediaItemCount()); 152 LocalMediaItem item = (LocalMediaItem) first.getMediaItem(0, 1).get(0); 153 assertEquals(3, item.id); 154 assertEquals(3000L, item.dateTakenInMs); 155 156 MediaSet second = mAlbumSet.getSubMediaSet(1); 157 assertEquals(2, second.getMediaItemCount()); 158 item = (LocalMediaItem) second.getMediaItem(0, 1).get(0); 159 assertEquals(2, item.id); 160 assertEquals(2000L, item.dateTakenInMs); 161 item = (LocalMediaItem) second.getMediaItem(1, 1).get(0); 162 assertEquals(1, item.id); 163 assertEquals(1000L, item.dateTakenInMs); 164 } 165 } 166 167 class OnContentDirtyLatch implements ContentListener { 168 private CountDownLatch mLatch = new CountDownLatch(1); 169 170 public void onContentDirty() { 171 mLatch.countDown(); 172 } 173 174 public boolean isOnContentDirtyBeCalled(long timeout) 175 throws InterruptedException { 176 return mLatch.await(timeout, TimeUnit.MILLISECONDS); 177 } 178 } 179 180 class TestDeleteOneAlbum extends TestLocalImageAlbum { 181 @Override 182 public void prepareData(SQLiteDatabase db) { 183 // Albums are sorted by names, and items are sorted by 184 // dateTimeTaken (descending) 185 createImageTable(db); 186 // bucket 0xB000 187 insertImageData(db, 1000, 0xB000, "second"); // id 1 188 insertImageData(db, 2000, 0xB000, "second"); // id 2 189 // bucket 0xB001 190 insertImageData(db, 3000, 0xB001, "first"); // id 3 191 } 192 193 @Override 194 public void verifyResult() throws Exception { 195 MediaSet sub = mAlbumSet.getSubMediaSet(1); // "second" 196 assertEquals(2, mAlbumSet.getSubMediaSetCount()); 197 OnContentDirtyLatch latch = new OnContentDirtyLatch(); 198 sub.addContentListener(latch); 199 assertTrue((sub.getSupportedOperations() & MediaSet.SUPPORT_DELETE) != 0); 200 sub.delete(); 201 mAlbumSet.fakeChange(); 202 latch.isOnContentDirtyBeCalled(DEFAULT_TIMEOUT); 203 mAlbumSet.reload(); 204 assertEquals(1, mAlbumSet.getSubMediaSetCount()); 205 } 206 } 207 208 class TestDeleteOneImage extends TestLocalImageAlbum { 209 210 @Override 211 public void prepareData(SQLiteDatabase db) { 212 createImageTable(db); 213 insertImageData(db); 214 } 215 216 @Override 217 public void verifyResult() { 218 MediaSet sub = mAlbumSet.getSubMediaSet(0); 219 LocalMediaItem item = (LocalMediaItem) sub.getMediaItem(0, 1).get(0); 220 assertEquals(1, sub.getMediaItemCount()); 221 assertTrue((sub.getSupportedOperations() & MediaSet.SUPPORT_DELETE) != 0); 222 sub.delete(); 223 sub.reload(); 224 assertEquals(0, sub.getMediaItemCount()); 225 } 226 } 227 228 static void createImageTable(SQLiteDatabase db) { 229 // This is copied from MediaProvider 230 db.execSQL("CREATE TABLE IF NOT EXISTS images (" + 231 "_id INTEGER PRIMARY KEY," + 232 "_data TEXT," + 233 "_size INTEGER," + 234 "_display_name TEXT," + 235 "mime_type TEXT," + 236 "title TEXT," + 237 "date_added INTEGER," + 238 "date_modified INTEGER," + 239 "description TEXT," + 240 "picasa_id TEXT," + 241 "isprivate INTEGER," + 242 "latitude DOUBLE," + 243 "longitude DOUBLE," + 244 "datetaken INTEGER," + 245 "orientation INTEGER," + 246 "mini_thumb_magic INTEGER," + 247 "bucket_id TEXT," + 248 "bucket_display_name TEXT" + 249 ");"); 250 } 251 252 static void insertImageData(SQLiteDatabase db) { 253 insertImageData(db, 0xD000, 0xB000, "name"); 254 } 255 256 static void insertImageData(SQLiteDatabase db, long dateTaken, 257 int bucketId, String bucketName) { 258 db.execSQL("INSERT INTO images (title, mime_type, latitude, longitude, " 259 + "datetaken, date_added, date_modified, bucket_id, " 260 + "bucket_display_name, _data, orientation) " 261 + "VALUES ('IMG_0072', 'image/jpeg', 12, 34, " 262 + dateTaken + ", 1280395646, 1275934796, '" + bucketId + "', " 263 + "'" + bucketName + "', " 264 + "'/mnt/sdcard/DCIM/100CANON/IMG_0072.JPG', 0)"); 265 } 266 267 class TestZeroVideo extends TestLocalVideoAlbum { 268 @Override 269 public void prepareData(SQLiteDatabase db) { 270 createVideoTable(db); 271 } 272 273 @Override 274 public void verifyResult() { 275 assertEquals(0, mAlbumSet.getMediaItemCount()); 276 assertEquals(0, mAlbumSet.getSubMediaSetCount()); 277 assertEquals(0, mAlbumSet.getTotalMediaItemCount()); 278 } 279 } 280 281 class TestOneVideo extends TestLocalVideoAlbum { 282 @Override 283 public void prepareData(SQLiteDatabase db) { 284 createVideoTable(db); 285 insertVideoData(db); 286 } 287 288 @Override 289 public void verifyResult() { 290 assertEquals(0, mAlbumSet.getMediaItemCount()); 291 assertEquals(1, mAlbumSet.getSubMediaSetCount()); 292 assertEquals(1, mAlbumSet.getTotalMediaItemCount()); 293 MediaSet sub = mAlbumSet.getSubMediaSet(0); 294 assertEquals(1, sub.getMediaItemCount()); 295 assertEquals(0, sub.getSubMediaSetCount()); 296 LocalMediaItem item = (LocalMediaItem) sub.getMediaItem(0, 1).get(0); 297 assertEquals(1, item.id); 298 assertEquals("VID_20100811_051413", item.caption); 299 assertEquals("video/mp4", item.mimeType); 300 assertEquals(11.0, item.latitude); 301 assertEquals(22.0, item.longitude); 302 assertEquals(0xD000, item.dateTakenInMs); 303 assertEquals(1281503663L, item.dateAddedInSec); 304 assertEquals(1281503662L, item.dateModifiedInSec); 305 assertEquals("/mnt/sdcard/DCIM/Camera/VID_20100811_051413.3gp", 306 item.filePath); 307 } 308 } 309 310 class TestMoreVideos extends TestLocalVideoAlbum { 311 @Override 312 public void prepareData(SQLiteDatabase db) { 313 // Albums are sorted by names, and items are sorted by 314 // dateTimeTaken (descending) 315 createVideoTable(db); 316 // bucket 0xB002 317 insertVideoData(db, 1000, 0xB000, "second"); // id 1 318 insertVideoData(db, 2000, 0xB000, "second"); // id 2 319 // bucket 0xB001 320 insertVideoData(db, 3000, 0xB001, "first"); // id 3 321 } 322 323 @Override 324 public void verifyResult() { 325 assertEquals(0, mAlbumSet.getMediaItemCount()); 326 assertEquals(2, mAlbumSet.getSubMediaSetCount()); 327 assertEquals(3, mAlbumSet.getTotalMediaItemCount()); 328 329 MediaSet first = mAlbumSet.getSubMediaSet(0); 330 assertEquals(1, first.getMediaItemCount()); 331 LocalMediaItem item = (LocalMediaItem) first.getMediaItem(0, 1).get(0); 332 assertEquals(3, item.id); 333 assertEquals(3000L, item.dateTakenInMs); 334 335 MediaSet second = mAlbumSet.getSubMediaSet(1); 336 assertEquals(2, second.getMediaItemCount()); 337 item = (LocalMediaItem) second.getMediaItem(0, 1).get(0); 338 assertEquals(2, item.id); 339 assertEquals(2000L, item.dateTakenInMs); 340 item = (LocalMediaItem) second.getMediaItem(1, 1).get(0); 341 assertEquals(1, item.id); 342 assertEquals(1000L, item.dateTakenInMs); 343 } 344 } 345 346 static void createVideoTable(SQLiteDatabase db) { 347 db.execSQL("CREATE TABLE IF NOT EXISTS video (" + 348 "_id INTEGER PRIMARY KEY," + 349 "_data TEXT NOT NULL," + 350 "_display_name TEXT," + 351 "_size INTEGER," + 352 "mime_type TEXT," + 353 "date_added INTEGER," + 354 "date_modified INTEGER," + 355 "title TEXT," + 356 "duration INTEGER," + 357 "artist TEXT," + 358 "album TEXT," + 359 "resolution TEXT," + 360 "description TEXT," + 361 "isprivate INTEGER," + // for YouTube videos 362 "tags TEXT," + // for YouTube videos 363 "category TEXT," + // for YouTube videos 364 "language TEXT," + // for YouTube videos 365 "mini_thumb_data TEXT," + 366 "latitude DOUBLE," + 367 "longitude DOUBLE," + 368 "datetaken INTEGER," + 369 "mini_thumb_magic INTEGER" + 370 ");"); 371 db.execSQL("ALTER TABLE video ADD COLUMN bucket_id TEXT;"); 372 db.execSQL("ALTER TABLE video ADD COLUMN bucket_display_name TEXT"); 373 } 374 375 static void insertVideoData(SQLiteDatabase db) { 376 insertVideoData(db, 0xD000, 0xB000, "name"); 377 } 378 379 static void insertVideoData(SQLiteDatabase db, long dateTaken, 380 int bucketId, String bucketName) { 381 db.execSQL("INSERT INTO video (title, mime_type, latitude, longitude, " 382 + "datetaken, date_added, date_modified, bucket_id, " 383 + "bucket_display_name, _data, duration) " 384 + "VALUES ('VID_20100811_051413', 'video/mp4', 11, 22, " 385 + dateTaken + ", 1281503663, 1281503662, '" + bucketId + "', " 386 + "'" + bucketName + "', " 387 + "'/mnt/sdcard/DCIM/Camera/VID_20100811_051413.3gp', 2964)"); 388 } 389 390 static GalleryAppStub newGalleryContext(SQLiteDatabase db, Looper mainLooper) { 391 MockContentResolver cr = new MockContentResolver(); 392 ContentProvider cp = new DbContentProvider(db, cr); 393 cr.addProvider("media", cp); 394 return new GalleryAppMock(null, cr, mainLooper); 395 } 396 } 397 398 class DbContentProvider extends MockContentProvider { 399 private static final String TAG = "DbContentProvider"; 400 private SQLiteDatabase mDatabase; 401 private ContentResolver mContentResolver; 402 403 DbContentProvider(SQLiteDatabase db, ContentResolver cr) { 404 mDatabase = db; 405 mContentResolver = cr; 406 } 407 408 @Override 409 public Cursor query(Uri uri, String[] projection, 410 String selection, String[] selectionArgs, String sortOrder) { 411 // This is a simplified version extracted from MediaProvider. 412 413 String tableName = getTableName(uri); 414 if (tableName == null) return null; 415 416 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 417 qb.setTables(tableName); 418 419 String groupBy = null; 420 String limit = uri.getQueryParameter("limit"); 421 422 if (uri.getQueryParameter("distinct") != null) { 423 qb.setDistinct(true); 424 } 425 426 Log.v(TAG, "query = " + qb.buildQuery(projection, selection, 427 selectionArgs, groupBy, null, sortOrder, limit)); 428 429 if (selectionArgs != null) { 430 for (String s : selectionArgs) { 431 Log.v(TAG, " selectionArgs = " + s); 432 } 433 } 434 435 Cursor c = qb.query(mDatabase, projection, selection, 436 selectionArgs, groupBy, null, sortOrder, limit); 437 438 return c; 439 } 440 441 @Override 442 public int delete(Uri uri, String whereClause, String[] whereArgs) { 443 Log.v(TAG, "delete " + uri + "," + whereClause + "," + whereArgs[0]); 444 String tableName = getTableName(uri); 445 if (tableName == null) return 0; 446 int count = mDatabase.delete(tableName, whereClause, whereArgs); 447 mContentResolver.notifyChange(uri, null); 448 return count; 449 } 450 451 private String getTableName(Uri uri) { 452 String uriString = uri.toString(); 453 if (uriString.startsWith("content://media/external/images/media")) { 454 return "images"; 455 } else if (uriString.startsWith("content://media/external/video/media")) { 456 return "video"; 457 } else { 458 return null; 459 } 460 } 461 } 462