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