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