diff --git a/app/src/main/java/eu/faircode/email/ActivitySetup.java b/app/src/main/java/eu/faircode/email/ActivitySetup.java index 2fd6ef5854..1fb609b3e4 100644 --- a/app/src/main/java/eu/faircode/email/ActivitySetup.java +++ b/app/src/main/java/eu/faircode/email/ActivitySetup.java @@ -394,7 +394,7 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On Bundle args = new Bundle(); args.putBoolean("export", export); - FragmentPassword fragment = new FragmentPassword(); + FragmentDialogPassword fragment = new FragmentDialogPassword(); fragment.setArguments(args); fragment.show(getSupportFragmentManager(), "password"); } @@ -1019,7 +1019,7 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On return intent; } - public static class FragmentPassword extends DialogFragment { + public static class FragmentDialogPassword extends DialogFragment { private TextInputLayout etPassword1; private TextInputLayout etPassword2; diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java index b95e83143e..32e3a51f4e 100644 --- a/app/src/main/java/eu/faircode/email/ActivityView.java +++ b/app/src/main/java/eu/faircode/email/ActivityView.java @@ -583,7 +583,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB private void checkFirst() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); if (prefs.getBoolean("first", true)) - new FragmentFirst().show(getSupportFragmentManager(), "first"); + new FragmentDialogFirst().show(getSupportFragmentManager(), "first"); } private void checkCrash() { @@ -862,7 +862,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB if (faq.resolveActivity(getPackageManager()) == null) Helper.view(this, this, getIntentRate(this)); else - new FragmentRate().show(getSupportFragmentManager(), "rate"); + new FragmentDialogRate().show(getSupportFragmentManager(), "rate"); } private void onMenuOtherApps() { @@ -1178,7 +1178,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB return intent; } - public static class FragmentFirst extends DialogFragment { + public static class FragmentDialogFirst extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { @@ -1195,7 +1195,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB } } - public static class FragmentRate extends DialogFragment { + public static class FragmentDialogRate extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index ff5cd01772..6b936c6fe9 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -2488,7 +2488,7 @@ public class AdapterMessage extends RecyclerView.Adapter 0 ? spans[0].getForegroundColor() : Color.TRANSPARENT); - FragmentColor fragment = new FragmentColor(); + FragmentDialogColor fragment = new FragmentDialogColor(); fragment.initialize(R.string.title_style_color, color, args, getContext()); fragment.setTargetFragment(FragmentCompose.this, REQUEST_COLOR); fragment.show(getFragmentManager(), "account:color"); diff --git a/app/src/main/java/eu/faircode/email/FragmentAsk.java b/app/src/main/java/eu/faircode/email/FragmentDialogAsk.java similarity index 98% rename from app/src/main/java/eu/faircode/email/FragmentAsk.java rename to app/src/main/java/eu/faircode/email/FragmentDialogAsk.java index 99e172f528..f2b0f8c519 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAsk.java +++ b/app/src/main/java/eu/faircode/email/FragmentDialogAsk.java @@ -37,7 +37,7 @@ import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; -public class FragmentAsk extends DialogFragment { +public class FragmentDialogAsk extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { diff --git a/app/src/main/java/eu/faircode/email/FragmentColor.java b/app/src/main/java/eu/faircode/email/FragmentDialogColor.java similarity index 97% rename from app/src/main/java/eu/faircode/email/FragmentColor.java rename to app/src/main/java/eu/faircode/email/FragmentDialogColor.java index f6a6d3d212..8ffbc8c768 100644 --- a/app/src/main/java/eu/faircode/email/FragmentColor.java +++ b/app/src/main/java/eu/faircode/email/FragmentDialogColor.java @@ -29,7 +29,7 @@ import com.android.colorpicker.ColorPickerDialog; import static android.app.Activity.RESULT_OK; -public class FragmentColor extends ColorPickerDialog { +public class FragmentDialogColor extends ColorPickerDialog { private Bundle args; public void initialize(int title, int color, Bundle args, Context context) { diff --git a/app/src/main/java/eu/faircode/email/FragmentDuration.java b/app/src/main/java/eu/faircode/email/FragmentDialogDuration.java similarity index 98% rename from app/src/main/java/eu/faircode/email/FragmentDuration.java rename to app/src/main/java/eu/faircode/email/FragmentDialogDuration.java index 3116b0f2f8..0f27240552 100644 --- a/app/src/main/java/eu/faircode/email/FragmentDuration.java +++ b/app/src/main/java/eu/faircode/email/FragmentDialogDuration.java @@ -44,7 +44,7 @@ import java.util.Date; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; -public class FragmentDuration extends DialogFragment { +public class FragmentDialogDuration extends DialogFragment { private Calendar cal = Calendar.getInstance(); @Override diff --git a/app/src/main/java/eu/faircode/email/FragmentSelectFolder.java b/app/src/main/java/eu/faircode/email/FragmentDialogFolder.java similarity index 98% rename from app/src/main/java/eu/faircode/email/FragmentSelectFolder.java rename to app/src/main/java/eu/faircode/email/FragmentDialogFolder.java index 6053391136..584fb81196 100644 --- a/app/src/main/java/eu/faircode/email/FragmentSelectFolder.java +++ b/app/src/main/java/eu/faircode/email/FragmentDialogFolder.java @@ -41,7 +41,7 @@ import java.util.List; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; -public class FragmentSelectFolder extends DialogFragment { +public class FragmentDialogFolder extends DialogFragment { private TwoStateOwner owner = new TwoStateOwner("folder:select"); @NonNull diff --git a/app/src/main/java/eu/faircode/email/FragmentFolder.java b/app/src/main/java/eu/faircode/email/FragmentFolder.java index fe2ef6f00a..a9614d3989 100644 --- a/app/src/main/java/eu/faircode/email/FragmentFolder.java +++ b/app/src/main/java/eu/faircode/email/FragmentFolder.java @@ -322,7 +322,7 @@ public class FragmentFolder extends FragmentBase { Bundle aargs = new Bundle(); aargs.putString("question", getString(R.string.title_folder_delete)); - FragmentAsk ask = new FragmentAsk(); + FragmentDialogAsk ask = new FragmentDialogAsk(); ask.setArguments(aargs); ask.setTargetFragment(FragmentFolder.this, REQUEST_DELETE_FOLDER); ask.show(getFragmentManager(), "folder:delete"); @@ -497,7 +497,7 @@ public class FragmentFolder extends FragmentBase { Bundle aargs = new Bundle(); aargs.putString("question", getString(R.string.title_ask_save)); - FragmentAsk ask = new FragmentAsk(); + FragmentDialogAsk ask = new FragmentDialogAsk(); ask.setArguments(aargs); ask.setTargetFragment(FragmentFolder.this, REQUEST_SAVE_CHANGES); ask.show(getFragmentManager(), "folder:save"); diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 7e723ba699..7b5cb1da5b 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -262,7 +262,7 @@ public class FragmentIdentity extends FragmentBase { btnColor.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - FragmentColor fragment = new FragmentColor(); + FragmentDialogColor fragment = new FragmentDialogColor(); fragment.initialize(R.string.title_flag_color, color, new Bundle(), getContext()); fragment.setTargetFragment(FragmentIdentity.this, REQUEST_COLOR); fragment.show(getFragmentManager(), "identity:color"); diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index bfe968a231..83af64c1f3 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -226,8 +226,8 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. private static final int REQUEST_DECRYPT = 4; private static final int REQUEST_DELETE = 5; private static final int REQUEST_JUNK = 6; - private static final int REQUEST_MOVE = 7; - private static final int REQUEST_MOVE_ACROSS = 8; + private static final int REQUEST_ASKED_MOVE = 7; + private static final int REQUEST_ASKED_MOVE_ACROSS = 8; static final int REQUEST_MESSAGE_COLOR = 9; private static final int REQUEST_MESSAGES_COLOR = 10; private static final int REQUEST_MESSAGE_SNOOZE = 11; @@ -631,7 +631,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. args.putLong("id", id); args.putBoolean("finish", true); - FragmentDuration fragment = new FragmentDuration(); + FragmentDialogDuration fragment = new FragmentDialogDuration(); fragment.setArguments(args); fragment.setTargetFragment(FragmentMessages.this, REQUEST_MESSAGE_SNOOZE); fragment.show(getFragmentManager(), "message:snooze"); @@ -724,7 +724,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. args.putLongArray("disabled", new long[]{}); args.putString("query", query); - FragmentSelectFolder fragment = new FragmentSelectFolder(); + FragmentDialogFolder fragment = new FragmentDialogFolder(); fragment.setArguments(args); fragment.setTargetFragment(FragmentMessages.this, FragmentMessages.REQUEST_SEARCH); fragment.show(getFragmentManager(), "messages:search"); @@ -1328,7 +1328,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. args.putLong("id", message.id); args.putBoolean("finish", false); - FragmentDuration fragment = new FragmentDuration(); + FragmentDialogDuration fragment = new FragmentDialogDuration(); fragment.setArguments(args); fragment.setTargetFragment(FragmentMessages.this, REQUEST_MESSAGE_SNOOZE); fragment.show(getFragmentManager(), "message:snooze"); @@ -1340,7 +1340,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. Bundle args = new Bundle(); args.putLong("id", message.id); - FragmentColor fragment = new FragmentColor(); + FragmentDialogColor fragment = new FragmentDialogColor(); fragment.initialize(R.string.title_flag_color, color, args, getContext()); fragment.setTargetFragment(FragmentMessages.this, FragmentMessages.REQUEST_MESSAGE_COLOR); fragment.show(getFragmentManager(), "message:color"); @@ -1354,7 +1354,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. args.putLong("message", message.id); args.putBoolean("copy", false); - FragmentSelectFolder fragment = new FragmentSelectFolder(); + FragmentDialogFolder fragment = new FragmentDialogFolder(); fragment.setArguments(args); fragment.setTargetFragment(FragmentMessages.this, FragmentMessages.REQUEST_MESSAGE_MOVE); fragment.show(getFragmentManager(), "message:move"); @@ -1662,7 +1662,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. Bundle args = new Bundle(); args.putString("title", getString(R.string.title_snooze)); - FragmentDuration fragment = new FragmentDuration(); + FragmentDialogDuration fragment = new FragmentDialogDuration(); fragment.setArguments(args); fragment.setTargetFragment(this, REQUEST_MESSAGES_SNOOZE); fragment.show(getFragmentManager(), "messages:snooze"); @@ -1714,7 +1714,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } private void onActionFlagColorSelection() { - FragmentColor fragment = new FragmentColor(); + FragmentDialogColor fragment = new FragmentDialogColor(); fragment.initialize(R.string.title_flag_color, Color.TRANSPARENT, new Bundle(), getContext()); fragment.setTargetFragment(FragmentMessages.this, REQUEST_MESSAGES_COLOR); fragment.show(getFragmentManager(), "messages:color"); @@ -1759,7 +1759,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. .getQuantityString(R.plurals.title_deleting_messages, ids.size(), ids.size())); aargs.putLongArray("ids", Helper.toLongArray(ids)); - FragmentAsk ask = new FragmentAsk(); + FragmentDialogAsk ask = new FragmentDialogAsk(); ask.setArguments(aargs); ask.setTargetFragment(FragmentMessages.this, REQUEST_DELETE); ask.show(getFragmentManager(), "messages:delete"); @@ -1779,7 +1779,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. aargs.putString("question", getResources() .getQuantityString(R.plurals.title_ask_spam, count, count)); - FragmentAsk ask = new FragmentAsk(); + FragmentDialogAsk ask = new FragmentDialogAsk(); ask.setArguments(aargs); ask.setTargetFragment(FragmentMessages.this, REQUEST_JUNK); ask.show(getFragmentManager(), "messages:junk"); @@ -1844,7 +1844,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. args.putLong("account", account); args.putLongArray("disabled", Helper.toLongArray(disabled)); - FragmentSelectFolder fragment = new FragmentSelectFolder(); + FragmentDialogFolder fragment = new FragmentDialogFolder(); fragment.setArguments(args); fragment.setTargetFragment(FragmentMessages.this, FragmentMessages.REQUEST_MESSAGES_MOVE); fragment.show(getFragmentManager(), "messages:move"); @@ -3058,33 +3058,12 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. aargs.putString("notagain", "automove"); aargs.putParcelableArrayList("result", result); - FragmentAsk ask = new FragmentAsk(); + FragmentDialogAsk ask = new FragmentDialogAsk(); ask.setArguments(aargs); - ask.setTargetFragment(FragmentMessages.this, REQUEST_MOVE); + ask.setTargetFragment(FragmentMessages.this, REQUEST_ASKED_MOVE); ask.show(getFragmentManager(), "messages:move"); } - private void moveAskAcross(final ArrayList result) { - boolean across = false; - for (MessageTarget target : result) - if (target.across) { - across = true; - break; - } - - if (across) { - Bundle aargs = new Bundle(); - aargs.putString("question", getString(R.string.title_accross_remark)); - aargs.putParcelableArrayList("result", result); - - FragmentAsk ask = new FragmentAsk(); - ask.setArguments(aargs); - ask.setTargetFragment(FragmentMessages.this, REQUEST_MOVE_ACROSS); - ask.show(getFragmentManager(), "messages:move:across"); - } else - moveAskConfirmed(result); - } - private void moveAskConfirmed(ArrayList result) { if (selectionTracker != null) selectionTracker.clearSelection(); @@ -3333,192 +3312,11 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. Intent data = new Intent(); data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); - decrypt(data, intent.getLongExtra("id", -1)); + onDecrypt(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().replace("\0", "")); - - db.message().setMessageStored(id, new Date().getTime()); - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - } else { - // Decode message - Properties props = MessageHelper.getSessionProperties(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); - String html = parts.getHtml(context); - if (html != null) - html = html.replace("\0", ""); - Helper.writeText(m.getFile(context), html); - - // 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); - } 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) { super.onActivityResult(requestCode, resultCode, data); @@ -3526,19 +3324,19 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. switch (requestCode) { case REQUEST_RAW: if (resultCode == RESULT_OK && data != null) - saveRaw(data); + onSaveRaw(data); break; case REQUEST_ATTACHMENT: if (resultCode == RESULT_OK && data != null) - saveAttachment(data); + onSaveAttachment(data); break; case REQUEST_ATTACHMENTS: if (resultCode == RESULT_OK && data != null) - saveAttachments(data); + onSaveAttachments(data); break; case REQUEST_DECRYPT: if (resultCode == RESULT_OK && data != null) - decrypt(data, message); + onDecrypt(data, message); break; case REQUEST_DELETE: if (resultCode == RESULT_OK && data != null) @@ -3548,20 +3346,18 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. if (resultCode == RESULT_OK) onActionMoveSelection(EntityFolder.JUNK); break; - case REQUEST_MOVE: + case REQUEST_ASKED_MOVE: if (resultCode == RESULT_OK && data != null) - moveAskAcross(data.getBundleExtra("args").getParcelableArrayList("result")); + onMoveAskAcross(data.getBundleExtra("args").getParcelableArrayList("result")); break; - case REQUEST_MOVE_ACROSS: + case REQUEST_ASKED_MOVE_ACROSS: if (resultCode == RESULT_OK && data != null) moveAskConfirmed(data.getBundleExtra("args").getParcelableArrayList("result")); break; case REQUEST_MESSAGE_COLOR: if (resultCode == RESULT_OK && data != null) { Bundle args = data.getBundleExtra("args"); - long id = args.getLong("id"); - int color = args.getInt("color"); - onColorSelected(id, color); + onColor(args.getLong("id"), args.getInt("color")); } break; case REQUEST_MESSAGES_COLOR: @@ -3573,9 +3369,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } Bundle args = data.getBundleExtra("args"); - int color = args.getInt("color"); - - onActionFlagSelection(true, color); + onActionFlagSelection(true, args.getInt("color")); } break; case REQUEST_MESSAGE_SNOOZE: @@ -3607,241 +3401,61 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } } - private void onDelete(long[] ids) { + private void onSaveRaw(Intent data) { Bundle args = new Bundle(); - args.putLongArray("ids", ids); - - selectionTracker.clearSelection(); + args.putLong("id", message); + args.putParcelable("uri", data.getData()); new SimpleTask() { @Override - protected Void onExecute(Context context, Bundle args) { - long[] ids = args.getLongArray("ids"); + 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 { - db.beginTransaction(); + pfd = context.getContentResolver().openFileDescriptor(uri, "w"); + os = new FileOutputStream(pfd.getFileDescriptor()); + is = new BufferedInputStream(new FileInputStream(file)); - for (long id : ids) { - EntityMessage message = db.message().getMessage(id); - if (message != null) - EntityOperation.queue(context, message, EntityOperation.DELETE); + 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); } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - return null; - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); - } - }.execute(FragmentMessages.this, args, "messages:delete:execute"); - } - - private void onSnooze(Bundle args) { - if (!Helper.isPro(getContext())) { - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext()); - lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO)); - return; - } - - long duration = args.getLong("duration"); - long time = args.getLong("time"); - args.putLong("wakeup", duration == 0 ? -1 : time); - - new SimpleTask() { - @Override - protected Long onExecute(Context context, Bundle args) { - long account = args.getLong("account"); - String thread = args.getString("thread"); - long id = args.getLong("id"); - Long wakeup = args.getLong("wakeup"); - if (wakeup < 0) - wakeup = null; - - DB db = DB.getInstance(context); - try { - db.beginTransaction(); - - List messages = db.message().getMessageByThread( - account, thread, threading ? null : id, null); - for (EntityMessage threaded : messages) { - db.message().setMessageSnoozed(threaded.id, wakeup); - EntityMessage.snooze(context, threaded.id, wakeup); - EntityOperation.queue(context, threaded, EntityOperation.SEEN, true); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - return wakeup; - } - - @Override - protected void onExecuted(Bundle args, Long wakeup) { - if (wakeup != null && args.getBoolean("finish")) - finish(); - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); - } - }.execute(getContext(), getViewLifecycleOwner(), args, "message:snooze"); - } - - private void onSnoozeSelection(Bundle args) { - if (!Helper.isPro(getContext())) { - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext()); - lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO)); - return; - } - - long duration = args.getLong("duration"); - long time = args.getLong("time"); - args.putLong("wakeup", duration == 0 ? -1 : time); - args.putLongArray("ids", getSelection()); - - selectionTracker.clearSelection(); - - new SimpleTask() { - @Override - protected Void onExecute(Context context, Bundle args) { - long[] ids = args.getLongArray("ids"); - Long wakeup = args.getLong("wakeup"); - if (wakeup < 0) - wakeup = null; - - DB db = DB.getInstance(context); - try { - db.beginTransaction(); - - for (long id : ids) { - EntityMessage message = db.message().getMessage(id); - if (message != null) { - List messages = db.message().getMessageByThread( - message.account, message.thread, threading ? null : id, message.folder); - for (EntityMessage threaded : messages) { - db.message().setMessageSnoozed(threaded.id, wakeup); - EntityMessage.snooze(context, threaded.id, wakeup); - EntityOperation.queue(context, threaded, EntityOperation.SEEN, true); - } - } - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - return null; - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); - } - }.execute(FragmentMessages.this, args, "messages:snooze"); - } - - private void onMove(Bundle args) { - new SimpleTask() { - @Override - protected Void onExecute(Context context, Bundle args) { - long id = args.getLong("message"); - boolean copy = args.getBoolean("copy"); - long target = args.getLong("folder"); - - DB db = DB.getInstance(context); - try { - db.beginTransaction(); - - EntityMessage message = db.message().getMessage(id); - if (message == null) - return null; - - if (copy) - EntityOperation.queue(context, message, EntityOperation.COPY, target); - else - EntityOperation.queue(context, message, EntityOperation.MOVE, target); - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - return null; - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); - } - }.execute(getContext(), getViewLifecycleOwner(), args, "message:copy"); - } - - 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; } @@ -3861,7 +3475,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. }.execute(this, args, "raw:save"); } - private void saveAttachment(Intent data) { + private void onSaveAttachment(Intent data) { Bundle args = new Bundle(); args.putLong("id", attachment); args.putParcelable("uri", data.getData()); @@ -3934,7 +3548,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. }.execute(this, args, "attachment:save"); } - private void saveAttachments(Intent data) { + private void onSaveAttachments(Intent data) { Bundle args = new Bundle(); args.putLong("id", message); args.putParcelable("uri", data.getData()); @@ -4007,36 +3621,274 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. }.execute(this, args, "attachments:save"); } - public void onColorSelected(long id, int color) { - if (!Helper.isPro(getContext())) { - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext()); - lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO)); - return; - } - + private void onDecrypt(Intent data, long id) { Bundle args = new Bundle(); args.putLong("id", id); - args.putInt("color", color); + args.putParcelable("data", data); - new SimpleTask() { + new SimpleTask() { @Override - protected Void onExecute(final Context context, Bundle args) { - final long id = args.getLong("id"); - final int color = args.getInt("color"); + protected PendingIntent onExecute(Context context, Bundle args) throws Throwable { + // Get arguments + long id = args.getLong("id"); + Intent data = args.getParcelable("data"); - final DB db = DB.getInstance(context); - db.runInTransaction(new Runnable() { - @Override - public void run() { - EntityMessage message = db.message().getMessage(id); - if (message == null) - return; + DB db = DB.getInstance(context); - EntityOperation.queue(context, message, EntityOperation.FLAG, true, color); + 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; } - }); - return null; - } + + 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().replace("\0", "")); + + db.message().setMessageStored(id, new Date().getTime()); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + } else { + // Decode message + Properties props = MessageHelper.getSessionProperties(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); + String html = parts.getHtml(context); + if (html != null) + html = html.replace("\0", ""); + Helper.writeText(m.getFile(context), html); + + // 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); + } 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"); + } + + private void onDelete(long[] ids) { + Bundle args = new Bundle(); + args.putLongArray("ids", ids); + + selectionTracker.clearSelection(); + + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + long[] ids = args.getLongArray("ids"); + + DB db = DB.getInstance(context); + try { + db.beginTransaction(); + + for (long id : ids) { + EntityMessage message = db.message().getMessage(id); + if (message != null) + EntityOperation.queue(context, message, EntityOperation.DELETE); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + return null; + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); + } + }.execute(FragmentMessages.this, args, "messages:delete:execute"); + } + + private void onMoveAskAcross(final ArrayList result) { + boolean across = false; + for (MessageTarget target : result) + if (target.across) { + across = true; + break; + } + + if (across) { + Bundle aargs = new Bundle(); + aargs.putString("question", getString(R.string.title_accross_remark)); + aargs.putParcelableArrayList("result", result); + + FragmentDialogAsk ask = new FragmentDialogAsk(); + ask.setArguments(aargs); + ask.setTargetFragment(FragmentMessages.this, REQUEST_ASKED_MOVE_ACROSS); + ask.show(getFragmentManager(), "messages:move:across"); + } else + moveAskConfirmed(result); + } + + private void onColor(long id, int color) { + if (!Helper.isPro(getContext())) { + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext()); + lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO)); + return; + } + + Bundle args = new Bundle(); + args.putLong("id", id); + args.putInt("color", color); + + new SimpleTask() { + @Override + protected Void onExecute(final Context context, Bundle args) { + final long id = args.getLong("id"); + final int color = args.getInt("color"); + + final DB db = DB.getInstance(context); + db.runInTransaction(new Runnable() { + @Override + public void run() { + EntityMessage message = db.message().getMessage(id); + if (message == null) + return; + + EntityOperation.queue(context, message, EntityOperation.FLAG, true, color); + } + }); + return null; + } @Override protected void onException(Bundle args, Throwable ex) { @@ -4045,6 +3897,150 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. }.execute(getContext(), getViewLifecycleOwner(), args, "message:color"); } + private void onSnooze(Bundle args) { + if (!Helper.isPro(getContext())) { + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext()); + lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO)); + return; + } + + long duration = args.getLong("duration"); + long time = args.getLong("time"); + args.putLong("wakeup", duration == 0 ? -1 : time); + + new SimpleTask() { + @Override + protected Long onExecute(Context context, Bundle args) { + long account = args.getLong("account"); + String thread = args.getString("thread"); + long id = args.getLong("id"); + Long wakeup = args.getLong("wakeup"); + if (wakeup < 0) + wakeup = null; + + DB db = DB.getInstance(context); + try { + db.beginTransaction(); + + List messages = db.message().getMessageByThread( + account, thread, threading ? null : id, null); + for (EntityMessage threaded : messages) { + db.message().setMessageSnoozed(threaded.id, wakeup); + EntityMessage.snooze(context, threaded.id, wakeup); + EntityOperation.queue(context, threaded, EntityOperation.SEEN, true); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + return wakeup; + } + + @Override + protected void onExecuted(Bundle args, Long wakeup) { + if (wakeup != null && args.getBoolean("finish")) + finish(); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); + } + }.execute(getContext(), getViewLifecycleOwner(), args, "message:snooze"); + } + + private void onSnoozeSelection(Bundle args) { + if (!Helper.isPro(getContext())) { + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext()); + lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO)); + return; + } + + long duration = args.getLong("duration"); + long time = args.getLong("time"); + args.putLong("wakeup", duration == 0 ? -1 : time); + args.putLongArray("ids", getSelection()); + + selectionTracker.clearSelection(); + + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + long[] ids = args.getLongArray("ids"); + Long wakeup = args.getLong("wakeup"); + if (wakeup < 0) + wakeup = null; + + DB db = DB.getInstance(context); + try { + db.beginTransaction(); + + for (long id : ids) { + EntityMessage message = db.message().getMessage(id); + if (message != null) { + List messages = db.message().getMessageByThread( + message.account, message.thread, threading ? null : id, message.folder); + for (EntityMessage threaded : messages) { + db.message().setMessageSnoozed(threaded.id, wakeup); + EntityMessage.snooze(context, threaded.id, wakeup); + EntityOperation.queue(context, threaded, EntityOperation.SEEN, true); + } + } + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + return null; + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); + } + }.execute(FragmentMessages.this, args, "messages:snooze"); + } + + private void onMove(Bundle args) { + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + long id = args.getLong("message"); + boolean copy = args.getBoolean("copy"); + long target = args.getLong("folder"); + + DB db = DB.getInstance(context); + try { + db.beginTransaction(); + + EntityMessage message = db.message().getMessage(id); + if (message == null) + return null; + + if (copy) + EntityOperation.queue(context, message, EntityOperation.COPY, target); + else + EntityOperation.queue(context, message, EntityOperation.MOVE, target); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + return null; + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); + } + }.execute(getContext(), getViewLifecycleOwner(), args, "message:copy"); + } + static void search( final Context context, final LifecycleOwner owner, final FragmentManager manager, long folder, boolean server, String query) { diff --git a/app/src/main/java/eu/faircode/email/FragmentRule.java b/app/src/main/java/eu/faircode/email/FragmentRule.java index a2a8108c4d..acfe30aa4c 100644 --- a/app/src/main/java/eu/faircode/email/FragmentRule.java +++ b/app/src/main/java/eu/faircode/email/FragmentRule.java @@ -297,7 +297,7 @@ public class FragmentRule extends FragmentBase { btnColor.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - FragmentColor fragment = new FragmentColor(); + FragmentDialogColor fragment = new FragmentDialogColor(); fragment.initialize(R.string.title_flag_color, color, new Bundle(), getContext()); fragment.setTargetFragment(FragmentRule.this, REQUEST_COLOR); fragment.show(getFragmentManager(), "rule:color");