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