1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-27 03:32:16 -05:00

Remove legacy APG interface

This commit is contained in:
cketti 2014-09-18 19:45:25 +02:00
parent 99991e6651
commit 352fb8fd25
14 changed files with 15 additions and 1146 deletions

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.fsck.k9.view.MessageCryptoView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_decrypt"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<LinearLayout
android:id="@+id/crypto_signature"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight="2"
android:gravity="center_vertical">
<RelativeLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content">
<ImageView
android:id="@+id/ic_crypto_signature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/signed_large"/>
<ImageView
android:id="@+id/ic_crypto_signature_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/overlay_error"/>
</RelativeLayout>
<LinearLayout
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingLeft="5dip">
<TextView
android:id="@+id/userId"
android:textAppearance="?android:attr/textAppearanceMedium"
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"/>
<TextView
android:id="@+id/userIdRest"
android:textAppearance="?android:attr/textAppearanceSmall"
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"/>
</LinearLayout>
</LinearLayout>
<Button
android:text="@string/btn_decrypt"
android:id="@+id/btn_decrypt"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</com.fsck.k9.view.MessageCryptoView>

View File

@ -250,8 +250,6 @@
</LinearLayout>
<include layout="@layout/message_view_crypto_layout" />
<include layout="@layout/message_view_openpgp_layout"/>
</com.fsck.k9.view.MessageHeader>

View File

@ -569,6 +569,7 @@ Please submit bug reports, contribute new features and ask questions at
<string name="account_settings_crypto_auto_encrypt">Auto-encrypt</string>
<string name="account_settings_crypto_auto_encrypt_summary">Automatically set encrypt if a public key matches a recipient.</string>
<string name="account_settings_crypto_apg_not_installed">APG not installed</string>
<string name="account_settings_no_openpgp_provider_installed">No OpenPGP Provider installed</string>
<string name="account_settings_mail_check_frequency_label">Folder poll frequency</string>

View File

