• 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.net.Uri;
21 import android.os.Environment;
22 import android.util.Log;
23 
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.RandomAccessFile;
27 import java.nio.ByteBuffer;
28 import java.nio.channels.FileChannel;
29 import java.nio.channels.FileLock;
30 import 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  */
46 public class MiniThumbFile {
47     private static final String TAG = "MiniThumbFile";
48     private static final int MINI_THUMB_DATA_FILE_VERSION = 3;
49     public static final int BYTES_PER_MINTHUMB = 10000;
50     private static final int HEADER_SIZE = 1 + 8 + 4;
51     private Uri mUri;
52     private RandomAccessFile mMiniThumbFile;
53     private FileChannel mChannel;
54     private ByteBuffer mBuffer;
55     private static final Hashtable<String, MiniThumbFile> sThumbFiles =
56         new Hashtable<String, MiniThumbFile>();
57 
58     /**
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.
61      */
reset()62     public static synchronized void reset() {
63         for (MiniThumbFile file : sThumbFiles.values()) {
64             file.deactivate();
65         }
66         sThumbFiles.clear();
67     }
68 
instance(Uri uri)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);
73         if (file == null) {
74             file = new MiniThumbFile(
75                     Uri.parse("content://media/external/" + type + "/media"));
76             sThumbFiles.put(type, file);
77         }
78 
79         return file;
80     }
81 
randomAccessFilePath(int version)82     private String randomAccessFilePath(int version) {
83         String directoryName =
84                 Environment.getExternalStorageDirectory().toString()
85                 + "/DCIM/.thumbnails";
86         return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode();
87     }
88 
removeOldFile()89     private void removeOldFile() {
90         String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1);
91         File oldFile = new File(oldPath);
92         if (oldFile.exists()) {
93             try {
94                 oldFile.delete();
95             } catch (SecurityException ex) {
96                 // ignore
97             }
98         }
99     }
100 
miniThumbDataFile()101     private RandomAccessFile miniThumbDataFile() {
102         if (mMiniThumbFile == null) {
103             removeOldFile();
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());
110                 }
111             }
112             File f = new File(path);
113             try {
114                 mMiniThumbFile = new RandomAccessFile(f, "rw");
115             } catch (IOException ex) {
116                 // Open as read-only so we can at least read the existing
117                 // thumbnails.
118                 try {
119                     mMiniThumbFile = new RandomAccessFile(f, "r");
120                 } catch (IOException ex2) {
121                     // ignore exception
122                 }
123             }
124             if (mMiniThumbFile != null) {
125                 mChannel = mMiniThumbFile.getChannel();
126             }
127         }
128         return mMiniThumbFile;
129     }
130 
MiniThumbFile(Uri uri)131     public MiniThumbFile(Uri uri) {
132         mUri = uri;
133         mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB);
134     }
135 
deactivate()136     public synchronized void deactivate() {
137         if (mMiniThumbFile != null) {
138             try {
139                 mMiniThumbFile.close();
140                 mMiniThumbFile = null;
141             } catch (IOException ex) {
142                 // ignore exception
143             }
144         }
145     }
146 
147     // Get the magic number for the specified id in the mini-thumb file.
148     // Returns 0 if the magic is not available.
getMagic(long id)149     public synchronized long getMagic(long id) {
150         // check the mini thumb file for the right data.  Right is
151         // defined as having the right magic number at the offset
152         // reserved for this "id".
153         RandomAccessFile r = miniThumbDataFile();
154         if (r != null) {
155             long pos = id * BYTES_PER_MINTHUMB;
156             FileLock lock = null;
157             try {
158                 mBuffer.clear();
159                 mBuffer.limit(1 + 8);
160 
161                 lock = mChannel.lock(pos, 1 + 8, true);
162                 // check that we can read the following 9 bytes
163                 // (1 for the "status" and 8 for the long)
164                 if (mChannel.read(mBuffer, pos) == 9) {
165                     mBuffer.position(0);
166                     if (mBuffer.get() == 1) {
167                         return mBuffer.getLong();
168                     }
169                 }
170             } catch (IOException ex) {
171                 Log.v(TAG, "Got exception checking file magic: ", ex);
172             } catch (RuntimeException ex) {
173                 // Other NIO related exception like disk full, read only channel..etc
174                 Log.e(TAG, "Got exception when reading magic, id = " + id +
175                         ", disk full or mount read-only? " + ex.getClass());
176             } finally {
177                 try {
178                     if (lock != null) lock.release();
179                 }
180                 catch (IOException ex) {
181                     // ignore it.
182                 }
183             }
184         }
185         return 0;
186     }
187 
saveMiniThumbToFile(byte[] data, long id, long magic)188     public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic)
189             throws IOException {
190         RandomAccessFile r = miniThumbDataFile();
191         if (r == null) return;
192 
193         long pos = id * BYTES_PER_MINTHUMB;
194         FileLock lock = null;
195         try {
196             if (data != null) {
197                 if (data.length > BYTES_PER_MINTHUMB - HEADER_SIZE) {
198                     // not enough space to store it.
199                     return;
200                 }
201                 mBuffer.clear();
202                 mBuffer.put((byte) 1);
203                 mBuffer.putLong(magic);
204                 mBuffer.putInt(data.length);
205                 mBuffer.put(data);
206                 mBuffer.flip();
207 
208                 lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false);
209                 mChannel.write(mBuffer, pos);
210             }
211         } catch (IOException ex) {
212             Log.e(TAG, "couldn't save mini thumbnail data for "
213                     + id + "; ", ex);
214             throw ex;
215         } catch (RuntimeException ex) {
216             // Other NIO related exception like disk full, read only channel..etc
217             Log.e(TAG, "couldn't save mini thumbnail data for "
218                     + id + "; disk full or mount read-only? " + ex.getClass());
219         } finally {
220             try {
221                 if (lock != null) lock.release();
222             }
223             catch (IOException ex) {
224                 // ignore it.
225             }
226         }
227     }
228 
229     /**
230      * Gallery app can use this method to retrieve mini-thumbnail. Full size
231      * images share the same IDs with their corresponding thumbnails.
232      *
233      * @param id the ID of the image (same of full size image).
234      * @param data the buffer to store mini-thumbnail.
235      */
getMiniThumbFromFile(long id, byte [] data)236     public synchronized byte [] getMiniThumbFromFile(long id, byte [] data) {
237         RandomAccessFile r = miniThumbDataFile();
238         if (r == null) return null;
239 
240         long pos = id * BYTES_PER_MINTHUMB;
241         FileLock lock = null;
242         try {
243             mBuffer.clear();
244             lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, true);
245             int size = mChannel.read(mBuffer, pos);
246             if (size > 1 + 8 + 4) { // flag, magic, length
247                 mBuffer.position(0);
248                 byte flag = mBuffer.get();
249                 long magic = mBuffer.getLong();
250                 int length = mBuffer.getInt();
251 
252                 if (size >= 1 + 8 + 4 + length && 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