MediaSet.java revision f9a0a4306d589b4a4e20554fed512a603426bfa1
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
17package com.android.gallery3d.data;
18
19import com.android.gallery3d.util.Future;
20
21import java.util.ArrayList;
22import java.util.WeakHashMap;
23
24// MediaSet is a directory-like data structure.
25// It contains MediaItems and sub-MediaSets.
26//
27// The primary interface are:
28// getMediaItemCount(), getMediaItem() and
29// getSubMediaSetCount(), getSubMediaSet().
30//
31// getTotalMediaItemCount() returns the number of all MediaItems, including
32// those in sub-MediaSets.
33public abstract class MediaSet extends MediaObject {
34    public static final int MEDIAITEM_BATCH_FETCH_COUNT = 500;
35    public static final int INDEX_NOT_FOUND = -1;
36
37    public MediaSet(Path path, long version) {
38        super(path, version);
39    }
40
41    public int getMediaItemCount() {
42        return 0;
43    }
44
45    // Returns the media items in the range [start, start + count).
46    //
47    // The number of media items returned may be less than the specified count
48    // if there are not enough media items available. The number of
49    // media items available may not be consistent with the return value of
50    // getMediaItemCount() because the contents of database may have already
51    // changed.
52    public ArrayList<MediaItem> getMediaItem(int start, int count) {
53        return new ArrayList<MediaItem>();
54    }
55
56    public int getSubMediaSetCount() {
57        return 0;
58    }
59
60    public MediaSet getSubMediaSet(int index) {
61        throw new IndexOutOfBoundsException();
62    }
63
64    public boolean isLeafAlbum() {
65        return false;
66    }
67
68    public int getTotalMediaItemCount() {
69        int total = getMediaItemCount();
70        for (int i = 0, n = getSubMediaSetCount(); i < n; i++) {
71            total += getSubMediaSet(i).getTotalMediaItemCount();
72        }
73        return total;
74    }
75
76    // TODO: we should have better implementation of sub classes
77    public int getIndexOfItem(Path path, int hint) {
78        // hint < 0 is handled below
79        // first, try to find it around the hint
80        int start = Math.max(0,
81                hint - MEDIAITEM_BATCH_FETCH_COUNT / 2);
82        ArrayList<MediaItem> list = getMediaItem(
83                start, MEDIAITEM_BATCH_FETCH_COUNT);
84        int index = getIndexOf(path, list);
85        if (index != INDEX_NOT_FOUND) return start + index;
86
87        // try to find it globally
88        start = start == 0 ? MEDIAITEM_BATCH_FETCH_COUNT : 0;
89        list = getMediaItem(start, MEDIAITEM_BATCH_FETCH_COUNT);
90        while (true) {
91            index = getIndexOf(path, list);
92            if (index != INDEX_NOT_FOUND) return start + index;
93            if (list.size() < MEDIAITEM_BATCH_FETCH_COUNT) return INDEX_NOT_FOUND;
94            start += MEDIAITEM_BATCH_FETCH_COUNT;
95            list = getMediaItem(start, MEDIAITEM_BATCH_FETCH_COUNT);
96        }
97    }
98
99    protected int getIndexOf(Path path, ArrayList<MediaItem> list) {
100        for (int i = 0, n = list.size(); i < n; ++i) {
101            if (list.get(i).mPath == path) return i;
102        }
103        return INDEX_NOT_FOUND;
104    }
105
106    public abstract String getName();
107
108    private WeakHashMap<ContentListener, Object> mListeners =
109            new WeakHashMap<ContentListener, Object>();
110
111    // NOTE: The MediaSet only keeps a weak reference to the listener. The
112    // listener is automatically removed when there is no other reference to
113    // the listener.
114    public void addContentListener(ContentListener listener) {
115        if (mListeners.containsKey(listener)) {
116            throw new IllegalArgumentException();
117        }
118        mListeners.put(listener, null);
119    }
120
121    public void removeContentListener(ContentListener listener) {
122        if (!mListeners.containsKey(listener)) {
123            throw new IllegalArgumentException();
124        }
125        mListeners.remove(listener);
126    }
127
128    // This should be called by subclasses when the content is changed.
129    public void notifyContentChanged() {
130        for (ContentListener listener : mListeners.keySet()) {
131            listener.onContentDirty();
132        }
133    }
134
135    // Reload the content. Return the current data version. reload() should be called
136    // in the same thread as getMediaItem(int, int) and getSubMediaSet(int).
137    public abstract long reload();
138
139    @Override
140    public MediaDetails getDetails() {
141        MediaDetails details = super.getDetails();
142        details.addDetail(MediaDetails.INDEX_TITLE, getName());
143        return details;
144    }
145
146    // Enumerate all media items in this media set (including the ones in sub
147    // media sets), in an efficient order. ItemConsumer.consumer() will be
148    // called for each media item with its index.
149    public void enumerateMediaItems(ItemConsumer consumer) {
150        enumerateMediaItems(consumer, 0);
151    }
152
153    public void enumerateTotalMediaItems(ItemConsumer consumer) {
154        enumerateTotalMediaItems(consumer, 0);
155    }
156
157    public static interface ItemConsumer {
158        void consume(int index, MediaItem item);
159    }
160
161    // The default implementation uses getMediaItem() for enumerateMediaItems().
162    // Subclasses may override this and use more efficient implementations.
163    // Returns the number of items enumerated.
164    protected int enumerateMediaItems(ItemConsumer consumer, int startIndex) {
165        int total = getMediaItemCount();
166        int start = 0;
167        while (start < total) {
168            int count = Math.min(MEDIAITEM_BATCH_FETCH_COUNT, total - start);
169            ArrayList<MediaItem> items = getMediaItem(start, count);
170            for (int i = 0, n = items.size(); i < n; i++) {
171                MediaItem item = items.get(i);
172                consumer.consume(startIndex + start + i, item);
173            }
174            start += count;
175        }
176        return total;
177    }
178
179    // Recursively enumerate all media items under this set.
180    // Returns the number of items enumerated.
181    protected int enumerateTotalMediaItems(
182            ItemConsumer consumer, int startIndex) {
183        int start = 0;
184        start += enumerateMediaItems(consumer, startIndex);
185        int m = getSubMediaSetCount();
186        for (int i = 0; i < m; i++) {
187            start += getSubMediaSet(i).enumerateTotalMediaItems(
188                    consumer, startIndex + start);
189        }
190        return start;
191    }
192
193    public Future<Void> requestSync() {
194        return FUTURE_STUB;
195    }
196
197    private static final Future<Void> FUTURE_STUB = new Future<Void>() {
198        @Override
199        public void cancel() {}
200
201        @Override
202        public boolean isCancelled() {
203            return false;
204        }
205
206        @Override
207        public boolean isDone() {
208            return true;
209        }
210
211        @Override
212        public Void get() {
213            return null;
214        }
215
216        @Override
217        public void waitDone() {}
218    };
219}
220