• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 com.android.email.provider;
18 
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.util.List;
26 
27 import android.content.ContentProvider;
28 import android.content.ContentResolver;
29 import android.content.ContentValues;
30 import android.database.Cursor;
31 import android.database.MatrixCursor;
32 import android.database.sqlite.SQLiteDatabase;
33 import android.graphics.Bitmap;
34 import android.graphics.BitmapFactory;
35 import android.net.Uri;
36 import android.os.ParcelFileDescriptor;
37 import android.provider.OpenableColumns;
38 import android.util.Config;
39 import android.util.Log;
40 
41 import com.android.email.Account;
42 import com.android.email.Email;
43 import com.android.email.Utility;
44 import com.android.email.mail.internet.MimeUtility;
45 
46 /*
47  * A simple ContentProvider that allows file access to Email's attachments.
48  */
49 public class AttachmentProvider extends ContentProvider {
50     public static final Uri CONTENT_URI = Uri.parse( "content://com.android.email.attachmentprovider");
51 
52     private static final String FORMAT_RAW = "RAW";
53     private static final String FORMAT_THUMBNAIL = "THUMBNAIL";
54 
55     public static class AttachmentProviderColumns {
56         public static final String _ID = "_id";
57         public static final String DATA = "_data";
58         public static final String DISPLAY_NAME = "_display_name";
59         public static final String SIZE = "_size";
60     }
61 
getAttachmentUri(Account account, long id)62     public static Uri getAttachmentUri(Account account, long id) {
63         return CONTENT_URI.buildUpon()
64                 .appendPath(account.getUuid() + ".db")
65                 .appendPath(Long.toString(id))
66                 .appendPath(FORMAT_RAW)
67                 .build();
68     }
69 
getAttachmentThumbnailUri(Account account, long id, int width, int height)70     public static Uri getAttachmentThumbnailUri(Account account, long id, int width, int height) {
71         return CONTENT_URI.buildUpon()
72                 .appendPath(account.getUuid() + ".db")
73                 .appendPath(Long.toString(id))
74                 .appendPath(FORMAT_THUMBNAIL)
75                 .appendPath(Integer.toString(width))
76                 .appendPath(Integer.toString(height))
77                 .build();
78     }
79 
getAttachmentUri(String db, long id)80     public static Uri getAttachmentUri(String db, long id) {
81         return CONTENT_URI.buildUpon()
82                 .appendPath(db)
83                 .appendPath(Long.toString(id))
84                 .appendPath(FORMAT_RAW)
85                 .build();
86     }
87 
88     @Override
onCreate()89     public boolean onCreate() {
90         /*
91          * We use the cache dir as a temporary directory (since Android doesn't give us one) so
92          * on startup we'll clean up any .tmp files from the last run.
93          */
94         File[] files = getContext().getCacheDir().listFiles();
95         for (File file : files) {
96             String filename = file.getName();
97             if (filename.endsWith(".tmp") || filename.startsWith("thmb_")) {
98                 file.delete();
99             }
100         }
101         return true;
102     }
103 
104     @Override
getType(Uri uri)105     public String getType(Uri uri) {
106         List<String> segments = uri.getPathSegments();
107         String dbName = segments.get(0);
108         String id = segments.get(1);
109         String format = segments.get(2);
110         if (FORMAT_THUMBNAIL.equals(format)) {
111             return "image/png";
112         }
113         else {
114             String path = getContext().getDatabasePath(dbName).getAbsolutePath();
115             SQLiteDatabase db = null;
116             Cursor cursor = null;
117             try {
118                 db = SQLiteDatabase.openDatabase(path, null, 0);
119                 cursor = db.query(
120                         "attachments",
121                         new String[] { "mime_type" },
122                         "id = ?",
123                         new String[] { id },
124                         null,
125                         null,
126                         null);
127                 cursor.moveToFirst();
128                 String type = cursor.getString(0);
129                 cursor.close();
130                 db.close();
131                 return type;
132 
133             }
134             finally {
135                 if (cursor != null) {
136                     cursor.close();
137                 }
138                 if (db != null) {
139                     db.close();
140                 }
141 
142             }
143         }
144     }
145 
146     @Override
openFile(Uri uri, String mode)147     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
148         List<String> segments = uri.getPathSegments();
149         String dbName = segments.get(0);
150         String id = segments.get(1);
151         String format = segments.get(2);
152         if (FORMAT_THUMBNAIL.equals(format)) {
153             int width = Integer.parseInt(segments.get(3));
154             int height = Integer.parseInt(segments.get(4));
155             String filename = "thmb_" + dbName + "_" + id;
156             File dir = getContext().getCacheDir();
157             File file = new File(dir, filename);
158             if (!file.exists()) {
159                 Uri attachmentUri = getAttachmentUri(dbName, Long.parseLong(id));
160                 Cursor c = query(attachmentUri,
161                         new String[] { AttachmentProviderColumns.DATA }, null, null, null);
162                 if (c != null) {
163                     try {
164                         if (c.moveToFirst()) {
165                             attachmentUri = Uri.parse(c.getString(0));
166                         }
167                     } finally {
168                         c.close();
169                     }
170                 }
171                 String type = getContext().getContentResolver().getType(attachmentUri);
172                 try {
173                     InputStream in =
174                         getContext().getContentResolver().openInputStream(attachmentUri);
175                     Bitmap thumbnail = createThumbnail(type, in);
176                     thumbnail = Bitmap.createScaledBitmap(thumbnail, width, height, true);
177                     FileOutputStream out = new FileOutputStream(file);
178                     thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
179                     out.close();
180                     in.close();
181                 }
182                 catch (IOException ioe) {
183                     return null;
184                 }
185             }
186             return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
187         }
188         else {
189             return ParcelFileDescriptor.open(
190                     new File(getContext().getDatabasePath(dbName + "_att"), id),
191                     ParcelFileDescriptor.MODE_READ_ONLY);
192         }
193     }
194 
195     @Override
delete(Uri uri, String arg1, String[] arg2)196     public int delete(Uri uri, String arg1, String[] arg2) {
197         return 0;
198     }
199 
200     @Override
insert(Uri uri, ContentValues values)201     public Uri insert(Uri uri, ContentValues values) {
202         return null;
203     }
204 
205     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)206     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
207             String sortOrder) {
208         if (projection == null) {
209             projection =
210                 new String[] {
211                     AttachmentProviderColumns._ID,
212                     AttachmentProviderColumns.DATA,
213                     };
214         }
215 
216         List<String> segments = uri.getPathSegments();
217         String dbName = segments.get(0);
218         String id = segments.get(1);
219         String format = segments.get(2);
220         String path = getContext().getDatabasePath(dbName).getAbsolutePath();
221         String name = null;
222         int size = -1;
223         String contentUri = null;
224         SQLiteDatabase db = null;
225         Cursor cursor = null;
226         try {
227             db = SQLiteDatabase.openDatabase(path, null, 0);
228             cursor = db.query(
229                     "attachments",
230                     new String[] { "name", "size", "content_uri" },
231                     "id = ?",
232                     new String[] { id },
233                     null,
234                     null,
235                     null);
236             if (!cursor.moveToFirst()) {
237                 return null;
238             }
239             name = cursor.getString(0);
240             size = cursor.getInt(1);
241             contentUri = cursor.getString(2);
242         }
243         finally {
244             if (cursor != null) {
245                 cursor.close();
246             }
247             if (db != null) {
248                 db.close();
249             }
250         }
251 
252         MatrixCursor ret = new MatrixCursor(projection);
253         Object[] values = new Object[projection.length];
254         for (int i = 0, count = projection.length; i < count; i++) {
255             String column = projection[i];
256             if (AttachmentProviderColumns._ID.equals(column)) {
257                 values[i] = id;
258             }
259             else if (AttachmentProviderColumns.DATA.equals(column)) {
260                 values[i] = contentUri;
261             }
262             else if (AttachmentProviderColumns.DISPLAY_NAME.equals(column)) {
263                 values[i] = name;
264             }
265             else if (AttachmentProviderColumns.SIZE.equals(column)) {
266                 values[i] = size;
267             }
268         }
269         ret.addRow(values);
270         return ret;
271     }
272 
273     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)274     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
275         return 0;
276     }
277 
createThumbnail(String type, InputStream data)278     private Bitmap createThumbnail(String type, InputStream data) {
279         if(MimeUtility.mimeTypeMatches(type, "image/*")) {
280             return createImageThumbnail(data);
281         }
282         return null;
283     }
284 
createImageThumbnail(InputStream data)285     private Bitmap createImageThumbnail(InputStream data) {
286         try {
287             Bitmap bitmap = BitmapFactory.decodeStream(data);
288             return bitmap;
289         }
290         catch (OutOfMemoryError oome) {
291             /*
292              * Improperly downloaded images, corrupt bitmaps and the like can commonly
293              * cause OOME due to invalid allocation sizes. We're happy with a null bitmap in
294              * that case. If the system is really out of memory we'll know about it soon
295              * enough.
296              */
297             return null;
298         }
299         catch (Exception e) {
300             return null;
301         }
302     }
303     /**
304      * Resolve attachment id to content URI.
305      *
306      * @param attachmentUri
307      * @return resolved content URI
308      */
resolveAttachmentIdToContentUri(ContentResolver resolver, Uri attachmentUri)309     public static Uri resolveAttachmentIdToContentUri(ContentResolver resolver, Uri attachmentUri) {
310         Cursor c = resolver.query(attachmentUri,
311                 new String[] { AttachmentProvider.AttachmentProviderColumns.DATA },
312                 null, null, null);
313         if (c != null) {
314             try {
315                 if (c.moveToFirst()) {
316                     return Uri.parse(c.getString(0));
317                 }
318             } finally {
319                 c.close();
320             }
321         }
322         return attachmentUri;
323     }
324 }
325