1666ea1b28a76aeba74744148b15099254d918671Owen Lin/*
2666ea1b28a76aeba74744148b15099254d918671Owen Lin * Copyright (C) 2009 The Android Open Source Project
3666ea1b28a76aeba74744148b15099254d918671Owen Lin *
4666ea1b28a76aeba74744148b15099254d918671Owen Lin * Licensed under the Apache License, Version 2.0 (the "License");
5666ea1b28a76aeba74744148b15099254d918671Owen Lin * you may not use this file except in compliance with the License.
6666ea1b28a76aeba74744148b15099254d918671Owen Lin * You may obtain a copy of the License at
7666ea1b28a76aeba74744148b15099254d918671Owen Lin *
8666ea1b28a76aeba74744148b15099254d918671Owen Lin *      http://www.apache.org/licenses/LICENSE-2.0
9666ea1b28a76aeba74744148b15099254d918671Owen Lin *
10666ea1b28a76aeba74744148b15099254d918671Owen Lin * Unless required by applicable law or agreed to in writing, software
11666ea1b28a76aeba74744148b15099254d918671Owen Lin * distributed under the License is distributed on an "AS IS" BASIS,
12666ea1b28a76aeba74744148b15099254d918671Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13666ea1b28a76aeba74744148b15099254d918671Owen Lin * See the License for the specific language governing permissions and
14666ea1b28a76aeba74744148b15099254d918671Owen Lin * limitations under the License.
15666ea1b28a76aeba74744148b15099254d918671Owen Lin */
16666ea1b28a76aeba74744148b15099254d918671Owen Lin
17666ea1b28a76aeba74744148b15099254d918671Owen Linpackage com.android.camera.gallery;
18666ea1b28a76aeba74744148b15099254d918671Owen Lin
19666ea1b28a76aeba74744148b15099254d918671Owen Linimport com.android.camera.ImageManager;
20666ea1b28a76aeba74744148b15099254d918671Owen Linimport com.android.camera.Util;
21666ea1b28a76aeba74744148b15099254d918671Owen Lin
22666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.content.ContentResolver;
23666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.content.ContentUris;
24666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.database.Cursor;
25666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.net.Uri;
26666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.util.Log;
27666ea1b28a76aeba74744148b15099254d918671Owen Lin
28666ea1b28a76aeba74744148b15099254d918671Owen Linimport java.util.regex.Matcher;
29666ea1b28a76aeba74744148b15099254d918671Owen Linimport java.util.regex.Pattern;
30666ea1b28a76aeba74744148b15099254d918671Owen Lin
31666ea1b28a76aeba74744148b15099254d918671Owen Lin/**
32666ea1b28a76aeba74744148b15099254d918671Owen Lin * A collection of <code>BaseImage</code>s.
33666ea1b28a76aeba74744148b15099254d918671Owen Lin */
34666ea1b28a76aeba74744148b15099254d918671Owen Linpublic abstract class BaseImageList implements IImageList {
35666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final String TAG = "BaseImageList";
36666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final int CACHE_CAPACITY = 512;
37666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final LruCache<Integer, BaseImage> mCache =
38666ea1b28a76aeba74744148b15099254d918671Owen Lin            new LruCache<Integer, BaseImage>(CACHE_CAPACITY);
39666ea1b28a76aeba74744148b15099254d918671Owen Lin
40666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected ContentResolver mContentResolver;
41666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected int mSort;
42666ea1b28a76aeba74744148b15099254d918671Owen Lin
43666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected Uri mBaseUri;
44666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected Cursor mCursor;
45666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected String mBucketId;
46666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected boolean mCursorDeactivated = false;
47666ea1b28a76aeba74744148b15099254d918671Owen Lin
48666ea1b28a76aeba74744148b15099254d918671Owen Lin    public BaseImageList(ContentResolver resolver, Uri uri, int sort,
49666ea1b28a76aeba74744148b15099254d918671Owen Lin            String bucketId) {
50666ea1b28a76aeba74744148b15099254d918671Owen Lin        mSort = sort;
51666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBaseUri = uri;
52666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBucketId = bucketId;
53666ea1b28a76aeba74744148b15099254d918671Owen Lin        mContentResolver = resolver;
54666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCursor = createCursor();
55666ea1b28a76aeba74744148b15099254d918671Owen Lin
56666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCursor == null) {
57666ea1b28a76aeba74744148b15099254d918671Owen Lin            Log.w(TAG, "createCursor returns null.");
58666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
59666ea1b28a76aeba74744148b15099254d918671Owen Lin
60666ea1b28a76aeba74744148b15099254d918671Owen Lin        // TODO: We need to clear the cache because we may "reopen" the image
61666ea1b28a76aeba74744148b15099254d918671Owen Lin        // list. After we implement the image list state, we can remove this
62666ea1b28a76aeba74744148b15099254d918671Owen Lin        // kind of usage.
63666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCache.clear();
64666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
65666ea1b28a76aeba74744148b15099254d918671Owen Lin
66666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void close() {
67666ea1b28a76aeba74744148b15099254d918671Owen Lin        try {
68666ea1b28a76aeba74744148b15099254d918671Owen Lin            invalidateCursor();
69666ea1b28a76aeba74744148b15099254d918671Owen Lin        } catch (IllegalStateException e) {
70666ea1b28a76aeba74744148b15099254d918671Owen Lin            // IllegalStateException may be thrown if the cursor is stale.
71666ea1b28a76aeba74744148b15099254d918671Owen Lin            Log.e(TAG, "Caught exception while deactivating cursor.", e);
72666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
73666ea1b28a76aeba74744148b15099254d918671Owen Lin        mContentResolver = null;
74666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCursor != null) {
75666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCursor.close();
76666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCursor = null;
77666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
78666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
79666ea1b28a76aeba74744148b15099254d918671Owen Lin
80666ea1b28a76aeba74744148b15099254d918671Owen Lin    // TODO: Change public to protected
81666ea1b28a76aeba74744148b15099254d918671Owen Lin    public Uri contentUri(long id) {
82666ea1b28a76aeba74744148b15099254d918671Owen Lin        // TODO: avoid using exception for most cases
83666ea1b28a76aeba74744148b15099254d918671Owen Lin        try {
84666ea1b28a76aeba74744148b15099254d918671Owen Lin            // does our uri already have an id (single image query)?
85666ea1b28a76aeba74744148b15099254d918671Owen Lin            // if so just return it
86666ea1b28a76aeba74744148b15099254d918671Owen Lin            long existingId = ContentUris.parseId(mBaseUri);
87666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (existingId != id) Log.e(TAG, "id mismatch");
88666ea1b28a76aeba74744148b15099254d918671Owen Lin            return mBaseUri;
89666ea1b28a76aeba74744148b15099254d918671Owen Lin        } catch (NumberFormatException ex) {
90666ea1b28a76aeba74744148b15099254d918671Owen Lin            // otherwise tack on the id
91666ea1b28a76aeba74744148b15099254d918671Owen Lin            return ContentUris.withAppendedId(mBaseUri, id);
92666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
93666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
94666ea1b28a76aeba74744148b15099254d918671Owen Lin
95666ea1b28a76aeba74744148b15099254d918671Owen Lin    public int getCount() {
96666ea1b28a76aeba74744148b15099254d918671Owen Lin        Cursor cursor = getCursor();
97666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (cursor == null) return 0;
98666ea1b28a76aeba74744148b15099254d918671Owen Lin        synchronized (this) {
99666ea1b28a76aeba74744148b15099254d918671Owen Lin            return cursor.getCount();
100666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
101666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
102666ea1b28a76aeba74744148b15099254d918671Owen Lin
103666ea1b28a76aeba74744148b15099254d918671Owen Lin    public boolean isEmpty() {
104666ea1b28a76aeba74744148b15099254d918671Owen Lin        return getCount() == 0;
105666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
106666ea1b28a76aeba74744148b15099254d918671Owen Lin
107666ea1b28a76aeba74744148b15099254d918671Owen Lin    private Cursor getCursor() {
108666ea1b28a76aeba74744148b15099254d918671Owen Lin        synchronized (this) {
109666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (mCursor == null) return null;
110666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (mCursorDeactivated) {
111666ea1b28a76aeba74744148b15099254d918671Owen Lin                mCursor.requery();
112666ea1b28a76aeba74744148b15099254d918671Owen Lin                mCursorDeactivated = false;
113666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
114666ea1b28a76aeba74744148b15099254d918671Owen Lin            return mCursor;
115666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
116666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
117666ea1b28a76aeba74744148b15099254d918671Owen Lin
118666ea1b28a76aeba74744148b15099254d918671Owen Lin    public IImage getImageAt(int i) {
119666ea1b28a76aeba74744148b15099254d918671Owen Lin        BaseImage result = mCache.get(i);
120666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (result == null) {
121666ea1b28a76aeba74744148b15099254d918671Owen Lin            Cursor cursor = getCursor();
122666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (cursor == null) return null;
123666ea1b28a76aeba74744148b15099254d918671Owen Lin            synchronized (this) {
124666ea1b28a76aeba74744148b15099254d918671Owen Lin                result = cursor.moveToPosition(i)
125666ea1b28a76aeba74744148b15099254d918671Owen Lin                        ? loadImageFromCursor(cursor)
126666ea1b28a76aeba74744148b15099254d918671Owen Lin                        : null;
127666ea1b28a76aeba74744148b15099254d918671Owen Lin                mCache.put(i, result);
128666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
129666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
130666ea1b28a76aeba74744148b15099254d918671Owen Lin        return result;
131666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
132666ea1b28a76aeba74744148b15099254d918671Owen Lin
133666ea1b28a76aeba74744148b15099254d918671Owen Lin    public boolean removeImage(IImage image) {
134666ea1b28a76aeba74744148b15099254d918671Owen Lin        // TODO: need to delete the thumbnails as well
135666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mContentResolver.delete(image.fullSizeImageUri(), null, null) > 0) {
136666ea1b28a76aeba74744148b15099254d918671Owen Lin            ((BaseImage) image).onRemove();
137666ea1b28a76aeba74744148b15099254d918671Owen Lin            invalidateCursor();
138666ea1b28a76aeba74744148b15099254d918671Owen Lin            invalidateCache();
139666ea1b28a76aeba74744148b15099254d918671Owen Lin            return true;
140666ea1b28a76aeba74744148b15099254d918671Owen Lin        } else {
141666ea1b28a76aeba74744148b15099254d918671Owen Lin            return false;
142666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
143666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
144666ea1b28a76aeba74744148b15099254d918671Owen Lin
145666ea1b28a76aeba74744148b15099254d918671Owen Lin    public boolean removeImageAt(int i) {
146666ea1b28a76aeba74744148b15099254d918671Owen Lin        // TODO: need to delete the thumbnails as well
147666ea1b28a76aeba74744148b15099254d918671Owen Lin        return removeImage(getImageAt(i));
148666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
149666ea1b28a76aeba74744148b15099254d918671Owen Lin
150666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected abstract Cursor createCursor();
151666ea1b28a76aeba74744148b15099254d918671Owen Lin
152666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected abstract BaseImage loadImageFromCursor(Cursor cursor);
153666ea1b28a76aeba74744148b15099254d918671Owen Lin
154666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected abstract long getImageId(Cursor cursor);
155666ea1b28a76aeba74744148b15099254d918671Owen Lin
156666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected void invalidateCursor() {
157666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCursor == null) return;
158666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCursor.deactivate();
159666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCursorDeactivated = true;
160666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
161666ea1b28a76aeba74744148b15099254d918671Owen Lin
162666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected void invalidateCache() {
163666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCache.clear();
164666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
165666ea1b28a76aeba74744148b15099254d918671Owen Lin
166666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final Pattern sPathWithId = Pattern.compile("(.*)/\\d+");
167666ea1b28a76aeba74744148b15099254d918671Owen Lin
168666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static String getPathWithoutId(Uri uri) {
169666ea1b28a76aeba74744148b15099254d918671Owen Lin        String path = uri.getPath();
170666ea1b28a76aeba74744148b15099254d918671Owen Lin        Matcher matcher = sPathWithId.matcher(path);
171666ea1b28a76aeba74744148b15099254d918671Owen Lin        return matcher.matches() ? matcher.group(1) : path;
172666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
173666ea1b28a76aeba74744148b15099254d918671Owen Lin
174666ea1b28a76aeba74744148b15099254d918671Owen Lin    private boolean isChildImageUri(Uri uri) {
175666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Sometimes, the URI of an image contains a query string with key
176666ea1b28a76aeba74744148b15099254d918671Owen Lin        // "bucketId" inorder to restore the image list. However, the query
177666ea1b28a76aeba74744148b15099254d918671Owen Lin        // string is not part of the mBaseUri. So, we check only other parts
178666ea1b28a76aeba74744148b15099254d918671Owen Lin        // of the two Uri to see if they are the same.
179666ea1b28a76aeba74744148b15099254d918671Owen Lin        Uri base = mBaseUri;
180666ea1b28a76aeba74744148b15099254d918671Owen Lin        return Util.equals(base.getScheme(), uri.getScheme())
181666ea1b28a76aeba74744148b15099254d918671Owen Lin                && Util.equals(base.getHost(), uri.getHost())
182666ea1b28a76aeba74744148b15099254d918671Owen Lin                && Util.equals(base.getAuthority(), uri.getAuthority())
183666ea1b28a76aeba74744148b15099254d918671Owen Lin                && Util.equals(base.getPath(), getPathWithoutId(uri));
184666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
185666ea1b28a76aeba74744148b15099254d918671Owen Lin
186666ea1b28a76aeba74744148b15099254d918671Owen Lin    public IImage getImageForUri(Uri uri) {
187666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!isChildImageUri(uri)) return null;
188666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Find the id of the input URI.
189666ea1b28a76aeba74744148b15099254d918671Owen Lin        long matchId;
190666ea1b28a76aeba74744148b15099254d918671Owen Lin        try {
191666ea1b28a76aeba74744148b15099254d918671Owen Lin            matchId = ContentUris.parseId(uri);
192666ea1b28a76aeba74744148b15099254d918671Owen Lin        } catch (NumberFormatException ex) {
193666ea1b28a76aeba74744148b15099254d918671Owen Lin            Log.i(TAG, "fail to get id in: " + uri, ex);
194666ea1b28a76aeba74744148b15099254d918671Owen Lin            return null;
195666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
196666ea1b28a76aeba74744148b15099254d918671Owen Lin        // TODO: design a better method to get URI of specified ID
197666ea1b28a76aeba74744148b15099254d918671Owen Lin        Cursor cursor = getCursor();
198666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (cursor == null) return null;
199666ea1b28a76aeba74744148b15099254d918671Owen Lin        synchronized (this) {
200666ea1b28a76aeba74744148b15099254d918671Owen Lin            cursor.moveToPosition(-1); // before first
201666ea1b28a76aeba74744148b15099254d918671Owen Lin            for (int i = 0; cursor.moveToNext(); ++i) {
202666ea1b28a76aeba74744148b15099254d918671Owen Lin                if (getImageId(cursor) == matchId) {
203666ea1b28a76aeba74744148b15099254d918671Owen Lin                    BaseImage image = mCache.get(i);
204666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (image == null) {
205666ea1b28a76aeba74744148b15099254d918671Owen Lin                        image = loadImageFromCursor(cursor);
206666ea1b28a76aeba74744148b15099254d918671Owen Lin                        mCache.put(i, image);
207666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
208666ea1b28a76aeba74744148b15099254d918671Owen Lin                    return image;
209666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
210666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
211666ea1b28a76aeba74744148b15099254d918671Owen Lin            return null;
212666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
213666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
214666ea1b28a76aeba74744148b15099254d918671Owen Lin
215666ea1b28a76aeba74744148b15099254d918671Owen Lin    public int getImageIndex(IImage image) {
216666ea1b28a76aeba74744148b15099254d918671Owen Lin        return ((BaseImage) image).mIndex;
217666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
218666ea1b28a76aeba74744148b15099254d918671Owen Lin
219666ea1b28a76aeba74744148b15099254d918671Owen Lin    // This provides a default sorting order string for subclasses.
220666ea1b28a76aeba74744148b15099254d918671Owen Lin    // The list is first sorted by date, then by id. The order can be ascending
221666ea1b28a76aeba74744148b15099254d918671Owen Lin    // or descending, depending on the mSort variable.
222666ea1b28a76aeba74744148b15099254d918671Owen Lin    // The date is obtained from the "datetaken" column. But if it is null,
223666ea1b28a76aeba74744148b15099254d918671Owen Lin    // the "date_modified" column is used instead.
224666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected String sortOrder() {
225666ea1b28a76aeba74744148b15099254d918671Owen Lin        String ascending =
226666ea1b28a76aeba74744148b15099254d918671Owen Lin                (mSort == ImageManager.SORT_ASCENDING)
227666ea1b28a76aeba74744148b15099254d918671Owen Lin                ? " ASC"
228666ea1b28a76aeba74744148b15099254d918671Owen Lin                : " DESC";
229666ea1b28a76aeba74744148b15099254d918671Owen Lin
230666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Use DATE_TAKEN if it's non-null, otherwise use DATE_MODIFIED.
231666ea1b28a76aeba74744148b15099254d918671Owen Lin        // DATE_TAKEN is in milliseconds, but DATE_MODIFIED is in seconds.
232666ea1b28a76aeba74744148b15099254d918671Owen Lin        String dateExpr =
233666ea1b28a76aeba74744148b15099254d918671Owen Lin                "case ifnull(datetaken,0)" +
234666ea1b28a76aeba74744148b15099254d918671Owen Lin                " when 0 then date_modified*1000" +
235666ea1b28a76aeba74744148b15099254d918671Owen Lin                " else datetaken" +
236666ea1b28a76aeba74744148b15099254d918671Owen Lin                " end";
237666ea1b28a76aeba74744148b15099254d918671Owen Lin
238666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Add id to the end so that we don't ever get random sorting
239666ea1b28a76aeba74744148b15099254d918671Owen Lin        // which could happen, I suppose, if the date values are the same.
240666ea1b28a76aeba74744148b15099254d918671Owen Lin        return dateExpr + ascending + ", _id" + ascending;
241666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
242666ea1b28a76aeba74744148b15099254d918671Owen Lin}
243