Refactoring

pull/156/head
M66B 5 years ago
parent b9408f26ce
commit ce0e2ba5f8

@ -147,10 +147,12 @@ abstract class ActivityBilling extends ActivityBase implements PurchasesUpdatedL
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_PURCHASE.equals(intent.getAction()))
onPurchase(intent);
else if (ACTION_ACTIVATE_PRO.equals(intent.getAction()))
onActivatePro(intent);
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
if (ACTION_PURCHASE.equals(intent.getAction()))
onPurchase(intent);
else if (ACTION_ACTIVATE_PRO.equals(intent.getAction()))
onActivatePro(intent);
}
}
};

@ -245,8 +245,10 @@ public class ActivityCompose extends ActivityBilling implements FragmentManager.
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_SHOW_PRO.equals(intent.getAction()))
onShowPro(intent);
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
if (ACTION_SHOW_PRO.equals(intent.getAction()))
onShowPro(intent);
}
}
};

@ -1044,19 +1044,21 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_QUICK_SETUP.equals(action))
onViewQuickSetup(intent);
else if (ACTION_VIEW_ACCOUNTS.equals(action))
onViewAccounts(intent);
else if (ACTION_VIEW_IDENTITIES.equals(action))
onViewIdentities(intent);
else if (ACTION_EDIT_ACCOUNT.equals(action))
onEditAccount(intent);
else if (ACTION_EDIT_IDENTITY.equals(action))
onEditIdentity(intent);
else if (ACTION_SHOW_PRO.equals(action))
onShowPro(intent);
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
String action = intent.getAction();
if (ACTION_QUICK_SETUP.equals(action))
onViewQuickSetup(intent);
else if (ACTION_VIEW_ACCOUNTS.equals(action))
onViewAccounts(intent);
else if (ACTION_VIEW_IDENTITIES.equals(action))
onViewIdentities(intent);
else if (ACTION_EDIT_ACCOUNT.equals(action))
onEditAccount(intent);
else if (ACTION_EDIT_IDENTITY.equals(action))
onEditIdentity(intent);
else if (ACTION_SHOW_PRO.equals(action))
onShowPro(intent);
}
}
};
}

