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