@ -24,8 +24,6 @@ import android.net.Uri;
import android.util.Log;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.crypto.Apg;
import com.fsck.k9.crypto.CryptoProvider;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException;
@ -135,6 +133,7 @@ public class Account implements BaseAccount {
public static final SortType DEFAULT_SORT_TYPE = SortType.SORT_DATE;
public static final boolean DEFAULT_SORT_ASCENDING = false;
public static final String NO_OPENPGP_PROVIDER = "";
/**
@ -206,16 +205,12 @@ public class Account implements BaseAccount {
private boolean mStripSignature;
private boolean mSyncRemoteDeletions;
private String mCryptoApp;
private boolean mCryptoAutoSignature;
private boolean mCryptoAutoEncrypt;
private boolean mMarkMessageAsReadOnView;
private boolean mAlwaysShowCcBcc;
private boolean mAllowRemoteSearch;
private boolean mRemoteSearchFullText;
private int mRemoteSearchNumResults;
private CryptoProvider mCryptoProvider = null;
private ColorChip mUnreadColorChip;
private ColorChip mReadColorChip;
@ -303,9 +298,7 @@ public class Account implements BaseAccount {
mReplyAfterQuote = DEFAULT_REPLY_AFTER_QUOTE;
mStripSignature = DEFAULT_STRIP_SIGNATURE;
mSyncRemoteDeletions = true;
mCryptoApp = Apg.NAME;
mCryptoAutoSignature = false;
mCryptoAutoEncrypt = false;
mCryptoApp = NO_OPENPGP_PROVIDER;
mAllowRemoteSearch = false;
mRemoteSearchFullText = false;
mRemoteSearchNumResults = DEFAULT_REMOTE_SEARCH_NUM_RESULTS;
@ -492,9 +485,7 @@ public class Account implements BaseAccount {
mIsSignatureBeforeQuotedText = prefs.getBoolean(mUuid + ".signatureBeforeQuotedText", false);
identities = loadIdentities(prefs);
mCryptoApp = prefs.getString(mUuid + ".cryptoApp", Apg.NAME);
mCryptoAutoSignature = prefs.getBoolean(mUuid + ".cryptoAutoSignature", false);
mCryptoAutoEncrypt = prefs.getBoolean(mUuid + ".cryptoAutoEncrypt", false);
mCryptoApp = prefs.getString(mUuid + ".cryptoApp", NO_OPENPGP_PROVIDER);
mAllowRemoteSearch = prefs.getBoolean(mUuid + ".allowRemoteSearch", false);
mRemoteSearchFullText = prefs.getBoolean(mUuid + ".remoteSearchFullText", false);
mRemoteSearchNumResults = prefs.getInt(mUuid + ".remoteSearchNumResults", DEFAULT_REMOTE_SEARCH_NUM_RESULTS);
@ -756,8 +747,6 @@ public class Account implements BaseAccount {
editor.putBoolean(mUuid + ".replyAfterQuote", mReplyAfterQuote);
editor.putBoolean(mUuid + ".stripSignature", mStripSignature);
editor.putString(mUuid + ".cryptoApp", mCryptoApp);
editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature);
editor.putBoolean(mUuid + ".cryptoAutoEncrypt", mCryptoAutoEncrypt);
editor.putBoolean(mUuid + ".allowRemoteSearch", mAllowRemoteSearch);
editor.putBoolean(mUuid + ".remoteSearchFullText", mRemoteSearchFullText);
editor.putInt(mUuid + ".remoteSearchNumResults", mRemoteSearchNumResults);
@ -1646,24 +1635,6 @@ public class Account implements BaseAccount {
public void setCryptoApp(String cryptoApp) {
mCryptoApp = cryptoApp;
// invalidate the provider
mCryptoProvider = null;
}
public boolean getCryptoAutoSignature() {
return mCryptoAutoSignature;
}
public void setCryptoAutoSignature(boolean cryptoAutoSignature) {
mCryptoAutoSignature = cryptoAutoSignature;
}
public boolean isCryptoAutoEncrypt() {
return mCryptoAutoEncrypt;
}
public void setCryptoAutoEncrypt(boolean cryptoAutoEncrypt) {
mCryptoAutoEncrypt = cryptoAutoEncrypt;
}
public boolean allowRemoteSearch() {
@ -1706,13 +1677,6 @@ public class Account implements BaseAccount {
lastSelectedFolderName = folderName;
}
public synchronized CryptoProvider getCryptoProvider() {
if (mCryptoProvider == null) {
mCryptoProvider = CryptoProvider.createInstance(getCryptoApp());
}
return mCryptoProvider;
}
public synchronized String getOpenPgpProvider() {
// return null if set to "APG" or "None"
if (getCryptoApp().equals("apg") || getCryptoApp().equals("")) {

View File

@ -82,7 +82,6 @@ import com.fsck.k9.activity.loader.AttachmentInfoLoader;
import com.fsck.k9.activity.misc.Attachment;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.crypto.CryptoProvider;
import com.fsck.k9.crypto.OpenPgpApiHelper;
import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.fragment.ProgressDialogFragment;
@ -127,9 +126,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1;
private static final int DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED = 2;
private static final int DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY = 3;
private static final int DIALOG_CONFIRM_DISCARD_ON_BACK = 4;
private static final int DIALOG_CHOOSE_IDENTITY = 5;
private static final int DIALOG_CONFIRM_DISCARD_ON_BACK = 3;
private static final int DIALOG_CHOOSE_IDENTITY = 4;
private static final long INVALID_DRAFT_ID = MessagingController.INVALID_MESSAGE_ID;
@ -313,9 +311,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private ImageButton mAddBccFromContacts;
private PgpData mPgpData = null;
private boolean mAutoEncrypt = false;
private boolean mContinueWithoutPublicKey = false;
private String mOpenPgpProvider;
private OpenPgpServiceConnection mOpenPgpServiceConnection;
@ -506,7 +501,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* Save will attempt to replace the message in the given folder with the updated version.
* Discard will delete the message from the given folder.
* @param context
* @param message
* @param messageReference
*/
public static void actionEditDraft(Context context, MessageReference messageReference) {
Intent i = new Intent(context, MessageCompose.class);
@ -656,17 +651,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override
public void afterTextChanged(android.text.Editable s) {
final CryptoProvider crypto = mAccount.getCryptoProvider();
if (mAutoEncrypt && crypto.isAvailable(getApplicationContext())) {
for (Address address : getRecipientAddresses()) {
if (crypto.hasPublicKeyForEmail(getApplicationContext(),
address.getAddress())) {
mEncryptCheckbox.setChecked(true);
mContinueWithoutPublicKey = false;
break;
}
}
}
/* do nothing */
}
};
@ -858,7 +843,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
initializeCrypto();
final CryptoProvider crypto = mAccount.getCryptoProvider();
mOpenPgpProvider = mAccount.getOpenPgpProvider();
if (mOpenPgpProvider != null) {
// New OpenPGP Provider API
@ -878,44 +862,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
});
if (mAccount.getCryptoAutoSignature()) {
// TODO: currently disabled for new openpgp providers (see AccountSettings)
}
updateMessageFormat();
// TODO: currently disabled for new openpgp providers (see AccountSettings)
mAutoEncrypt = false;
//mAutoEncrypt = mAccount.isCryptoAutoEncrypt();
} else if (crypto.isAvailable(this)) {
mEncryptLayout.setVisibility(View.VISIBLE);
mCryptoSignatureCheckbox.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
CheckBox checkBox = (CheckBox) v;
if (checkBox.isChecked()) {
mPreventDraftSaving = true;
if (!crypto.selectSecretKey(MessageCompose.this, mPgpData)) {
mPreventDraftSaving = false;
}
checkBox.setChecked(false);
} else {
mPgpData.setSignatureKeyId(0);
updateEncryptLayout();
}
}
});
if (mAccount.getCryptoAutoSignature()) {
long ids[] = crypto.getSecretKeyIdsFromEmail(this, mIdentity.getEmail());
if (ids != null && ids.length > 0) {
mPgpData.setSignatureKeyId(ids[0]);
mPgpData.setSignatureUserId(crypto.getUserId(this, ids[0]));
} else {
mPgpData.setSignatureKeyId(0);
mPgpData.setSignatureUserId(null);
}
}
updateEncryptLayout();
mAutoEncrypt = mAccount.isCryptoAutoEncrypt();
} else {
mEncryptLayout.setVisibility(View.GONE);
}
@ -1108,11 +1055,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
mCryptoSignatureUserIdRest.setText("");
String userId = mPgpData.getSignatureUserId();
if (userId == null) {
userId = mAccount.getCryptoProvider().getUserId(this, mPgpData.getSignatureKeyId());
mPgpData.setSignatureUserId(userId);
}
if (userId != null) {
String chunks[] = mPgpData.getSignatureUserId().split(" <", 2);
mCryptoSignatureUserId.setText(chunks[0]);
@ -1795,8 +1737,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
private void performSend() {
final CryptoProvider crypto = mAccount.getCryptoProvider();
if (mOpenPgpProvider != null) {
// OpenPGP Provider API
@ -1831,44 +1771,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
// encryptedData set in pgpData!
return;
}
} else if (crypto.isAvailable(this)) {
// Legacy APG API
if (mEncryptCheckbox.isChecked() && !mPgpData.hasEncryptionKeys()) {
// key selection before encryption
StringBuilder emails = new StringBuilder();
for (Address address : getRecipientAddresses()) {
if (emails.length() != 0) {
emails.append(',');
}
emails.append(address.getAddress());
if (!mContinueWithoutPublicKey &&
!crypto.hasPublicKeyForEmail(this, address.getAddress())) {
showDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY);
return;
}
}
if (emails.length() != 0) {
emails.append(',');
}
emails.append(mIdentity.getEmail());
mPreventDraftSaving = true;
if (!crypto.selectEncryptionKeys(MessageCompose.this, emails.toString(), mPgpData)) {
mPreventDraftSaving = false;
}
return;
}
if (mPgpData.hasEncryptionKeys() || mPgpData.hasSignatureKey()) {
if (mPgpData.getEncryptedData() == null) {
String text = buildText(false).getText();
mPreventDraftSaving = true;
crypto.encrypt(this, text, mPgpData);
return;
}
}
}
sendMessage();
@ -2053,7 +1955,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
*/
@SuppressLint("InlinedApi")
private void onAddAttachment2(final String mime_type) {
if (mAccount.getCryptoProvider().isAvailable(this) || mAccount.getOpenPgpProvider() != null) {
if (mAccount.getOpenPgpProvider() != null) {
Toast.makeText(this, R.string.attachment_encryption_unsupported, Toast.LENGTH_LONG).show();
}
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
@ -2252,10 +2154,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
return;
}
if (mAccount.getCryptoProvider().onActivityResult(this, requestCode, resultCode, data, mPgpData)) {
return;
}
if (resultCode != RESULT_OK) {
return;
}
@ -2661,26 +2559,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
})
.create();
case DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY:
return new AlertDialog.Builder(this)
.setTitle(R.string.continue_without_public_key_dlg_title)
.setMessage(R.string.continue_without_public_key_instructions_fmt)
.setPositiveButton(R.string.continue_action, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY);
mContinueWithoutPublicKey = true;
onSend();
}
})
.setNegativeButton(R.string.back_action, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY);
mContinueWithoutPublicKey = false;
}
})
.create();
case DIALOG_CONFIRM_DISCARD_ON_BACK:
return new AlertDialog.Builder(this)
.setTitle(R.string.confirm_discard_draft_message_title)

