1f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin/* 2f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Copyright (C) 2010 The Android Open Source Project 3f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 4f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Licensed under the Apache License, Version 2.0 (the "License"); 5f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * you may not use this file except in compliance with the License. 6f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * You may obtain a copy of the License at 7f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 8f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * http://www.apache.org/licenses/LICENSE-2.0 9f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 10f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Unless required by applicable law or agreed to in writing, software 11f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * distributed under the License is distributed on an "AS IS" BASIS, 12f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * See the License for the specific language governing permissions and 14f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * limitations under the License. 15f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */ 16f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 17f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpackage com.android.gallery3d.data; 18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 19f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.net.Uri; 209ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Linimport android.os.Handler; 21f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.provider.MediaStore.Images; 22f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.provider.MediaStore.Video; 23f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 242b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.R; 252b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.app.GalleryApp; 2690df352389eda7c964e6a43a0b752d27f16e02a7Owen Linimport com.android.gallery3d.data.BucketHelper.BucketEntry; 27aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Linimport com.android.gallery3d.util.Future; 28aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Linimport com.android.gallery3d.util.FutureListener; 292b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.util.MediaSetUtils; 30aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Linimport com.android.gallery3d.util.ThreadPool; 31aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Linimport com.android.gallery3d.util.ThreadPool.JobContext; 322b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Lin 33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.ArrayList; 34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.Comparator; 35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin// LocalAlbumSet lists all image or video albums in the local storage. 37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin// The path should be "/local/image", "local/video" or "/local/all" 38aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Linpublic class LocalAlbumSet extends MediaSet 39aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin implements FutureListener<ArrayList<MediaSet>> { 4028cb4161da5fc3756933ca67d509b8af1c6275f1Owen Lin @SuppressWarnings("unused") 4128cb4161da5fc3756933ca67d509b8af1c6275f1Owen Lin private static final String TAG = "LocalAlbumSet"; 4228cb4161da5fc3756933ca67d509b8af1c6275f1Owen Lin 43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public static final Path PATH_ALL = Path.fromString("/local/all"); 44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public static final Path PATH_IMAGE = Path.fromString("/local/image"); 45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public static final Path PATH_VIDEO = Path.fromString("/local/video"); 46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 47a0ce682e40c39ce046c48d8a7f989fff01bb9f2cWu-cheng Li private static final Uri[] mWatchUris = 48a0ce682e40c39ce046c48d8a7f989fff01bb9f2cWu-cheng Li {Images.Media.EXTERNAL_CONTENT_URI, Video.Media.EXTERNAL_CONTENT_URI}; 49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final GalleryApp mApplication; 51f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final int mType; 52f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private ArrayList<MediaSet> mAlbums = new ArrayList<MediaSet>(); 53a0ce682e40c39ce046c48d8a7f989fff01bb9f2cWu-cheng Li private final ChangeNotifier mNotifier; 54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final String mName; 559ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Lin private final Handler mHandler; 56e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen private boolean mIsLoading; 579ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Lin 58aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin private Future<ArrayList<MediaSet>> mLoadTask; 59aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin private ArrayList<MediaSet> mLoadBuffer; 60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public LocalAlbumSet(Path path, GalleryApp application) { 62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin super(path, nextVersionNumber()); 63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mApplication = application; 649ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Lin mHandler = new Handler(application.getMainLooper()); 65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mType = getTypeFromPath(path); 66a0ce682e40c39ce046c48d8a7f989fff01bb9f2cWu-cheng Li mNotifier = new ChangeNotifier(this, mWatchUris, application); 67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mName = application.getResources().getString( 68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin R.string.set_label_local_albums); 69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static int getTypeFromPath(Path path) { 72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String name[] = path.split(); 73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (name.length < 2) { 74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin throw new IllegalArgumentException(path.toString()); 75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 76d134e18e3c2d9614099d3b74e1a6a12159a1e81aRay Chen return getTypeFromString(name[1]); 77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public MediaSet getSubMediaSet(int index) { 81f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mAlbums.get(index); 82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public int getSubMediaSetCount() { 86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mAlbums.size(); 87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public String getName() { 91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mName; 92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static int findBucket(BucketEntry entries[], int bucketId) { 95c64d127c976f8ef647552063ff14ead4388ce699Angus Kong for (int i = 0, n = entries.length; i < n; ++i) { 96f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (entries[i].bucketId == bucketId) return i; 97f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 98f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return -1; 99f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 101aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin private class AlbumsLoader implements ThreadPool.Job<ArrayList<MediaSet>> { 102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 103aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin @Override 104aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin @SuppressWarnings("unchecked") 105aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin public ArrayList<MediaSet> run(JobContext jc) { 106aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin // Note: it will be faster if we only select media_type and bucket_id. 107aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin // need to test the performance if that is worth 10890df352389eda7c964e6a43a0b752d27f16e02a7Owen Lin BucketEntry[] entries = BucketHelper.loadBucketEntries( 10990df352389eda7c964e6a43a0b752d27f16e02a7Owen Lin jc, mApplication.getContentResolver(), mType); 110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 111aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin if (jc.isCancelled()) return null; 112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 113aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin int offset = 0; 114aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin // Move camera and download bucket to the front, while keeping the 115aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin // order of others. 116aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin int index = findBucket(entries, MediaSetUtils.CAMERA_BUCKET_ID); 117aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin if (index != -1) { 118aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin circularShiftRight(entries, offset++, index); 119aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin } 120aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin index = findBucket(entries, MediaSetUtils.DOWNLOAD_BUCKET_ID); 121aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin if (index != -1) { 122aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin circularShiftRight(entries, offset++, index); 123aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin } 124aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin 125aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin ArrayList<MediaSet> albums = new ArrayList<MediaSet>(); 126aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin DataManager dataManager = mApplication.getDataManager(); 127aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin for (BucketEntry entry : entries) { 128aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin MediaSet album = getLocalAlbum(dataManager, 129aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin mType, mPath, entry.bucketId, entry.bucketName); 130aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin albums.add(album); 131aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin } 132aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin return albums; 133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private MediaSet getLocalAlbum( 137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin DataManager manager, int type, Path parent, int id, String name) { 138aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin synchronized (DataManager.LOCK) { 139aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin Path path = parent.getChild(id); 140aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin MediaObject object = manager.peekMediaObject(path); 141aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin if (object != null) return (MediaSet) object; 142aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin switch (type) { 143aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin case MEDIA_TYPE_IMAGE: 144aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin return new LocalAlbum(path, mApplication, id, true, name); 145aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin case MEDIA_TYPE_VIDEO: 146aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin return new LocalAlbum(path, mApplication, id, false, name); 147aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin case MEDIA_TYPE_ALL: 148aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin Comparator<MediaItem> comp = DataManager.sDateTakenComparator; 149aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin return new LocalMergeAlbum(path, comp, new MediaSet[] { 150aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin getLocalAlbum(manager, MEDIA_TYPE_IMAGE, PATH_IMAGE, id, name), 151aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin getLocalAlbum(manager, MEDIA_TYPE_VIDEO, PATH_VIDEO, id, name)}, id); 152aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin } 153aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin throw new IllegalArgumentException(String.valueOf(type)); 154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 158e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen public synchronized boolean isLoading() { 159e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen return mIsLoading; 160e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen } 161e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen 162e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen @Override 163aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin // synchronized on this function for 164aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin // 1. Prevent calling reload() concurrently. 165aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin // 2. Prevent calling onFutureDone() and reload() concurrently 166aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin public synchronized long reload() { 167a0ce682e40c39ce046c48d8a7f989fff01bb9f2cWu-cheng Li if (mNotifier.isDirty()) { 168aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin if (mLoadTask != null) mLoadTask.cancel(); 169e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen mIsLoading = true; 170aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this); 171aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin } 172aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin if (mLoadBuffer != null) { 173aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin mAlbums = mLoadBuffer; 174aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin mLoadBuffer = null; 17591adea8b4fc292b046b66c6501166fe7a151d253Owen Lin for (MediaSet album : mAlbums) { 17691adea8b4fc292b046b66c6501166fe7a151d253Owen Lin album.reload(); 17791adea8b4fc292b046b66c6501166fe7a151d253Owen Lin } 178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mDataVersion = nextVersionNumber(); 179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mDataVersion; 181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 183aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin @Override 184aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin public synchronized void onFutureDone(Future<ArrayList<MediaSet>> future) { 185aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin if (mLoadTask != future) return; // ignore, wait for the latest task 186aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin mLoadBuffer = future.get(); 187e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen mIsLoading = false; 188aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin if (mLoadBuffer == null) mLoadBuffer = new ArrayList<MediaSet>(); 1899ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Lin mHandler.post(new Runnable() { 1909ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Lin @Override 1919ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Lin public void run() { 1929ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Lin notifyContentChanged(); 1939ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Lin } 1949ca9c3705c2176f1da98c573a9d765fc65b840ceOwen Lin }); 195aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin } 196aa6ac7f6ce0e1cab5b36e6bba97d2fec082f4759Owen Lin 197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // For debug only. Fake there is a ContentObserver.onChange() event. 198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin void fakeChange() { 199a0ce682e40c39ce046c48d8a7f989fff01bb9f2cWu-cheng Li mNotifier.fakeChange(); 200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 2024bdd71bcde93e6f942e2a947a10ee32f63b37828Chih-Chung Chang // Circular shift the array range from a[i] to a[j] (inclusive). That is, 2034bdd71bcde93e6f942e2a947a10ee32f63b37828Chih-Chung Chang // a[i] -> a[i+1] -> a[i+2] -> ... -> a[j], and a[j] -> a[i] 2044bdd71bcde93e6f942e2a947a10ee32f63b37828Chih-Chung Chang private static <T> void circularShiftRight(T[] array, int i, int j) { 2054bdd71bcde93e6f942e2a947a10ee32f63b37828Chih-Chung Chang T temp = array[j]; 2064bdd71bcde93e6f942e2a947a10ee32f63b37828Chih-Chung Chang for (int k = j; k > i; k--) { 2074bdd71bcde93e6f942e2a947a10ee32f63b37828Chih-Chung Chang array[k] = array[k - 1]; 2084bdd71bcde93e6f942e2a947a10ee32f63b37828Chih-Chung Chang } 2094bdd71bcde93e6f942e2a947a10ee32f63b37828Chih-Chung Chang array[i] = temp; 2104bdd71bcde93e6f942e2a947a10ee32f63b37828Chih-Chung Chang } 211f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin} 212