• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.graphics.Bitmap;
20 import android.media.ThumbnailUtil;
21 import android.net.Uri;
22 import android.os.Environment;
23 import android.util.Log;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.RandomAccessFile;
28 import java.nio.ByteBuffer;
29 import java.nio.channels.FileChannel;
30 import java.nio.channels.FileLock;
31 import 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  */
47 public 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      */
reset()63     public static synchronized void reset() {
64         for (MiniThumbFile file : sThumbFiles.values()) {
65             file.deactivate();
66         }
67         sThumbFiles.clear();
68     }
69 
instance(Uri uri)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 
randomAccessFilePath(int version)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 
removeOldFile()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 
miniThumbDataFile()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 
MiniThumbFile(Uri uri)132     public MiniThumbFile(Uri uri) {
133         mUri = uri;
134         mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB);
135     }
136 
deactivate()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.
getMagic(long id)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 
saveMiniThumbToFile(byte[] data, long id, long magic)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      */
getMiniThumbFromFile(long id, byte [] data)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