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