diff --git a/app/src/main/java/eu/faircode/email/ActivityBilling.java b/app/src/main/java/eu/faircode/email/ActivityBilling.java index e5d4459440..5d94a6f081 100644 --- a/app/src/main/java/eu/faircode/email/ActivityBilling.java +++ b/app/src/main/java/eu/faircode/email/ActivityBilling.java @@ -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); + } } }; diff --git a/app/src/main/java/eu/faircode/email/ActivityCompose.java b/app/src/main/java/eu/faircode/email/ActivityCompose.java index 6193028ad7..a216532729 100644 --- a/app/src/main/java/eu/faircode/email/ActivityCompose.java +++ b/app/src/main/java/eu/faircode/email/ActivityCompose.java @@ -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); + } } }; diff --git a/app/src/main/java/eu/faircode/email/ActivitySetup.java b/app/src/main/java/eu/faircode/email/ActivitySetup.java index f6a2b015b5..673abe3e4a 100644 --- a/app/src/main/java/eu/faircode/email/ActivitySetup.java +++ b/app/src/main/java/eu/faircode/email/ActivitySetup.java @@ -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); + } } }; } diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java index 31f640868a..bb99379893 100644 --- a/app/src/main/java/eu/faircode/email/ActivityView.java +++ b/app/src/main/java/eu/faircode/email/ActivityView.java @@ -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() { - @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 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("
"); - List 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 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() { - @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() { - @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() { - @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 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"); - } } diff --git a/app/src/main/java/eu/faircode/email/AdapterAttachment.java b/app/src/main/java/eu/faircode/email/AdapterAttachment.java index 8e52a525fc..8971383c58 100644 --- a/app/src/main/java/eu/faircode/email/AdapterAttachment.java +++ b/app/src/main/java/eu/faircode/email/AdapterAttachment.java @@ -187,7 +187,7 @@ public class AdapterAttachment extends RecyclerView.Adapter 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() { + @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 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("
"); + List 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 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() { + @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() { + @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() { + @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 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) {