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; 20705dc819661cb8c8e839a04577b0641bd6caad53John Reckimport 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; 27705dc819661cb8c8e839a04577b0641bd6caad53John Reckimport com.android.gallery3d.util.Future; 28705dc819661cb8c8e839a04577b0641bd6caad53John Reckimport com.android.gallery3d.util.FutureListener; 292b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.util.MediaSetUtils; 30705dc819661cb8c8e839a04577b0641bd6caad53John Reckimport com.android.gallery3d.util.ThreadPool; 31705dc819661cb8c8e839a04577b0641bd6caad53John Reckimport 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" 38705dc819661cb8c8e839a04577b0641bd6caad53John Reckpublic class LocalAlbumSet extends MediaSet 39705dc819661cb8c8e839a04577b0641bd6caad53John Reck 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; 55705dc819661cb8c8e839a04577b0641bd6caad53John Reck private final Handler mHandler; 56705dc819661cb8c8e839a04577b0641bd6caad53John Reck private boolean mIsLoading; 57705dc819661cb8c8e839a04577b0641bd6caad53John Reck 58705dc819661cb8c8e839a04577b0641bd6caad53John Reck private Future<ArrayList<MediaSet>> mLoadTask; 59705dc819661cb8c8e839a04577b0641bd6caad53John Reck private ArrayList<MediaSet> mLoadBuffer; 60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public LocalAlbumSet(Path path, GalleryApp application) { 62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin super(path, nextVersionNumber()); 63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mApplication = application; 64705dc819661cb8c8e839a04577b0641bd6caad53John Reck 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 101705dc819661cb8c8e839a04577b0641bd6caad53John Reck private class AlbumsLoader implements ThreadPool.Job<ArrayList<MediaSet>> { 102705dc819661cb8c8e839a04577b0641bd6caad53John Reck 103705dc819661cb8c8e839a04577b0641bd6caad53John Reck @Override 104705dc819661cb8c8e839a04577b0641bd6caad53John Reck @SuppressWarnings("unchecked") 105705dc819661cb8c8e839a04577b0641bd6caad53John Reck public ArrayList<MediaSet> run(JobContext jc) { 106705dc819661cb8c8e839a04577b0641bd6caad53John Reck // Note: it will be faster if we only select media_type and bucket_id. 107705dc819661cb8c8e839a04577b0641bd6caad53John Reck // need to test the performance if that is worth 108705dc819661cb8c8e839a04577b0641bd6caad53John Reck BucketEntry[] entries = BucketHelper.loadBucketEntries( 109705dc819661cb8c8e839a04577b0641bd6caad53John Reck jc, mApplication.getContentResolver(), mType); 110705dc819661cb8c8e839a04577b0641bd6caad53John Reck 111705dc819661cb8c8e839a04577b0641bd6caad53John Reck if (jc.isCancelled()) return null; 112705dc819661cb8c8e839a04577b0641bd6caad53John Reck 113705dc819661cb8c8e839a04577b0641bd6caad53John Reck int offset = 0; 114705dc819661cb8c8e839a04577b0641bd6caad53John Reck // Move camera and download bucket to the front, while keeping the 115705dc819661cb8c8e839a04577b0641bd6caad53John Reck // order of others. 116705dc819661cb8c8e839a04577b0641bd6caad53John Reck int index = findBucket(entries, MediaSetUtils.CAMERA_BUCKET_ID); 117705dc819661cb8c8e839a04577b0641bd6caad53John Reck if (index != -1) { 118705dc819661cb8c8e839a04577b0641bd6caad53John Reck circularShiftRight(entries, offset++, index); 119705dc819661cb8c8e839a04577b0641bd6caad53John Reck } 120705dc819661cb8c8e839a04577b0641bd6caad53John Reck index = findBucket(entries, MediaSetUtils.DOWNLOAD_BUCKET_ID); 121705dc819661cb8c8e839a04577b0641bd6caad53John Reck if (index != -1) { 122705dc819661cb8c8e839a04577b0641bd6caad53John Reck circularShiftRight(entries, offset++, index); 123705dc819661cb8c8e839a04577b0641bd6caad53John Reck } 124705dc819661cb8c8e839a04577b0641bd6caad53John Reck 125705dc819661cb8c8e839a04577b0641bd6caad53John Reck ArrayList<MediaSet> albums = new ArrayList<MediaSet>(); 126705dc819661cb8c8e839a04577b0641bd6caad53John Reck DataManager dataManager = mApplication.getDataManager(); 127705dc819661cb8c8e839a04577b0641bd6caad53John Reck for (BucketEntry entry : entries) { 128705dc819661cb8c8e839a04577b0641bd6caad53John Reck MediaSet album = getLocalAlbum(dataManager, 129705dc819661cb8c8e839a04577b0641bd6caad53John Reck mType, mPath, entry.bucketId, entry.bucketName); 130705dc819661cb8c8e839a04577b0641bd6caad53John Reck albums.add(album); 131705dc819661cb8c8e839a04577b0641bd6caad53John Reck } 132705dc819661cb8c8e839a04577b0641bd6caad53John Reck return albums; 133705dc819661cb8c8e839a04577b0641bd6caad53John Reck } 134705dc819661cb8c8e839a04577b0641bd6caad53John Reck } 135705dc819661cb8c8e839a04577b0641bd6caad53John Reck 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 158705dc819661cb8c8e839a04577b0641bd6caad53John Reck public synchronized boolean isLoading() { 159705dc819661cb8c8e839a04577b0641bd6caad53John Reck return mIsLoading; 160e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen } 161e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen 162e8c1e69f85efb8673d0606f3aca729a366038753Ray Chen @Override 163705dc819661cb8c8e839a04577b0641bd6caad53John Reck // synchronized on this function for 164705dc819661cb8c8e839a04577b0641bd6caad53John Reck // 1. Prevent calling reload() concurrently. 165705dc819661cb8c8e839a04577b0641bd6caad53John Reck // 2. Prevent calling onFutureDone() and reload() concurrently 166705dc819661cb8c8e839a04577b0641bd6caad53John Reck public synchronized long reload() { 167705dc819661cb8c8e839a04577b0641bd6caad53John Reck if (mNotifier.isDirty()) { 168705dc819661cb8c8e839a04577b0641bd6caad53John Reck if (mLoadTask != null) mLoadTask.cancel(); 169705dc819661cb8c8e839a04577b0641bd6caad53John Reck mIsLoading = true; 170705dc819661cb8c8e839a04577b0641bd6caad53John Reck mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this); 171f4f43e7dbc85ab8b7437e8f1d6ab0317470e70b6John Reck } 172705dc819661cb8c8e839a04577b0641bd6caad53John Reck if (mLoadBuffer != null) { 173705dc819661cb8c8e839a04577b0641bd6caad53John Reck mAlbums = mLoadBuffer; 174705dc819661cb8c8e839a04577b0641bd6caad53John Reck mLoadBuffer = null; 175705dc819661cb8c8e839a04577b0641bd6caad53John Reck for (MediaSet album : mAlbums) { 176705dc819661cb8c8e839a04577b0641bd6caad53John Reck album.reload(); 177705dc819661cb8c8e839a04577b0641bd6caad53John Reck } 178705dc819661cb8c8e839a04577b0641bd6caad53John Reck mDataVersion = nextVersionNumber(); 179f4f43e7dbc85ab8b7437e8f1d6ab0317470e70b6John Reck } 180705dc819661cb8c8e839a04577b0641bd6caad53John Reck return mDataVersion; 181705dc819661cb8c8e839a04577b0641bd6caad53John Reck } 182f4f43e7dbc85ab8b7437e8f1d6ab0317470e70b6John Reck 183705dc819661cb8c8e839a04577b0641bd6caad53John Reck @Override 184705dc819661cb8c8e839a04577b0641bd6caad53John Reck public synchronized void onFutureDone(Future<ArrayList<MediaSet>> future) { 185705dc819661cb8c8e839a04577b0641bd6caad53John Reck if (mLoadTask != future) return; // ignore, wait for the latest task 186705dc819661cb8c8e839a04577b0641bd6caad53John Reck mLoadBuffer = future.get(); 187705dc819661cb8c8e839a04577b0641bd6caad53John Reck mIsLoading = false; 188705dc819661cb8c8e839a04577b0641bd6caad53John Reck if (mLoadBuffer == null) mLoadBuffer = new ArrayList<MediaSet>(); 189705dc819661cb8c8e839a04577b0641bd6caad53John Reck mHandler.post(new Runnable() { 190705dc819661cb8c8e839a04577b0641bd6caad53John Reck @Override 191705dc819661cb8c8e839a04577b0641bd6caad53John Reck public void run() { 192705dc819661cb8c8e839a04577b0641bd6caad53John Reck notifyContentChanged(); 193705dc819661cb8c8e839a04577b0641bd6caad53John Reck } 194705dc819661cb8c8e839a04577b0641bd6caad53John Reck }); 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