1 /*
2  * Copyright (C) 2011 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.gadget;
18 
19 import android.graphics.Bitmap;
20 import android.net.Uri;
21 import android.os.Binder;
22 
23 import com.android.gallery3d.common.Utils;
24 import com.android.gallery3d.data.ContentListener;
25 import com.android.gallery3d.data.DataManager;
26 import com.android.gallery3d.data.MediaItem;
27 import com.android.gallery3d.data.MediaObject;
28 import com.android.gallery3d.data.MediaSet;
29 import com.android.gallery3d.data.Path;
30 
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 
34 public class MediaSetSource implements WidgetSource, ContentListener {
35     private static final String TAG = "MediaSetSource";
36 
37     private DataManager mDataManager;
38     private Path mAlbumPath;
39 
40     private WidgetSource mSource;
41 
42     private MediaSet mRootSet;
43     private ContentListener mListener;
44 
MediaSetSource(DataManager manager, String albumPath)45     public MediaSetSource(DataManager manager, String albumPath) {
46         MediaSet mediaSet = (MediaSet) manager.getMediaObject(albumPath);
47         if (mediaSet != null) {
48             mSource = new CheckedMediaSetSource(mediaSet);
49             return;
50         }
51 
52         // Initialize source to an empty source until the album path can be resolved
53         mDataManager = Utils.checkNotNull(manager);
54         mAlbumPath = Path.fromString(albumPath);
55         mSource = new EmptySource();
56         monitorRootPath();
57     }
58 
59     @Override
size()60     public int size() {
61         return mSource.size();
62     }
63 
64     @Override
getImage(int index)65     public Bitmap getImage(int index) {
66         return mSource.getImage(index);
67     }
68 
69     @Override
getContentUri(int index)70     public Uri getContentUri(int index) {
71         return mSource.getContentUri(index);
72     }
73 
74     @Override
setContentListener(ContentListener listener)75     public synchronized void setContentListener(ContentListener listener) {
76         if (mRootSet != null) {
77             mListener = listener;
78         } else {
79             mSource.setContentListener(listener);
80         }
81     }
82 
83     @Override
reload()84     public void reload() {
85         mSource.reload();
86     }
87 
88     @Override
close()89     public void close() {
90         mSource.close();
91     }
92 
93     @Override
onContentDirty()94     public void onContentDirty() {
95         resolveAlbumPath();
96     }
97 
monitorRootPath()98     private void monitorRootPath() {
99         String rootPath = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL);
100         mRootSet = (MediaSet) mDataManager.getMediaObject(rootPath);
101         mRootSet.addContentListener(this);
102     }
103 
resolveAlbumPath()104     private synchronized void resolveAlbumPath() {
105         if (mDataManager == null) return;
106         MediaSet mediaSet = (MediaSet) mDataManager.getMediaObject(mAlbumPath);
107         if (mediaSet != null) {
108             // Clear the reference instead of removing the listener
109             // to get around a concurrent modification exception.
110             mRootSet = null;
111 
112             mSource = new CheckedMediaSetSource(mediaSet);
113             if (mListener != null) {
114                 mListener.onContentDirty();
115                 mSource.setContentListener(mListener);
116                 mListener = null;
117             }
118             mDataManager = null;
119             mAlbumPath = null;
120         }
121     }
122 
123     private static class CheckedMediaSetSource implements WidgetSource, ContentListener {
124         private static final int CACHE_SIZE = 32;
125 
126         @SuppressWarnings("unused")
127         private static final String TAG = "CheckedMediaSetSource";
128 
129         private MediaSet mSource;
130         private MediaItem mCache[] = new MediaItem[CACHE_SIZE];
131         private int mCacheStart;
132         private int mCacheEnd;
133         private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
134 
135         private ContentListener mContentListener;
136 
CheckedMediaSetSource(MediaSet source)137         public CheckedMediaSetSource(MediaSet source) {
138             mSource = Utils.checkNotNull(source);
139             mSource.addContentListener(this);
140         }
141 
142         @Override
close()143         public void close() {
144             mSource.removeContentListener(this);
145         }
146 
ensureCacheRange(int index)147         private void ensureCacheRange(int index) {
148             if (index >= mCacheStart && index < mCacheEnd) return;
149 
150             long token = Binder.clearCallingIdentity();
151             try {
152                 mCacheStart = index;
153                 ArrayList<MediaItem> items = mSource.getMediaItem(mCacheStart, CACHE_SIZE);
154                 mCacheEnd = mCacheStart + items.size();
155                 items.toArray(mCache);
156             } finally {
157                 Binder.restoreCallingIdentity(token);
158             }
159         }
160 
161         @Override
getContentUri(int index)162         public synchronized Uri getContentUri(int index) {
163             ensureCacheRange(index);
164             if (index < mCacheStart || index >= mCacheEnd) return null;
165             return mCache[index - mCacheStart].getContentUri();
166         }
167 
168         @Override
getImage(int index)169         public synchronized Bitmap getImage(int index) {
170             ensureCacheRange(index);
171             if (index < mCacheStart || index >= mCacheEnd) return null;
172             return WidgetUtils.createWidgetBitmap(mCache[index - mCacheStart]);
173         }
174 
175         @Override
reload()176         public void reload() {
177             long version = mSource.reload();
178             if (mSourceVersion != version) {
179                 mSourceVersion = version;
180                 mCacheStart = 0;
181                 mCacheEnd = 0;
182                 Arrays.fill(mCache, null);
183             }
184         }
185 
186         @Override
setContentListener(ContentListener listener)187         public void setContentListener(ContentListener listener) {
188             mContentListener = listener;
189         }
190 
191         @Override
size()192         public int size() {
193             long token = Binder.clearCallingIdentity();
194             try {
195                 return mSource.getMediaItemCount();
196             } finally {
197                 Binder.restoreCallingIdentity(token);
198             }
199         }
200 
201         @Override
onContentDirty()202         public void onContentDirty() {
203             if (mContentListener != null) mContentListener.onContentDirty();
204         }
205     }
206 
207     private static class EmptySource implements WidgetSource {
208 
209         @Override
size()210         public int size() {
211             return 0;
212         }
213 
214         @Override
getImage(int index)215         public Bitmap getImage(int index) {
216             throw new UnsupportedOperationException();
217         }
218 
219         @Override
getContentUri(int index)220         public Uri getContentUri(int index) {
221             throw new UnsupportedOperationException();
222         }
223 
224         @Override
setContentListener(ContentListener listener)225         public void setContentListener(ContentListener listener) {}
226 
227         @Override
reload()228         public void reload() {}
229 
230         @Override
close()231         public void close() {}
232     }
233 }
234