2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.android.email.provider;
|
|
|
|
|
|
|
|
import android.content.ContentProvider;
|
2009-07-27 22:52:42 +00:00
|
|
|
import android.content.ContentUris;
|
2009-03-04 03:32:22 +00:00
|
|
|
import android.content.ContentValues;
|
2011-02-14 22:39:27 +00:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.pm.PackageManager;
|
2009-03-04 03:32:22 +00:00
|
|
|
import android.database.Cursor;
|
|
|
|
import android.database.MatrixCursor;
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.BitmapFactory;
|
|
|
|
import android.net.Uri;
|
2010-08-12 00:31:45 +00:00
|
|
|
import android.os.Binder;
|
2009-03-04 03:32:22 +00:00
|
|
|
import android.os.ParcelFileDescriptor;
|
|
|
|
|
2013-02-06 23:21:15 +00:00
|
|
|
import com.android.emailcommon.Logging;
|
|
|
|
import com.android.emailcommon.internet.MimeUtility;
|
|
|
|
import com.android.emailcommon.provider.EmailContent;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.Attachment;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
|
|
|
|
import com.android.emailcommon.utility.AttachmentUtilities;
|
|
|
|
import com.android.emailcommon.utility.AttachmentUtilities.Columns;
|
2013-05-26 04:32:32 +00:00
|
|
|
import com.android.mail.utils.LogUtils;
|
2013-02-06 23:21:15 +00:00
|
|
|
import com.android.mail.utils.MatrixCursorWithCachedColumns;
|
|
|
|
|
2009-06-08 22:27:17 +00:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.util.List;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* A simple ContentProvider that allows file access to Email's attachments.
|
2010-06-28 23:51:14 +00:00
|
|
|
*
|
2009-07-27 22:52:42 +00:00
|
|
|
* The URI scheme is as follows. For raw file access:
|
2012-08-23 05:25:42 +00:00
|
|
|
* content://com.android.mail.attachmentprovider/acct#/attach#/RAW
|
2010-06-28 23:51:14 +00:00
|
|
|
*
|
2009-07-27 22:52:42 +00:00
|
|
|
* And for access to thumbnails:
|
2012-08-23 05:25:42 +00:00
|
|
|
* content://com.android.mail.attachmentprovider/acct#/attach#/THUMBNAIL/width#/height#
|
2009-07-28 02:52:21 +00:00
|
|
|
*
|
|
|
|
* The on-disk (storage) schema is as follows.
|
2010-06-28 23:51:14 +00:00
|
|
|
*
|
2009-07-28 02:52:21 +00:00
|
|
|
* Attachments are stored at: <database-path>/account#.db_att/item#
|
|
|
|
* Thumbnails are stored at: <cache-path>/thmb_account#_item#
|
2010-06-28 23:51:14 +00:00
|
|
|
*
|
2009-07-28 02:52:21 +00:00
|
|
|
* Using the standard application context, account #10 and attachment # 20, this would be:
|
|
|
|
* /data/data/com.android.email/databases/10.db_att/20
|
|
|
|
* /data/data/com.android.email/cache/thmb_10_20
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
|
|
|
public class AttachmentProvider extends ContentProvider {
|
2009-07-27 18:20:15 +00:00
|
|
|
|
2010-04-30 22:01:10 +00:00
|
|
|
private static final String[] MIME_TYPE_PROJECTION = new String[] {
|
|
|
|
AttachmentColumns.MIME_TYPE, AttachmentColumns.FILENAME };
|
|
|
|
private static final int MIME_TYPE_COLUMN_MIME_TYPE = 0;
|
|
|
|
private static final int MIME_TYPE_COLUMN_FILENAME = 1;
|
|
|
|
|
|
|
|
private static final String[] PROJECTION_QUERY = new String[] { AttachmentColumns.FILENAME,
|
2009-07-27 22:52:42 +00:00
|
|
|
AttachmentColumns.SIZE, AttachmentColumns.CONTENT_URI };
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
|
|
|
public boolean onCreate() {
|
|
|
|
/*
|
|
|
|
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
|
|
|
|
* on startup we'll clean up any .tmp files from the last run.
|
|
|
|
*/
|
2013-08-16 22:53:36 +00:00
|
|
|
|
|
|
|
final File[] files = getContext().getCacheDir().listFiles();
|
|
|
|
if (files != null) {
|
|
|
|
for (File file : files) {
|
|
|
|
final String filename = file.getName();
|
|
|
|
if (filename.endsWith(".tmp") || filename.startsWith("thmb_")) {
|
|
|
|
file.delete();
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2009-07-27 18:20:15 +00:00
|
|
|
/**
|
|
|
|
* Returns the mime type for a given attachment. There are three possible results:
|
|
|
|
* - If thumbnail Uri, always returns "image/png" (even if there's no attachment)
|
|
|
|
* - If the attachment does not exist, returns null
|
|
|
|
* - Returns the mime type of the attachment
|
|
|
|
*/
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
|
|
|
public String getType(Uri uri) {
|
2010-08-12 00:31:45 +00:00
|
|
|
long callingId = Binder.clearCallingIdentity();
|
|
|
|
try {
|
|
|
|
List<String> segments = uri.getPathSegments();
|
|
|
|
String id = segments.get(1);
|
|
|
|
String format = segments.get(2);
|
2011-02-09 01:50:30 +00:00
|
|
|
if (AttachmentUtilities.FORMAT_THUMBNAIL.equals(format)) {
|
2010-08-12 00:31:45 +00:00
|
|
|
return "image/png";
|
|
|
|
} else {
|
|
|
|
uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, Long.parseLong(id));
|
2011-02-09 01:50:30 +00:00
|
|
|
Cursor c = getContext().getContentResolver().query(uri, MIME_TYPE_PROJECTION, null,
|
|
|
|
null, null);
|
2010-08-12 00:31:45 +00:00
|
|
|
try {
|
|
|
|
if (c.moveToFirst()) {
|
|
|
|
String mimeType = c.getString(MIME_TYPE_COLUMN_MIME_TYPE);
|
|
|
|
String fileName = c.getString(MIME_TYPE_COLUMN_FILENAME);
|
2011-02-09 01:50:30 +00:00
|
|
|
mimeType = AttachmentUtilities.inferMimeType(fileName, mimeType);
|
2010-08-12 00:31:45 +00:00
|
|
|
return mimeType;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-08-12 00:31:45 +00:00
|
|
|
return null;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-08-12 00:31:45 +00:00
|
|
|
} finally {
|
|
|
|
Binder.restoreCallingIdentity(callingId);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-27 18:20:15 +00:00
|
|
|
/**
|
2011-02-14 22:39:27 +00:00
|
|
|
* Open an attachment file. There are two "formats" - "raw", which returns an actual file,
|
2009-07-27 18:20:15 +00:00
|
|
|
* and "thumbnail", which attempts to generate a thumbnail image.
|
2010-06-28 23:51:14 +00:00
|
|
|
*
|
2009-07-27 18:20:15 +00:00
|
|
|
* Thumbnails are cached for easy space recovery and cleanup.
|
2010-06-28 23:51:14 +00:00
|
|
|
*
|
2011-02-14 22:39:27 +00:00
|
|
|
* TODO: The thumbnail format returns null for its failure cases, instead of throwing
|
2009-07-27 18:20:15 +00:00
|
|
|
* FileNotFoundException, and should be fixed for consistency.
|
2010-06-28 23:51:14 +00:00
|
|
|
*
|
2009-07-27 18:20:15 +00:00
|
|
|
* @throws FileNotFoundException
|
|
|
|
*/
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
|
|
|
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
2011-02-14 22:39:27 +00:00
|
|
|
// If this is a write, the caller must have the EmailProvider permission, which is
|
|
|
|
// based on signature only
|
|
|
|
if (mode.equals("w")) {
|
|
|
|
Context context = getContext();
|
2012-06-28 17:40:46 +00:00
|
|
|
if (context.checkCallingOrSelfPermission(EmailContent.PROVIDER_PERMISSION)
|
2011-02-14 22:39:27 +00:00
|
|
|
!= PackageManager.PERMISSION_GRANTED) {
|
|
|
|
throw new FileNotFoundException();
|
|
|
|
}
|
|
|
|
List<String> segments = uri.getPathSegments();
|
|
|
|
String accountId = segments.get(0);
|
|
|
|
String id = segments.get(1);
|
|
|
|
File saveIn =
|
|
|
|
AttachmentUtilities.getAttachmentDirectory(context, Long.parseLong(accountId));
|
|
|
|
if (!saveIn.exists()) {
|
|
|
|
saveIn.mkdirs();
|
|
|
|
}
|
|
|
|
File newFile = new File(saveIn, id);
|
|
|
|
return ParcelFileDescriptor.open(
|
|
|
|
newFile, ParcelFileDescriptor.MODE_READ_WRITE |
|
|
|
|
ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE);
|
|
|
|
}
|
2010-08-12 00:31:45 +00:00
|
|
|
long callingId = Binder.clearCallingIdentity();
|
|
|
|
try {
|
|
|
|
List<String> segments = uri.getPathSegments();
|
|
|
|
String accountId = segments.get(0);
|
|
|
|
String id = segments.get(1);
|
|
|
|
String format = segments.get(2);
|
2011-02-09 01:50:30 +00:00
|
|
|
if (AttachmentUtilities.FORMAT_THUMBNAIL.equals(format)) {
|
2010-08-12 00:31:45 +00:00
|
|
|
int width = Integer.parseInt(segments.get(3));
|
|
|
|
int height = Integer.parseInt(segments.get(4));
|
|
|
|
String filename = "thmb_" + accountId + "_" + id;
|
|
|
|
File dir = getContext().getCacheDir();
|
|
|
|
File file = new File(dir, filename);
|
|
|
|
if (!file.exists()) {
|
2011-02-09 01:50:30 +00:00
|
|
|
Uri attachmentUri = AttachmentUtilities.
|
2010-08-12 00:31:45 +00:00
|
|
|
getAttachmentUri(Long.parseLong(accountId), Long.parseLong(id));
|
|
|
|
Cursor c = query(attachmentUri,
|
2011-02-09 01:50:30 +00:00
|
|
|
new String[] { Columns.DATA }, null, null, null);
|
2010-08-12 00:31:45 +00:00
|
|
|
if (c != null) {
|
|
|
|
try {
|
|
|
|
if (c.moveToFirst()) {
|
|
|
|
attachmentUri = Uri.parse(c.getString(0));
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
2009-06-01 18:28:33 +00:00
|
|
|
}
|
2010-08-12 00:31:45 +00:00
|
|
|
}
|
|
|
|
String type = getContext().getContentResolver().getType(attachmentUri);
|
|
|
|
try {
|
|
|
|
InputStream in =
|
|
|
|
getContext().getContentResolver().openInputStream(attachmentUri);
|
|
|
|
Bitmap thumbnail = createThumbnail(type, in);
|
2010-09-21 23:49:13 +00:00
|
|
|
if (thumbnail == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2010-08-12 00:31:45 +00:00
|
|
|
thumbnail = Bitmap.createScaledBitmap(thumbnail, width, height, true);
|
|
|
|
FileOutputStream out = new FileOutputStream(file);
|
|
|
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
|
|
|
|
out.close();
|
|
|
|
in.close();
|
2010-09-21 23:49:13 +00:00
|
|
|
} catch (IOException ioe) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG, "openFile/thumbnail failed with " +
|
2011-02-11 23:05:17 +00:00
|
|
|
ioe.getMessage());
|
2010-09-21 23:49:13 +00:00
|
|
|
return null;
|
|
|
|
} catch (OutOfMemoryError oome) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG, "openFile/thumbnail failed with " +
|
2011-02-11 23:05:17 +00:00
|
|
|
oome.getMessage());
|
2010-08-12 00:31:45 +00:00
|
|
|
return null;
|
2009-06-01 18:28:33 +00:00
|
|
|
}
|
|
|
|
}
|
2010-08-12 00:31:45 +00:00
|
|
|
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-08-12 00:31:45 +00:00
|
|
|
else {
|
|
|
|
return ParcelFileDescriptor.open(
|
|
|
|
new File(getContext().getDatabasePath(accountId + ".db_att"), id),
|
|
|
|
ParcelFileDescriptor.MODE_READ_ONLY);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
Binder.restoreCallingIdentity(callingId);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int delete(Uri uri, String arg1, String[] arg2) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Uri insert(Uri uri, ContentValues values) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2009-07-27 18:20:15 +00:00
|
|
|
/**
|
|
|
|
* Returns a cursor based on the data in the attachments table, or null if the attachment
|
|
|
|
* is not recorded in the table.
|
2010-06-28 23:51:14 +00:00
|
|
|
*
|
2009-07-27 18:20:15 +00:00
|
|
|
* Supports REST Uri only, for a single row - selection, selection args, and sortOrder are
|
|
|
|
* ignored (non-null values should probably throw an exception....)
|
|
|
|
*/
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
|
|
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
|
|
|
String sortOrder) {
|
2010-08-12 00:31:45 +00:00
|
|
|
long callingId = Binder.clearCallingIdentity();
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
2010-08-12 00:31:45 +00:00
|
|
|
if (projection == null) {
|
|
|
|
projection =
|
|
|
|
new String[] {
|
2011-02-09 01:50:30 +00:00
|
|
|
Columns._ID,
|
|
|
|
Columns.DATA,
|
2010-08-12 00:31:45 +00:00
|
|
|
};
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-08-12 00:31:45 +00:00
|
|
|
List<String> segments = uri.getPathSegments();
|
|
|
|
String accountId = segments.get(0);
|
|
|
|
String id = segments.get(1);
|
|
|
|
String format = segments.get(2);
|
|
|
|
String name = null;
|
|
|
|
int size = -1;
|
|
|
|
String contentUri = null;
|
|
|
|
|
|
|
|
uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, Long.parseLong(id));
|
|
|
|
Cursor c = getContext().getContentResolver().query(uri, PROJECTION_QUERY,
|
|
|
|
null, null, null);
|
|
|
|
try {
|
|
|
|
if (c.moveToFirst()) {
|
|
|
|
name = c.getString(0);
|
|
|
|
size = c.getInt(1);
|
|
|
|
contentUri = c.getString(2);
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-08-12 00:31:45 +00:00
|
|
|
|
2013-02-06 23:21:15 +00:00
|
|
|
MatrixCursor ret = new MatrixCursorWithCachedColumns(projection);
|
2010-08-12 00:31:45 +00:00
|
|
|
Object[] values = new Object[projection.length];
|
|
|
|
for (int i = 0, count = projection.length; i < count; i++) {
|
|
|
|
String column = projection[i];
|
2011-02-09 01:50:30 +00:00
|
|
|
if (Columns._ID.equals(column)) {
|
2010-08-12 00:31:45 +00:00
|
|
|
values[i] = id;
|
|
|
|
}
|
2011-02-09 01:50:30 +00:00
|
|
|
else if (Columns.DATA.equals(column)) {
|
2010-08-12 00:31:45 +00:00
|
|
|
values[i] = contentUri;
|
|
|
|
}
|
2011-02-09 01:50:30 +00:00
|
|
|
else if (Columns.DISPLAY_NAME.equals(column)) {
|
2010-08-12 00:31:45 +00:00
|
|
|
values[i] = name;
|
|
|
|
}
|
2011-02-09 01:50:30 +00:00
|
|
|
else if (Columns.SIZE.equals(column)) {
|
2010-08-12 00:31:45 +00:00
|
|
|
values[i] = size;
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-08-12 00:31:45 +00:00
|
|
|
ret.addRow(values);
|
|
|
|
return ret;
|
|
|
|
} finally {
|
|
|
|
Binder.restoreCallingIdentity(callingId);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-05-26 04:32:32 +00:00
|
|
|
private static Bitmap createThumbnail(String type, InputStream data) {
|
2009-03-04 03:32:22 +00:00
|
|
|
if(MimeUtility.mimeTypeMatches(type, "image/*")) {
|
|
|
|
return createImageThumbnail(data);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2013-05-26 04:32:32 +00:00
|
|
|
private static Bitmap createImageThumbnail(InputStream data) {
|
2009-03-04 03:32:22 +00:00
|
|
|
try {
|
|
|
|
Bitmap bitmap = BitmapFactory.decodeStream(data);
|
|
|
|
return bitmap;
|
2010-09-21 23:49:13 +00:00
|
|
|
} catch (OutOfMemoryError oome) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG, "createImageThumbnail failed with " + oome.getMessage());
|
2009-03-04 03:32:22 +00:00
|
|
|
return null;
|
2010-09-21 23:49:13 +00:00
|
|
|
} catch (Exception e) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(Logging.LOG_TAG, "createImageThumbnail failed with " + e.getMessage());
|
2009-03-04 03:32:22 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2010-09-21 23:49:13 +00:00
|
|
|
|
2010-08-20 21:56:44 +00:00
|
|
|
/**
|
|
|
|
* Need this to suppress warning in unit tests.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void shutdown() {
|
|
|
|
// Don't call super.shutdown(), which emits a warning...
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|