View File

@ -697,7 +697,7 @@ public class AccountSettings extends K9PreferenceActivity {
} else {
final Preference mCryptoMenu = findPreference(PREFERENCE_CRYPTO);
mCryptoMenu.setEnabled(false);
mCryptoMenu.setSummary(R.string.account_settings_crypto_apg_not_installed);
mCryptoMenu.setSummary(R.string.account_settings_no_openpgp_provider_installed);
}
}

View File

@ -1,588 +0,0 @@
package com.fsck.k9.crypto;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.app.Activity;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.ContentUris;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.Uri;
import android.widget.Toast;
import com.fsck.k9.R;
import com.fsck.k9.activity.MessageCompose;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeUtility;
/**
* APG integration.
*/
public class Apg extends CryptoProvider {
static final long serialVersionUID = 0x21071235;
public static final String NAME = "apg";
private static final String mApgPackageName = "org.thialfihar.android.apg";
private static final int mMinRequiredVersion = 16;
public static final String AUTHORITY = "org.thialfihar.android.apg.provider";
public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID =
Uri.parse("content://" + AUTHORITY + "/key_rings/secret/key_id/");
public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_EMAILS =
Uri.parse("content://" + AUTHORITY + "/key_rings/secret/emails/");
public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_KEY_ID =
Uri.parse("content://" + AUTHORITY + "/key_rings/public/key_id/");
public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS =
Uri.parse("content://" + AUTHORITY + "/key_rings/public/emails/");
public static class Intent {
public static final String DECRYPT = "org.thialfihar.android.apg.intent.DECRYPT";
public static final String ENCRYPT = "org.thialfihar.android.apg.intent.ENCRYPT";
public static final String DECRYPT_FILE = "org.thialfihar.android.apg.intent.DECRYPT_FILE";
public static final String ENCRYPT_FILE = "org.thialfihar.android.apg.intent.ENCRYPT_FILE";
public static final String DECRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.DECRYPT_AND_RETURN";
public static final String ENCRYPT_AND_RETURN = "org.thialfihar.android.apg.intent.ENCRYPT_AND_RETURN";
public static final String SELECT_PUBLIC_KEYS = "org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS";
public static final String SELECT_SECRET_KEY = "org.thialfihar.android.apg.intent.SELECT_SECRET_KEY";
}
public static final String EXTRA_TEXT = "text";
public static final String EXTRA_DATA = "data";
public static final String EXTRA_ERROR = "error";
public static final String EXTRA_DECRYPTED_MESSAGE = "decryptedMessage";
public static final String EXTRA_ENCRYPTED_MESSAGE = "encryptedMessage";
public static final String EXTRA_SIGNATURE = "signature";
public static final String EXTRA_SIGNATURE_KEY_ID = "signatureKeyId";
public static final String EXTRA_SIGNATURE_USER_ID = "signatureUserId";
public static final String EXTRA_SIGNATURE_SUCCESS = "signatureSuccess";
public static final String EXTRA_SIGNATURE_UNKNOWN = "signatureUnknown";
public static final String EXTRA_USER_ID = "userId";
public static final String EXTRA_KEY_ID = "keyId";
public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryptionKeyIds";
public static final String EXTRA_SELECTION = "selection";
public static final String EXTRA_MESSAGE = "message";
public static final String EXTRA_INTENT_VERSION = "intentVersion";
public static final String INTENT_VERSION = "1";
// Note: The support package only allows us to use the lower 16 bits of a request code.
public static final int DECRYPT_MESSAGE = 0x0000A001;
public static final int ENCRYPT_MESSAGE = 0x0000A002;
public static final int SELECT_PUBLIC_KEYS = 0x0000A003;
public static final int SELECT_SECRET_KEY = 0x0000A004;
public static Pattern PGP_MESSAGE =
Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
Pattern.DOTALL);
public static Pattern PGP_SIGNED_MESSAGE =
Pattern.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
Pattern.DOTALL);
public static Apg createInstance() {
return new Apg();
}
/**
* Check whether APG is installed and at a high enough version.
*
* @param context
* @return whether a suitable version of APG was found
*/
@Override
public boolean isAvailable(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(mApgPackageName, 0);
if (pi.versionCode >= mMinRequiredVersion) {
return true;
} else {
Toast.makeText(context,
R.string.error_apg_version_not_supported, Toast.LENGTH_SHORT).show();
}
} catch (NameNotFoundException e) {
// not found
}
return false;
}
/**
* Select the signature key.
*
* @param activity
* @param pgpData
* @return success or failure
*/
@Override
public boolean selectSecretKey(Activity activity, PgpData pgpData) {
android.content.Intent intent = new android.content.Intent(Intent.SELECT_SECRET_KEY);
intent.putExtra(EXTRA_INTENT_VERSION, INTENT_VERSION);
try {
activity.startActivityForResult(intent, Apg.SELECT_SECRET_KEY);
return true;
} catch (ActivityNotFoundException e) {
Toast.makeText(activity,
R.string.error_activity_not_found,
Toast.LENGTH_SHORT).show();
return false;
}
}
/**
* Select encryption keys.
*
* @param activity
* @param emails The emails that should be used for preselection.
* @param pgpData
* @return success or failure
*/
@Override
public boolean selectEncryptionKeys(Activity activity, String emails, PgpData pgpData) {
android.content.Intent intent = new android.content.Intent(Apg.Intent.SELECT_PUBLIC_KEYS);
intent.putExtra(EXTRA_INTENT_VERSION, INTENT_VERSION);
long[] initialKeyIds = null;
if (!pgpData.hasEncryptionKeys()) {
List<Long> keyIds = new ArrayList<Long>();
if (pgpData.hasSignatureKey()) {
keyIds.add(pgpData.getSignatureKeyId());
}
try {
Uri contentUri = Uri.withAppendedPath(
Apg.CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS,
emails);
Cursor c = activity.getContentResolver().query(contentUri,
new String[] { "master_key_id" },
null, null, null);
if (c != null) {
while (c.moveToNext()) {
keyIds.add(c.getLong(0));
}
}
if (c != null) {
c.close();
}
} catch (SecurityException e) {
Toast.makeText(activity,
activity.getResources().getString(R.string.insufficient_apg_permissions),
Toast.LENGTH_LONG).show();
}
if (!keyIds.isEmpty()) {
initialKeyIds = new long[keyIds.size()];
for (int i = 0, size = keyIds.size(); i < size; ++i) {
initialKeyIds[i] = keyIds.get(i);
}
}
} else {
initialKeyIds = pgpData.getEncryptionKeys();
}
intent.putExtra(Apg.EXTRA_SELECTION, initialKeyIds);
try {
activity.startActivityForResult(intent, Apg.SELECT_PUBLIC_KEYS);
return true;
} catch (ActivityNotFoundException e) {
Toast.makeText(activity,
R.string.error_activity_not_found,
Toast.LENGTH_SHORT).show();
return false;
}
}
/**
* Get secret key ids based on a given email.
*
* @param context
* @param email The email in question.
* @return key ids
*/
@Override
public long[] getSecretKeyIdsFromEmail(Context context, String email) {
long ids[] = null;
try {
Uri contentUri = Uri.withAppendedPath(Apg.CONTENT_URI_SECRET_KEY_RING_BY_EMAILS,
email);
Cursor c = context.getContentResolver().query(contentUri,
new String[] { "master_key_id" },
null, null, null);
if (c != null && c.getCount() > 0) {
ids = new long[c.getCount()];
while (c.moveToNext()) {
ids[c.getPosition()] = c.getLong(0);
}
}
if (c != null) {
c.close();
}
} catch (SecurityException e) {
Toast.makeText(context,
context.getResources().getString(R.string.insufficient_apg_permissions),
Toast.LENGTH_LONG).show();
}
return ids;
}
/**
* Get public key ids based on a given email.
*
* @param context
* @param email The email in question.
* @return key ids
*/
@Override
public long[] getPublicKeyIdsFromEmail(Context context, String email) {
long ids[] = null;
try {
Uri contentUri = Uri.withAppendedPath(Apg.CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS, email);
Cursor c = context.getContentResolver().query(contentUri,
new String[] { "master_key_id" }, null, null, null);
if (c != null && c.getCount() > 0) {
ids = new long[c.getCount()];
while (c.moveToNext()) {
ids[c.getPosition()] = c.getLong(0);
}
}
if (c != null) {
c.close();
}
} catch (SecurityException e) {
Toast.makeText(context,
context.getResources().getString(R.string.insufficient_apg_permissions),
Toast.LENGTH_LONG).show();
}
return ids;
}
/**
* Find out if a given email has a secret key.
*
* @param context
* @param email The email in question.
* @return true if there is a secret key for this email.
*/
@Override
public boolean hasSecretKeyForEmail(Context context, String email) {
try {
Uri contentUri = Uri.withAppendedPath(Apg.CONTENT_URI_SECRET_KEY_RING_BY_EMAILS, email);
Cursor c = context.getContentResolver().query(contentUri,
new String[] { "master_key_id" }, null, null, null);
if (c != null && c.getCount() > 0) {
c.close();
return true;
}
if (c != null) {
c.close();
}
} catch (SecurityException e) {
Toast.makeText(context,
context.getResources().getString(R.string.insufficient_apg_permissions),
Toast.LENGTH_LONG).show();
}
return false;
}
/**
* Find out if a given email has a public key.
*
* @param context
* @param email The email in question.
* @return true if there is a public key for this email.
*/
@Override
public boolean hasPublicKeyForEmail(Context context, String email) {
try {
Uri contentUri = Uri.withAppendedPath(Apg.CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS, email);
Cursor c = context.getContentResolver().query(contentUri,
new String[] { "master_key_id" }, null, null, null);
if (c != null && c.getCount() > 0) {
c.close();
return true;
}
if (c != null) {
c.close();
}
} catch (SecurityException e) {
Toast.makeText(context,
context.getResources().getString(R.string.insufficient_apg_permissions),
Toast.LENGTH_LONG).show();
}
return false;
}
/**
* Get the user id based on the key id.
*
* @param context
* @param keyId
* @return user id
*/
@Override
public String getUserId(Context context, long keyId) {
String userId = null;
try {
Uri contentUri = ContentUris.withAppendedId(
Apg.CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID,
keyId);
Cursor c = context.getContentResolver().query(contentUri,
new String[] { "user_id" },
null, null, null);
if (c != null && c.moveToFirst()) {
userId = c.getString(0);
}
if (c != null) {
c.close();
}
} catch (SecurityException e) {
Toast.makeText(context,
context.getResources().getString(R.string.insufficient_apg_permissions),
Toast.LENGTH_LONG).show();
}
if (userId == null) {
userId = context.getString(R.string.unknown_crypto_signature_user_id);
}
return userId;
}
/**
* Handle the activity results that concern us.
*
* @param activity
* @param requestCode
* @param resultCode
* @param data
* @return handled or not
*/
@Override
public boolean onActivityResult(Activity activity, int requestCode, int resultCode,
android.content.Intent data, PgpData pgpData) {
switch (requestCode) {
case Apg.SELECT_SECRET_KEY:
if (resultCode != Activity.RESULT_OK || data == null) {
break;
}
pgpData.setSignatureKeyId(data.getLongExtra(Apg.EXTRA_KEY_ID, 0));
pgpData.setSignatureUserId(data.getStringExtra(Apg.EXTRA_USER_ID));
((MessageCompose) activity).updateEncryptLayout();
break;
case Apg.SELECT_PUBLIC_KEYS:
if (resultCode != Activity.RESULT_OK || data == null) {
pgpData.setEncryptionKeys(null);
((MessageCompose) activity).onEncryptionKeySelectionDone();
break;
}
pgpData.setEncryptionKeys(data.getLongArrayExtra(Apg.EXTRA_SELECTION));
((MessageCompose) activity).onEncryptionKeySelectionDone();
break;
case Apg.ENCRYPT_MESSAGE:
if (resultCode != Activity.RESULT_OK || data == null) {
pgpData.setEncryptionKeys(null);
((MessageCompose) activity).onEncryptDone();
break;
}
pgpData.setEncryptedData(data.getStringExtra(Apg.EXTRA_ENCRYPTED_MESSAGE));
// this was a stupid bug in an earlier version, just gonna leave this in for an APG
// version or two
if (pgpData.getEncryptedData() == null) {
pgpData.setEncryptedData(data.getStringExtra(Apg.EXTRA_DECRYPTED_MESSAGE));
}
if (pgpData.getEncryptedData() != null) {
((MessageCompose) activity).onEncryptDone();
}
break;
default:
return false;
}
return true;
}
@Override
public boolean onDecryptActivityResult(CryptoDecryptCallback callback, int requestCode,
int resultCode, android.content.Intent data, PgpData pgpData) {
switch (requestCode) {
case Apg.DECRYPT_MESSAGE: {
if (resultCode != Activity.RESULT_OK || data == null) {
break;
}
pgpData.setSignatureUserId(data.getStringExtra(Apg.EXTRA_SIGNATURE_USER_ID));
pgpData.setSignatureKeyId(data.getLongExtra(Apg.EXTRA_SIGNATURE_KEY_ID, 0));
pgpData.setSignatureSuccess(data.getBooleanExtra(Apg.EXTRA_SIGNATURE_SUCCESS, false));
pgpData.setSignatureUnknown(data.getBooleanExtra(Apg.EXTRA_SIGNATURE_UNKNOWN, false));
pgpData.setDecryptedData(data.getStringExtra(Apg.EXTRA_DECRYPTED_MESSAGE));
callback.onDecryptDone(pgpData);
break;
}
default: {
return false;
}
}
return true;
}
/**
* Start the encrypt activity.
*
* @param activity
* @param data
* @param pgpData
* @return success or failure
*/
@Override
public boolean encrypt(Activity activity, String data, PgpData pgpData) {
android.content.Intent intent = new android.content.Intent(Intent.ENCRYPT_AND_RETURN);
intent.putExtra(EXTRA_INTENT_VERSION, INTENT_VERSION);
intent.setType("text/plain");
intent.putExtra(Apg.EXTRA_TEXT, data);
intent.putExtra(Apg.EXTRA_ENCRYPTION_KEY_IDS, pgpData.getEncryptionKeys());
intent.putExtra(Apg.EXTRA_SIGNATURE_KEY_ID, pgpData.getSignatureKeyId());
try {
activity.startActivityForResult(intent, Apg.ENCRYPT_MESSAGE);
return true;
} catch (ActivityNotFoundException e) {
Toast.makeText(activity,
R.string.error_activity_not_found,
Toast.LENGTH_SHORT).show();
return false;
}
}
/**
* Start the decrypt activity.
*
* @param fragment
* @param data
* @param pgpData
* @return success or failure
*/
@Override
public boolean decrypt(Fragment fragment, String data, PgpData pgpData) {
android.content.Intent intent = new android.content.Intent(Apg.Intent.DECRYPT_AND_RETURN);
intent.putExtra(EXTRA_INTENT_VERSION, INTENT_VERSION);
intent.setType("text/plain");
if (data == null) {
return false;
}
try {
intent.putExtra(EXTRA_TEXT, data);
fragment.startActivityForResult(intent, Apg.DECRYPT_MESSAGE);
return true;
} catch (ActivityNotFoundException e) {
Toast.makeText(fragment.getActivity(), R.string.error_activity_not_found, Toast.LENGTH_SHORT).show();
return false;
}
}
@Override
public boolean isEncrypted(Message message) {
String data = null;
try {
Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
data = MimeUtility.getTextFromPart(part);
}
} catch (MessagingException e) {
// guess not...
// TODO: maybe log this?
}
if (data == null) {
return false;
}
Matcher matcher = PGP_MESSAGE.matcher(data);
return matcher.matches();
}
@Override
public boolean isSigned(Message message) {
String data = null;
try {
Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
data = MimeUtility.getTextFromPart(part);
}
} catch (MessagingException e) {
// guess not...
// TODO: maybe log this?
}
if (data == null) {
return false;
}
Matcher matcher = PGP_SIGNED_MESSAGE.matcher(data);
return matcher.matches();
}
/**
* Get the name of the provider.
*
* @return provider name
*/
@Override
public String getName() {
return NAME;
}
/**
* Test the APG installation.
*
* @return success or failure
*/
@Override
public boolean test(Context context) {
if (!isAvailable(context)) {
return false;
}
try {
// try out one content provider to check permissions
Uri contentUri = ContentUris.withAppendedId(
Apg.CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID,
12345);
Cursor c = context.getContentResolver().query(contentUri,
new String[] { "user_id" },
null, null, null);
if (c != null) {
c.close();
}
} catch (SecurityException e) {
// if there was a problem, then let the user know, this will not stop K9/APG from
// working, but some features won't be available, so we can still return "true"
Toast.makeText(context,
context.getResources().getString(R.string.insufficient_apg_permissions),
Toast.LENGTH_LONG).show();
}
return true;
}
}

