package eu.faircode.email; /* This file is part of Safe email. Safe email is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. NetGuard is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NetGuard. If not, see <http://www.gnu.org/licenses/>. Copyright 2018 by Marcel Bokhorst (M66B) */ import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract; import android.provider.OpenableColumns; import android.text.Html; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.webkit.MimeTypeMap; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.EditText; import android.widget.FilterQueryProvider; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.Spinner; import android.widget.Toast; import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.snackbar.Snackbar; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import javax.mail.Address; import javax.mail.MessageRemovedException; import javax.mail.internet.InternetAddress; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.Group; import androidx.core.content.ContextCompat; import androidx.cursoradapter.widget.SimpleCursorAdapter; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import static android.app.Activity.RESULT_OK; public class FragmentCompose extends FragmentEx { private ViewGroup view; private Spinner spFrom; private ImageView ivIdentityAdd; private AutoCompleteTextView etTo; private ImageView ivToAdd; private AutoCompleteTextView etCc; private ImageView ivCcAdd; private AutoCompleteTextView etBcc; private ImageView ivBccAdd; private EditText etSubject; private RecyclerView rvAttachment; private EditText etBody; private BottomNavigationView bottom_navigation; private ProgressBar pbWait; private Group grpAddresses; private Group grpAttachments; private Group grpReady; private AdapterAttachment adapter; private boolean attaching = false; private String action = null; private long id = -1; // draft id private long account = -1; private long reference = -1; private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes @Override public void onSaveInstanceState(Bundle outState) { Log.i(Helper.TAG, "Saving state"); outState.putString("action", action); outState.putLong("id", id); outState.putLong("account", account); outState.putLong("reference", reference); super.onSaveInstanceState(outState); } @Override @Nullable public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // Get arguments if (savedInstanceState == null) { if (action == null) { action = getArguments().getString("action"); id = getArguments().getLong("id", -1); account = getArguments().getLong("account", -1); reference = getArguments().getLong("reference", -1); } } else { Log.i(Helper.TAG, "Restoring state"); action = savedInstanceState.getString("action"); id = savedInstanceState.getLong("id", -1); account = savedInstanceState.getLong("account", -1); reference = savedInstanceState.getLong("reference", -1); } setSubtitle(R.string.title_compose); view = (ViewGroup) inflater.inflate(R.layout.fragment_compose, container, false); // Get controls spFrom = view.findViewById(R.id.spFrom); ivIdentityAdd = view.findViewById(R.id.ivIdentityAdd); etTo = view.findViewById(R.id.etTo); ivToAdd = view.findViewById(R.id.ivToAdd); etCc = view.findViewById(R.id.etCc); ivCcAdd = view.findViewById(R.id.ivCcAdd); etBcc = view.findViewById(R.id.etBcc); ivBccAdd = view.findViewById(R.id.ivBccAdd); etSubject = view.findViewById(R.id.etSubject); rvAttachment = view.findViewById(R.id.rvAttachment); etBody = view.findViewById(R.id.etBody); bottom_navigation = view.findViewById(R.id.bottom_navigation); pbWait = view.findViewById(R.id.pbWait); grpAddresses = view.findViewById(R.id.grpAddresses); grpAttachments = view.findViewById(R.id.grpAttachments); grpReady = view.findViewById(R.id.grpReady); // Wire controls ivIdentityAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Bundle args = new Bundle(); args.putLong("id", -1); FragmentIdentity fragment = new FragmentIdentity(); fragment.setArguments(args); FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("identity"); fragmentTransaction.commit(); } }); ivToAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI); startActivityForResult(intent, ActivityCompose.REQUEST_CONTACT_TO); } }); ivCcAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI); startActivityForResult(intent, ActivityCompose.REQUEST_CONTACT_CC); } }); ivBccAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI); startActivityForResult(intent, ActivityCompose.REQUEST_CONTACT_BCC); } }); bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { onAction(item.getItemId()); return false; } }); setHasOptionsMenu(true); // Initialize spFrom.setVisibility(View.GONE); ivIdentityAdd.setVisibility(View.GONE); grpAddresses.setVisibility(View.GONE); grpAttachments.setVisibility(View.GONE); grpReady.setVisibility(View.GONE); pbWait.setVisibility(View.VISIBLE); bottom_navigation.getMenu().setGroupEnabled(0, false); if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { SimpleCursorAdapter adapter = new SimpleCursorAdapter( getContext(), android.R.layout.simple_list_item_2, null, new String[]{ ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.DATA }, new int[]{ android.R.id.text1, android.R.id.text2 }, 0); etTo.setAdapter(adapter); etCc.setAdapter(adapter); etBcc.setAdapter(adapter); adapter.setFilterQueryProvider(new FilterQueryProvider() { public Cursor runQuery(CharSequence typed) { return getContext().getContentResolver().query( ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[]{ ContactsContract.RawContacts._ID, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.DATA }, ContactsContract.CommonDataKinds.Email.DATA + " <> ''" + " AND (" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE '%" + typed + "%'" + " OR " + ContactsContract.CommonDataKinds.Email.DATA + " LIKE '%" + typed + "%')", null, "CASE WHEN " + ContactsContract.Contacts.DISPLAY_NAME + " NOT LIKE '%@%' THEN 0 ELSE 1 END" + ", " + ContactsContract.Contacts.DISPLAY_NAME + ", " + ContactsContract.CommonDataKinds.Email.DATA + " COLLATE NOCASE"); } }); adapter.setCursorToStringConverter(new SimpleCursorAdapter.CursorToStringConverter() { public CharSequence convertToString(Cursor cursor) { int colName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); int colEmail = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA); return cursor.getString(colName) + "<" + cursor.getString(colEmail) + ">"; } }); } rvAttachment.setHasFixedSize(false); LinearLayoutManager llm = new LinearLayoutManager(getContext()); rvAttachment.setLayoutManager(llm); adapter = new AdapterAttachment(getContext()); rvAttachment.setAdapter(adapter); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); DB.getInstance(getContext()).identity().liveIdentities(true).observe(getViewLifecycleOwner(), new Observer<List<EntityIdentity>>() { @Override public void onChanged(@Nullable final List<EntityIdentity> identities) { Log.i(Helper.TAG, "Set identities=" + identities.size()); // Sort identities Collections.sort(identities, new Comparator<EntityIdentity>() { @Override public int compare(EntityIdentity i1, EntityIdentity i2) { return i1.name.compareTo(i2.name); } }); // Show identities ArrayAdapter<EntityIdentity> adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, identities); adapter.setDropDownViewResource(R.layout.spinner_dropdown_item); spFrom.setAdapter(adapter); // Select primary identity for (int pos = 0; pos < identities.size(); pos++) if (identities.get(pos).primary) { spFrom.setSelection(pos); break; } spFrom.setVisibility(View.VISIBLE); ivIdentityAdd.setVisibility(View.VISIBLE); // Get draft, might select another identity Bundle args = new Bundle(); args.putString("action", action); args.putLong("id", id); args.putLong("account", account); args.putLong("reference", reference); getLoader.load(FragmentCompose.this, ActivityCompose.LOADER_COMPOSE_GET, args); } }); } @Override public void onPause() { if (!attaching) onAction(R.id.action_save); super.onPause(); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_compose, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); menu.findItem(R.id.menu_attachment).setEnabled(id >= 0); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_attachment: onMenuAttachment(); return true; case R.id.menu_addresses: onMenuAddresses(); return true; default: return super.onOptionsItemSelected(item); } } private void onMenuAttachment() { attaching = true; Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); startActivityForResult(intent, ActivityCompose.REQUEST_ATTACHMENT); } private void onMenuAddresses() { grpAddresses.setVisibility(grpAddresses.getVisibility() == View.GONE ? View.VISIBLE : View.GONE); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { if (requestCode == ActivityCompose.REQUEST_ATTACHMENT) { if (data != null) handleAddAttachment(data); } else handlePickContact(requestCode, data); } } private void handlePickContact(int requestCode, Intent data) { Cursor cursor = null; try { cursor = getContext().getContentResolver().query(data.getData(), new String[]{ ContactsContract.CommonDataKinds.Email.ADDRESS, ContactsContract.Contacts.DISPLAY_NAME }, null, null, null); if (cursor != null && cursor.moveToFirst()) { int colEmail = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS); int colName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); String email = cursor.getString(colEmail); String name = cursor.getString(colName); String text = null; if (requestCode == ActivityCompose.REQUEST_CONTACT_TO) text = etTo.getText().toString(); else if (requestCode == ActivityCompose.REQUEST_CONTACT_CC) text = etCc.getText().toString(); else if (requestCode == ActivityCompose.REQUEST_CONTACT_BCC) text = etBcc.getText().toString(); InternetAddress address = new InternetAddress(email, name); StringBuilder sb = new StringBuilder(text); if (sb.length() > 0) sb.append(", "); sb.append(address.toString()); if (requestCode == ActivityCompose.REQUEST_CONTACT_TO) etTo.setText(sb.toString()); else if (requestCode == ActivityCompose.REQUEST_CONTACT_CC) etCc.setText(sb.toString()); else if (requestCode == ActivityCompose.REQUEST_CONTACT_BCC) etBcc.setText(sb.toString()); } } catch (Throwable ex) { Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); } finally { if (cursor != null) cursor.close(); } } private void handleAddAttachment(Intent data) { Bundle args = new Bundle(); args.putLong("id", id); args.putParcelable("uri", data.getData()); new SimpleLoader<Void>() { @Override public Void onLoad(Bundle args) throws IOException { Cursor cursor = null; try { Uri uri = args.getParcelable("uri"); cursor = getContext().getContentResolver().query(uri, null, null, null, null, null); if (cursor == null || !cursor.moveToFirst()) return null; DB db = DB.getInstance(getContext()); long id = args.getLong("id"); EntityMessage draft = db.message().getMessage(id); EntityAttachment attachment = new EntityAttachment(); attachment.message = draft.id; attachment.sequence = db.attachment().getAttachmentCount(draft.id); attachment.name = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); String extension = MimeTypeMap.getFileExtensionFromUrl(attachment.name.toLowerCase()); if (extension != null) attachment.type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); if (extension == null) attachment.type = "application/octet-stream"; String size = cursor.getString(cursor.getColumnIndex(OpenableColumns.SIZE)); attachment.size = (size == null ? null : Integer.parseInt(size)); attachment.progress = 0; attachment.id = db.attachment().insertAttachment(attachment); InputStream is = null; try { is = getContext().getContentResolver().openInputStream(uri); ByteArrayOutputStream os = new ByteArrayOutputStream(); int len; byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE]; while ((len = is.read(buffer)) > 0) { os.write(buffer, 0, len); // Update progress if (attachment.size != null) { attachment.progress = os.size() * 100 / attachment.size; db.attachment().updateAttachment(attachment); } } attachment.size = os.size(); attachment.progress = null; attachment.content = os.toByteArray(); db.attachment().updateAttachment(attachment); } finally { if (is != null) is.close(); } return null; } finally { if (cursor != null) cursor.close(); } } @Override public void onLoaded(Bundle args, Void data) { attaching = false; } @Override public void onException(Bundle args, Throwable ex) { Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); } }.load(this, ActivityCompose.LOADER_COMPOSE_ATTACHMENT, args); } private void onAction(int action) { bottom_navigation.getMenu().setGroupEnabled(0, false); EntityIdentity identity = (EntityIdentity) spFrom.getSelectedItem(); Bundle args = new Bundle(); args.putLong("id", id); args.putInt("action", action); args.putLong("identity", identity == null ? -1 : identity.id); args.putString("to", etTo.getText().toString()); args.putString("cc", etCc.getText().toString()); args.putString("bcc", etBcc.getText().toString()); args.putString("subject", etSubject.getText().toString()); args.putString("body", etBody.getText().toString()); putLoader.load(this, ActivityCompose.LOADER_COMPOSE_PUT, args); } private SimpleLoader<EntityMessage> getLoader = new SimpleLoader<EntityMessage>() { @Override public EntityMessage onLoad(Bundle args) { String action = args.getString("action"); long id = args.getLong("id", -1); long account = args.getLong("account", -1); long reference = args.getLong("reference", -1); Log.i(Helper.TAG, "Get load action=" + action + " id=" + id + " account=" + account + " reference=" + reference); DB db = DB.getInstance(getContext()); EntityMessage draft = db.message().getMessage(id); if (draft == null) { if ("edit".equals(action)) throw new IllegalStateException("Message to edit not found"); try { db.beginTransaction(); EntityMessage ref = db.message().getMessage(reference); if (ref != null) account = ref.account; EntityFolder drafts; drafts = db.folder().getFolderByType(account, EntityFolder.DRAFTS); if (drafts == null) drafts = db.folder().getPrimaryDrafts(); draft = new EntityMessage(); draft.account = account; draft.folder = drafts.id; if (ref != null) { draft.thread = ref.thread; if ("reply".equals(action)) { draft.replying = ref.id; draft.to = (ref.reply == null || ref.reply.length == 0 ? ref.from : ref.reply); draft.from = ref.to; } else if ("reply_all".equals(action)) { draft.replying = ref.id; List<Address> addresses = new ArrayList<>(); if (draft.reply != null && ref.reply.length > 0) addresses.addAll(Arrays.asList(ref.reply)); else if (draft.from != null) addresses.addAll(Arrays.asList(ref.from)); if (draft.cc != null) addresses.addAll(Arrays.asList(ref.cc)); draft.to = addresses.toArray(new Address[0]); draft.from = ref.to; } else if ("forward".equals(action)) { //msg.replying = ref.id; draft.from = ref.to; } if ("reply".equals(action) || "reply_all".equals(action)) { draft.subject = getContext().getString(R.string.title_subject_reply, ref.subject); draft.body = String.format("<br><br>%s %s:<br><br>%s", Html.escapeHtml(new Date().toString()), Html.escapeHtml(TextUtils.join(", ", draft.to)), HtmlHelper.sanitize(getContext(), ref.body, true)); } else if ("forward".equals(action)) { draft.subject = getContext().getString(R.string.title_subject_forward, ref.subject); draft.body = String.format("<br><br>%s %s:<br><br>%s", Html.escapeHtml(new Date().toString()), Html.escapeHtml(TextUtils.join(", ", ref.from)), HtmlHelper.sanitize(getContext(), ref.body, true)); } } if ("new".equals(action)) draft.body = ""; draft.received = new Date().getTime(); draft.seen = false; draft.ui_seen = false; draft.ui_hide = false; draft.id = db.message().insertMessage(draft); draft.msgid = draft.generateMessageId(); db.message().updateMessage(draft); args.putLong("id", draft.id); EntityOperation.queue(db, draft, EntityOperation.ADD); db.setTransactionSuccessful(); } finally { db.endTransaction(); } EntityOperation.process(getContext()); } return draft; } @Override public void onLoaded(Bundle args, EntityMessage draft) { id = draft.id; if ("new".equals(args.getString("action"))) action = "edit"; Log.i(Helper.TAG, "Get loaded action=" + action + " id=" + id); getActivity().invalidateOptionsMenu(); pbWait.setVisibility(View.GONE); grpAddresses.setVisibility("reply_all".equals(action) ? View.VISIBLE : View.GONE); grpReady.setVisibility(View.VISIBLE); ArrayAdapter aa = (ArrayAdapter) spFrom.getAdapter(); if (aa != null) { for (int pos = 0; pos < aa.getCount(); pos++) { EntityIdentity identity = (EntityIdentity) aa.getItem(pos); if (draft.identity == null ? draft.from != null && draft.from.length > 0 && ((InternetAddress) draft.from[0]).getAddress().equals(identity.email) : draft.identity.equals(identity.id)) { spFrom.setSelection(pos); break; } } } etTo.setText(draft.to == null ? null : TextUtils.join(", ", draft.to)); etCc.setText(draft.cc == null ? null : TextUtils.join(", ", draft.cc)); etBcc.setText(draft.bcc == null ? null : TextUtils.join(", ", draft.bcc)); etSubject.setText(draft.subject); DB db = DB.getInstance(getContext()); db.attachment().liveAttachments(draft.id).removeObservers(getViewLifecycleOwner()); db.attachment().liveAttachments(draft.id).observe(getViewLifecycleOwner(), new Observer<List<TupleAttachment>>() { @Override public void onChanged(@Nullable List<TupleAttachment> attachments) { adapter.set(attachments); grpAttachments.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE); } }); etBody.setText(TextUtils.isEmpty(draft.body) ? null : Html.fromHtml(draft.body)); if ("edit".equals(action)) etTo.requestFocus(); else if ("reply".equals(action) || "reply_all".equals(action)) etBody.requestFocus(); else if ("forward".equals(action)) etTo.requestFocus(); bottom_navigation.getMenu().setGroupEnabled(0, true); } @Override public void onException(Bundle args, Throwable ex) { Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); } }; private SimpleLoader<EntityMessage> putLoader = new SimpleLoader<EntityMessage>() { @Override public EntityMessage onLoad(Bundle args) throws Throwable { // Get data long id = args.getLong("id"); int action = args.getInt("action"); long iid = args.getLong("identity"); String to = args.getString("to"); String cc = args.getString("cc"); String bcc = args.getString("bcc"); String subject = args.getString("subject"); String body = args.getString("body"); Log.i(Helper.TAG, "Put load action=" + action + " id=" + id); // Get draft & selected identity DB db = DB.getInstance(getContext()); EntityMessage draft = db.message().getMessage(id); EntityIdentity identity = db.identity().getIdentity(iid); // Draft deleted by server // TODO: better handling of remote deleted message if (draft == null) throw new MessageRemovedException(); // Convert data Address afrom[] = (identity == null ? null : new Address[]{new InternetAddress(identity.email, identity.name)}); Address ato[] = (TextUtils.isEmpty(to) ? null : InternetAddress.parse(to)); Address acc[] = (TextUtils.isEmpty(cc) ? null : InternetAddress.parse(cc)); Address abcc[] = (TextUtils.isEmpty(bcc) ? null : InternetAddress.parse(bcc)); // Update draft draft.identity = (identity == null ? null : identity.id); draft.from = afrom; draft.to = ato; draft.cc = acc; draft.bcc = abcc; draft.subject = subject; draft.body = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>"; draft.received = new Date().getTime(); db.message().updateMessage(draft); // Check data try { db.beginTransaction(); if (action == R.id.action_trash) { draft.ui_hide = true; db.message().updateMessage(draft); EntityFolder trash = db.folder().getFolderByType(draft.account, EntityFolder.TRASH); EntityOperation.queue(db, draft, EntityOperation.MOVE, trash.id); } else if (action == R.id.action_save) { String msgid = draft.msgid; // Save attachments List<EntityAttachment> attachments = db.attachment().getAttachments(draft.id); for (EntityAttachment attachment : attachments) attachment.content = db.attachment().getContent(attachment.id); // Delete previous draft draft.msgid = null; draft.ui_hide = true; db.message().updateMessage(draft); EntityOperation.queue(db, draft, EntityOperation.DELETE); // Create new draft draft.id = null; draft.uid = null; // unique index folder/uid draft.msgid = msgid; draft.ui_hide = false; draft.id = db.message().insertMessage(draft); // Restore attachments for (EntityAttachment attachment : attachments) { attachment.message = draft.id; db.attachment().insertAttachment(attachment); } EntityOperation.queue(db, draft, EntityOperation.ADD); } else if (action == R.id.action_send) { // Check data if (draft.identity == null) throw new IllegalArgumentException(getContext().getString(R.string.title_from_missing)); if (draft.to == null && draft.cc == null && draft.bcc == null) throw new IllegalArgumentException(getContext().getString(R.string.title_to_missing)); if (db.attachment().getAttachmentCountWithoutContent(draft.id) > 0) throw new IllegalArgumentException(getContext().getString(R.string.title_attachments_missing)); List<EntityAttachment> attachments = db.attachment().getAttachments(draft.id); for (EntityAttachment attachment : attachments) attachment.content = db.attachment().getContent(attachment.id); String msgid = draft.msgid; // Delete draft (cannot move to outbox) draft.msgid = null; draft.ui_hide = true; db.message().updateMessage(draft); EntityOperation.queue(db, draft, EntityOperation.DELETE); // Copy message to outbox draft.id = null; draft.folder = db.folder().getOutbox().id; draft.uid = null; draft.msgid = msgid; draft.ui_hide = false; draft.id = db.message().insertMessage(draft); for (EntityAttachment attachment : attachments) { attachment.message = draft.id; db.attachment().insertAttachment(attachment); } EntityOperation.queue(db, draft, EntityOperation.SEND); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } EntityOperation.process(getContext()); return draft; } @Override public void onLoaded(Bundle args, EntityMessage draft) { id = draft.id; int action = args.getInt("action"); Log.i(Helper.TAG, "Get loaded action=" + action + " id=" + id); bottom_navigation.getMenu().setGroupEnabled(0, true); if (action == R.id.action_trash) { getFragmentManager().popBackStack(); Toast.makeText(getContext(), R.string.title_draft_trashed, Toast.LENGTH_LONG).show(); } else if (action == R.id.action_save) Toast.makeText(getContext(), R.string.title_draft_saved, Toast.LENGTH_LONG).show(); else if (action == R.id.action_send) { getFragmentManager().popBackStack(); Toast.makeText(getContext(), R.string.title_queued, Toast.LENGTH_LONG).show(); } } @Override public void onException(Bundle args, Throwable ex) { bottom_navigation.getMenu().setGroupEnabled(0, true); if (ex instanceof IllegalArgumentException) Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); else Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); } }; }