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