View File

@ -1,48 +0,0 @@
package com.fsck.k9.crypto;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.app.Fragment;
import com.fsck.k9.mail.Message;
/**
* A CryptoProvider provides functionalities such as encryption, decryption, digital signatures.
* It currently also stores the results of such encryption or decryption.
* TODO: separate the storage from the provider
*/
abstract public class CryptoProvider {
static final long serialVersionUID = 0x21071234;
abstract public boolean isAvailable(Context context);
abstract public boolean isEncrypted(Message message);
abstract public boolean isSigned(Message message);
abstract public boolean onActivityResult(Activity activity, int requestCode, int resultCode,
Intent data, PgpData pgpData);
abstract public boolean onDecryptActivityResult(CryptoDecryptCallback callback,
int requestCode, int resultCode, Intent data, PgpData pgpData);
abstract public boolean selectSecretKey(Activity activity, PgpData pgpData);
abstract public boolean selectEncryptionKeys(Activity activity, String emails, PgpData pgpData);
abstract public boolean encrypt(Activity activity, String data, PgpData pgpData);
abstract public boolean decrypt(Fragment fragment, String data, PgpData pgpData);
abstract public long[] getSecretKeyIdsFromEmail(Context context, String email);
abstract public long[] getPublicKeyIdsFromEmail(Context context, String email);
abstract public boolean hasSecretKeyForEmail(Context context, String email);
abstract public boolean hasPublicKeyForEmail(Context context, String email);
abstract public String getUserId(Context context, long keyId);
abstract public String getName();
abstract public boolean test(Context context);
public static CryptoProvider createInstance(String name) {
if (Apg.NAME.equals(name)) {
return Apg.createInstance();
}
return None.createInstance();
}
public interface CryptoDecryptCallback {
void onDecryptDone(PgpData pgpData);
}
}

