Home | History | Annotate | Download | only in data
      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.net.Uri;
     20 import android.os.Handler;
     21 import android.provider.MediaStore.Images;
     22 import android.provider.MediaStore.Video;
     23 
     24 import com.android.gallery3d.R;
     25 import com.android.gallery3d.app.GalleryApp;
     26 import com.android.gallery3d.data.BucketHelper.BucketEntry;
     27 import com.android.gallery3d.util.Future;
     28 import com.android.gallery3d.util.FutureListener;
     29 import com.android.gallery3d.util.MediaSetUtils;
     30 import com.android.gallery3d.util.ThreadPool;
     31 import com.android.gallery3d.util.ThreadPool.JobContext;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Comparator;
     35 
     36 // LocalAlbumSet lists all image or video albums in the local storage.
     37 // The path should be "/local/image", "local/video" or "/local/all"
     38 public class LocalAlbumSet extends MediaSet
     39         implements FutureListener<ArrayList<MediaSet>> {
     40     @SuppressWarnings("unused")
     41     private static final String TAG = "LocalAlbumSet";
     42 
     43     public static final Path PATH_ALL = Path.fromString("/local/all");
     44     public static final Path PATH_IMAGE = Path.fromString("/local/image");
     45     public static final Path PATH_VIDEO = Path.fromString("/local/video");
     46 
     47     private static final Uri[] mWatchUris =
     48         {Images.Media.EXTERNAL_CONTENT_URI, Video.Media.EXTERNAL_CONTENT_URI};
     49 
     50     private final GalleryApp mApplication;
     51     private final int mType;
     52     private ArrayList<MediaSet> mAlbums = new ArrayList<MediaSet>();
     53     private final ChangeNotifier mNotifier;
     54     private final String mName;
     55     private final Handler mHandler;
     56     private boolean mIsLoading;
     57 
     58     private Future<ArrayList<MediaSet>> mLoadTask;
     59     private ArrayList<MediaSet> mLoadBuffer;
     60 
     61     public LocalAlbumSet(Path path, GalleryApp application) {
     62         super(path, nextVersionNumber());
     63         mApplication = application;
     64         mHandler = new Handler(application.getMainLooper());
     65         mType = getTypeFromPath(path);
     66         mNotifier = new ChangeNotifier(this, mWatchUris, application);
     67         mName = application.getResources().getString(
     68                 R.string.set_label_local_albums);
     69     }
     70 
     71     private static int getTypeFromPath(Path path) {
     72         String name[] = path.split();
     73         if (name.length < 2) {
     74             throw new IllegalArgumentException(path.toString());
     75         }
     76         return getTypeFromString(name[1]);
     77     }
     78 
     79     @Override
     80     public MediaSet getSubMediaSet(int index) {
     81         return mAlbums.get(index);
     82     }
     83 
     84     @Override
     85     public int getSubMediaSetCount() {
     86         return mAlbums.size();
     87     }
     88 
     89     @Override
     90     public String getName() {
     91         return mName;
     92     }
     93 
     94     private static int findBucket(BucketEntry entries[], int bucketId) {
     95         for (int i = 0, n = entries.length; i < n; ++i) {
     96             if (entries[i].bucketId == bucketId) return i;
     97         }
     98         return -1;
     99     }
    100 
    101     private class AlbumsLoader implements ThreadPool.Job<ArrayList<MediaSet>> {
    102 
    103         @Override
    104         @SuppressWarnings("unchecked")
    105         public ArrayList<MediaSet> run(JobContext jc) {
    106             // Note: it will be faster if we only select media_type and bucket_id.
    107             //       need to test the performance if that is worth
    108             BucketEntry[] entries = BucketHelper.loadBucketEntries(
    109                     jc, mApplication.getContentResolver(), mType);
    110 
    111             if (jc.isCancelled()) return null;
    112 
    113             int offset = 0;
    114             // Move camera and download bucket to the front, while keeping the
    115             // order of others.
    116             int index = findBucket(entries, MediaSetUtils.CAMERA_BUCKET_ID);
    117             if (index != -1) {
    118                 circularShiftRight(entries, offset++, index);
    119             }
    120             index = findBucket(entries, MediaSetUtils.DOWNLOAD_BUCKET_ID);
    121             if (index != -1) {
    122                 circularShiftRight(entries, offset++, index);
    123             }
    124 
    125             ArrayList<MediaSet> albums = new ArrayList<MediaSet>();
    126             DataManager dataManager = mApplication.getDataManager();
    127             for (BucketEntry entry : entries) {
    128                 MediaSet album = getLocalAlbum(dataManager,
    129                         mType, mPath, entry.bucketId, entry.bucketName);
    130                 albums.add(album);
    131             }
    132             return albums;
    133         }
    134     }
    135 
    136     private MediaSet getLocalAlbum(
    137             DataManager manager, int type, Path parent, int id, String name) {
    138         synchronized (DataManager.LOCK) {
    139             Path path = parent.getChild(id);
    140             MediaObject object = manager.peekMediaObject(path);
    141             if (object != null) return (MediaSet) object;
    142             switch (type) {
    143                 case MEDIA_TYPE_IMAGE:
    144                     return new LocalAlbum(path, mApplication, id, true, name);
    145                 case MEDIA_TYPE_VIDEO:
    146                     return new LocalAlbum(path, mApplication, id, false, name);
    147                 case MEDIA_TYPE_ALL:
    148                     Comparator<MediaItem> comp = DataManager.sDateTakenComparator;
    149                     return new LocalMergeAlbum(path, comp, new MediaSet[] {
    150                             getLocalAlbum(manager, MEDIA_TYPE_IMAGE, PATH_IMAGE, id, name),
    151                             getLocalAlbum(manager, MEDIA_TYPE_VIDEO, PATH_VIDEO, id, name)}, id);
    152             }
    153             throw new IllegalArgumentException(String.valueOf(type));
    154         }
    155     }
    156 
    157     @Override
    158     public synchronized boolean isLoading() {
    159         return mIsLoading;
    160     }
    161 
    162     @Override
    163     // synchronized on this function for
    164     //   1. Prevent calling reload() concurrently.
    165     //   2. Prevent calling onFutureDone() and reload() concurrently
    166     public synchronized long reload() {
    167         if (mNotifier.isDirty()) {
    168             if (mLoadTask != null) mLoadTask.cancel();
    169             mIsLoading = true;
    170             mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this);
    171         }
    172         if (mLoadBuffer != null) {
    173             mAlbums = mLoadBuffer;
    174             mLoadBuffer = null;
    175             for (MediaSet album : mAlbums) {
    176                 album.reload();
    177             }
    178             mDataVersion = nextVersionNumber();
    179         }
    180         return mDataVersion;
    181     }
    182 
    183     @Override
    184     public synchronized void onFutureDone(Future<ArrayList<MediaSet>> future) {
    185         if (mLoadTask != future) return; // ignore, wait for the latest task
    186         mLoadBuffer = future.get();
    187         mIsLoading = false;
    188         if (mLoadBuffer == null) mLoadBuffer = new ArrayList<MediaSet>();
    189         mHandler.post(new Runnable() {
    190             @Override
    191             public void run() {
    192                 notifyContentChanged();
    193             }
    194         });
    195     }
    196 
    197     // For debug only. Fake there is a ContentObserver.onChange() event.
    198     void fakeChange() {
    199         mNotifier.fakeChange();
    200     }
    201 
    202     // Circular shift the array range from a[i] to a[j] (inclusive). That is,
    203     // a[i] -> a[i+1] -> a[i+2] -> ... -> a[j], and a[j] -> a[i]
    204     private static <T> void circularShiftRight(T[] array, int i, int j) {
    205         T temp = array[j];
    206         for (int k = j; k > i; k--) {
    207             array[k] = array[k - 1];
    208         }
    209         array[i] = temp;
    210     }
    211 }
    212