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 com.android.gallery3d.common.Utils;
     20 import com.android.gallery3d.util.Future;
     21 
     22 import java.util.ArrayList;
     23 import java.util.WeakHashMap;
     24 
     25 // MediaSet is a directory-like data structure.
     26 // It contains MediaItems and sub-MediaSets.
     27 //
     28 // The primary interface are:
     29 // getMediaItemCount(), getMediaItem() and
     30 // getSubMediaSetCount(), getSubMediaSet().
     31 //
     32 // getTotalMediaItemCount() returns the number of all MediaItems, including
     33 // those in sub-MediaSets.
     34 public abstract class MediaSet extends MediaObject {
     35     private static final String TAG = "MediaSet";
     36 
     37     public static final int MEDIAITEM_BATCH_FETCH_COUNT = 500;
     38     public static final int INDEX_NOT_FOUND = -1;
     39 
     40     public static final int SYNC_RESULT_SUCCESS = 0;
     41     public static final int SYNC_RESULT_CANCELLED = 1;
     42     public static final int SYNC_RESULT_ERROR = 2;
     43 
     44     /** Listener to be used with requestSync(SyncListener). */
     45     public static interface SyncListener {
     46         /**
     47          * Called when the sync task completed. Completion may be due to normal termination,
     48          * an exception, or cancellation.
     49          *
     50          * @param mediaSet the MediaSet that's done with sync
     51          * @param resultCode one of the SYNC_RESULT_* constants
     52          */
     53         void onSyncDone(MediaSet mediaSet, int resultCode);
     54     }
     55 
     56     public MediaSet(Path path, long version) {
     57         super(path, version);
     58     }
     59 
     60     public int getMediaItemCount() {
     61         return 0;
     62     }
     63 
     64     // Returns the media items in the range [start, start + count).
     65     //
     66     // The number of media items returned may be less than the specified count
     67     // if there are not enough media items available. The number of
     68     // media items available may not be consistent with the return value of
     69     // getMediaItemCount() because the contents of database may have already
     70     // changed.
     71     public ArrayList<MediaItem> getMediaItem(int start, int count) {
     72         return new ArrayList<MediaItem>();
     73     }
     74 
     75     public MediaItem getCoverMediaItem() {
     76         ArrayList<MediaItem> items = getMediaItem(0, 1);
     77         if (items.size() > 0) return items.get(0);
     78         for (int i = 0, n = getSubMediaSetCount(); i < n; i++) {
     79             MediaItem cover = getSubMediaSet(i).getCoverMediaItem();
     80             if (cover != null) return cover;
     81         }
     82         return null;
     83     }
     84 
     85     public int getSubMediaSetCount() {
     86         return 0;
     87     }
     88 
     89     public MediaSet getSubMediaSet(int index) {
     90         throw new IndexOutOfBoundsException();
     91     }
     92 
     93     public boolean isLeafAlbum() {
     94         return false;
     95     }
     96 
     97     /**
     98      * Method {@link #reload()} may process the loading task in background, this method tells
     99      * its client whether the loading is still in process or not.
    100      */
    101     public boolean isLoading() {
    102         return false;
    103     }
    104 
    105     public int getTotalMediaItemCount() {
    106         int total = getMediaItemCount();
    107         for (int i = 0, n = getSubMediaSetCount(); i < n; i++) {
    108             total += getSubMediaSet(i).getTotalMediaItemCount();
    109         }
    110         return total;
    111     }
    112 
    113     // TODO: we should have better implementation of sub classes
    114     public int getIndexOfItem(Path path, int hint) {
    115         // hint < 0 is handled below
    116         // first, try to find it around the hint
    117         int start = Math.max(0,
    118                 hint - MEDIAITEM_BATCH_FETCH_COUNT / 2);
    119         ArrayList<MediaItem> list = getMediaItem(
    120                 start, MEDIAITEM_BATCH_FETCH_COUNT);
    121         int index = getIndexOf(path, list);
    122         if (index != INDEX_NOT_FOUND) return start + index;
    123 
    124         // try to find it globally
    125         start = start == 0 ? MEDIAITEM_BATCH_FETCH_COUNT : 0;
    126         list = getMediaItem(start, MEDIAITEM_BATCH_FETCH_COUNT);
    127         while (true) {
    128             index = getIndexOf(path, list);
    129             if (index != INDEX_NOT_FOUND) return start + index;
    130             if (list.size() < MEDIAITEM_BATCH_FETCH_COUNT) return INDEX_NOT_FOUND;
    131             start += MEDIAITEM_BATCH_FETCH_COUNT;
    132             list = getMediaItem(start, MEDIAITEM_BATCH_FETCH_COUNT);
    133         }
    134     }
    135 
    136     protected int getIndexOf(Path path, ArrayList<MediaItem> list) {
    137         for (int i = 0, n = list.size(); i < n; ++i) {
    138             if (list.get(i).mPath == path) return i;
    139         }
    140         return INDEX_NOT_FOUND;
    141     }
    142 
    143     public abstract String getName();
    144 
    145     private WeakHashMap<ContentListener, Object> mListeners =
    146             new WeakHashMap<ContentListener, Object>();
    147 
    148     // NOTE: The MediaSet only keeps a weak reference to the listener. The
    149     // listener is automatically removed when there is no other reference to
    150     // the listener.
    151     public void addContentListener(ContentListener listener) {
    152         if (mListeners.containsKey(listener)) {
    153             throw new IllegalArgumentException();
    154         }
    155         mListeners.put(listener, null);
    156     }
    157 
    158     public void removeContentListener(ContentListener listener) {
    159         if (!mListeners.containsKey(listener)) {
    160             throw new IllegalArgumentException();
    161         }
    162         mListeners.remove(listener);
    163     }
    164 
    165     // This should be called by subclasses when the content is changed.
    166     public void notifyContentChanged() {
    167         for (ContentListener listener : mListeners.keySet()) {
    168             listener.onContentDirty();
    169         }
    170     }
    171 
    172     // Reload the content. Return the current data version. reload() should be called
    173     // in the same thread as getMediaItem(int, int) and getSubMediaSet(int).
    174     public abstract long reload();
    175 
    176     @Override
    177     public MediaDetails getDetails() {
    178         MediaDetails details = super.getDetails();
    179         details.addDetail(MediaDetails.INDEX_TITLE, getName());
    180         return details;
    181     }
    182 
    183     // Enumerate all media items in this media set (including the ones in sub
    184     // media sets), in an efficient order. ItemConsumer.consumer() will be
    185     // called for each media item with its index.
    186     public void enumerateMediaItems(ItemConsumer consumer) {
    187         enumerateMediaItems(consumer, 0);
    188     }
    189 
    190     public void enumerateTotalMediaItems(ItemConsumer consumer) {
    191         enumerateTotalMediaItems(consumer, 0);
    192     }
    193 
    194     public static interface ItemConsumer {
    195         void consume(int index, MediaItem item);
    196     }
    197 
    198     // The default implementation uses getMediaItem() for enumerateMediaItems().
    199     // Subclasses may override this and use more efficient implementations.
    200     // Returns the number of items enumerated.
    201     protected int enumerateMediaItems(ItemConsumer consumer, int startIndex) {
    202         int total = getMediaItemCount();
    203         int start = 0;
    204         while (start < total) {
    205             int count = Math.min(MEDIAITEM_BATCH_FETCH_COUNT, total - start);
    206             ArrayList<MediaItem> items = getMediaItem(start, count);
    207             for (int i = 0, n = items.size(); i < n; i++) {
    208                 MediaItem item = items.get(i);
    209                 consumer.consume(startIndex + start + i, item);
    210             }
    211             start += count;
    212         }
    213         return total;
    214     }
    215 
    216     // Recursively enumerate all media items under this set.
    217     // Returns the number of items enumerated.
    218     protected int enumerateTotalMediaItems(
    219             ItemConsumer consumer, int startIndex) {
    220         int start = 0;
    221         start += enumerateMediaItems(consumer, startIndex);
    222         int m = getSubMediaSetCount();
    223         for (int i = 0; i < m; i++) {
    224             start += getSubMediaSet(i).enumerateTotalMediaItems(
    225                     consumer, startIndex + start);
    226         }
    227         return start;
    228     }
    229 
    230     /**
    231      * Requests sync on this MediaSet. It returns a Future object that can be used by the caller
    232      * to query the status of the sync. The sync result code is one of the SYNC_RESULT_* constants
    233      * defined in this class and can be obtained by Future.get().
    234      *
    235      * Subclasses should perform sync on a different thread.
    236      *
    237      * The default implementation here returns a Future stub that does nothing and returns
    238      * SYNC_RESULT_SUCCESS by get().
    239      */
    240     public Future<Integer> requestSync(SyncListener listener) {
    241         listener.onSyncDone(this, SYNC_RESULT_SUCCESS);
    242         return FUTURE_STUB;
    243     }
    244 
    245     private static final Future<Integer> FUTURE_STUB = new Future<Integer>() {
    246         @Override
    247         public void cancel() {}
    248 
    249         @Override
    250         public boolean isCancelled() {
    251             return false;
    252         }
    253 
    254         @Override
    255         public boolean isDone() {
    256             return true;
    257         }
    258 
    259         @Override
    260         public Integer get() {
    261             return SYNC_RESULT_SUCCESS;
    262         }
    263 
    264         @Override
    265         public void waitDone() {}
    266     };
    267 
    268     protected Future<Integer> requestSyncOnMultipleSets(MediaSet[] sets, SyncListener listener) {
    269         return new MultiSetSyncFuture(sets, listener);
    270     }
    271 
    272     private class MultiSetSyncFuture implements Future<Integer>, SyncListener {
    273         private static final String TAG = "Gallery.MultiSetSync";
    274 
    275         private final SyncListener mListener;
    276         private final Future<Integer> mFutures[];
    277 
    278         private boolean mIsCancelled = false;
    279         private int mResult = -1;
    280         private int mPendingCount;
    281 
    282         @SuppressWarnings("unchecked")
    283         MultiSetSyncFuture(MediaSet[] sets, SyncListener listener) {
    284             mListener = listener;
    285             mPendingCount = sets.length;
    286             mFutures = new Future[sets.length];
    287 
    288             synchronized (this) {
    289                 for (int i = 0, n = sets.length; i < n; ++i) {
    290                     mFutures[i] = sets[i].requestSync(this);
    291                     Log.d(TAG, "  request sync: " + Utils.maskDebugInfo(sets[i].getName()));
    292                 }
    293             }
    294         }
    295 
    296         @Override
    297         public synchronized void cancel() {
    298             if (mIsCancelled) return;
    299             mIsCancelled = true;
    300             for (Future<Integer> future : mFutures) future.cancel();
    301             if (mResult < 0) mResult = SYNC_RESULT_CANCELLED;
    302         }
    303 
    304         @Override
    305         public synchronized boolean isCancelled() {
    306             return mIsCancelled;
    307         }
    308 
    309         @Override
    310         public synchronized boolean isDone() {
    311             return mPendingCount == 0;
    312         }
    313 
    314         @Override
    315         public synchronized Integer get() {
    316             waitDone();
    317             return mResult;
    318         }
    319 
    320         @Override
    321         public synchronized void waitDone() {
    322             try {
    323                 while (!isDone()) wait();
    324             } catch (InterruptedException e) {
    325                 Log.d(TAG, "waitDone() interrupted");
    326             }
    327         }
    328 
    329         // SyncListener callback
    330         @Override
    331         public void onSyncDone(MediaSet mediaSet, int resultCode) {
    332             SyncListener listener = null;
    333             synchronized (this) {
    334                 if (resultCode == SYNC_RESULT_ERROR) mResult = SYNC_RESULT_ERROR;
    335                 --mPendingCount;
    336                 if (mPendingCount == 0) {
    337                     listener = mListener;
    338                     notifyAll();
    339                 }
    340                 Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName())
    341                         + " #pending=" + mPendingCount);
    342             }
    343             if (listener != null) listener.onSyncDone(MediaSet.this, mResult);
    344         }
    345     }
    346 }
    347