View File

@ -1,103 +0,0 @@
package com.fsck.k9.crypto;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import com.fsck.k9.mail.Message;
/**
* Dummy CryptoProvider for when cryptography is disabled. It is never "available" and doesn't
* do anything.
*/
public class None extends CryptoProvider {
static final long serialVersionUID = 0x21071230;
public static final String NAME = "";
public static None createInstance() {
return new None();
}
@Override
public boolean isAvailable(Context context) {
return false;
}
@Override
public boolean selectSecretKey(Activity activity, PgpData pgpData) {
return false;
}
@Override
public boolean selectEncryptionKeys(Activity activity, String emails, PgpData pgpData) {
return false;
}
@Override
public long[] getSecretKeyIdsFromEmail(Context context, String email) {
return null;
}
@Override
public long[] getPublicKeyIdsFromEmail(Context context, String email) {
return null;
}
@Override
public boolean hasSecretKeyForEmail(Context context, String email) {
return false;
}
@Override
public boolean hasPublicKeyForEmail(Context context, String email) {
return false;
}
@Override
public String getUserId(Context context, long keyId) {
return null;
}
@Override
public boolean onActivityResult(Activity activity, int requestCode, int resultCode,
android.content.Intent data, PgpData pgpData) {
return false;
}
@Override
public boolean onDecryptActivityResult(CryptoDecryptCallback callback, int requestCode,
int resultCode, Intent data, PgpData pgpData) {
return false;
}
@Override
public boolean encrypt(Activity activity, String data, PgpData pgpData) {
return false;
}
@Override
public boolean decrypt(Fragment fragment, String data, PgpData pgpData) {
return false;
}
@Override
public boolean isEncrypted(Message message) {
return false;
}
@Override
public boolean isSigned(Message message) {
return false;
}
@Override
public String getName() {
return NAME;
}
@Override
public boolean test(Context context) {
return true;
}
}

