MediaThumbRequest.java revision 8078c91873489e34bfddb0d3c7e4f3589d3e39a2
1/*
2 * Copyright (C) 2009 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.providers.media;
18
19import java.io.ByteArrayOutputStream;
20import java.io.IOException;
21import java.util.Comparator;
22import java.util.Random;
23
24import android.content.ContentResolver;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.database.Cursor;
28import android.graphics.Bitmap;
29import android.graphics.BitmapFactory;
30import android.media.MiniThumbFile;
31import android.media.ThumbnailUtil;
32import android.net.Uri;
33import android.os.Binder;
34import android.os.ParcelFileDescriptor;
35import android.provider.BaseColumns;
36import android.provider.MediaStore.Images;
37import android.provider.MediaStore.MediaColumns;
38import android.provider.MediaStore.Images.ImageColumns;
39import android.util.Log;
40
41/**
42 * Instances of this class are created and put in a queue to be executed sequentially to see if
43 * it needs to (re)generate the thumbnails.
44 */
45class MediaThumbRequest {
46    private static final String TAG = "MediaThumbRequest";
47    static final int PRIORITY_LOW = 20;
48    static final int PRIORITY_NORMAL = 10;
49    static final int PRIORITY_HIGH = 5;
50    static final int PRIORITY_CRITICAL = 0;
51    static enum State {WAIT, DONE, CANCEL}
52    private static final String[] THUMB_PROJECTION = new String[] {
53        BaseColumns._ID // 0
54    };
55
56    ContentResolver mCr;
57    String mPath;
58    long mRequestTime = System.currentTimeMillis();
59    int mCallingPid = Binder.getCallingPid();
60    int mPriority;
61    Uri mUri;
62    boolean mIsVideo;
63    long mOrigId;
64    State mState = State.WAIT;
65    long mMagic;
66
67    private BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();
68    private final Random mRandom = new Random();
69
70    static Comparator<MediaThumbRequest> getComparator() {
71        return new Comparator<MediaThumbRequest>() {
72            public int compare(MediaThumbRequest r1, MediaThumbRequest r2) {
73                if (r1.mPriority != r2.mPriority) {
74                    return r1.mPriority < r2.mPriority ? -1 : 1;
75                }
76                return r1.mRequestTime == r2.mRequestTime ? 0 :
77                        r1.mRequestTime < r2.mRequestTime ? -1 : 1;
78            }
79        };
80    }
81
82    MediaThumbRequest(ContentResolver cr, String path, Uri uri, int priority) {
83        mCr = cr;
84        mPath = path;
85        mPriority = priority;
86        mUri = uri;
87        mIsVideo = "video".equals(uri.getPathSegments().get(1));
88        mOrigId = ContentUris.parseId(uri);
89    }
90
91    /**
92     * Check if the corresponding thumbnail and mini-thumb have been created
93     * for the given uri. This method creates both of them if they do not
94     * exist yet or have been changed since last check. After thumbnails are
95     * created, MINI_KIND thumbnail is stored in JPEG file and MICRO_KIND
96     * thumbnail is stored in a random access file (MiniThumbFile).
97     *
98     * @throws IOException
99     */
100    void execute() throws IOException {
101        MiniThumbFile miniThumbFile = MiniThumbFile.instance(mUri);
102        long magic = mMagic;
103        if (magic != 0) {
104            long fileMagic = miniThumbFile.getMagic(mOrigId);
105            if (fileMagic == magic) {
106                // signature is identical. skip this item!
107                // Log.v(TAG, "signature is identical. skip this item!");
108                if (mIsVideo) return;
109
110                Cursor c = mCr.query(Images.Thumbnails.EXTERNAL_CONTENT_URI,
111                        new String[] {MediaColumns._ID}, "image_id = " + mOrigId, null, null);
112                ParcelFileDescriptor pfd = null;
113                try {
114                    if (c != null && c.moveToFirst()) {
115                        pfd = mCr.openFileDescriptor(
116                                Images.Thumbnails.EXTERNAL_CONTENT_URI.buildUpon()
117                                .appendPath(c.getString(0)).build(), "r");
118                    }
119                } finally {
120                    if (c != null) c.close();
121                    if (pfd != null) {
122                        pfd.close();
123                        return;
124                    }
125                }
126            }
127        }
128
129        // If we can't retrieve the thumbnail, first check if there is one
130        // embedded in the EXIF data. If not, or it's not big enough,
131        // decompress the full size image.
132        Bitmap bitmap = null;
133
134        if (mPath != null) {
135            if (mIsVideo) {
136                bitmap = ThumbnailUtil.createVideoThumbnail(mPath);
137                if (bitmap != null) {
138                    bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
139                    ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
140                    ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.RECYCLE_INPUT);
141                }
142            } else {
143                bitmap = ThumbnailUtil.createImageThumbnail(mCr, mPath, mUri, mOrigId,
144                        Images.Thumbnails.MICRO_KIND, true);
145            }
146        }
147
148        // make a new magic number since things are out of sync
149        do {
150            magic = mRandom.nextLong();
151        } while (magic == 0);
152
153        if (bitmap != null) {
154            ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream();
155            bitmap.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream);
156            bitmap.recycle();
157            byte [] data = null;
158
159            try {
160                miniOutStream.close();
161                data = miniOutStream.toByteArray();
162            } catch (java.io.IOException ex) {
163                Log.e(TAG, "got exception ex " + ex);
164            }
165
166            // We may consider retire this proprietary format, after all it's size is only
167            // 128 x 128 at most, which is still reasonable to be stored in database.
168            // Gallery application can use the MINI_THUMB_MAGIC value to determine if it's
169            // time to query and fetch by using Cursor.getBlob
170            if (data != null) {
171                miniThumbFile.saveMiniThumbToFile(data, mOrigId, magic);
172                ContentValues values = new ContentValues();
173                // both video/images table use the same column name "mini_thumb_magic"
174                values.put(ImageColumns.MINI_THUMB_MAGIC, magic);
175                mCr.update(mUri, values, null, null);
176            }
177        } else {
178            Log.w(TAG, "can't create bitmap for thumbnail.");
179        }
180    }
181}
182