diff --git a/app/src/main/java/eu/faircode/email/DaoMessage.java b/app/src/main/java/eu/faircode/email/DaoMessage.java index bc082491b3..95719953fb 100644 --- a/app/src/main/java/eu/faircode/email/DaoMessage.java +++ b/app/src/main/java/eu/faircode/email/DaoMessage.java @@ -274,6 +274,11 @@ public interface DaoMessage { " AND folder.type <> '" + EntityFolder.OUTBOX + "'") LiveData liveFts(); + @Query("SELECT COUNT(*) FROM message" + + " WHERE id IN (:ids)" + + " AND raw IS NULL or NOT raw") + LiveData liveRaw(long[] ids); + @Query("SELECT *" + " FROM message" + " WHERE id = :id") diff --git a/app/src/main/java/eu/faircode/email/EntityMessage.java b/app/src/main/java/eu/faircode/email/EntityMessage.java index 7ab2e4f3b7..bbe8110ab3 100644 --- a/app/src/main/java/eu/faircode/email/EntityMessage.java +++ b/app/src/main/java/eu/faircode/email/EntityMessage.java @@ -441,6 +441,10 @@ public class EntityMessage implements Serializable { } File getRawFile(Context context) { + return getRawFile(context, id); + } + + static File getRawFile(Context context, Long id) { File dir = new File(context.getFilesDir(), "raw"); if (!dir.exists()) dir.mkdir(); diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index 638b0a197f..50b449b30d 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -98,6 +98,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.PopupMenu; import androidx.constraintlayout.widget.Group; import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; import androidx.core.graphics.ColorUtils; import androidx.core.view.MenuItemCompat; import androidx.fragment.app.FragmentActivity; @@ -106,6 +107,7 @@ import androidx.fragment.app.FragmentResultListener; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -2645,6 +2647,11 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. if (message == null) continue; + result.count++; + + if (message.raw != null && message.raw) + result.raw++; + EntityAccount account = accounts.get(message.account); if (account == null) { account = db.account().getAccount(message.account); @@ -2816,6 +2823,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. if (result.hasJunk && !result.isJunk && !result.isDrafts) // has junk and not junk/drafts popupMenu.getMenu().add(Menu.NONE, R.string.title_spam, order++, R.string.title_spam); + if (result.accounts.size() > 0 /* IMAP */ && BuildConfig.DEBUG) + popupMenu.getMenu().add(Menu.NONE, R.string.title_raw_send, order++, R.string.title_raw_send); + for (EntityAccount account : result.accounts) { String title = getString(R.string.title_move_to_account, account.name); SpannableString ss = new SpannableString(title); @@ -2882,6 +2892,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } else if (itemId == R.string.title_spam) { onActionJunkSelection(); return true; + } else if (itemId == R.string.title_raw_send) { + onActionRaw(); + return true; } else if (itemId == R.string.title_move_to_account) { long account = target.getIntent().getLongExtra("account", -1); onActionMoveSelectionAccount(account, false, result.folders); @@ -3298,6 +3311,105 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. }.execute(this, args, "messages:move"); } + private void onActionRaw() { + Bundle args = new Bundle(); + args.putLongArray("ids", getSelection()); + + selectionTracker.clearSelection(); + + new SimpleTask() { + @Override + protected Integer onExecute(Context context, Bundle args) { + long[] ids = args.getLongArray("ids"); + + int count = 0; + DB db = DB.getInstance(context); + for (long id : ids) { + EntityMessage message = db.message().getMessage(id); + if (message == null) + continue; + + if (message.raw == null || !message.raw) { + count++; + EntityOperation.queue(context, message, EntityOperation.RAW); + } + } + + return count; + } + + @Override + protected void onExecuted(Bundle args, Integer count) { + long[] ids = args.getLongArray("ids"); + + if (count == 0) { + send(ids); + return; + } + + final Context context = getContext(); + + LayoutInflater inflator = LayoutInflater.from(context); + View dview = inflator.inflate(R.layout.dialog_forward, null); + TextView tvMessages = dview.findViewById(R.id.tvMessages); + + tvMessages.setText(null); + + final AlertDialog dialog = new AlertDialog.Builder(context) + .setView(dview) + .setNegativeButton(android.R.string.cancel, null) + .show(); + + DB db = DB.getInstance(context); + final LiveData ld = db.message().liveRaw(ids); + ld.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Integer remaining) { + if (remaining == null) + return; + + tvMessages.setText(getResources().getQuantityString(R.plurals.title_moving_messages, remaining, remaining)); + + if (remaining == 0) { + ld.removeObserver(this); + dialog.dismiss(); + send(ids); + } + } + }); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Log.unexpectedError(getParentFragmentManager(), ex); + } + + private void send(long[] ids) { + try { + final Context context = getContext(); + + ArrayList uris = new ArrayList<>(); + for (long id : ids) { + File file = EntityMessage.getRawFile(context, id); + Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID, file); + uris.add(uri); + } + + Intent send = new Intent(Intent.ACTION_SEND_MULTIPLE); + send.setPackage(BuildConfig.APPLICATION_ID); + send.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + send.setType("message/rfc822"); + send.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + context.startActivity(send); + } catch (Throwable ex) { + // java.lang.IllegalArgumentException: Failed to resolve canonical path for ... + Log.unexpectedError(getParentFragmentManager(), ex); + } + } + }.execute(this, args, "messages:forward"); + } + private void onActionMoveSelectionAccount(long account, boolean copy, List disabled) { Bundle args = new Bundle(); args.putString("title", getString(copy ? R.string.title_copy_to : R.string.title_move_to_folder)); @@ -7804,6 +7916,8 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } private class MoreResult { + int count; + int raw; boolean seen; boolean unseen; boolean visible; diff --git a/app/src/main/res/layout/dialog_forward.xml b/app/src/main/res/layout/dialog_forward.xml new file mode 100644 index 0000000000..8b366316cd --- /dev/null +++ b/app/src/main/res/layout/dialog_forward.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file