disarmored = new ArrayList<>();
for (String line : lines)
if (!TextUtils.isEmpty(line) && !line.contains(": "))
disarmored.add(line);
String pgpMessage = TextUtils.join("\n\r", disarmored);
inline = true;
in = new ByteArrayInputStream(pgpMessage.getBytes());
out = new FileOutputStream(plain);
}
}
}
}
if (in == null)
if (auto)
return null;
else
throw new IllegalArgumentException(context.getString(R.string.title_not_encrypted));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean autocrypt = prefs.getBoolean("autocrypt", true);
if (autocrypt &&
message.from != null && message.from.length > 0 &&
message.autocrypt != null &&
OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(data.getAction()))
try {
String peer = ((InternetAddress) message.from[0]).getAddress();
String addr = null;
boolean mutual = false;
byte[] keydata = null;
// https://autocrypt.org/level1.html#the-autocrypt-header
String[] param = message.autocrypt.split(";");
for (int i = 0; i < param.length; i++) {
int e = param[i].indexOf("=");
if (e > 0) {
String key = param[i].substring(0, e).trim().toLowerCase(Locale.ROOT);
String value = param[i].substring(e + 1);
Log.i("Autocrypt " + key + "=" + value);
switch (key) {
case "addr":
addr = value;
break;
case "prefer-encrypt":
mutual = value.trim().toLowerCase(Locale.ROOT).equals("mutual");
break;
case "keydata":
keydata = Base64.decode(value, Base64.DEFAULT);
break;
}
}
}
if (addr == null)
throw new IllegalArgumentException("addr not found");
if (!addr.equalsIgnoreCase(peer))
throw new IllegalArgumentException("addr different from peer");
if (keydata == null)
throw new IllegalArgumentException("keydata not found");
AutocryptPeerUpdate update = AutocryptPeerUpdate.create(
keydata, new Date(message.received), mutual);
data.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, addr);
data.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE, update);
} catch (Throwable ex) {
Log.w(ex);
}
Intent result;
try {
// Decrypt message
Log.i("Executing " + data.getAction());
Log.logExtras(data);
OpenPgpApi api = new OpenPgpApi(context, pgpService.getService());
result = api.executeApi(data, in, out);
int resultCode = result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
Log.i("Result action=" + data.getAction() + " code=" + resultCode);
Log.logExtras(data);
switch (resultCode) {
case OpenPgpApi.RESULT_CODE_SUCCESS:
if (out != null)
if (inline) {
try {
db.beginTransaction();
// Write decrypted body
String text = Helper.readText(plain);
String html = "" + HtmlHelper.formatPre(text) + "
";
Helper.writeText(message.getFile(context), html);
db.message().setMessageStored(message.id, new Date().getTime());
db.message().setMessageFts(message.id, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
WorkerFts.init(context, false);
} else {
// Decode message
MessageHelper.MessageParts parts;
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
try (InputStream fis = new FileInputStream(plain)) {
MimeMessage imessage = new MimeMessage(isession, fis);
MessageHelper helper = new MessageHelper(imessage);
parts = helper.getMessageParts(context);
}
try {
db.beginTransaction();
// Write decrypted body
String html = parts.getHtml(context);
Helper.writeText(message.getFile(context), html);
// Remove existing attachments
db.attachment().deleteAttachments(message.id);
// Add decrypted attachments
List remotes = parts.getAttachments();
for (int index = 0; index < remotes.size(); index++) {
EntityAttachment remote = remotes.get(index);
remote.message = message.id;
remote.sequence = index + 1;
remote.id = db.attachment().insertAttachment(remote);
try {
parts.downloadAttachment(context, index, remote);
} catch (Throwable ex) {
Log.e(ex);
}
}
db.message().setMessageEncrypt(message.id, parts.getEncryption());
db.message().setMessageStored(message.id, new Date().getTime());
db.message().setMessageFts(message.id, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
WorkerFts.init(context, false);
}
// Check signature status
OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
int sresult = (sigResult == null ? RESULT_NO_SIGNATURE : sigResult.getResult());
if (sigResult == null)
Log.w("PGP signature result missing");
else
Log.i("PGP signature result=" + sresult);
if (sresult == RESULT_NO_SIGNATURE)
args.putString("sigresult", context.getString(R.string.title_signature_none));
else if (sresult == RESULT_VALID_KEY_CONFIRMED || sresult == RESULT_VALID_KEY_UNCONFIRMED) {
List users = sigResult.getConfirmedUserIds();
String text;
if (users.size() > 0)
text = getString(sresult == RESULT_VALID_KEY_UNCONFIRMED
? R.string.title_signature_unconfirmed_from
: R.string.title_signature_valid_from,
TextUtils.join(", ", users));
else
text = getString(sresult == RESULT_VALID_KEY_UNCONFIRMED
? R.string.title_signature_unconfirmed
: R.string.title_signature_valid);
args.putString("sigresult", text);
} else if (sresult == RESULT_KEY_MISSING)
args.putString("sigresult", context.getString(R.string.title_signature_key_missing));
else {
String text = getString(R.string.title_signature_invalid_reason, Integer.toString(sresult));
args.putString("sigresult", text);
}
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
if (auto)
return null;
return result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
throw new IllegalArgumentException(
"OpenPgp" +
" error " + (error == null ? "?" : error.getErrorId()) +
": " + (error == null ? "?" : error.getMessage()));
default:
throw new IllegalStateException("OpenPgp unknown result code=" + resultCode);
}
} finally {
plain.delete();
}
return null;
}
@Override
protected void onExecuted(Bundle args, PendingIntent pi) {
if (args.containsKey("sigresult")) {
String text = args.getString("sigresult");
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
}
if (pi != null)
try {
Log.i("Executing pi=" + pi);
startIntentSenderForResult(
pi.getIntentSender(),
REQUEST_OPENPGP,
null, 0, 0, 0, null);
} catch (IntentSender.SendIntentException ex) {
// Likely cancelled
Log.w(ex);
}
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException) {
Log.i(ex);
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
} else
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "decrypt:pgp");
}
private void onSmime(Bundle args) {
new SimpleTask() {
@Override
protected X509Certificate onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
int type = args.getInt("type");
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
X509Certificate result = null;
if (EntityMessage.SMIME_SIGNONLY.equals(type)) {
// Get content/signature
boolean sdata = false;
File content = null;
File signature = null;
List attachments = db.attachment().getAttachments(message.id);
for (EntityAttachment attachment : attachments)
if (EntityAttachment.SMIME_SIGNATURE.equals(attachment.encryption)) {
if (!attachment.available)
throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing));
signature = attachment.getFile(context);
} else if (EntityAttachment.SMIME_SIGNED_DATA.equals(attachment.encryption)) {
if (!attachment.available)
throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing));
sdata = true;
signature = attachment.getFile(context);
} else if (EntityAttachment.SMIME_CONTENT.equals(attachment.encryption)) {
if (!attachment.available)
throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing));
content = attachment.getFile(context);
}
if (content == null && !sdata)
throw new IllegalArgumentException("Signed content missing");
if (signature == null)
throw new IllegalArgumentException("Signature missing");
// Build signed data
InputStream is = null;
FileInputStream fis = new FileInputStream(signature);
CMSSignedData signedData;
// TODO: CMSSignedDataParser
if (sdata) {
signedData = new CMSSignedData(fis);
CMSTypedData sc = signedData.getSignedContent();
if (sc == null)
throw new IllegalArgumentException("Signed content missing");
is = new ByteArrayInputStream((byte[]) sc.getContent());
} else {
CMSProcessable signedContent = new CMSProcessableFile(content);
signedData = new CMSSignedData(signedContent, fis);
}
// Check signature
Store store = signedData.getCertificates();
SignerInformationStore signerInfos = signedData.getSignerInfos();
for (SignerInformation signer : signerInfos.getSigners()) {
for (Object match : store.getMatches(signer.getSID())) {
X509CertificateHolder certHolder = (X509CertificateHolder) match;
X509Certificate cert = new JcaX509CertificateConverter()
.getCertificate(certHolder);
try {
Date signingTime;
AttributeTable at = signer.getSignedAttributes();
Attribute attr = (at == null ? null : at.get(CMSAttributes.signingTime));
if (attr != null && attr.getAttrValues().size() == 1)
signingTime = Time.getInstance(attr.getAttrValues()
.getObjectAt(0).toASN1Primitive()).getDate();
else
signingTime = new Date(message.received);
args.putSerializable("time", signingTime);
SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder()
.build(cert);
SignerInformation s = new SignerInformation(signer) {
@Override
public AttributeTable getSignedAttributes() {
// The certificate validity will be check below
return super.getSignedAttributes().remove(CMSAttributes.signingTime);
}
};
if (s.verify(verifier)) {
boolean known = true;
String fingerprint = EntityCertificate.getFingerprint(cert);
List emails = EntityCertificate.getEmailAddresses(cert);
for (String email : emails) {
EntityCertificate record = db.certificate().getCertificate(fingerprint, email);
if (record == null)
known = false;
}
String sender = null;
if (message.from != null && message.from.length == 1)
sender = ((InternetAddress) message.from[0]).getAddress();
args.putString("sender", sender);
args.putBoolean("known", known);
List certs = new ArrayList<>();
try {
for (Object m : store.getMatches(null)) {
X509CertificateHolder h = (X509CertificateHolder) m;
certs.add(new JcaX509CertificateConverter().getCertificate(h));
}
} catch (Throwable ex) {
Log.w(ex);
}
try {
// https://tools.ietf.org/html/rfc3852#section-10.2.3
KeyStore ks = KeyStore.getInstance("AndroidCAStore");
ks.load(null, null);
// https://docs.oracle.com/javase/7/docs/technotes/guides/security/certpath/CertPathProgGuide.html
X509CertSelector target = new X509CertSelector();
target.setCertificate(cert);
// Load/store intermediate certificates
List local = new ArrayList<>(certs);
try {
List ecs = db.certificate().getIntermediateCertificate();
for (EntityCertificate ec : ecs)
local.add(ec.getCertificate());
for (X509Certificate c : certs) {
boolean[] usage = c.getKeyUsage();
boolean root = (usage != null && usage[5]);
boolean selfSigned = c.getIssuerX500Principal().equals(c.getSubjectX500Principal());
if (root && !selfSigned && ks.getCertificateAlias(c) == null) {
boolean found = false;
String issuer = (c.getIssuerDN() == null ? "" : c.getIssuerDN().getName());
EntityCertificate record = EntityCertificate.from(c, true, issuer);
for (EntityCertificate ec : ecs)
if (ec.fingerprint.equals(record.fingerprint)) {
found = true;
break;
}
if (!found) {
Log.i("Storing certificate subject=" + record.subject);
local.add(record.getCertificate());
db.certificate().insertCertificate(record);
}
}
}
} catch (Throwable ex) {
Log.e(ex);
}
// Intermediate certificates
Log.i("Intermediate certificates=" + local.size());
PKIXBuilderParameters params = new PKIXBuilderParameters(ks, target);
CertStoreParameters intermediates = new CollectionCertStoreParameters(local);
params.addCertStore(CertStore.getInstance("Collection", intermediates));
params.setRevocationEnabled(false);
params.setDate(signingTime);
CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
CertPathBuilderResult path = builder.build(params);
CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
cpv.validate(path.getCertPath(), params);
List pcerts = new ArrayList<>();
pcerts.addAll(path.getCertPath().getCertificates());
if (path instanceof PKIXCertPathValidatorResult) {
X509Certificate root = ((PKIXCertPathValidatorResult) path).getTrustAnchor().getTrustedCert();
if (root != null)
pcerts.add(root);
}
ArrayList trace = new ArrayList<>();
for (Certificate pcert : pcerts)
if (pcert instanceof X509Certificate) {
// https://tools.ietf.org/html/rfc5280#section-4.2.1.3
X509Certificate c = (X509Certificate) pcert;
boolean[] usage = c.getKeyUsage();
boolean root = (usage != null && usage[5]);
boolean selfSigned = c.getIssuerX500Principal().equals(c.getSubjectX500Principal());
EntityCertificate record = EntityCertificate.from(c, null);
trace.add((root ? "* " : "") + (selfSigned ? "# " : "") + record.subject);
}
args.putStringArrayList("trace", trace);
boolean valid = true;
for (Certificate pcert : pcerts)
try {
((X509Certificate) pcert).checkValidity(signingTime);
} catch (CertificateException ex) {
Log.w(ex);
valid = false;
break;
}
args.putBoolean("valid", valid);
} catch (Throwable ex) {
Log.w(ex);
args.putString("reason", ex.getMessage());
ArrayList trace = new ArrayList<>();
for (X509Certificate c : certs) {
boolean[] usage = c.getKeyUsage();
boolean root = (usage != null && usage[5]);
boolean selfSigned = c.getIssuerX500Principal().equals(c.getSubjectX500Principal());
EntityCertificate record = EntityCertificate.from(c, null);
trace.add((root ? "* " : "") + (selfSigned ? "# " : "") + record.subject);
}
args.putStringArrayList("trace", trace);
}
result = cert;
break;
} else
Log.w("Signature invalid");
} catch (CMSException ex) {
Log.w(ex);
args.putString("reason", ex.getMessage());
}
}
if (result != null)
break;
}
if (is != null)
decodeMessage(context, is, message, args);
} else {
// Check alias
String alias = args.getString("alias");
if (alias == null)
throw new IllegalArgumentException("Key alias missing");
// Get private key
PrivateKey privkey = KeyChain.getPrivateKey(context, alias);
if (privkey == null)
throw new IllegalArgumentException("Private key missing");
// Get public key
X509Certificate[] chain = KeyChain.getCertificateChain(context, alias);
if (chain == null || chain.length == 0)
throw new IllegalArgumentException("Public key missing");
// Get encrypted message
File input = null;
List attachments = db.attachment().getAttachments(message.id);
for (EntityAttachment attachment : attachments)
if (EntityAttachment.SMIME_MESSAGE.equals(attachment.encryption)) {
if (!attachment.available)
throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing));
input = attachment.getFile(context);
break;
}
if (input == null)
throw new IllegalArgumentException("Encrypted message missing");
int count = -1;
boolean decoded = false;
while (!decoded)
try (FileInputStream fis = new FileInputStream(input)) {
// Create parser
CMSEnvelopedDataParser envelopedData = new CMSEnvelopedDataParser(fis);
// Get recipient info
JceKeyTransRecipient recipient = new JceKeyTransEnvelopedRecipient(privkey);
Collection recipients = envelopedData.getRecipientInfos().getRecipients(); // KeyTransRecipientInformation
// Find recipient
if (count < 0) {
BigInteger serialno = chain[0].getSerialNumber();
for (RecipientInformation recipientInfo : recipients) {
KeyTransRecipientId recipientId = (KeyTransRecipientId) recipientInfo.getRID();
if (serialno != null && serialno.equals(recipientId.getSerialNumber())) {
try {
InputStream is = recipientInfo.getContentStream(recipient).getContentStream();
decodeMessage(context, is, message, args);
decoded = true;
} catch (CMSException ex) {
Log.w(ex);
}
break; // only one try
}
}
} else {
List list = new ArrayList<>(recipients);
if (count < list.size()) {
RecipientInformation recipientInfo = list.get(count);
try {
InputStream is = recipientInfo.getContentStream(recipient).getContentStream();
decodeMessage(context, is, message, args);
decoded = true;
break;
} catch (CMSException ex) {
Log.w(ex);
}
} else
break; // out of recipients
}
count++;
}
if (!decoded) {
if (message.identity != null)
db.identity().setIdentitySignKeyAlias(message.identity, null);
throw new IllegalArgumentException(context.getString(R.string.title_unknown_key));
}
}
return result;
}
@Override
protected void onExecuted(final Bundle args, X509Certificate cert) {
int type = args.getInt("type");
if (EntityMessage.SMIME_SIGNONLY.equals(type)) {
if (cert == null) {
String message;
String reason = args.getString("reason");
if (TextUtils.isEmpty(reason))
message = getString(R.string.title_signature_invalid);
else
message = getString(R.string.title_signature_invalid_reason, reason);
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
} else
try {
String sender = args.getString("sender");
Date time = (Date) args.getSerializable("time");
boolean known = args.getBoolean("known");
boolean valid = args.getBoolean("valid");
String reason = args.getString("reason");
final ArrayList trace = args.getStringArrayList("trace");
EntityCertificate record = EntityCertificate.from(cert, null);
if (time == null)
time = new Date();
boolean match = false;
List emails = EntityCertificate.getEmailAddresses(cert);
for (String email : emails)
if (email.equalsIgnoreCase(sender)) {
match = true;
break;
}
if (known && !record.isExpired(time) && match && valid)
Snackbar.make(view, R.string.title_signature_valid, Snackbar.LENGTH_LONG).show();
else {
LayoutInflater inflator = LayoutInflater.from(getContext());
View dview = inflator.inflate(R.layout.dialog_certificate, null);
TextView tvCertificateInvalid = dview.findViewById(R.id.tvCertificateInvalid);
TextView tvCertificateReason = dview.findViewById(R.id.tvCertificateReason);
TextView tvSender = dview.findViewById(R.id.tvSender);
TextView tvEmail = dview.findViewById(R.id.tvEmail);
TextView tvEmailInvalid = dview.findViewById(R.id.tvEmailInvalid);
TextView tvSubject = dview.findViewById(R.id.tvSubject);
TextView tvAfter = dview.findViewById(R.id.tvAfter);
TextView tvBefore = dview.findViewById(R.id.tvBefore);
TextView tvExpired = dview.findViewById(R.id.tvExpired);
tvCertificateInvalid.setVisibility(valid ? View.GONE : View.VISIBLE);
tvCertificateReason.setText(reason);
tvCertificateReason.setVisibility(reason == null ? View.GONE : View.VISIBLE);
tvSender.setText(sender);
tvEmail.setText(TextUtils.join(",", emails));
tvEmailInvalid.setVisibility(match ? View.GONE : View.VISIBLE);
tvSubject.setText(record.subject);
DateFormat TF = Helper.getDateTimeInstance(getContext(), SimpleDateFormat.SHORT, SimpleDateFormat.SHORT);
tvAfter.setText(record.after == null ? null : TF.format(record.after));
tvBefore.setText(record.before == null ? null : TF.format(record.before));
tvExpired.setVisibility(record.isExpired(time) ? View.VISIBLE : View.GONE);
if (trace != null && trace.size() > 0)
tvSubject.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < trace.size(); i++) {
if (i > 0)
sb.append("\n\n");
sb.append(i + 1).append(") ").append(trace.get(i));
}
new AlertDialog.Builder(getContext())
.setMessage(sb.toString())
.show();
}
});
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setView(dview)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.title_info, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Helper.viewFAQ(getContext(), 12);
}
});
if (!TextUtils.isEmpty(sender) && !known && emails.size() > 0)
builder.setPositiveButton(R.string.title_signature_store, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
args.putByteArray("encoded", cert.getEncoded());
new SimpleTask() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
byte[] encoded = args.getByteArray("encoded");
X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(encoded));
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
String fingerprint = EntityCertificate.getFingerprint(cert);
List emails = EntityCertificate.getEmailAddresses(cert);
for (String email : emails) {
EntityCertificate record = db.certificate().getCertificate(fingerprint, email);
if (record == null) {
record = EntityCertificate.from(cert, email);
record.id = db.certificate().insertCertificate(record);
}
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(FragmentMessages.this, args, "certificate:store");
} catch (Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}
});
builder.show();
}
} catch (Throwable ex) {
Snackbar.make(view, Log.formatThrowable(ex), Snackbar.LENGTH_LONG).show();
}
}
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException ||
ex instanceof CMSException || ex instanceof KeyChainException)
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
else
Log.unexpectedError(getParentFragmentManager(), ex);
}
private void decodeMessage(Context context, InputStream is, EntityMessage message, Bundle args) throws MessagingException, IOException {
String alias = args.getString("alias");
boolean duplicate = args.getBoolean("duplicate");
// Decode message
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
MimeMessage imessage = new MimeMessage(isession, is);
MessageHelper helper = new MessageHelper(imessage);
MessageHelper.MessageParts parts = helper.getMessageParts(context);
DB db = DB.getInstance(context);
try {
db.beginTransaction();
// Write decrypted body
String html = parts.getHtml(context);
Helper.writeText(message.getFile(context), html);
Log.i("s/mime html=" + (html == null ? null : html.length()));
// Remove existing attachments
db.attachment().deleteAttachments(message.id);
// Add decrypted attachments
List remotes = parts.getAttachments();
for (int index = 0; index < remotes.size(); index++) {
EntityAttachment remote = remotes.get(index);
remote.message = message.id;
remote.sequence = index + 1;
remote.id = db.attachment().insertAttachment(remote);
try {
parts.downloadAttachment(context, index, remote);
} catch (Throwable ex) {
Log.e(ex);
}
Log.i("s/mime attachment=" + remote);
}
db.message().setMessageEncrypt(message.id, parts.getEncryption());
db.message().setMessageStored(message.id, new Date().getTime());
db.message().setMessageFts(message.id, false);
if (alias != null && !duplicate && message.identity != null)
db.identity().setIdentitySignKeyAlias(message.identity, alias);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
WorkerFts.init(context, false);
}
}.execute(this, args, "decrypt:s/mime");
}
private void onDelete(long id) {
Bundle args = new Bundle();
args.putLong("id", id);
new SimpleTask() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityAccount account = db.account().getAccount(message.account);
if (account == null)
return null;
EntityFolder folder = db.folder().getFolder(message.folder);
if (folder == null)
return null;
if (EntityFolder.OUTBOX.equals(folder.type)) {
db.message().deleteMessage(id);
db.folder().setFolderError(message.folder, null);
if (message.identity != null) {
db.identity().setIdentityError(message.identity, null);
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel("send:" + message.identity, 1);
}
} else if (message.uid == null && account.protocol == EntityAccount.TYPE_IMAP) {
db.message().deleteMessage(id);
db.folder().setFolderError(message.folder, null);
} else
EntityOperation.queue(context, message, EntityOperation.DELETE);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ServiceSynchronize.eval(context, "delete");
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "message:delete");
}
private void onDelete(Bundle args) {
if (selectionTracker != null)
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)
continue;
EntityOperation.queue(context, message, EntityOperation.DELETE);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ServiceSynchronize.eval(context, "delete");
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "messages:delete:execute");
}
private void onJunk(Bundle args) {
new SimpleTask() {
@Override
protected Void onExecute(Context context, Bundle args) throws JSONException {
long id = args.getLong("id");
boolean block_sender = args.getBoolean("block_sender");
boolean block_domain = args.getBoolean("block_domain");
List whitelist = EmailProvider.getDomainNames(context);
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityFolder junk = db.folder().getFolderByType(message.account, EntityFolder.JUNK);
if (junk == null)
return null;
EntityOperation.queue(context, message, EntityOperation.MOVE, junk.id);
if ((block_sender || block_domain) &&
(message.from != null && message.from.length > 0)) {
EntityRule rule = EntityRule.blockSender(context, message, junk, block_domain, whitelist);
rule.id = db.rule().insertRule(rule);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ServiceSynchronize.eval(context, "junk");
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "message:junk");
}
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(getParentFragmentManager(), "messages:move:across");
} else
moveAskConfirmed(result);
}
private void onColor(long id, int color) {
if (!ActivityBilling.isPro(getContext())) {
startActivity(new Intent(getContext(), ActivityBilling.class));
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);
}
});
ServiceSynchronize.eval(context, "flag");
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "message:color");
}
private void onSnooze(Bundle args) {
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;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean flag_snoozed = prefs.getBoolean("flag_snoozed", false);
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return wakeup;
List messages = db.message().getMessagesByThread(
account, thread, threading ? null : id, null);
for (EntityMessage threaded : messages) {
db.message().setMessageSnoozed(threaded.id, wakeup);
db.message().setMessageUiIgnored(threaded.id, true);
if (flag_snoozed && threaded.folder.equals(message.folder))
EntityOperation.queue(context, threaded, EntityOperation.FLAG, wakeup != null);
EntityMessage.snooze(context, threaded.id, wakeup);
}
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) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "message:snooze");
}
private void onSnoozeSelection(Bundle args) {
if (!ActivityBilling.isPro(getContext())) {
startActivity(new Intent(getContext(), ActivityBilling.class));
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;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean flag_snoozed = prefs.getBoolean("flag_snoozed", false);
DB db = DB.getInstance(context);
try {
db.beginTransaction();
for (long id : ids) {
EntityMessage message = db.message().getMessage(id);
if (message == null)
continue;
List messages = db.message().getMessagesByThread(
message.account, message.thread, threading ? null : id, message.folder);
for (EntityMessage threaded : messages) {
db.message().setMessageSnoozed(threaded.id, wakeup);
db.message().setMessageUiIgnored(message.id, true);
if (flag_snoozed && threaded.folder.equals(message.folder))
EntityOperation.queue(context, threaded, EntityOperation.FLAG, wakeup != null);
EntityMessage.snooze(context, threaded.id, wakeup);
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "messages:snooze");
}
private void onMove(Bundle args) {
new SimpleTask>() {
@Override
protected ArrayList onExecute(Context context, Bundle args) {
long id = args.getLong("message");
long tid = args.getLong("folder");
boolean copy = args.getBoolean("copy");
boolean similar = args.getBoolean("similar");
ArrayList result = new ArrayList<>();
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return result;
EntityFolder target = db.folder().getFolder(tid);
if (target == null)
return result;
EntityAccount account = db.account().getAccount(target.account);
if (account != null) {
List messages = db.message().getMessagesByThread(
message.account, message.thread, threading && similar ? null : id, message.folder);
for (EntityMessage threaded : messages)
if (copy)
EntityOperation.queue(context, message, EntityOperation.COPY, tid);
else
result.add(new MessageTarget(threaded, account, target));
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (copy)
ServiceSynchronize.eval(context, "copy");
return result;
}
@Override
protected void onExecuted(Bundle args, ArrayList result) {
boolean copy = args.getBoolean("copy");
if (copy)
ToastEx.makeText(getContext(), R.string.title_completed, Toast.LENGTH_LONG).show();
else
moveAsk(result, false, !autoclose && onclose == null);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "message:move");
}
private void onPrint(Bundle args) {
new SimpleTask() {
private WebView printWebView = null;
@Override
protected String[] onExecute(Context context, Bundle args) throws IOException {
long id = args.getLong("id");
boolean headers = args.getBoolean("headers");
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
if (message == null || !message.content)
return null;
File file = message.getFile(context);
if (!file.exists())
return null;
List attachments = db.attachment().getAttachments(message.id);
if (attachments == null)
return null;
Document document = JsoupEx.parse(file);
HtmlHelper.truncate(document, false);
HtmlHelper.embedInlineImages(context, id, document, true);
Element header = document.createElement("p");
if (message.from != null && message.from.length > 0) {
Element span = document.createElement("span");
Element strong = document.createElement("strong");
strong.text(getString(R.string.title_from));
span.appendChild(strong);
span.appendText(" " + MessageHelper.formatAddresses(message.from));
span.appendElement("br");
header.appendChild(span);
}
if (message.to != null && message.to.length > 0) {
Element span = document.createElement("span");
Element strong = document.createElement("strong");
strong.text(getString(R.string.title_to));
span.appendChild(strong);
span.appendText(" " + MessageHelper.formatAddresses(message.to));
span.appendElement("br");
header.appendChild(span);
}
if (message.cc != null && message.cc.length > 0) {
Element span = document.createElement("span");
Element strong = document.createElement("strong");
strong.text(getString(R.string.title_cc));
span.appendChild(strong);
span.appendText(" " + MessageHelper.formatAddresses(message.cc));
span.appendElement("br");
header.appendChild(span);
}
{
DateFormat DTF = Helper.getDateTimeInstance(context, SimpleDateFormat.LONG, SimpleDateFormat.LONG);
Element span = document.createElement("span");
Element strong = document.createElement("strong");
strong.text(getString(R.string.title_received));
span.appendChild(strong);
span.appendText(" " + DTF.format(message.received));
span.appendElement("br");
header.appendChild(span);
}
if (!TextUtils.isEmpty(message.subject)) {
Element span = document.createElement("span");
span.appendText(message.subject);
span.appendElement("br");
header.appendChild(span);
}
if (headers && message.headers != null) {
header.appendElement("hr");
Element pre = document.createElement("pre");
pre.text(message.headers);
header.appendChild(pre);
}
header.appendElement("hr").appendElement("br");
document.prependChild(header);
boolean hasAttachments = false;
Element footer = document.createElement("p");
footer.appendElement("br").appendElement("hr");
for (EntityAttachment attachment : attachments)
if (attachment.isAttachment()) {
hasAttachments = true;
Element strong = document.createElement("strong");
strong.text(getString(R.string.title_attachment));
footer.appendChild(strong);
if (!TextUtils.isEmpty(attachment.name))
footer.appendText(" " + attachment.name);
if (attachment.size != null)
footer.appendText(" " + Helper.humanReadableByteCount(attachment.size, true));
footer.appendElement("br");
}
if (hasAttachments)
document.appendChild(footer);
return new String[]{message.subject, document.html()};
}
@Override
protected void onExecuted(Bundle args, final String[] data) {
if (data == null)
return;
// https://developer.android.com/training/printing/html-docs.html
printWebView = new WebView(getContext());
WebSettings settings = printWebView.getSettings();
settings.setLoadsImagesAutomatically(true);
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
settings.setAllowFileAccess(false);
printWebView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
@Override
public void onPageFinished(WebView view, String url) {
try {
if (printWebView == null)
return;
ActivityBase activity = (ActivityBase) getActivity();
if (activity == null)
return;
PrintManager printManager = (PrintManager) activity.getOriginalContext().getSystemService(Context.PRINT_SERVICE);
String jobName = getString(R.string.app_name);
if (!TextUtils.isEmpty(data[0]))
jobName += " - " + data[0];
PrintDocumentAdapter adapter = printWebView.createPrintDocumentAdapter(jobName);
printManager.print(jobName, adapter, new PrintAttributes.Builder().build());
} catch (Throwable ex) {
Log.e(ex);
} finally {
printWebView = null;
}
}
});
printWebView.loadDataWithBaseURL("about:blank", data[1], "text/html", StandardCharsets.UTF_8.name(), null);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "message:print");
}
private void onEmptyFolder(Bundle args) {
new SimpleTask() {
@Override
protected Void onExecute(Context context, Bundle args) {
long aid = args.getLong("account");
String type = args.getString("type");
EntityLog.log(context, "Empty account=" + account + " type=" + type);
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List accounts;
if (account < 0)
accounts = db.account().getSynchronizingAccounts();
else {
EntityAccount account = db.account().getAccount(aid);
if (account == null)
return null;
accounts = Arrays.asList(account);
}
for (EntityAccount account : accounts) {
EntityFolder folder = db.folder().getFolderByType(account.id, type);
EntityLog.log(context,
"Empty account=" + account.name + " folder=" + (folder == null ? null : folder.name));
if (folder == null)
continue;
List ids = db.message().getMessageByFolder(folder.id);
for (Long id : ids) {
EntityMessage message = db.message().getMessage(id);
if (message != null &&
(account.protocol == EntityAccount.TYPE_POP ||
message.uid != null || !TextUtils.isEmpty(message.msgid))) {
Log.i("Deleting account=" + account.id + " folder=" + folder.id + " message=" + message.id);
EntityOperation.queue(context, message, EntityOperation.DELETE);
}
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ServiceSynchronize.eval(context, "delete");
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "folder:delete");
}
private void onBoundaryRetry() {
ViewModelMessages model = new ViewModelProvider(getActivity()).get(ViewModelMessages.class);
model.retry(viewType);
}
private void onPickContact(Uri contactUri) {
String name = kv.get("name");
String email = kv.get("email");
// This requires contacts permission
ContentResolver resolver = getContext().getContentResolver();
Uri lookupUri = ContactsContract.Contacts.getLookupUri(resolver, contactUri);
Intent edit = new Intent();
edit.putExtra(ContactsContract.Intents.Insert.EMAIL, email);
if (!TextUtils.isEmpty(name))
edit.putExtra(ContactsContract.Intents.Insert.NAME, name);
edit.setAction(Intent.ACTION_EDIT);
edit.setDataAndTypeAndNormalize(lookupUri, ContactsContract.Contacts.CONTENT_ITEM_TYPE);
startActivity(edit);
}
static void search(
final Context context, final LifecycleOwner owner, final FragmentManager manager,
long account, long folder, boolean server, String query) {
search(context, owner, manager,
account, folder,
server, new BoundaryCallbackMessages.SearchCriteria(query));
}
static void search(
final Context context, final LifecycleOwner owner, final FragmentManager manager,
long account, long folder, boolean server, BoundaryCallbackMessages.SearchCriteria criteria) {
if (server && !ActivityBilling.isPro(context)) {
context.startActivity(new Intent(context, ActivityBilling.class));
return;
}
if (owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED))
manager.popBackStack("search", FragmentManager.POP_BACK_STACK_INCLUSIVE);
Bundle args = new Bundle();
args.putLong("account", account);
args.putLong("folder", folder);
args.putBoolean("server", server);
args.putSerializable("criteria", criteria);
FragmentMessages fragment = new FragmentMessages();
fragment.setArguments(args);
FragmentTransaction fragmentTransaction = manager.beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("search");
fragmentTransaction.commit();
}
private class ReplyData {
List identities;
List answers;
}
private class MoreResult {
boolean seen;
boolean unseen;
boolean visible;
boolean hidden;
boolean flagged;
boolean unflagged;
Integer importance;
Boolean hasArchive;
Boolean hasTrash;
Boolean hasJunk;
Boolean isArchive;
Boolean isTrash;
Boolean isJunk;
Boolean isDrafts;
List folders;
List accounts;
}
private static class MessageTarget implements Parcelable {
long id;
boolean across;
EntityAccount account;
EntityFolder folder;
MessageTarget(EntityMessage message, EntityAccount account, EntityFolder folder) {
this.id = message.id;
this.across = !folder.account.equals(message.account);
this.account = account;
this.folder = folder;
}
protected MessageTarget(Parcel in) {
id = in.readLong();
across = (in.readInt() != 0);
account = (EntityAccount) in.readSerializable();
folder = (EntityFolder) in.readSerializable();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeInt(across ? 1 : 0);
dest.writeSerializable(account);
dest.writeSerializable(folder);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator CREATOR = new Creator() {
@Override
public MessageTarget createFromParcel(Parcel in) {
return new MessageTarget(in);
}
@Override
public MessageTarget[] newArray(int size) {
return new MessageTarget[size];
}
};
}
public static class FragmentDialogReporting extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_error_reporting, null);
Button btnInfo = dview.findViewById(R.id.btnInfo);
btnInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(getContext(), 104);
}
});
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("crash_reports", true).apply();
Log.setCrashReporting(true);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("crash_reports_asked", true).apply();
}
})
.create();
}
}
public static class FragmentDialogReview extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_review, null);
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("review_asked", true).apply();
startActivity(Helper.getIntentRate(getContext()));
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("review_asked", true).apply();
}
})
.setNeutralButton(R.string.title_later, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putLong("review_later", new Date().getTime()).apply();
}
})
.create();
}
}
public static class FragmentDialogAccount extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final ArrayAdapter adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1);
// TODO: spinner
new SimpleTask>() {
@Override
protected List onExecute(Context context, Bundle args) {
DB db = DB.getInstance(context);
return db.account().getSynchronizingAccounts();
}
@Override
protected void onExecuted(Bundle args, List accounts) {
adapter.addAll(accounts);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, new Bundle(), "messages:accounts");
return new AlertDialog.Builder(getContext())
.setAdapter(adapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EntityAccount account = adapter.getItem(which);
getArguments().putLong("account", account.id);
sendResult(RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
public static class FragmentDialogIdentity extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_identity, null);
final Spinner spIdentity = dview.findViewById(R.id.spIdentity);
final CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
final Button btnFix = dview.findViewById(R.id.btnFix);
final Group grpIdentities = dview.findViewById(R.id.grpIdentities);
final Group grpNoIdentities = dview.findViewById(R.id.grpNoIdentities);
final ContentLoadingProgressBar pbWait = dview.findViewById(R.id.pbWait);
cbNotAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("identities_asked", isChecked).apply();
}
});
btnFix.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(getContext(), ActivitySetup.class));
getActivity().finish();
dismiss();
}
});
grpIdentities.setVisibility(View.GONE);
grpNoIdentities.setVisibility(View.GONE);
new SimpleTask>() {
@Override
protected void onPreExecute(Bundle args) {
pbWait.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Bundle args) {
pbWait.setVisibility(View.GONE);
}
@Override
protected List onExecute(Context context, Bundle args) {
DB db = DB.getInstance(getContext());
return db.identity().getComposableIdentities(null);
}
@Override
protected void onExecuted(Bundle args, List identities) {
AdapterIdentitySelect iadapter = new AdapterIdentitySelect(getContext(), identities);
spIdentity.setAdapter(iadapter);
Integer fallback = null;
long account = getArguments().getLong("account");
for (int pos = 0; pos < identities.size(); pos++) {
EntityIdentity identity = identities.get(pos);
if (identity.account.equals(account)) {
if (identity.primary) {
fallback = null;
spIdentity.setSelection(pos);
break;
}
if (fallback == null)
fallback = pos;
}
}
if (fallback != null)
spIdentity.setSelection(fallback);
grpIdentities.setVisibility(identities.size() > 0 ? View.VISIBLE : View.GONE);
grpNoIdentities.setVisibility(identities.size() > 0 ? View.GONE : View.VISIBLE);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, new Bundle(), "identity:select");
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TupleIdentityEx identity = (TupleIdentityEx) spIdentity.getSelectedItem();
if (identity != null)
startActivity(new Intent(getContext(), ActivityCompose.class)
.putExtra("action", "new")
.putExtra("account", identity.account)
.putExtra("identity", identity.id)
);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
public static class FragmentDialogBoundaryError extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
String error = getArguments().getString("error");
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_boundary_error, null);
TextView tvError = dview.findViewById(R.id.tvError);
tvError.setText(error);
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(R.string.title_boundary_retry, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
}
}