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     public int getTotalMediaItemCount() {
     98         int total = getMediaItemCount();
     99         for (int i = 0, n = getSubMediaSetCount(); i < n; i++) {
    100             total += getSubMediaSet(i).getTotalMediaItemCount();
    101         }
    102         return total;
    103     }
    104 
    105     // TODO: we should have better implementation of sub classes
    106     public int getIndexOfItem(Path path, int hint) {
    107         // hint < 0 is handled below
    108         // first, try to find it around the hint
    109         int start = Math.max(0,
    110                 hint - MEDIAITEM_BATCH_FETCH_COUNT / 2);
    111         ArrayList<MediaItem> list = getMediaItem(
    112                 start, MEDIAITEM_BATCH_FETCH_COUNT);
    113         int index = getIndexOf(path, list);
    114         if (index != INDEX_NOT_FOUND) return start + index;
    115 
    116         // try to find it globally
    117         start = start == 0 ? MEDIAITEM_BATCH_FETCH_COUNT : 0;
    118         list = getMediaItem(start, MEDIAITEM_BATCH_FETCH_COUNT);
    119         while (true) {
    120             index = getIndexOf(path, list);
    121             if (index != INDEX_NOT_FOUND) return start + index;
    122             if (list.size() < MEDIAITEM_BATCH_FETCH_COUNT) return INDEX_NOT_FOUND;
    123             start += MEDIAITEM_BATCH_FETCH_COUNT;
    124             list = getMediaItem(start, MEDIAITEM_BATCH_FETCH_COUNT);
    125         }
    126     }
    127 
    128     protected int getIndexOf(Path path, ArrayList<MediaItem> list) {
    129         for (int i = 0, n = list.size(); i < n; ++i) {
    130             if (list.get(i).mPath == path) return i;
    131         }
    132         return INDEX_NOT_FOUND;
    133     }
    134 
    135     public abstract String getName();
    136 
    137     private WeakHashMap<ContentListener, Object> mListeners =
    138             new WeakHashMap<ContentListener, Object>();
    139 
    140     // NOTE: The MediaSet only keeps a weak reference to the listener. The
    141     // listener is automatically removed when there is no other reference to
    142     // the listener.
    143     public void addContentListener(ContentListener listener) {
    144         if (mListeners.containsKey(listener)) {
    145             throw new IllegalArgumentException();
    146         }
    147         mListeners.put(listener, null);
    148     }
    149 
    150     public void removeContentListener(ContentListener listener) {
    151         if (!mListeners.containsKey(listener)) {
    152             throw new IllegalArgumentException();
    153         }
    154         mListeners.remove(listener);
    155     }
    156 
    157     // This should be called by subclasses when the content is changed.
    158     public void notifyContentChanged() {
    159         for (ContentListener listener : mListeners.keySet()) {
    160             listener.onContentDirty();
    161         }
    162     }
    163 
    164     // Reload the content. Return the current data version. reload() should be called
    165     // in the same thread as getMediaItem(int, int) and getSubMediaSet(int).
    166     public abstract long reload();
    167 
    168     @Override
    169     public MediaDetails getDetails() {
    170         MediaDetails details = super.getDetails();
    171         details.addDetail(MediaDetails.INDEX_TITLE, getName());
    172         return details;
    173     }
    174 
    175     // Enumerate all media items in this media set (including the ones in sub
    176     // media sets), in an efficient order. ItemConsumer.consumer() will be
    177     // called for each media item with its index.
    178     public void enumerateMediaItems(ItemConsumer consumer) {
    179         enumerateMediaItems(consumer, 0);
    180     }
    181 
    182     public void enumerateTotalMediaItems(ItemConsumer consumer) {
    183         enumerateTotalMediaItems(consumer, 0);
    184     }
    185 
    186     public static interface ItemConsumer {
    187         void consume(int index, MediaItem item);
    188     }
    189 
    190     // The default implementation uses getMediaItem() for enumerateMediaItems().
    191     // Subclasses may override this and use more efficient implementations.
    192     // Returns the number of items enumerated.
    193     protected int enumerateMediaItems(ItemConsumer consumer, int startIndex) {
    194         int total = getMediaItemCount();
    195         int start = 0;
    196         while (start < total) {
    197             int count = Math.min(MEDIAITEM_BATCH_FETCH_COUNT, total - start);
    198             ArrayList<MediaItem> items = getMediaItem(start, count);
    199             for (int i = 0, n = items.size(); i < n; i++) {
    200                 MediaItem item = items.get(i);
    201                 consumer.consume(startIndex + start + i, item);
    202             }
    203             start += count;
    204         }
    205         return total;
    206     }
    207 
    208     // Recursively enumerate all media items under this set.
    209     // Returns the number of items enumerated.
    210     protected int enumerateTotalMediaItems(
    211             ItemConsumer consumer, int startIndex) {
    212         int start = 0;
    213         start += enumerateMediaItems(consumer, startIndex);
    214         int m = getSubMediaSetCount();
    215         for (int i = 0; i < m; i++) {
    216             start += getSubMediaSet(i).enumerateTotalMediaItems(
    217                     consumer, startIndex + start);
    218         }
    219         return start;
    220     }
    221 
    222     /**
    223      * Requests sync on this MediaSet. It returns a Future object that can be used by the caller
    224      * to query the status of the sync. The sync result code is one of the SYNC_RESULT_* constants
    225      * defined in this class and can be obtained by Future.get().
    226      *
    227      * Subclasses should perform sync on a different thread.
    228      *
    229      * The default implementation here returns a Future stub that does nothing and returns
    230      * SYNC_RESULT_SUCCESS by get().
    231      */
    232     public Future<Integer> requestSync(SyncListener listener) {
    233         listener.onSyncDone(this, SYNC_RESULT_SUCCESS);
    234         return FUTURE_STUB;
    235     }
    236 
    237     private static final Future<Integer> FUTURE_STUB = new Future<Integer>() {
    238         @Override
    239         public void cancel() {}
    240 
    241         @Override
    242         public boolean isCancelled() {
    243             return false;
    244         }
    245 
    246         @Override
    247         public boolean isDone() {
    248             return true;
    249         }
    250 
    251         @Override
    252         public Integer get() {
    253             return SYNC_RESULT_SUCCESS;
    254         }
    255 
    256         @Override
    257         public void waitDone() {}
    258     };
    259 
    260     protected Future<Integer> requestSyncOnMultipleSets(MediaSet[] sets, SyncListener listener) {
    261         return new MultiSetSyncFuture(sets, listener);
    262     }
    263 
    264     private class MultiSetSyncFuture implements Future<Integer>, SyncListener {
    265         private static final String TAG = "Gallery.MultiSetSync";
    266 
    267         private final SyncListener mListener;
    268         private final Future<Integer> mFutures[];
    269 
    270         private boolean mIsCancelled = false;
    271         private int mResult = -1;
    272         private int mPendingCount;
    273 
    274         @SuppressWarnings("unchecked")
    275         MultiSetSyncFuture(MediaSet[] sets, SyncListener listener) {
    276             mListener = listener;
    277             mPendingCount = sets.length;
    278             mFutures = new Future[sets.length];
    279 
    280             synchronized (this) {
    281                 for (int i = 0, n = sets.length; i < n; ++i) {
    282                     mFutures[i] = sets[i].requestSync(this);
    283                     Log.d(TAG, "  request sync: " + Utils.maskDebugInfo(sets[i].getName()));
    284                 }
    285             }
    286         }
    287 
    288         @Override
    289         public synchronized void cancel() {
    290             if (mIsCancelled) return;
    291             mIsCancelled = true;
    292             for (Future<Integer> future : mFutures) future.cancel();
    293             if (mResult < 0) mResult = SYNC_RESULT_CANCELLED;
    294         }
    295 
    296         @Override
    297         public synchronized boolean isCancelled() {
    298             return mIsCancelled;
    299         }
    300 
    301         @Override
    302         public synchronized boolean isDone() {
    303             return mPendingCount == 0;
    304         }
    305 
    306         @Override
    307         public synchronized Integer get() {
    308             waitDone();
    309             return mResult;
    310         }
    311 
    312         @Override
    313         public synchronized void waitDone() {
    314             try {
    315                 while (!isDone()) wait();
    316             } catch (InterruptedException e) {
    317                 Log.d(TAG, "waitDone() interrupted");
    318             }
    319         }
    320 
    321         // SyncListener callback
    322         @Override
    323         public void onSyncDone(MediaSet mediaSet, int resultCode) {
    324             SyncListener listener = null;
    325             synchronized (this) {
    326                 if (resultCode == SYNC_RESULT_ERROR) mResult = SYNC_RESULT_ERROR;
    327                 --mPendingCount;
    328                 if (mPendingCount == 0) {
    329                     listener = mListener;
    330                     notifyAll();
    331                 }
    332                 Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName())
    333                         + " #pending=" + mPendingCount);
    334             }
    335             if (listener != null) listener.onSyncDone(MediaSet.this, mResult);
    336         }
    337     }
    338 }
    339