@ -19,14 +19,11 @@ package eu.faircode.email;
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
@ -35,7 +32,6 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintManager;
@ -53,7 +49,6 @@ import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.constraintlayout.widget.Group;
import androidx.documentfile.provider.DocumentFile;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@ -71,37 +66,20 @@ import com.android.colorpicker.ColorPickerSwatch;
import org.json.JSONArray;
import org.json.JSONObject;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import javax.net.ssl.HttpsURLConnection;
import static org.openintents.openpgp.OpenPgpSignatureResult.RESULT_NO_SIGNATURE;
import static org.openintents.openpgp.OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED;
public class ActivityView extends ActivityBilling implements FragmentManager.OnBackStackChangedListener {
private String startup;
@ -118,11 +96,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
private boolean exit = false;
private long message = -1;
private long attachment = -1;
private WebView printWebView = null;
private OpenPgpServiceConnection pgpService;
static final int REQUEST_UNIFIED = 1;
static final int REQUEST_WHY = 2;
@ -130,35 +104,24 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
static final int REQUEST_OUTBOX = 4;
static final int REQUEST_ERROR = 5;
static final int REQUEST_RAW = 1;
static final int REQUEST_ATTACHMENT = 2;
static final int REQUEST_ATTACHMENTS = 3;
static final int REQUEST_DECRYPT = 4;
static final int REQUEST_SENDER = 5;
static final int REQUEST_RECIPIENT = 6;
static final int REQUEST_SENDER = 1;
static final int REQUEST_RECIPIENT = 2;
static final String ACTION_VIEW_FOLDERS = BuildConfig.APPLICATION_ID + ".VIEW_FOLDERS";
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
static final String ACTION_SEARCH = BuildConfig.APPLICATION_ID + ".SEARCH";
static final String ACTION_VIEW_THREAD = BuildConfig.APPLICATION_ID + ".VIEW_THREAD";
static final String ACTION_STORE_RAW = BuildConfig.APPLICATION_ID + ".STORE_RAW";
static final String ACTION_EDIT_FOLDER = BuildConfig.APPLICATION_ID + ".EDIT_FOLDER";
static final String ACTION_EDIT_ANSWERS = BuildConfig.APPLICATION_ID + ".EDIT_ANSWERS";
static final String ACTION_EDIT_ANSWER = BuildConfig.APPLICATION_ID + ".EDIT_ANSWER";
static final String ACTION_EDIT_RULES = BuildConfig.APPLICATION_ID + ".EDIT_RULES";
static final String ACTION_EDIT_RULE = BuildConfig.APPLICATION_ID + ".EDIT_RULE";
static final String ACTION_STORE_ATTACHMENT = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENT";
static final String ACTION_STORE_ATTACHMENTS = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENTS";
static final String ACTION_COLOR = BuildConfig.APPLICATION_ID + ".COLOR";
static final String ACTION_PRINT = BuildConfig.APPLICATION_ID + ".PRINT";
static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT";
static final String ACTION_SHOW_PRO = BuildConfig.APPLICATION_ID + ".SHOW_PRO";
static final long UPDATE_INTERVAL = (BuildConfig.BETA_RELEASE ? 4 : 12) * 3600 * 1000L; // milliseconds
private static final String PGP_BEGIN_MESSAGE = "-----BEGIN PGP MESSAGE-----";
private static final String PGP_END_MESSAGE = "-----END PGP MESSAGE-----";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -422,9 +385,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
checkFirst();
checkCrash();
pgpService = new OpenPgpServiceConnection(this, "org.sufficientlysecure.keychain");
pgpService.bindToService();
Shortcuts.update(this, this);
}
@ -544,23 +504,16 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
iff.addAction(ACTION_VIEW_MESSAGES);
iff.addAction(ACTION_SEARCH);
iff.addAction(ACTION_VIEW_THREAD);
iff.addAction(ACTION_STORE_RAW);
iff.addAction(ACTION_EDIT_FOLDER);
iff.addAction(ACTION_EDIT_ANSWERS);
iff.addAction(ACTION_EDIT_ANSWER);
iff.addAction(ACTION_EDIT_RULES);
iff.addAction(ACTION_EDIT_RULE);
iff.addAction(ACTION_STORE_ATTACHMENT);
iff.addAction(ACTION_STORE_ATTACHMENTS);
iff.addAction(ACTION_COLOR);
iff.addAction(ACTION_PRINT);
iff.addAction(ACTION_DECRYPT);
iff.addAction(ACTION_SHOW_PRO);
lbm.registerReceiver(receiver, iff);
if (!pgpService.isBound())
pgpService.bindToService();
checkUpdate(false);
}
@ -571,14 +524,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
lbm.unregisterReceiver(receiver);
}
@Override
protected void onDestroy() {
if (pgpService != null)
pgpService.unbindFromService();
super.onDestroy();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@ -1019,8 +964,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
onSearchMessages(intent);
else if (ACTION_VIEW_THREAD.equals(action))
onViewThread(intent);
else if (ACTION_STORE_RAW.equals(action))
onStoreRaw(intent);
else if (ACTION_EDIT_FOLDER.equals(action))
onEditFolder(intent);
else if (ACTION_EDIT_ANSWERS.equals(action))
@ -1031,16 +974,10 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
onEditRules(intent);
else if (ACTION_EDIT_RULE.equals(action))
onEditRule(intent);
else if (ACTION_STORE_ATTACHMENT.equals(action))
onStoreAttachment(intent);
else if (ACTION_STORE_ATTACHMENTS.equals(action))
onStoreAttachments(intent);
else if (ACTION_COLOR.equals(action))
onColor(intent);
else if (ACTION_PRINT.equals(action))
onPrint(intent);
else if (ACTION_DECRYPT.equals(action))
onDecrypt(intent);
else if (ACTION_SHOW_PRO.equals(action))
onShowPro(intent);
}
@ -1105,18 +1042,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
fragmentTransaction.commit();
}
private void onStoreRaw(Intent intent) {
message = intent.getLongExtra("id", -1);
Intent create = new Intent(Intent.ACTION_CREATE_DOCUMENT);
create.addCategory(Intent.CATEGORY_OPENABLE);
create.setType("*/*");
create.putExtra(Intent.EXTRA_TITLE, "email.eml");
if (create.resolveActivity(getPackageManager()) == null)
Toast.makeText(ActivityView.this, R.string.title_no_saf, Toast.LENGTH_LONG).show();
else
startActivityForResult(Helper.getChooser(this, create), REQUEST_RAW);
}
private void onEditFolder(Intent intent) {
FragmentFolder fragment = new FragmentFolder();
fragment.setArguments(intent.getExtras());
@ -1155,28 +1080,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
fragmentTransaction.commit();
}
private void onStoreAttachment(Intent intent) {
attachment = intent.getLongExtra("id", -1);
Intent create = new Intent(Intent.ACTION_CREATE_DOCUMENT);
create.addCategory(Intent.CATEGORY_OPENABLE);
create.setType(intent.getStringExtra("type"));
create.putExtra(Intent.EXTRA_TITLE, intent.getStringExtra("name"));
if (create.resolveActivity(getPackageManager()) == null)
Toast.makeText(ActivityView.this, R.string.title_no_saf, Toast.LENGTH_LONG).show();
else
startActivityForResult(Helper.getChooser(this, create), REQUEST_ATTACHMENT);
}
private void onStoreAttachments(Intent intent) {
message = intent.getLongExtra("id", -1);
Intent tree = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
//tree.putExtra("android.content.extra.SHOW_ADVANCED", true);
if (tree.resolveActivity(getPackageManager()) == null)
Toast.makeText(ActivityView.this, R.string.title_no_saf, Toast.LENGTH_LONG).show();
else
startActivityForResult(Helper.getChooser(this, tree), REQUEST_ATTACHMENTS);
}
private void onColor(final Intent intent) {
int color = intent.getIntExtra("color", -1);
int[] colors = getResources().getIntArray(R.array.colorPicker);
@ -1293,21 +1196,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
}.execute(ActivityView.this, args, "message:print");
}
private void onDecrypt(Intent intent) {
if (!Helper.isPro(this)) {
onShowPro(intent);
return;
}
if (pgpService.isBound()) {
Intent data = new Intent();
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
decrypt(data, intent.getLongExtra("id", -1));
} else
Toast.makeText(ActivityView.this, R.string.title_no_openpgp, Toast.LENGTH_LONG).show();
}
private void onShowPro(Intent intent) {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))
getSupportFragmentManager().popBackStack("pro", FragmentManager.POP_BACK_STACK_INCLUSIVE);
@ -1316,423 +1204,4 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
fragmentTransaction.replace(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
fragmentTransaction.commit();
}
private void decrypt(Intent data, long id) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putParcelable("data", data);
new SimpleTask<PendingIntent>() {
@Override
protected PendingIntent onExecute(Context context, Bundle args) throws Throwable {
// Get arguments
long id = args.getLong("id");
Intent data = args.getParcelable("data");
DB db = DB.getInstance(context);
boolean inline = false;
InputStream encrypted = null;
// Find encrypted data
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
for (EntityAttachment attachment : attachments)
if (EntityAttachment.PGP_MESSAGE.equals(attachment.encryption)) {
if (!attachment.available)
throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing));
File file = attachment.getFile(context);
encrypted = new BufferedInputStream(new FileInputStream(file));
break;
}
if (encrypted == null) {
EntityMessage message = db.message().getMessage(id);
if (message != null && message.content) {
File file = message.getFile(context);
if (file.exists()) {
// https://tools.ietf.org/html/rfc4880#section-6.2
String body = Helper.readText(file);
int begin = body.indexOf(PGP_BEGIN_MESSAGE);
int end = body.indexOf(PGP_END_MESSAGE);
if (begin >= 0 && begin < end) {
String section = body.substring(begin, end + PGP_END_MESSAGE.length());
String[] lines = section.split("<br />");
List<String> disarmored = new ArrayList<>();
for (String line : lines)
if (!TextUtils.isEmpty(line) && !line.contains(": "))
disarmored.add(line);
section = TextUtils.join("\n\r", disarmored);
inline = true;
encrypted = new ByteArrayInputStream(section.getBytes());
}
}
}
}
if (encrypted == null)
throw new IllegalArgumentException(context.getString(R.string.title_not_encrypted));
ByteArrayOutputStream decrypted = new ByteArrayOutputStream();
// Decrypt message
OpenPgpApi api = new OpenPgpApi(context, pgpService.getService());
Intent result = api.executeApi(data, encrypted, decrypted);
Log.i("PGP result=" + result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR));
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
case OpenPgpApi.RESULT_CODE_SUCCESS:
if (inline) {
try {
db.beginTransaction();
// Write decrypted body
EntityMessage m = db.message().getMessage(id);
Helper.writeText(m.getFile(context), decrypted.toString());
db.message().setMessageStored(id, new Date().getTime());
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} else {
// Decode message
Properties props = MessageHelper.getSessionProperties(
ConnectionHelper.AUTH_TYPE_PASSWORD, null, false);
Session isession = Session.getInstance(props, null);
ByteArrayInputStream is = new ByteArrayInputStream(decrypted.toByteArray());
MimeMessage imessage = new MimeMessage(isession, is);
MessageHelper helper = new MessageHelper(imessage);
MessageHelper.MessageParts parts = helper.getMessageParts();
try {
db.beginTransaction();
// Write decrypted body
EntityMessage m = db.message().getMessage(id);
Helper.writeText(m.getFile(context), parts.getHtml(context));
// Remove previously decrypted attachments
for (EntityAttachment local : attachments)
if (local.encryption == null)
db.attachment().deleteAttachment(local.id);
int sequence = db.attachment().getAttachmentSequence(id);
// Add decrypted attachments
List<EntityAttachment> remotes = parts.getAttachments();
for (int index = 0; index < remotes.size(); index++) {
EntityAttachment remote = remotes.get(index);
remote.message = id;
remote.sequence = ++sequence;
remote.id = db.attachment().insertAttachment(remote);
try {
parts.downloadAttachment(context, index, remote.id, remote.name);
} catch (Throwable ex) {
Log.e(ex);
}
}
db.message().setMessageStored(id, new Date().getTime());
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
// Check signature status
OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
int sresult = (sigResult == null ? RESULT_NO_SIGNATURE : sigResult.getResult());
int resid;
if (sresult == RESULT_NO_SIGNATURE)
resid = R.string.title_signature_none;
else if (sresult == RESULT_VALID_KEY_CONFIRMED)
resid = R.string.title_signature_valid;
else
resid = R.string.title_signature_invalid;
Toast.makeText(ActivityView.this, resid, Toast.LENGTH_LONG).show();
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
message = id;
return result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
throw new IllegalArgumentException(error.getMessage());
}
return null;
}
@Override
protected void onExecuted(Bundle args, PendingIntent pi) {
if (pi != null)
try {
Log.i("PGP executing pi=" + pi);
startIntentSenderForResult(
pi.getIntentSender(),
ActivityView.REQUEST_DECRYPT,
null, 0, 0, 0, null);
} catch (IntentSender.SendIntentException ex) {
Log.e(ex);
Helper.unexpectedError(ActivityView.this, ActivityView.this, ex);
}
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException)
Toast.makeText(ActivityView.this, ex.getMessage(), Toast.LENGTH_LONG).show();
else
Helper.unexpectedError(ActivityView.this, ActivityView.this, ex);
}
}.execute(ActivityView.this, args, "decrypt");
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK)
if (requestCode == REQUEST_RAW) {
if (data != null)
saveRaw(data);
} else if (requestCode == REQUEST_ATTACHMENT) {
if (data != null)
saveAttachment(data);
} else if (requestCode == REQUEST_ATTACHMENTS) {
if (data != null)
saveAttachments(data);
} else if (requestCode == REQUEST_DECRYPT) {
if (data != null)
decrypt(data, message);
}
super.onActivityResult(requestCode, resultCode, data);
}
private void saveRaw(Intent data) {
Bundle args = new Bundle();
args.putLong("id", message);
args.putParcelable("uri", data.getData());
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
Uri uri = args.getParcelable("uri");
if ("file".equals(uri.getScheme())) {
Log.w("Save raw uri=" + uri);
throw new IllegalArgumentException(context.getString(R.string.title_no_stream));
}
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
if (message == null)
throw new FileNotFoundException();
File file = message.getRawFile(context);
Log.i("Raw file=" + file);
ParcelFileDescriptor pfd = null;
OutputStream os = null;
InputStream is = null;
try {
pfd = context.getContentResolver().openFileDescriptor(uri, "w");
os = new FileOutputStream(pfd.getFileDescriptor());
is = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[MessageHelper.ATTACHMENT_BUFFER_SIZE];
int read;
while ((read = is.read(buffer)) != -1)
os.write(buffer, 0, read);
} finally {
try {
if (pfd != null)
pfd.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (os != null)
os.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (is != null)
is.close();
} catch (Throwable ex) {
Log.w(ex);
}
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
Toast.makeText(ActivityView.this, R.string.title_raw_saved, Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException)
Toast.makeText(ActivityView.this, ex.getMessage(), Toast.LENGTH_LONG).show();
else
Helper.unexpectedError(ActivityView.this, ActivityView.this, ex);
}
}.execute(this, args, "raw:save");
}
private void saveAttachment(Intent data) {
Bundle args = new Bundle();
args.putLong("id", attachment);
args.putParcelable("uri", data.getData());
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
Uri uri = args.getParcelable("uri");
if ("file".equals(uri.getScheme())) {
Log.w("Save attachment uri=" + uri);
throw new IllegalArgumentException(context.getString(R.string.title_no_stream));
}
DB db = DB.getInstance(context);
EntityAttachment attachment = db.attachment().getAttachment(id);
if (attachment == null)
return null;
File file = attachment.getFile(context);
ParcelFileDescriptor pfd = null;
OutputStream os = null;
InputStream is = null;
try {
pfd = context.getContentResolver().openFileDescriptor(uri, "w");
os = new FileOutputStream(pfd.getFileDescriptor());
is = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[MessageHelper.ATTACHMENT_BUFFER_SIZE];
int read;
while ((read = is.read(buffer)) != -1)
os.write(buffer, 0, read);
} finally {
try {
if (pfd != null)
pfd.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (os != null)
os.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (is != null)
is.close();
} catch (Throwable ex) {
Log.w(ex);
}
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
Toast.makeText(ActivityView.this, R.string.title_attachment_saved, Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException)
Toast.makeText(ActivityView.this, ex.getMessage(), Toast.LENGTH_LONG).show();
else
Helper.unexpectedError(ActivityView.this, ActivityView.this, ex);
}
}.execute(this, args, "attachment:save");
}
private void saveAttachments(Intent data) {
Bundle args = new Bundle();
args.putLong("id", message);
args.putParcelable("uri", data.getData());
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
Uri uri = args.getParcelable("uri");
DB db = DB.getInstance(context);
DocumentFile tree = DocumentFile.fromTreeUri(context, uri);
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
for (EntityAttachment attachment : attachments) {
File file = attachment.getFile(context);
String name = Helper.sanitizeFilename(attachment.name);
if (TextUtils.isEmpty(name))
name = Long.toString(attachment.id);
DocumentFile document = tree.createFile(attachment.type, name);
if (document == null)
throw new FileNotFoundException(uri + ":" + name);
ParcelFileDescriptor pfd = null;
OutputStream os = null;
InputStream is = null;
try {
pfd = context.getContentResolver().openFileDescriptor(document.getUri(), "w");
os = new FileOutputStream(pfd.getFileDescriptor());
is = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[MessageHelper.ATTACHMENT_BUFFER_SIZE];
int read;
while ((read = is.read(buffer)) != -1)
os.write(buffer, 0, read);
} finally {
try {
if (pfd != null)
pfd.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (os != null)
os.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (is != null)
is.close();
} catch (Throwable ex) {
Log.w(ex);
}
}
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
Toast.makeText(ActivityView.this, R.string.title_attachments_saved, Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(ActivityView.this, ActivityView.this, ex);
}
}.execute(this, args, "attachments:save");
}
}

