mirror of https://github.com/M66B/FairEmail.git
parent
d8298fb615
commit
d4662d1b8a
Binary file not shown.
|
@ -67,6 +67,8 @@ dependencies {
|
|||
// https://developer.android.com/topic/libraries/architecture/adding-components.html
|
||||
// https://developer.android.com/jetpack/docs/release-notes
|
||||
|
||||
// https://github.com/open-keychain/openpgp-api
|
||||
|
||||
def androidx_version = "1.0.0-rc01"
|
||||
def constraintlayout_version = "1.1.2"
|
||||
def lifecycle_version = "2.0.0-rc01"
|
||||
|
@ -76,6 +78,7 @@ dependencies {
|
|||
def javamail_version = "1.6.0"
|
||||
def jsoup_version = "1.11.3"
|
||||
def jcharset_version = "2.0"
|
||||
def openpgp_version = "12.0"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$androidx_version"
|
||||
implementation "androidx.recyclerview:recyclerview:$androidx_version"
|
||||
|
@ -99,4 +102,6 @@ dependencies {
|
|||
implementation "org.jsoup:jsoup:$jsoup_version"
|
||||
|
||||
implementation "net.freeutils:jcharset:$jcharset_version"
|
||||
|
||||
implementation "org.sufficientlysecure:openpgp-api:$openpgp_version"
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ public class ActivityCompose extends ActivityBase implements FragmentManager.OnB
|
|||
static final int REQUEST_CONTACT_CC = 2;
|
||||
static final int REQUEST_CONTACT_BCC = 3;
|
||||
static final int REQUEST_ATTACHMENT = 4;
|
||||
static final int REQUEST_OPENPGP = 5;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
|
|
@ -91,6 +91,8 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
|||
|
||||
static final int REQUEST_VIEW = 1;
|
||||
static final int REQUEST_UNSEEN = 2;
|
||||
static final int REQUEST_OPENPGP = 3;
|
||||
static final int REQUEST_ATTACHMENT_OFFSET = 10;
|
||||
|
||||
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
|
||||
static final String ACTION_VIEW_MESSAGE = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGE";
|
||||
|
@ -692,7 +694,7 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
|||
create.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
create.setType(intent.getStringExtra("type"));
|
||||
create.putExtra(Intent.EXTRA_TITLE, intent.getStringExtra("name"));
|
||||
startActivityForResult(create, (int) intent.getLongExtra("id", -1));
|
||||
startActivityForResult(create, (int) intent.getLongExtra("id", -1) + REQUEST_ATTACHMENT_OFFSET);
|
||||
|
||||
} else if (ACTION_ACTIVATE_PRO.equals(intent.getAction())) {
|
||||
try {
|
||||
|
@ -724,9 +726,9 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
|
|||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (resultCode == Activity.RESULT_OK && requestCode > REQUEST_ATTACHMENT_OFFSET) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", requestCode);
|
||||
args.putLong("id", requestCode - REQUEST_ATTACHMENT_OFFSET);
|
||||
args.putParcelable("uri", data.getData());
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
|
|
|
@ -453,6 +453,7 @@ public class FragmentAccount extends FragmentEx {
|
|||
account.password = password;
|
||||
account.synchronize = synchronize;
|
||||
account.primary = (account.synchronize && primary);
|
||||
account.store_sent = false;
|
||||
account.poll_interval = Integer.parseInt(poll_interval);
|
||||
|
||||
if (!synchronize)
|
||||
|
|
|
@ -20,6 +20,7 @@ package eu.faircode.email;
|
|||
*/
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
|
@ -54,7 +55,13 @@ import android.widget.Toast;
|
|||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -110,8 +117,27 @@ public class FragmentCompose extends FragmentEx {
|
|||
private boolean addresses;
|
||||
private boolean autosave = true;
|
||||
|
||||
private String encrypted = null;
|
||||
private OpenPgpServiceConnection openPgpConnection = null;
|
||||
|
||||
private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
openPgpConnection = new OpenPgpServiceConnection(getContext(), "org.sufficientlysecure.keychain");
|
||||
openPgpConnection.bindToService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (openPgpConnection != null) {
|
||||
openPgpConnection.unbindFromService();
|
||||
openPgpConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
@ -317,6 +343,7 @@ public class FragmentCompose extends FragmentEx {
|
|||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putLong("working", working);
|
||||
outState.putString("encrypted", encrypted);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -337,6 +364,8 @@ public class FragmentCompose extends FragmentEx {
|
|||
args.putParcelableArrayList("attachments", getArguments().getParcelableArrayList("attachments"));
|
||||
draftLoader.load(this, args);
|
||||
} else {
|
||||
encrypted = savedInstanceState.getString("encrypted");
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString("action", "edit");
|
||||
args.putLong("id", savedInstanceState.getLong("working"));
|
||||
|
@ -365,6 +394,7 @@ public class FragmentCompose extends FragmentEx {
|
|||
menu.findItem(R.id.menu_attachment).setVisible(!free && working >= 0);
|
||||
menu.findItem(R.id.menu_attachment).setEnabled(etBody.isEnabled());
|
||||
menu.findItem(R.id.menu_addresses).setVisible(!free && working >= 0);
|
||||
menu.findItem(R.id.menu_encrypt).setVisible(encrypted == null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -376,6 +406,9 @@ public class FragmentCompose extends FragmentEx {
|
|||
case R.id.menu_addresses:
|
||||
onMenuAddresses();
|
||||
return true;
|
||||
case R.id.menu_encrypt:
|
||||
onMenuEncrypt();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
@ -392,12 +425,77 @@ public class FragmentCompose extends FragmentEx {
|
|||
grpAddresses.setVisibility(grpAddresses.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void onMenuEncrypt() {
|
||||
Log.i(Helper.TAG, "On encrypt");
|
||||
try {
|
||||
if (openPgpConnection == null)
|
||||
throw new IllegalArgumentException();
|
||||
if (!openPgpConnection.isBound())
|
||||
throw new IllegalArgumentException("OpenPgp not available");
|
||||
|
||||
EntityIdentity identity = (EntityIdentity) spFrom.getSelectedItem();
|
||||
if (identity == null)
|
||||
throw new IllegalArgumentException("No identity selected");
|
||||
|
||||
Intent data = new Intent();
|
||||
data.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
||||
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[]{identity.email});
|
||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
String plain = etBody.getText().toString();
|
||||
final InputStream is = new ByteArrayInputStream(plain.getBytes("UTF-8"));
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
OpenPgpApi api = new OpenPgpApi(getContext(), openPgpConnection.getService());
|
||||
api.executeApiAsync(data, is, os, new OpenPgpApi.IOpenPgpCallback() {
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
try {
|
||||
int code = result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||
switch (code) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS: {
|
||||
Log.i(Helper.TAG, "Encrypted");
|
||||
FragmentCompose.this.encrypted = os.toString("UTF-8");
|
||||
getActivity().invalidateOptionsMenu();
|
||||
etBody.setText(FragmentCompose.this.encrypted);
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
|
||||
Log.i(Helper.TAG, "User interaction");
|
||||
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
getActivity().startIntentSenderForResult(
|
||||
pi.getIntentSender(),
|
||||
ActivityCompose.REQUEST_OPENPGP,
|
||||
null, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_ERROR: {
|
||||
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
||||
Log.e(Helper.TAG, error.getMessage());
|
||||
Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@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 if (requestCode == ActivityCompose.REQUEST_OPENPGP) {
|
||||
Log.i(Helper.TAG, "User interacted");
|
||||
onMenuEncrypt();
|
||||
} else
|
||||
handlePickContact(requestCode, data);
|
||||
}
|
||||
|
@ -531,7 +629,6 @@ public class FragmentCompose extends FragmentEx {
|
|||
if (attachment.type == null)
|
||||
attachment.type = "application/octet-stream";
|
||||
|
||||
|
||||
attachment.size = (s == null ? null : Integer.parseInt(s));
|
||||
attachment.progress = 0;
|
||||
|
||||
|
@ -903,7 +1000,11 @@ public class FragmentCompose extends FragmentEx {
|
|||
draft.subject = subject;
|
||||
draft.received = new Date().getTime();
|
||||
|
||||
String pbody = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
String pbody;
|
||||
if (encrypted == null)
|
||||
pbody = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
else
|
||||
pbody = encrypted;
|
||||
|
||||
// Execute action
|
||||
if (action == R.id.action_trash) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package eu.faircode.email;
|
|||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
|
@ -55,8 +56,13 @@ import android.widget.Toast;
|
|||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
@ -73,6 +79,8 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
@ -86,6 +94,8 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class FragmentMessage extends FragmentEx {
|
||||
private ViewGroup view;
|
||||
private TextView tvFrom;
|
||||
|
@ -116,11 +126,30 @@ public class FragmentMessage extends FragmentEx {
|
|||
private boolean free = false;
|
||||
private AdapterAttachment adapter;
|
||||
|
||||
private String decrypted = null;
|
||||
private OpenPgpServiceConnection openPgpConnection = null;
|
||||
|
||||
private boolean debug;
|
||||
private DateFormat df = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
|
||||
|
||||
private static final long CACHE_IMAGE_DURATION = 24 * 3600 * 1000L;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
openPgpConnection = new OpenPgpServiceConnection(getContext(), "org.sufficientlysecure.keychain");
|
||||
openPgpConnection.bindToService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (openPgpConnection != null) {
|
||||
openPgpConnection.unbindFromService();
|
||||
openPgpConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
@ -611,6 +640,7 @@ public class FragmentMessage extends FragmentEx {
|
|||
menu.findItem(R.id.menu_seen).setVisible(!free && message != null && !inOutbox);
|
||||
menu.findItem(R.id.menu_forward).setVisible(!free && message != null && !inOutbox);
|
||||
menu.findItem(R.id.menu_reply_all).setVisible(!free && message != null && message.cc != null && !inOutbox);
|
||||
menu.findItem(R.id.menu_decrypt).setVisible(decrypted == null);
|
||||
|
||||
if (message != null) {
|
||||
MenuItem menuSeen = menu.findItem(R.id.menu_seen);
|
||||
|
@ -639,6 +669,9 @@ public class FragmentMessage extends FragmentEx {
|
|||
case R.id.menu_reply_all:
|
||||
onMenuReplyAll(message.id);
|
||||
return true;
|
||||
case R.id.menu_decrypt:
|
||||
onMenuDecrypt(message);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
@ -719,6 +752,75 @@ public class FragmentMessage extends FragmentEx {
|
|||
.putExtra("reference", id));
|
||||
}
|
||||
|
||||
private void onMenuDecrypt(EntityMessage message) {
|
||||
Log.i(Helper.TAG, "On decrypt");
|
||||
try {
|
||||
if (openPgpConnection == null)
|
||||
throw new IllegalArgumentException();
|
||||
if (!openPgpConnection.isBound())
|
||||
throw new IllegalArgumentException("OpenPgp not available");
|
||||
|
||||
InternetAddress to = (InternetAddress) message.to[0];
|
||||
|
||||
Intent data = new Intent();
|
||||
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[]{to.getAddress()});
|
||||
|
||||
String encrypted = message.read(getContext());
|
||||
final InputStream is = new ByteArrayInputStream(encrypted.getBytes("UTF-8"));
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
OpenPgpApi api = new OpenPgpApi(getContext(), openPgpConnection.getService());
|
||||
api.executeApiAsync(data, is, os, new OpenPgpApi.IOpenPgpCallback() {
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
try {
|
||||
int code = result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||
switch (code) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS: {
|
||||
Log.i(Helper.TAG, "Decrypted");
|
||||
FragmentMessage.this.decrypted = os.toString("UTF-8");
|
||||
getActivity().invalidateOptionsMenu();
|
||||
tvBody.setText(decrypted);
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
|
||||
Log.i(Helper.TAG, "User interaction");
|
||||
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
getActivity().startIntentSenderForResult(
|
||||
pi.getIntentSender(),
|
||||
ActivityView.REQUEST_OPENPGP,
|
||||
null, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_ERROR: {
|
||||
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
||||
Log.e(Helper.TAG, error.getMessage());
|
||||
Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (requestCode == ActivityView.REQUEST_OPENPGP && message != null) {
|
||||
Log.i(Helper.TAG, "User interacted");
|
||||
onMenuDecrypt(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onActionSpam(final long id) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder
|
||||
|
|
|
@ -13,4 +13,9 @@
|
|||
android:icon="@drawable/baseline_people_24"
|
||||
android:title="@string/title_show_addresses"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_encrypt"
|
||||
android:title="@string/title_encrypt"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
|
|
@ -31,4 +31,9 @@
|
|||
android:icon="@drawable/baseline_reply_all_24"
|
||||
android:title="@string/title_reply_all"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_decrypt"
|
||||
android:title="@string/title_decrypt"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
|
|
@ -160,6 +160,9 @@
|
|||
<string name="title_show_addresses">Show CC/BCC</string>
|
||||
<string name="title_add_attachment">Add attachment</string>
|
||||
|
||||
<string name="title_encrypt">Encrypt</string>
|
||||
<string name="title_decrypt">Decrypt</string>
|
||||
|
||||
<string name="title_from_missing">Sender missing</string>
|
||||
<string name="title_to_missing">Recipient missing</string>
|
||||
<string name="title_attachments_missing">Attachments still loading</string>
|
||||
|
|
Loading…
Reference in New Issue