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