@ -187,7 +187,7 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
private void onSave(EntityAttachment attachment) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_STORE_ATTACHMENT)
new Intent(FragmentMessages.ACTION_STORE_ATTACHMENT)
.putExtra("id", attachment.id)
.putExtra("name", Helper.sanitizeFilename(attachment.name))
.putExtra("type", attachment.type));

@ -1693,7 +1693,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private void onSaveAttachments(TupleMessageEx message) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_STORE_ATTACHMENTS)
new Intent(FragmentMessages.ACTION_STORE_ATTACHMENTS)
.putExtra("id", message.id));
}
@ -2430,7 +2430,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private void onMenuDecrypt(ActionData data) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_DECRYPT)
new Intent(FragmentMessages.ACTION_DECRYPT)
.putExtra("id", data.message.id));
}
@ -2788,7 +2788,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
} else {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_STORE_RAW)
new Intent(FragmentMessages.ACTION_STORE_RAW)
.putExtra("id", data.message.id));
}
}

@ -21,11 +21,13 @@ package eu.faircode.email;
import android.app.Activity;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
@ -40,6 +42,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.text.Spanned;
import android.text.TextUtils;
@ -69,6 +72,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.SearchView;
import androidx.constraintlayout.widget.Group;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
@ -95,6 +99,20 @@ import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.Collator;
import java.text.NumberFormat;
import java.util.ArrayList;
@ -108,11 +126,17 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.DateUtils.FORMAT_SHOW_WEEKDAY;
import static org.openintents.openpgp.OpenPgpSignatureResult.RESULT_NO_SIGNATURE;
import static org.openintents.openpgp.OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED;
public class FragmentMessages extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener {
private ViewGroup view;
@ -147,6 +171,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
private String query;
private boolean pane;
private long message = -1;
private long attachment = -1;
private OpenPgpServiceConnection pgpService;
private boolean date;
private boolean threading;
private boolean pull;
@ -194,6 +222,19 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
private static final int UNDO_TIMEOUT = 5000; // milliseconds
private static final int SWIPE_DISABLE_SELECT_DURATION = 1500; // milliseconds
static final int REQUEST_RAW = 1;
static final int REQUEST_ATTACHMENT = 2;
static final int REQUEST_ATTACHMENTS = 3;
static final int REQUEST_DECRYPT = 4;
static final String ACTION_STORE_RAW = BuildConfig.APPLICATION_ID + ".STORE_RAW";
static final String ACTION_STORE_ATTACHMENT = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENT";
static final String ACTION_STORE_ATTACHMENTS = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENTS";
static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT";
private static final String PGP_BEGIN_MESSAGE = "-----BEGIN PGP MESSAGE-----";
private static final String PGP_END_MESSAGE = "-----END PGP MESSAGE-----";
private static final List<String> DUPLICATE_ORDER = Collections.unmodifiableList(Arrays.asList(
EntityFolder.INBOX,
EntityFolder.OUTBOX,
@ -770,10 +811,13 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
updateSwipeRefresh();
pgpService = new OpenPgpServiceConnection(getContext(), "org.sufficientlysecure.keychain");
pgpService.bindToService();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
IntentFilter iff = new IntentFilter();
iff.addAction(SimpleTask.ACTION_TASK_COUNT);
lbm.registerReceiver(receiver, iff);
lbm.registerReceiver(creceiver, iff);
return view;
}
@ -781,12 +825,15 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
@Override
public void onDestroyView() {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
lbm.unregisterReceiver(receiver);
lbm.unregisterReceiver(creceiver);
if (pgpService != null && pgpService.isBound())
pgpService.unbindFromService();
super.onDestroyView();
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
private BroadcastReceiver creceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
busy = intent.getIntExtra("count", 0);
@ -2150,6 +2197,17 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
public void onResume() {
super.onResume();
if (!pgpService.isBound())
pgpService.bindToService();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
IntentFilter iff = new IntentFilter();
iff.addAction(ACTION_STORE_RAW);
iff.addAction(ACTION_STORE_ATTACHMENT);
iff.addAction(ACTION_STORE_ATTACHMENTS);
iff.addAction(ACTION_DECRYPT);
lbm.registerReceiver(receiver, iff);
ConnectivityManager cm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
@ -2180,6 +2238,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
ConnectivityManager cm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
cm.unregisterNetworkCallback(networkCallback);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
lbm.unregisterReceiver(receiver);
}
@Override
@ -3301,6 +3362,493 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
return super.onCreateAnimation(transit, enter, nextAnim);
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
String action = intent.getAction();
if (ACTION_STORE_RAW.equals(action))
onStoreRaw(intent);
else if (ACTION_STORE_ATTACHMENT.equals(action))
onStoreAttachment(intent);
else if (ACTION_STORE_ATTACHMENTS.equals(action))
onStoreAttachments(intent);
else if (ACTION_DECRYPT.equals(action))
onDecrypt(intent);
}
}
};
private void onStoreRaw(Intent intent) {
message = intent.getLongExtra("id", -1);
Intent create = new Intent(Intent.ACTION_CREATE_DOCUMENT);
create.addCategory(Intent.CATEGORY_OPENABLE);
create.setType("*/*");
create.putExtra(Intent.EXTRA_TITLE, "email.eml");
if (create.resolveActivity(getContext().getPackageManager()) == null)
Snackbar.make(view, R.string.title_no_saf, Snackbar.LENGTH_LONG).show();
else
startActivityForResult(Helper.getChooser(getContext(), create), REQUEST_RAW);
}
private void onStoreAttachment(Intent intent) {
attachment = intent.getLongExtra("id", -1);
Intent create = new Intent(Intent.ACTION_CREATE_DOCUMENT);
create.addCategory(Intent.CATEGORY_OPENABLE);
create.setType(intent.getStringExtra("type"));
create.putExtra(Intent.EXTRA_TITLE, intent.getStringExtra("name"));
if (create.resolveActivity(getContext().getPackageManager()) == null)
Snackbar.make(view, R.string.title_no_saf, Snackbar.LENGTH_LONG).show();
else
startActivityForResult(Helper.getChooser(getContext(), create), REQUEST_ATTACHMENT);
}
private void onStoreAttachments(Intent intent) {
message = intent.getLongExtra("id", -1);
Intent tree = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
//tree.putExtra("android.content.extra.SHOW_ADVANCED", true);
if (tree.resolveActivity(getContext().getPackageManager()) == null)
Snackbar.make(view, R.string.title_no_saf, Snackbar.LENGTH_LONG).show();
else
startActivityForResult(Helper.getChooser(getContext(), tree), REQUEST_ATTACHMENTS);
}
private void onDecrypt(Intent intent) {
if (!Helper.isPro(getContext())) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO));
return;
}
if (pgpService.isBound()) {
Intent data = new Intent();
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
decrypt(data, intent.getLongExtra("id", -1));
} else
Snackbar.make(view, R.string.title_no_openpgp, Snackbar.LENGTH_LONG).show();
}
private void decrypt(Intent data, long id) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putParcelable("data", data);
new SimpleTask<PendingIntent>() {
@Override
protected PendingIntent onExecute(Context context, Bundle args) throws Throwable {
// Get arguments
long id = args.getLong("id");
Intent data = args.getParcelable("data");
DB db = DB.getInstance(context);
boolean inline = false;
InputStream encrypted = null;
// Find encrypted data
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
for (EntityAttachment attachment : attachments)
if (EntityAttachment.PGP_MESSAGE.equals(attachment.encryption)) {
if (!attachment.available)
throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing));
File file = attachment.getFile(context);
encrypted = new BufferedInputStream(new FileInputStream(file));
break;
}
if (encrypted == null) {
EntityMessage message = db.message().getMessage(id);
if (message != null && message.content) {
File file = message.getFile(context);
if (file.exists()) {
// https://tools.ietf.org/html/rfc4880#section-6.2
String body = Helper.readText(file);
int begin = body.indexOf(PGP_BEGIN_MESSAGE);
int end = body.indexOf(PGP_END_MESSAGE);
if (begin >= 0 && begin < end) {
String section = body.substring(begin, end + PGP_END_MESSAGE.length());
String[] lines = section.split("<br />");
List<String> disarmored = new ArrayList<>();
for (String line : lines)
if (!TextUtils.isEmpty(line) && !line.contains(": "))
disarmored.add(line);
section = TextUtils.join("\n\r", disarmored);
inline = true;
encrypted = new ByteArrayInputStream(section.getBytes());
}
}
}
}
if (encrypted == null)
throw new IllegalArgumentException(context.getString(R.string.title_not_encrypted));
ByteArrayOutputStream decrypted = new ByteArrayOutputStream();
// Decrypt message
OpenPgpApi api = new OpenPgpApi(context, pgpService.getService());
Intent result = api.executeApi(data, encrypted, decrypted);
Log.i("PGP result=" + result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR));
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
case OpenPgpApi.RESULT_CODE_SUCCESS:
if (inline) {
try {
db.beginTransaction();
// Write decrypted body
EntityMessage m = db.message().getMessage(id);
Helper.writeText(m.getFile(context), decrypted.toString());
db.message().setMessageStored(id, new Date().getTime());
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} else {
// Decode message
Properties props = MessageHelper.getSessionProperties(
ConnectionHelper.AUTH_TYPE_PASSWORD, null, false);
Session isession = Session.getInstance(props, null);
ByteArrayInputStream is = new ByteArrayInputStream(decrypted.toByteArray());
MimeMessage imessage = new MimeMessage(isession, is);
MessageHelper helper = new MessageHelper(imessage);
MessageHelper.MessageParts parts = helper.getMessageParts();
try {
db.beginTransaction();
// Write decrypted body
EntityMessage m = db.message().getMessage(id);
Helper.writeText(m.getFile(context), parts.getHtml(context));
// Remove previously decrypted attachments
for (EntityAttachment local : attachments)
if (local.encryption == null)
db.attachment().deleteAttachment(local.id);
int sequence = db.attachment().getAttachmentSequence(id);
// Add decrypted attachments
List<EntityAttachment> remotes = parts.getAttachments();
for (int index = 0; index < remotes.size(); index++) {
EntityAttachment remote = remotes.get(index);
remote.message = id;
remote.sequence = ++sequence;
remote.id = db.attachment().insertAttachment(remote);
try {
parts.downloadAttachment(context, index, remote.id, remote.name);
} catch (Throwable ex) {
Log.e(ex);
}
}
db.message().setMessageStored(id, new Date().getTime());
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
// Check signature status
OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
int sresult = (sigResult == null ? RESULT_NO_SIGNATURE : sigResult.getResult());
int resid;
if (sresult == RESULT_NO_SIGNATURE)
resid = R.string.title_signature_none;
else if (sresult == RESULT_VALID_KEY_CONFIRMED)
resid = R.string.title_signature_valid;
else
resid = R.string.title_signature_invalid;
Snackbar.make(view, resid, Snackbar.LENGTH_LONG).show();
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
message = id;
return result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
throw new IllegalArgumentException(error.getMessage());
}
return null;
}
@Override
protected void onExecuted(Bundle args, PendingIntent pi) {
if (pi != null)
try {
Log.i("PGP executing pi=" + pi);
startIntentSenderForResult(
pi.getIntentSender(),
REQUEST_DECRYPT,
null, 0, 0, 0, null);
} catch (IntentSender.SendIntentException ex) {
Log.e(ex);
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException)
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
else
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}.execute(FragmentMessages.this, args, "decrypt");
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK)
if (requestCode == REQUEST_RAW) {
if (data != null)
saveRaw(data);
} else if (requestCode == REQUEST_ATTACHMENT) {
if (data != null)
saveAttachment(data);
} else if (requestCode == REQUEST_ATTACHMENTS) {
if (data != null)
saveAttachments(data);
} else if (requestCode == REQUEST_DECRYPT) {
if (data != null)
decrypt(data, message);
}
super.onActivityResult(requestCode, resultCode, data);
}
private void saveRaw(Intent data) {
Bundle args = new Bundle();
args.putLong("id", message);
args.putParcelable("uri", data.getData());
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
Uri uri = args.getParcelable("uri");
if ("file".equals(uri.getScheme())) {
Log.w("Save raw uri=" + uri);
throw new IllegalArgumentException(context.getString(R.string.title_no_stream));
}
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
if (message == null)
throw new FileNotFoundException();
File file = message.getRawFile(context);
Log.i("Raw file=" + file);
ParcelFileDescriptor pfd = null;
OutputStream os = null;
InputStream is = null;
try {
pfd = context.getContentResolver().openFileDescriptor(uri, "w");
os = new FileOutputStream(pfd.getFileDescriptor());
is = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[MessageHelper.ATTACHMENT_BUFFER_SIZE];
int read;
while ((read = is.read(buffer)) != -1)
os.write(buffer, 0, read);
} finally {
try {
if (pfd != null)
pfd.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (os != null)
os.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (is != null)
is.close();
} catch (Throwable ex) {
Log.w(ex);
}
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
Snackbar.make(view, R.string.title_raw_saved, Snackbar.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException)
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
else
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}.execute(this, args, "raw:save");
}
private void saveAttachment(Intent data) {
Bundle args = new Bundle();
args.putLong("id", attachment);
args.putParcelable("uri", data.getData());
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
Uri uri = args.getParcelable("uri");
if ("file".equals(uri.getScheme())) {
Log.w("Save attachment uri=" + uri);
throw new IllegalArgumentException(context.getString(R.string.title_no_stream));
}
DB db = DB.getInstance(context);
EntityAttachment attachment = db.attachment().getAttachment(id);
if (attachment == null)
return null;
File file = attachment.getFile(context);
ParcelFileDescriptor pfd = null;
OutputStream os = null;
InputStream is = null;
try {
pfd = context.getContentResolver().openFileDescriptor(uri, "w");
os = new FileOutputStream(pfd.getFileDescriptor());
is = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[MessageHelper.ATTACHMENT_BUFFER_SIZE];
int read;
while ((read = is.read(buffer)) != -1)
os.write(buffer, 0, read);
} finally {
try {
if (pfd != null)
pfd.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (os != null)
os.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (is != null)
is.close();
} catch (Throwable ex) {
Log.w(ex);
}
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
Snackbar.make(view, R.string.title_attachment_saved, Snackbar.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException)
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
else
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}.execute(this, args, "attachment:save");
}
private void saveAttachments(Intent data) {
Bundle args = new Bundle();
args.putLong("id", message);
args.putParcelable("uri", data.getData());
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
Uri uri = args.getParcelable("uri");
DB db = DB.getInstance(context);
DocumentFile tree = DocumentFile.fromTreeUri(context, uri);
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
for (EntityAttachment attachment : attachments) {
File file = attachment.getFile(context);
String name = Helper.sanitizeFilename(attachment.name);
if (TextUtils.isEmpty(name))
name = Long.toString(attachment.id);
DocumentFile document = tree.createFile(attachment.type, name);
if (document == null)
throw new FileNotFoundException(uri + ":" + name);
ParcelFileDescriptor pfd = null;
OutputStream os = null;
InputStream is = null;
try {
pfd = context.getContentResolver().openFileDescriptor(document.getUri(), "w");
os = new FileOutputStream(pfd.getFileDescriptor());
is = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[MessageHelper.ATTACHMENT_BUFFER_SIZE];
int read;
while ((read = is.read(buffer)) != -1)
os.write(buffer, 0, read);
} finally {
try {
if (pfd != null)
pfd.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (os != null)
os.close();
} catch (Throwable ex) {
Log.w(ex);
}
try {
if (is != null)
is.close();
} catch (Throwable ex) {
Log.w(ex);
}
}
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
Snackbar.make(view, R.string.title_attachments_saved, Snackbar.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}.execute(this, args, "attachments:save");
}
static void search(
final Context context, final LifecycleOwner owner, final FragmentManager manager,
long folder, boolean server, String query) {

Loading…
Cancel
Save