View File

@ -30,7 +30,6 @@ import com.fsck.k9.activity.ChooseFolder;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.crypto.CryptoProvider.CryptoDecryptCallback;
import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
import com.fsck.k9.helper.FileBrowserHelper;
@ -49,7 +48,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
public class MessageViewFragment extends Fragment implements OnClickListener,
CryptoDecryptCallback, ConfirmationDialogFragmentListener {
ConfirmationDialogFragmentListener {
private static final String ARG_REFERENCE = "reference";
@ -427,10 +426,6 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mAccount.getCryptoProvider().onDecryptActivityResult(this, requestCode, resultCode, data, mPgpData)) {
return;
}
if (resultCode != Activity.RESULT_OK) {
return;
}
@ -732,20 +727,6 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
}
}
// This REALLY should be in MessageCryptoView
@Override
public void onDecryptDone(PgpData pgpData) {
Account account = mAccount;
LocalMessage message = (LocalMessage) mMessage;
MessagingController controller = mController;
Listener listener = mListener;
try {
mMessageView.setMessage(account, message, pgpData, controller, listener);
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "displayMessageBody failed", e);
}
}
private void showDialog(int dialogId) {
DialogFragment fragment;
switch (dialogId) {

View File

@ -13,7 +13,6 @@ import com.fsck.k9.Account.SortType;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.Account.FolderMode;
import com.fsck.k9.crypto.Apg;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.preferences.Settings.*;
@ -50,13 +49,8 @@ public class AccountSettings {
new V(1, new ColorSetting(0xFF0000FF))
));
s.put("cryptoApp", Settings.versions(
new V(1, new StringSetting(Apg.NAME))
));
s.put("cryptoAutoEncrypt", Settings.versions(
new V(3, new BooleanSetting(false))
));
s.put("cryptoAutoSignature", Settings.versions(
new V(1, new BooleanSetting(false))
new V(1, new StringSetting("apg")),
new V(36, new StringSetting(Account.NO_OPENPGP_PROVIDER))
));
s.put("defaultQuotedTextShown", Settings.versions(
new V(1, new BooleanSetting(Account.DEFAULT_QUOTED_TEXT_SHOWN))

View File

@ -35,7 +35,7 @@ public class Settings {
*
* @see SettingsExporter
*/
public static final int VERSION = 35;
public static final int VERSION = 36;
public static Map<String, Object> validate(int version, Map<String,
TreeMap<Integer, SettingsDescription>> settings,

View File

@ -1,140 +0,0 @@
package com.fsck.k9.view;
import android.content.Context;
import android.app.Fragment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.*;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.crypto.CryptoProvider;
import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeUtility;
public class MessageCryptoView extends LinearLayout {
private Context mContext;
private Fragment mFragment;
private Button mDecryptButton;
private LinearLayout mCryptoSignatureLayout = null;
private ImageView mCryptoSignatureStatusImage = null;
private TextView mCryptoSignatureUserId = null;
private TextView mCryptoSignatureUserIdRest = null;
public MessageCryptoView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
public void setupChildViews() {
mCryptoSignatureLayout = (LinearLayout) findViewById(R.id.crypto_signature);
mCryptoSignatureStatusImage = (ImageView) findViewById(R.id.ic_crypto_signature_status);
mCryptoSignatureUserId = (TextView) findViewById(R.id.userId);
mCryptoSignatureUserIdRest = (TextView) findViewById(R.id.userIdRest);
mCryptoSignatureLayout.setVisibility(View.INVISIBLE);
mDecryptButton = (Button) findViewById(R.id.btn_decrypt);
}
public void setFragment(Fragment fragment) {
mFragment = fragment;
}
public void hide() {
this.setVisibility(View.GONE);
}
/**
* Fill the decrypt layout with signature data, if known, make controls visible, if
* they should be visible.
*/
public void updateLayout(final CryptoProvider cryptoProvider, final PgpData pgpData, final Message message) {
if (pgpData.getSignatureKeyId() != 0) {
mCryptoSignatureUserIdRest.setText(
mContext.getString(R.string.key_id, Long.toHexString(pgpData.getSignatureKeyId() & 0xffffffffL)));
String userId = pgpData.getSignatureUserId();
if (userId == null) {
userId = mContext.getString(R.string.unknown_crypto_signature_user_id);
}
String chunks[] = userId.split(" <", 2);
String name = chunks[0];
if (chunks.length > 1) {
mCryptoSignatureUserIdRest.setText("<" + chunks[1]);
}
mCryptoSignatureUserId.setText(name);
if (pgpData.getSignatureSuccess()) {
mCryptoSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
} else if (pgpData.getSignatureUnknown()) {
mCryptoSignatureStatusImage.setImageResource(R.drawable.overlay_error);
} else {
mCryptoSignatureStatusImage.setImageResource(R.drawable.overlay_error);
}
mCryptoSignatureLayout.setVisibility(View.VISIBLE);
this.setVisibility(View.VISIBLE);
} else {
mCryptoSignatureLayout.setVisibility(View.INVISIBLE);
}
if ((message == null) && (pgpData.getDecryptedData() == null)) {
this.setVisibility(View.GONE);
return;
}
if (pgpData.getDecryptedData() != null) {
if (pgpData.getSignatureKeyId() == 0) {
this.setVisibility(View.GONE);
} else {
// no need to show this after decryption/verification
mDecryptButton.setVisibility(View.GONE);
}
return;
}
mDecryptButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
String data = null;
Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
data = MimeUtility.getTextFromPart(part);
}
cryptoProvider.decrypt(mFragment, data, pgpData);
} catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Unable to decrypt email.", me);
}
}
});
mDecryptButton.setVisibility(View.VISIBLE);
if (cryptoProvider.isEncrypted(message)) {
mDecryptButton.setText(R.string.btn_decrypt);
this.setVisibility(View.VISIBLE);
} else if (cryptoProvider.isSigned(message)) {
mDecryptButton.setText(R.string.btn_verify);
this.setVisibility(View.VISIBLE);
} else {
this.setVisibility(View.GONE);
try {
// check for PGP/MIME encryption
Part pgp = MimeUtility.findFirstPartByMimeType(message, "application/pgp-encrypted");
if (pgp != null) {
Toast.makeText(mContext, R.string.pgp_mime_unsupported, Toast.LENGTH_LONG).show();
}
} catch (MessagingException e) {
// nothing to do...
}
}
}
}

