mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-12 04:25:08 -05:00
aae734c175
This happens regularly when AttachmentView tries to get a thumbnail for attachments that haven't been downloaded yet.
318 lines
11 KiB
Java
318 lines
11 KiB
Java
package com.fsck.k9.provider;
|
|
|
|
import android.content.ContentProvider;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.database.Cursor;
|
|
import android.database.MatrixCursor;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapFactory;
|
|
import android.net.Uri;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.util.Log;
|
|
import com.fsck.k9.Account;
|
|
import com.fsck.k9.K9;
|
|
import com.fsck.k9.Preferences;
|
|
import com.fsck.k9.mail.MessagingException;
|
|
import com.fsck.k9.mail.internet.MimeUtility;
|
|
import com.fsck.k9.mail.store.LocalStore;
|
|
import com.fsck.k9.mail.store.LocalStore.AttachmentInfo;
|
|
import com.fsck.k9.mail.store.StorageManager;
|
|
|
|
import java.io.*;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* A simple ContentProvider that allows file access to attachments.
|
|
*
|
|
* <p>
|
|
* Warning! We make heavy assumptions about the Uris used by the {@link LocalStore} for an
|
|
* {@link Account} here.
|
|
* </p>
|
|
*/
|
|
public class AttachmentProvider extends ContentProvider {
|
|
public static final Uri CONTENT_URI = Uri.parse("content://com.fsck.k9.attachmentprovider");
|
|
|
|
private static final String FORMAT_RAW = "RAW";
|
|
private static final String FORMAT_VIEW = "VIEW";
|
|
private static final String FORMAT_THUMBNAIL = "THUMBNAIL";
|
|
|
|
private static final String[] DEFAULT_PROJECTION = new String[] {
|
|
AttachmentProviderColumns._ID,
|
|
AttachmentProviderColumns.DATA,
|
|
};
|
|
|
|
public static class AttachmentProviderColumns {
|
|
public static final String _ID = "_id";
|
|
public static final String DATA = "_data";
|
|
public static final String DISPLAY_NAME = "_display_name";
|
|
public static final String SIZE = "_size";
|
|
}
|
|
|
|
|
|
public static Uri getAttachmentUri(Account account, long id) {
|
|
return getAttachmentUri(account.getUuid(), id, true);
|
|
}
|
|
|
|
public static Uri getAttachmentUriForViewing(Account account, long id) {
|
|
return getAttachmentUri(account.getUuid(), id, false);
|
|
}
|
|
|
|
public static Uri getAttachmentThumbnailUri(Account account, long id, int width, int height) {
|
|
return CONTENT_URI.buildUpon()
|
|
.appendPath(account.getUuid())
|
|
.appendPath(Long.toString(id))
|
|
.appendPath(FORMAT_THUMBNAIL)
|
|
.appendPath(Integer.toString(width))
|
|
.appendPath(Integer.toString(height))
|
|
.build();
|
|
}
|
|
|
|
private static Uri getAttachmentUri(String db, long id, boolean raw) {
|
|
return CONTENT_URI.buildUpon()
|
|
.appendPath(db)
|
|
.appendPath(Long.toString(id))
|
|
.appendPath(raw ? FORMAT_RAW : FORMAT_VIEW)
|
|
.build();
|
|
}
|
|
|
|
public static void clear(Context context) {
|
|
/*
|
|
* 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.
|
|
*/
|
|
File[] files = context.getCacheDir().listFiles();
|
|
for (File file : files) {
|
|
try {
|
|
if (K9.DEBUG) {
|
|
Log.d(K9.LOG_TAG, "Deleting file " + file.getCanonicalPath());
|
|
}
|
|
} catch (IOException ioe) { /* No need to log failure to log */ }
|
|
file.delete();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete the thumbnail of an attachment.
|
|
*
|
|
* @param context
|
|
* The application context.
|
|
* @param accountUuid
|
|
* The UUID of the account the attachment belongs to.
|
|
* @param attachmentId
|
|
* The ID of the attachment the thumbnail was created for.
|
|
*/
|
|
public static void deleteThumbnail(Context context, String accountUuid, String attachmentId) {
|
|
File file = getThumbnailFile(context, accountUuid, attachmentId);
|
|
if (file.exists()) {
|
|
file.delete();
|
|
}
|
|
}
|
|
|
|
private static File getThumbnailFile(Context context, String accountUuid,
|
|
String attachmentId) {
|
|
String filename = "thmb_" + accountUuid + "_" + attachmentId + ".tmp";
|
|
File dir = context.getCacheDir();
|
|
return new File(dir, filename);
|
|
}
|
|
|
|
|
|
@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.
|
|
*/
|
|
final File cacheDir = getContext().getCacheDir();
|
|
if (cacheDir == null) {
|
|
return true;
|
|
}
|
|
File[] files = cacheDir.listFiles();
|
|
if (files == null) {
|
|
return true;
|
|
}
|
|
for (File file : files) {
|
|
if (file.getName().endsWith(".tmp")) {
|
|
file.delete();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public String getType(Uri uri) {
|
|
List<String> segments = uri.getPathSegments();
|
|
String dbName = segments.get(0);
|
|
String id = segments.get(1);
|
|
String format = segments.get(2);
|
|
|
|
return getType(dbName, id, format);
|
|
}
|
|
|
|
@Override
|
|
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
|
File file;
|
|
|
|
List<String> segments = uri.getPathSegments();
|
|
String accountUuid = segments.get(0);
|
|
String attachmentId = segments.get(1);
|
|
String format = segments.get(2);
|
|
|
|
if (FORMAT_THUMBNAIL.equals(format)) {
|
|
int width = Integer.parseInt(segments.get(3));
|
|
int height = Integer.parseInt(segments.get(4));
|
|
|
|
file = getThumbnailFile(getContext(), accountUuid, attachmentId);
|
|
if (!file.exists()) {
|
|
String type = getType(accountUuid, attachmentId, FORMAT_VIEW);
|
|
try {
|
|
FileInputStream in = new FileInputStream(getFile(accountUuid, attachmentId));
|
|
try {
|
|
Bitmap thumbnail = createThumbnail(type, in);
|
|
if (thumbnail != null) {
|
|
thumbnail = Bitmap.createScaledBitmap(thumbnail, width, height, true);
|
|
FileOutputStream out = new FileOutputStream(file);
|
|
try {
|
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
|
|
} finally {
|
|
out.close();
|
|
}
|
|
}
|
|
} finally {
|
|
try { in.close(); } catch (Throwable ignore) { /* ignore */ }
|
|
}
|
|
} catch (IOException ioe) {
|
|
return null;
|
|
}
|
|
}
|
|
} else {
|
|
file = getFile(accountUuid, attachmentId);
|
|
}
|
|
|
|
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
}
|
|
|
|
@Override
|
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
|
String sortOrder) {
|
|
|
|
String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection;
|
|
|
|
List<String> segments = uri.getPathSegments();
|
|
String dbName = segments.get(0);
|
|
String id = segments.get(1);
|
|
|
|
// Versions of K-9 before 3.400 had a database name here, not an
|
|
// account UID, so implement a bit of backcompat
|
|
if (dbName.endsWith(".db")) {
|
|
dbName = dbName.substring(0, dbName.length() - 3);
|
|
}
|
|
|
|
final AttachmentInfo attachmentInfo;
|
|
try {
|
|
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
|
attachmentInfo = LocalStore.getLocalInstance(account, K9.app).getAttachmentInfo(id);
|
|
} catch (MessagingException e) {
|
|
Log.e(K9.LOG_TAG, "Unable to retrieve attachment info from local store for ID: " + id, e);
|
|
return null;
|
|
}
|
|
|
|
MatrixCursor ret = new MatrixCursor(columnNames);
|
|
Object[] values = new Object[columnNames.length];
|
|
for (int i = 0, count = columnNames.length; i < count; i++) {
|
|
String column = columnNames[i];
|
|
if (AttachmentProviderColumns._ID.equals(column)) {
|
|
values[i] = id;
|
|
} else if (AttachmentProviderColumns.DATA.equals(column)) {
|
|
values[i] = uri.toString();
|
|
} else if (AttachmentProviderColumns.DISPLAY_NAME.equals(column)) {
|
|
values[i] = attachmentInfo.name;
|
|
} else if (AttachmentProviderColumns.SIZE.equals(column)) {
|
|
values[i] = attachmentInfo.size;
|
|
}
|
|
}
|
|
ret.addRow(values);
|
|
return ret;
|
|
}
|
|
|
|
@Override
|
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public int delete(Uri uri, String arg1, String[] arg2) {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public Uri insert(Uri uri, ContentValues values) {
|
|
return null;
|
|
}
|
|
|
|
private String getType(String dbName, String id, String format) {
|
|
String type;
|
|
if (FORMAT_THUMBNAIL.equals(format)) {
|
|
type = "image/png";
|
|
} else {
|
|
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
|
|
|
try {
|
|
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
|
|
|
|
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
|
|
if (FORMAT_VIEW.equals(format)) {
|
|
type = MimeUtility.getMimeTypeForViewing(attachmentInfo.type, attachmentInfo.name);
|
|
} else {
|
|
// When accessing the "raw" message we deliver the original MIME type.
|
|
type = attachmentInfo.type;
|
|
}
|
|
} catch (MessagingException e) {
|
|
Log.e(K9.LOG_TAG, "Unable to retrieve LocalStore for " + account, e);
|
|
type = null;
|
|
}
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
private File getFile(String dbName, String id) throws FileNotFoundException {
|
|
Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
|
|
|
File attachmentsDir = StorageManager.getInstance(K9.app).getAttachmentDirectory(dbName,
|
|
account.getLocalStorageProviderId());
|
|
|
|
File file = new File(attachmentsDir, id);
|
|
if (!file.exists()) {
|
|
throw new FileNotFoundException(file.getAbsolutePath());
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
private Bitmap createThumbnail(String type, InputStream data) {
|
|
if (MimeUtility.mimeTypeMatches(type, "image/*")) {
|
|
return createImageThumbnail(data);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private Bitmap createImageThumbnail(InputStream data) {
|
|
try {
|
|
Bitmap bitmap = BitmapFactory.decodeStream(data);
|
|
return bitmap;
|
|
} catch (OutOfMemoryError oome) {
|
|
/*
|
|
* Improperly downloaded images, corrupt bitmaps and the like can commonly
|
|
* cause OOME due to invalid allocation sizes. We're happy with a null bitmap in
|
|
* that case. If the system is really out of memory we'll know about it soon
|
|
* enough.
|
|
*/
|
|
return null;
|
|
} catch (Exception e) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|