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
17package com.android.gallery3d.data;
18
19import android.content.ContentProvider;
20import android.content.ContentResolver;
21import android.database.Cursor;
22import android.database.sqlite.SQLiteDatabase;
23import android.database.sqlite.SQLiteQueryBuilder;
24import android.net.Uri;
25import android.os.Looper;
26import android.test.AndroidTestCase;
27import android.test.mock.MockContentProvider;
28import android.test.mock.MockContentResolver;
29import android.test.suitebuilder.annotation.MediumTest;
30import android.util.Log;
31
32import java.util.concurrent.CountDownLatch;
33import java.util.concurrent.TimeUnit;
34
35public 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
398class 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