package com.fsck.k9.provider; import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import timber.log.Timber; import com.fsck.k9.Account; import com.fsck.k9.BuildConfig; 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.mailstore.LocalStore; import com.fsck.k9.mailstore.LocalStore.AttachmentInfo; import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource; import org.openintents.openpgp.util.ParcelFileDescriptorUtil; /** * A simple ContentProvider that allows file access to attachments. */ public class AttachmentProvider extends ContentProvider { private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".attachmentprovider"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); 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(String accountUuid, long id) { return CONTENT_URI.buildUpon() .appendPath(accountUuid) .appendPath(Long.toString(id)) .build(); } @Override public boolean onCreate() { return true; } @Override public String getType(@NonNull Uri uri) { List<String> segments = uri.getPathSegments(); String accountUuid = segments.get(0); String id = segments.get(1); String mimeType = (segments.size() < 3) ? null : segments.get(2); return getType(accountUuid, id, mimeType); } @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { List<String> segments = uri.getPathSegments(); String accountUuid = segments.get(0); String attachmentId = segments.get(1); ParcelFileDescriptor parcelFileDescriptor = openAttachment(accountUuid, attachmentId); if (parcelFileDescriptor == null) { throw new FileNotFoundException("Attachment missing or cannot be opened!"); } return parcelFileDescriptor; } @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection; List<String> segments = uri.getPathSegments(); String accountUuid = segments.get(0); String id = segments.get(1); final AttachmentInfo attachmentInfo; try { final Account account = Preferences.getPreferences(getContext()).getAccount(accountUuid); attachmentInfo = LocalStore.getInstance(account, getContext()).getAttachmentInfo(id); } catch (MessagingException e) { Timber.e(e, "Unable to retrieve attachment info from local store for ID: %s", id); return null; } if (attachmentInfo == null) { Timber.d("No attachment info for ID: %s", id); 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(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException(); } @Override public int delete(@NonNull Uri uri, String arg1, String[] arg2) { throw new UnsupportedOperationException(); } @Override public Uri insert(@NonNull Uri uri, ContentValues values) { throw new UnsupportedOperationException(); } private String getType(String accountUuid, String id, String mimeType) { String type; final Account account = Preferences.getPreferences(getContext()).getAccount(accountUuid); try { final LocalStore localStore = LocalStore.getInstance(account, getContext()); AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id); if (mimeType != null) { type = mimeType; } else { type = attachmentInfo.type; } } catch (MessagingException e) { Timber.e(e, "Unable to retrieve LocalStore for %s", account); type = MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE; } return type; } @Nullable private ParcelFileDescriptor openAttachment(String accountUuid, String attachmentId) { try { OpenPgpDataSource openPgpDataSource = getAttachmentDataSource(accountUuid, attachmentId); if (openPgpDataSource == null) { Timber.e("Error getting data source for attachment (part doesn't exist?)"); return null; } return openPgpDataSource.startPumpThread(); } catch (MessagingException e) { Timber.e(e, "Error getting InputStream for attachment"); return null; } catch (IOException e) { Timber.e(e, "Error creating ParcelFileDescriptor"); return null; } } @Nullable private OpenPgpDataSource getAttachmentDataSource(String accountUuid, String attachmentId) throws MessagingException { final Account account = Preferences.getPreferences(getContext()).getAccount(accountUuid); LocalStore localStore = LocalStore.getInstance(account, getContext()); return localStore.getAttachmentDataSource(attachmentId); } }