• 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     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          */
onSyncDone(MediaSet mediaSet, int resultCode)53         void onSyncDone(MediaSet mediaSet, int resultCode);
54     }
55 
MediaSet(Path path, long version)56     public MediaSet(Path path, long version) {
57         super(path, version);
58     }
59 
getMediaItemCount()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.
getMediaItem(int start, int count)71     public ArrayList<MediaItem> getMediaItem(int start, int count) {
72         return new ArrayList<MediaItem>();
73     }
74 
getCoverMediaItem()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 
getSubMediaSetCount()85     public int getSubMediaSetCount() {
86         return 0;
87     }
88 
getSubMediaSet(int index)89     public MediaSet getSubMediaSet(int index) {
90         throw new IndexOutOfBoundsException();
91     }
92 
isLeafAlbum()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      */
isLoading()101     public boolean isLoading() {
102         return false;
103     }
104 
getTotalMediaItemCount()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
getIndexOfItem(Path path, int hint)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 
getIndexOf(Path path, ArrayList<MediaItem> list)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 
getName()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.
addContentListener(ContentListener listener)151     public void addContentListener(ContentListener listener) {
152         if (mListeners.containsKey(listener)) {
153             throw new IllegalArgumentException();
154         }
155         mListeners.put(listener, null);
156     }
157 
removeContentListener(ContentListener listener)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.
notifyContentChanged()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).
reload()174     public abstract long reload();
175 
176     @Override
getDetails()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.
enumerateMediaItems(ItemConsumer consumer)186     public void enumerateMediaItems(ItemConsumer consumer) {
187         enumerateMediaItems(consumer, 0);
188     }
189 
enumerateTotalMediaItems(ItemConsumer consumer)190     public void enumerateTotalMediaItems(ItemConsumer consumer) {
191         enumerateTotalMediaItems(consumer, 0);
192     }
193 
194     public static interface ItemConsumer {
consume(int index, MediaItem item)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.
enumerateMediaItems(ItemConsumer consumer, int startIndex)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.
enumerateTotalMediaItems( ItemConsumer consumer, int startIndex)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      */
requestSync(SyncListener listener)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 
requestSyncOnMultipleSets(MediaSet[] sets, SyncListener listener)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")
MultiSetSyncFuture(MediaSet[] sets, SyncListener listener)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
cancel()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
isCancelled()305         public synchronized boolean isCancelled() {
306             return mIsCancelled;
307         }
308 
309         @Override
isDone()310         public synchronized boolean isDone() {
311             return mPendingCount == 0;
312         }
313 
314         @Override
get()315         public synchronized Integer get() {
316             waitDone();
317             return mResult;
318         }
319 
320         @Override
waitDone()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
onSyncDone(MediaSet mediaSet, int resultCode)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