View File

@ -6,6 +6,7 @@ import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import android.app.Activity;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
@ -41,7 +42,6 @@ import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.crypto.CryptoProvider;
import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.fragment.MessageViewFragment;
import com.fsck.k9.helper.ClipboardManager;
@ -87,7 +87,6 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
private static final int DISPLAY_NAME_INDEX = 1;
private MessageCryptoView mCryptoView;
private MessageOpenPgpView mOpenPgpView;
private MessageWebView mMessageContentView;
private MessageHeader mHeaderContainer;
@ -125,9 +124,6 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
mHiddenAttachments.setVisibility(View.GONE);
mShowHiddenAttachments = (Button) findViewById(R.id.show_hidden_attachments);
mShowHiddenAttachments.setVisibility(View.GONE);
mCryptoView = (MessageCryptoView) findViewById(R.id.layout_decrypt);
mCryptoView.setFragment(fragment);
mCryptoView.setupChildViews();
mOpenPgpView = (MessageOpenPgpView) findViewById(R.id.layout_decrypt_openpgp);
mOpenPgpView.setFragment(fragment);
mOpenPgpView.setupChildViews();
@ -571,7 +567,6 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
if (text != null) {
loadBodyFromText(text);
updateCryptoLayout(account.getCryptoProvider(), pgpData, message);
mOpenPgpView.updateLayout(account, pgpData.getDecryptedData(),
pgpData.getSignatureResult(), message);
} else {
@ -582,17 +577,12 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
public void showStatusMessage(String status) {
String text = "<div style=\"text-align:center; color: grey;\">" + status + "</div>";
loadBodyFromText(text);
mCryptoView.hide();
}
private void loadBodyFromText(String emailText) {
mMessageContentView.setText(emailText);
}
public void updateCryptoLayout(CryptoProvider cp, PgpData pgpData, Message message) {
mCryptoView.updateLayout(cp, pgpData, message);
}
public void showAttachments(boolean show) {
mAttachmentsContainer.setVisibility(show ? View.VISIBLE : View.GONE);
boolean showHidden = (show && mHiddenAttachments.getVisibility() == View.GONE &&