Implemented AttachmentProvider, removed ContentAttachment.getUri()
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="i2p.bote.android" >
|
||||
<manifest
|
||||
package="i2p.bote.android"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
@@ -76,12 +77,16 @@
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="i2p.bote.android.addressbook.ViewContactActivity"/>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="vnd.android.nfc"
|
||||
|
||||
<data
|
||||
android:host="ext"
|
||||
android:pathPrefix="/i2p.bote:contact"/>
|
||||
android:pathPrefix="/i2p.bote:contact"
|
||||
android:scheme="vnd.android.nfc"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
|
||||
@@ -133,6 +138,14 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="i2p.bote.android.config.SettingsActivity"/>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name=".provider.AttachmentProvider"
|
||||
android:authorities="${applicationId}.attachmentprovider"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -117,8 +117,7 @@ public class EmailListAdapter extends ArrayAdapter<Email> {
|
||||
List<Part> parts = email.getParts();
|
||||
for (Part part : parts) {
|
||||
if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
|
||||
((ImageView) v.findViewById(
|
||||
R.id.email_attachment)).setVisibility(View.VISIBLE);
|
||||
v.findViewById(R.id.email_attachment).setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +339,10 @@ public class EmailListFragment extends AuthenticatedListFragment implements
|
||||
for (int i = (toDelete.size() - 1); i >= 0; i--) {
|
||||
if (toDelete.valueAt(i)) {
|
||||
Email email = (Email) listView.getItemAtPosition(toDelete.keyAt(i));
|
||||
BoteHelper.revokeAttachmentUriPermissions(
|
||||
getActivity(),
|
||||
mFolder.getName(),
|
||||
email);
|
||||
// The Loader will update mAdapter
|
||||
I2PBote.getInstance().deleteEmail(mFolder, email.getMessageID());
|
||||
}
|
||||
|
||||
@@ -444,11 +444,11 @@ public class NewEmailFragment extends Fragment {
|
||||
private void addAttachment(Uri uri) {
|
||||
// Try to create a ContentAttachment using the provided Uri.
|
||||
try {
|
||||
final ContentAttachment attachment = new ContentAttachment(getActivity().getContentResolver(), uri);
|
||||
final ContentAttachment attachment = new ContentAttachment(getActivity(), uri);
|
||||
final View v = getActivity().getLayoutInflater().inflate(R.layout.listitem_attachment, mAttachments, false);
|
||||
v.setTag(attachment);
|
||||
((TextView) v.findViewById(R.id.filename)).setText(attachment.getFileName());
|
||||
((TextView) v.findViewById(R.id.size)).setText(attachment.getHumanReadableSize(getActivity()));
|
||||
((TextView) v.findViewById(R.id.size)).setText(attachment.getHumanReadableSize());
|
||||
v.findViewById(R.id.remove_attachment).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import javax.mail.Address;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Part;
|
||||
|
||||
import i2p.bote.android.provider.AttachmentProvider;
|
||||
import i2p.bote.android.util.BoteHelper;
|
||||
import i2p.bote.android.util.ContentAttachment;
|
||||
import i2p.bote.email.Email;
|
||||
@@ -168,15 +169,17 @@ public class ViewEmailFragment extends Fragment {
|
||||
for (int partIndex=0; partIndex < parts.size(); partIndex++) {
|
||||
Part part = parts.get(partIndex);
|
||||
if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
|
||||
ContentAttachment attachment = new ContentAttachment(part);
|
||||
ContentAttachment attachment = new ContentAttachment(getActivity(), part);
|
||||
|
||||
View a = getActivity().getLayoutInflater().inflate(R.layout.listitem_attachment, attachments, false);
|
||||
((TextView)a.findViewById(R.id.filename)).setText(attachment.getFileName());
|
||||
((TextView)a.findViewById(R.id.size)).setText(attachment.getHumanReadableSize(getActivity()));
|
||||
((TextView)a.findViewById(R.id.size)).setText(attachment.getHumanReadableSize());
|
||||
a.findViewById(R.id.remove_attachment).setVisibility(View.GONE);
|
||||
|
||||
final Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(attachment.getUri());
|
||||
i.setData(AttachmentProvider.getUriForAttachment(mFolderName, mMessageId, partIndex));
|
||||
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||
Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
a.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package i2p.bote.android.provider;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Part;
|
||||
|
||||
import i2p.bote.Util;
|
||||
import i2p.bote.android.BuildConfig;
|
||||
import i2p.bote.android.util.BoteHelper;
|
||||
import i2p.bote.email.Email;
|
||||
import i2p.bote.fileencryption.PasswordException;
|
||||
|
||||
public class AttachmentProvider extends ContentProvider {
|
||||
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".attachmentprovider";
|
||||
|
||||
private static final int RAW_ATTACHMENT = 1;
|
||||
|
||||
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
|
||||
static {
|
||||
sUriMatcher.addURI(AUTHORITY, "*/*/#/RAW", RAW_ATTACHMENT);
|
||||
}
|
||||
|
||||
private final static String[] OPENABLE_PROJECTION = {
|
||||
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
|
||||
|
||||
public static Uri getUriForAttachment(String folderName, String messageId, int partNum) {
|
||||
return new Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority(AUTHORITY)
|
||||
.appendPath(folderName)
|
||||
.appendPath(messageId)
|
||||
.appendPath(Integer.toString(partNum))
|
||||
.appendPath("RAW")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
if (sUriMatcher.match(uri) == UriMatcher.NO_MATCH)
|
||||
throw new IllegalArgumentException("Invalid URI: " + uri);
|
||||
if (projection == null) {
|
||||
projection = OPENABLE_PROJECTION;
|
||||
}
|
||||
|
||||
final MatrixCursor cursor = new MatrixCursor(projection, 1);
|
||||
MatrixCursor.RowBuilder b = cursor.newRow();
|
||||
|
||||
try {
|
||||
Part attachment = getAttachment(uri);
|
||||
if (attachment != null) {
|
||||
for (String col : projection) {
|
||||
switch (col) {
|
||||
case OpenableColumns.DISPLAY_NAME:
|
||||
b.add(attachment.getFileName());
|
||||
break;
|
||||
case OpenableColumns.SIZE:
|
||||
b.add(Util.getPartSize(attachment));
|
||||
break;
|
||||
default:
|
||||
b.add(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (PasswordException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (MessagingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
System.out.println("getType(): URI: " + uri);
|
||||
System.out.println("Match: " + sUriMatcher.match(uri));
|
||||
if (sUriMatcher.match(uri) != UriMatcher.NO_MATCH) {
|
||||
try {
|
||||
Part attachment = getAttachment(uri);
|
||||
if (attachment != null) {
|
||||
System.out.println("Content type: " + attachment.getContentType());
|
||||
return attachment.getContentType();
|
||||
}
|
||||
} catch (PasswordException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (MessagingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
if (sUriMatcher.match(uri) == UriMatcher.NO_MATCH)
|
||||
throw new FileNotFoundException("Invalid URI: " + uri);
|
||||
if (!"r".equals(mode))
|
||||
throw new FileNotFoundException("Attachments can only be read");
|
||||
|
||||
ParcelFileDescriptor[] pipe;
|
||||
try {
|
||||
pipe = ParcelFileDescriptor.createPipe();
|
||||
} catch (IOException e) {
|
||||
Log.e(getClass().getSimpleName(), "Exception opening pipe", e);
|
||||
throw new FileNotFoundException("Could not open pipe for: "
|
||||
+ uri.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
Part attachment = getAttachment(uri);
|
||||
if (attachment == null)
|
||||
throw new FileNotFoundException("Unknown email or attachment for URI " + uri);
|
||||
|
||||
new TransferThread(attachment.getInputStream(),
|
||||
new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])).start();
|
||||
} catch (Exception e) {
|
||||
Log.e(getClass().getSimpleName(), "Exception accessing attachment", e);
|
||||
throw new FileNotFoundException("Exception accessing attachment: " + e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
return pipe[0];
|
||||
}
|
||||
|
||||
static class TransferThread extends Thread {
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
|
||||
TransferThread(InputStream in, OutputStream out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
byte[] buf = new byte[8192];
|
||||
int len;
|
||||
|
||||
try {
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
|
||||
in.close();
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(getClass().getSimpleName(), "Exception transferring file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
// Copied from ContentProvider
|
||||
return uri.buildUpon().appendPath("0").build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private Part getAttachment(Uri uri) throws PasswordException, IOException, MessagingException {
|
||||
List<String> segments = uri.getPathSegments();
|
||||
String folderName = segments.get(0);
|
||||
String messageId = segments.get(1);
|
||||
int partNum = Integer.valueOf(segments.get(2));
|
||||
|
||||
Email email = BoteHelper.getEmail(folderName, messageId);
|
||||
if (email != null) {
|
||||
if (partNum >= 0 && partNum < email.getParts().size())
|
||||
return email.getParts().get(partNum);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -29,8 +31,10 @@ import java.util.List;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Part;
|
||||
|
||||
import i2p.bote.android.R;
|
||||
import i2p.bote.android.provider.AttachmentProvider;
|
||||
import i2p.bote.email.Email;
|
||||
import i2p.bote.email.EmailDestination;
|
||||
import i2p.bote.email.EmailIdentity;
|
||||
@@ -415,4 +419,36 @@ public class BoteHelper extends GeneralHelper {
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to revoke any URI permissions that were granted on an Email's attachments.
|
||||
* This is best-effort; exceptions are silently ignored.
|
||||
*
|
||||
* @param context the Context in which permissions were granted
|
||||
* @param folderName where the Email is
|
||||
* @param email the Email to revoke permissions for
|
||||
*/
|
||||
public static void revokeAttachmentUriPermissions(Context context, String folderName, Email email) {
|
||||
List<Part> parts;
|
||||
try {
|
||||
parts = email.getParts();
|
||||
} catch (Exception e) {
|
||||
// Nothing we can do, abort
|
||||
return;
|
||||
}
|
||||
|
||||
for (Part part : parts) {
|
||||
try {
|
||||
if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
|
||||
Uri uri = AttachmentProvider.getUriForAttachment(folderName,
|
||||
email.getMessageID(), parts.indexOf(part));
|
||||
context.revokeUriPermission(uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
// Ignore and carry on
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,8 @@ import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -23,22 +19,19 @@ import javax.mail.MessagingException;
|
||||
import javax.mail.Part;
|
||||
|
||||
import i2p.bote.Util;
|
||||
import i2p.bote.android.Constants;
|
||||
import i2p.bote.android.R;
|
||||
import i2p.bote.email.Attachment;
|
||||
|
||||
public class ContentAttachment implements Attachment {
|
||||
private ParcelFileDescriptor mAttachmentPFD;
|
||||
private Context mCtx;
|
||||
private String mFileName;
|
||||
private long mSize;
|
||||
private DataHandler mDataHandler;
|
||||
private Uri mUri;
|
||||
|
||||
public ContentAttachment(ContentResolver cr, Uri uri) throws FileNotFoundException {
|
||||
// Get the content resolver instance for this context, and use it
|
||||
// to get a ParcelFileDescriptor for the file.
|
||||
mAttachmentPFD = cr.openFileDescriptor(uri, "r");
|
||||
// If we get to here, the file exists
|
||||
public ContentAttachment(Context context, final Uri uri) throws FileNotFoundException {
|
||||
mCtx = context;
|
||||
// Get the content resolver instance for this context
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
|
||||
Cursor returnCursor = cr.query(
|
||||
uri,
|
||||
@@ -46,18 +39,19 @@ public class ContentAttachment implements Attachment {
|
||||
null, null, null);
|
||||
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
|
||||
returnCursor.moveToFirst();
|
||||
|
||||
if (!returnCursor.moveToFirst())
|
||||
throw new FileNotFoundException();
|
||||
|
||||
mFileName = returnCursor.getString(nameIndex);
|
||||
mSize = returnCursor.getLong(sizeIndex);
|
||||
returnCursor.close();
|
||||
|
||||
// Get a regular file descriptor for the file
|
||||
final FileDescriptor fd = mAttachmentPFD.getFileDescriptor();
|
||||
final String mimeType = cr.getType(uri);
|
||||
mDataHandler = new DataHandler(new DataSource() {
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new FileInputStream(fd);
|
||||
return mCtx.getContentResolver().openInputStream(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,15 +69,14 @@ public class ContentAttachment implements Attachment {
|
||||
return mFileName;
|
||||
}
|
||||
});
|
||||
// mUri is not set here because uri is only usable by us.
|
||||
// Viewing attachments is only allowed once the email has been created.
|
||||
}
|
||||
|
||||
public ContentAttachment(final Part part) throws IOException, MessagingException {
|
||||
public ContentAttachment(Context context, Part part)
|
||||
throws IOException, MessagingException {
|
||||
mCtx = context;
|
||||
mFileName = part.getFileName();
|
||||
mSize = Util.getPartSize(part);
|
||||
mDataHandler = part.getDataHandler();
|
||||
// TODO: Set mUri
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,7 +88,7 @@ public class ContentAttachment implements Attachment {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
public String getHumanReadableSize(Context context) {
|
||||
public String getHumanReadableSize() {
|
||||
int unit = (63-Long.numberOfLeadingZeros(mSize)) / 10; // 0 if totalBytes<1K, 1 if 1K<=totalBytes<1M, etc.
|
||||
double value = (double)mSize / (1<<(10*unit));
|
||||
int formatStr;
|
||||
@@ -109,7 +102,7 @@ public class ContentAttachment implements Attachment {
|
||||
formatter.setMaximumFractionDigits(1);
|
||||
else
|
||||
formatter.setMaximumFractionDigits(0);
|
||||
return context.getString(formatStr, formatter.format(value));
|
||||
return mCtx.getString(formatStr, formatter.format(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,21 +110,8 @@ public class ContentAttachment implements Attachment {
|
||||
return mDataHandler;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clean() {
|
||||
if (mAttachmentPFD == null)
|
||||
return true;
|
||||
|
||||
try {
|
||||
mAttachmentPFD.close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.ANDROID_LOG_TAG, "Can't close ParcelFileDescriptor: <" + mFileName + ">", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user