2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.media;
19 import android.net.Uri;
20 import android.os.Environment;
21 import android.util.Log;
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;
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
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)
40 * (the remaining bytes are unused)
42 * @hide This file is shared between MediaStore and MediaProvider and should remained internal use
45 public class MiniThumbFile {
46 private static final String TAG = "MiniThumbFile";
47 private static final int MINI_THUMB_DATA_FILE_VERSION = 4;
48 public static final int BYTES_PER_MINTHUMB = 10000;
49 private static final int HEADER_SIZE = 1 + 8 + 4;
51 private RandomAccessFile mMiniThumbFile;
52 private FileChannel mChannel;
53 private ByteBuffer mBuffer;
54 private ByteBuffer mEmptyBuffer;
55 private static final Hashtable<String, MiniThumbFile> sThumbFiles =
56 new Hashtable<String, MiniThumbFile>();
59 * We store different types of thumbnails in different files. To remain backward compatibility,
60 * we should hashcode of content://media/external/images/media remains the same.
62 public static synchronized void reset() {
63 for (MiniThumbFile file : sThumbFiles.values()) {
69 public static synchronized MiniThumbFile instance(Uri uri) {
70 String type = uri.getPathSegments().get(1);
71 MiniThumbFile file = sThumbFiles.get(type);
72 // Log.v(TAG, "get minithumbfile for type: "+type);
74 file = new MiniThumbFile(
75 Uri.parse("content://media/external/" + type + "/media"));
76 sThumbFiles.put(type, file);
82 private String randomAccessFilePath(int version) {
83 String directoryName =
84 Environment.getExternalStorageDirectory().toString()
85 + "/DCIM/.thumbnails";
86 return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode();
89 private void removeOldFile() {
90 String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1);
91 File oldFile = new File(oldPath);
92 if (oldFile.exists()) {
95 } catch (SecurityException ex) {
101 private RandomAccessFile miniThumbDataFile() {
102 if (mMiniThumbFile == null) {
104 String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION);
105 File directory = new File(path).getParentFile();
106 if (!directory.isDirectory()) {
107 if (!directory.mkdirs()) {
108 Log.e(TAG, "Unable to create .thumbnails directory "
109 + directory.toString());
112 File f = new File(path);
114 mMiniThumbFile = new RandomAccessFile(f, "rw");
115 } catch (IOException ex) {
116 // Open as read-only so we can at least read the existing
119 mMiniThumbFile = new RandomAccessFile(f, "r");
120 } catch (IOException ex2) {
124 if (mMiniThumbFile != null) {
125 mChannel = mMiniThumbFile.getChannel();
128 return mMiniThumbFile;
131 private MiniThumbFile(Uri uri) {
133 mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB);
134 mEmptyBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB);
137 public synchronized void deactivate() {
138 if (mMiniThumbFile != null) {
140 mMiniThumbFile.close();
141 mMiniThumbFile = null;
142 } catch (IOException ex) {
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();
156 long pos = id * BYTES_PER_MINTHUMB;
157 FileLock lock = null;
160 mBuffer.limit(1 + 8);
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) {
167 if (mBuffer.get() == 1) {
168 return mBuffer.getLong();
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());
179 if (lock != null) lock.release();
181 catch (IOException ex) {
189 public synchronized void eraseMiniThumb(long id) {
190 RandomAccessFile r = miniThumbDataFile();
192 long pos = id * BYTES_PER_MINTHUMB;
193 FileLock lock = null;
196 mBuffer.limit(1 + 8);
198 lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false);
199 // check that we can read the following 9 bytes
200 // (1 for the "status" and 8 for the long)
201 if (mChannel.read(mBuffer, pos) == 9) {
203 if (mBuffer.get() == 1) {
204 long currentMagic = mBuffer.getLong();
205 if (currentMagic == 0) {
206 // there is no thumbnail stored here
207 Log.i(TAG, "no thumbnail for id " + id);
210 // zero out the thumbnail slot
211 // Log.v(TAG, "clearing slot " + id + ", magic " + currentMagic
212 // + " at offset " + pos);
213 mChannel.write(mEmptyBuffer, pos);
216 // Log.v(TAG, "No slot");
218 } catch (IOException ex) {
219 Log.v(TAG, "Got exception checking file magic: ", ex);
220 } catch (RuntimeException ex) {
221 // Other NIO related exception like disk full, read only channel..etc
222 Log.e(TAG, "Got exception when reading magic, id = " + id +
223 ", disk full or mount read-only? " + ex.getClass());
226 if (lock != null) lock.release();
228 catch (IOException ex) {
233 // Log.v(TAG, "No data file");
237 public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic)
239 RandomAccessFile r = miniThumbDataFile();
240 if (r == null) return;
242 long pos = id * BYTES_PER_MINTHUMB;
243 FileLock lock = null;
246 if (data.length > BYTES_PER_MINTHUMB - HEADER_SIZE) {
247 // not enough space to store it.
251 mBuffer.put((byte) 1);
252 mBuffer.putLong(magic);
253 mBuffer.putInt(data.length);
257 lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false);
258 mChannel.write(mBuffer, pos);
260 } catch (IOException ex) {
261 Log.e(TAG, "couldn't save mini thumbnail data for "
264 } catch (RuntimeException ex) {
265 // Other NIO related exception like disk full, read only channel..etc
266 Log.e(TAG, "couldn't save mini thumbnail data for "
267 + id + "; disk full or mount read-only? " + ex.getClass());
270 if (lock != null) lock.release();
272 catch (IOException ex) {
279 * Gallery app can use this method to retrieve mini-thumbnail. Full size
280 * images share the same IDs with their corresponding thumbnails.
282 * @param id the ID of the image (same of full size image).
283 * @param data the buffer to store mini-thumbnail.
285 public synchronized byte [] getMiniThumbFromFile(long id, byte [] data) {
286 RandomAccessFile r = miniThumbDataFile();
287 if (r == null) return null;
289 long pos = id * BYTES_PER_MINTHUMB;
290 FileLock lock = null;
293 lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, true);
294 int size = mChannel.read(mBuffer, pos);
295 if (size > 1 + 8 + 4) { // flag, magic, length
297 byte flag = mBuffer.get();
298 long magic = mBuffer.getLong();
299 int length = mBuffer.getInt();
301 if (size >= 1 + 8 + 4 + length && length != 0 && magic != 0 && flag == 1 &&
302 data.length >= length) {
303 mBuffer.get(data, 0, length);
307 } catch (IOException ex) {
308 Log.w(TAG, "got exception when reading thumbnail id=" + id + ", exception: " + ex);
309 } catch (RuntimeException ex) {
310 // Other NIO related exception like disk full, read only channel..etc
311 Log.e(TAG, "Got exception when reading thumbnail, id = " + id +
312 ", disk full or mount read-only? " + ex.getClass());
315 if (lock != null) lock.release();
317 catch (IOException ex) {