MiniThumbFile.java revision ef093cd6c4ab4d3c8a1c8be5ed7147d5f06d7027
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 android.media; 18 19import android.graphics.Bitmap; 20import android.media.ThumbnailUtil; 21import android.net.Uri; 22import android.os.Environment; 23import android.util.Log; 24 25import java.io.File; 26import java.io.IOException; 27import java.io.RandomAccessFile; 28import java.nio.ByteBuffer; 29import java.nio.channels.FileChannel; 30import java.nio.channels.FileLock; 31import java.util.Hashtable; 32 33/** 34 * This class handles the mini-thumb file. A mini-thumb file consists 35 * of blocks, indexed by id. Each block has BYTES_PER_MINTHUMB bytes in the 36 * following format: 37 * 38 * 1 byte status (0 = empty, 1 = mini-thumb available) 39 * 8 bytes magic (a magic number to match what's in the database) 40 * 4 bytes data length (LEN) 41 * LEN bytes jpeg data 42 * (the remaining bytes are unused) 43 * 44 * @hide This file is shared between MediaStore and MediaProvider and should remained internal use 45 * only. 46 */ 47public class MiniThumbFile { 48 private static final String TAG = "MiniThumbFile"; 49 private static final int MINI_THUMB_DATA_FILE_VERSION = 3; 50 public static final int BYTES_PER_MINTHUMB = 10000; 51 private static final int HEADER_SIZE = 1 + 8 + 4; 52 private Uri mUri; 53 private RandomAccessFile mMiniThumbFile; 54 private FileChannel mChannel; 55 private ByteBuffer mBuffer; 56 private static Hashtable<String, MiniThumbFile> sThumbFiles = 57 new Hashtable<String, MiniThumbFile>(); 58 59 /** 60 * We store different types of thumbnails in different files. To remain backward compatibility, 61 * we should hashcode of content://media/external/images/media remains the same. 62 */ 63 public static synchronized void reset() { 64 for (MiniThumbFile file : sThumbFiles.values()) { 65 file.deactivate(); 66 } 67 sThumbFiles.clear(); 68 } 69 70 public static synchronized MiniThumbFile instance(Uri uri) { 71 String type = uri.getPathSegments().get(1); 72 MiniThumbFile file = sThumbFiles.get(type); 73 // Log.v(TAG, "get minithumbfile for type: "+type); 74 if (file == null) { 75 file = new MiniThumbFile( 76 Uri.parse("content://media/external/" + type + "/media")); 77 sThumbFiles.put(type, file); 78 } 79 80 return file; 81 } 82 83 private String randomAccessFilePath(int version) { 84 String directoryName = 85 Environment.getExternalStorageDirectory().toString() 86 + "/DCIM/.thumbnails"; 87 return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode(); 88 } 89 90 private void removeOldFile() { 91 String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1); 92 File oldFile = new File(oldPath); 93 if (oldFile.exists()) { 94 try { 95 oldFile.delete(); 96 } catch (SecurityException ex) { 97 // ignore 98 } 99 } 100 } 101 102 private RandomAccessFile miniThumbDataFile() { 103 if (mMiniThumbFile == null) { 104 removeOldFile(); 105 String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION); 106 File directory = new File(path).getParentFile(); 107 if (!directory.isDirectory()) { 108 if (!directory.mkdirs()) { 109 Log.e(TAG, "Unable to create .thumbnails directory " 110 + directory.toString()); 111 } 112 } 113 File f = new File(path); 114 try { 115 mMiniThumbFile = new RandomAccessFile(f, "rw"); 116 } catch (IOException ex) { 117 // Open as read-only so we can at least read the existing 118 // thumbnails. 119 try { 120 mMiniThumbFile = new RandomAccessFile(f, "r"); 121 } catch (IOException ex2) { 122 // ignore exception 123 } 124 } 125 if (mMiniThumbFile != null) { 126 mChannel = mMiniThumbFile.getChannel(); 127 } 128 } 129 return mMiniThumbFile; 130 } 131 132 public MiniThumbFile(Uri uri) { 133 mUri = uri; 134 mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); 135 } 136 137 public synchronized void deactivate() { 138 if (mMiniThumbFile != null) { 139 try { 140 mMiniThumbFile.close(); 141 mMiniThumbFile = null; 142 } catch (IOException ex) { 143 // ignore exception 144 } 145 } 146 } 147 148 // Get the magic number for the specified id in the mini-thumb file. 149 // Returns 0 if the magic is not available. 150 public synchronized long getMagic(long id) { 151 // check the mini thumb file for the right data. Right is 152 // defined as having the right magic number at the offset 153 // reserved for this "id". 154 RandomAccessFile r = miniThumbDataFile(); 155 if (r != null) { 156 long pos = id * BYTES_PER_MINTHUMB; 157 FileLock lock = null; 158 try { 159 mBuffer.clear(); 160 mBuffer.limit(1 + 8); 161 162 lock = mChannel.lock(pos, 1 + 8, true); 163 // check that we can read the following 9 bytes 164 // (1 for the "status" and 8 for the long) 165 if (mChannel.read(mBuffer, pos) == 9) { 166 mBuffer.position(0); 167 if (mBuffer.get() == 1) { 168 return mBuffer.getLong(); 169 } 170 } 171 } catch (IOException ex) { 172 Log.v(TAG, "Got exception checking file magic: ", ex); 173 } catch (RuntimeException ex) { 174 // Other NIO related exception like disk full, read only channel..etc 175 Log.e(TAG, "Got exception when reading magic, id = " + id + 176 ", disk full or mount read-only? " + ex.getClass()); 177 } finally { 178 try { 179 if (lock != null) lock.release(); 180 } 181 catch (IOException ex) { 182 // ignore it. 183 } 184 } 185 } 186 return 0; 187 } 188 189 public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) 190 throws IOException { 191 RandomAccessFile r = miniThumbDataFile(); 192 if (r == null) return; 193 194 long pos = id * BYTES_PER_MINTHUMB; 195 FileLock lock = null; 196 try { 197 if (data != null) { 198 if (data.length > BYTES_PER_MINTHUMB - HEADER_SIZE) { 199 // not enough space to store it. 200 return; 201 } 202 mBuffer.clear(); 203 mBuffer.put((byte) 1); 204 mBuffer.putLong(magic); 205 mBuffer.putInt(data.length); 206 mBuffer.put(data); 207 mBuffer.flip(); 208 209 lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false); 210 mChannel.write(mBuffer, pos); 211 } 212 } catch (IOException ex) { 213 Log.e(TAG, "couldn't save mini thumbnail data for " 214 + id + "; ", ex); 215 throw ex; 216 } catch (RuntimeException ex) { 217 // Other NIO related exception like disk full, read only channel..etc 218 Log.e(TAG, "couldn't save mini thumbnail data for " 219 + id + "; disk full or mount read-only? " + ex.getClass()); 220 } finally { 221 try { 222 if (lock != null) lock.release(); 223 } 224 catch (IOException ex) { 225 // ignore it. 226 } 227 } 228 } 229 230 /** 231 * Gallery app can use this method to retrieve mini-thumbnail. Full size 232 * images share the same IDs with their corresponding thumbnails. 233 * 234 * @param id the ID of the image (same of full size image). 235 * @param data the buffer to store mini-thumbnail. 236 */ 237 public synchronized byte [] getMiniThumbFromFile(long id, byte [] data) { 238 RandomAccessFile r = miniThumbDataFile(); 239 if (r == null) return null; 240 241 long pos = id * BYTES_PER_MINTHUMB; 242 FileLock lock = null; 243 try { 244 mBuffer.clear(); 245 lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, true); 246 int size = mChannel.read(mBuffer, pos); 247 if (size > 1 + 8 + 4) { // flag, magic, length 248 mBuffer.position(0); 249 byte flag = mBuffer.get(); 250 long magic = mBuffer.getLong(); 251 int length = mBuffer.getInt(); 252 253 if (size >= 1 + 8 + 4 + length && data.length >= length) { 254 mBuffer.get(data, 0, length); 255 return data; 256 } 257 } 258 } catch (IOException ex) { 259 Log.w(TAG, "got exception when reading thumbnail id=" + id + ", exception: " + ex); 260 } catch (RuntimeException ex) { 261 // Other NIO related exception like disk full, read only channel..etc 262 Log.e(TAG, "Got exception when reading thumbnail, id = " + id + 263 ", disk full or mount read-only? " + ex.getClass()); 264 } finally { 265 try { 266 if (lock != null) lock.release(); 267 } 268 catch (IOException ex) { 269 // ignore it. 270 } 271 } 272 return null; 273 } 274} 275