From 4c3506f4b1878bd785df9f8a639275bba243d945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 1 Apr 2014 13:25:44 +0200 Subject: Experimental work on a fragment-based encrypt activity --- .../keychain/ui/DecryptActivity.java | 13 - .../keychain/ui/DecryptFileFragment.java | 26 +- .../keychain/ui/DecryptFragment.java | 6 +- .../keychain/ui/DecryptMessageFragment.java | 11 +- .../keychain/ui/EncryptActivity.java | 969 ++++----------------- .../keychain/ui/EncryptActivityInterface.java | 14 + .../keychain/ui/EncryptAsymmetricFragment.java | 283 ++++++ .../keychain/ui/EncryptFileFragment.java | 415 +++++++++ .../keychain/ui/EncryptMessageFragment.java | 289 ++++++ .../keychain/ui/EncryptSymmetricFragment.java | 96 ++ .../src/main/res/layout/decrypt_file_fragment.xml | 3 +- .../res/layout/encrypt_asymmetric_fragment.xml | 4 +- .../src/main/res/layout/encrypt_content.xml | 339 +------ .../src/main/res/layout/encrypt_file_fragment.xml | 8 +- OpenPGP-Keychain/src/main/res/values/strings.xml | 2 - 15 files changed, 1302 insertions(+), 1176 deletions(-) create mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java create mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java create mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java create mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java create mode 100644 OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index d6dad424e..046d80b72 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -22,23 +22,13 @@ import android.net.Uri; import android.os.Bundle; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.TextView; import android.widget.Toast; -import com.beardedhen.androidbootstrap.BootstrapButton; -import com.devspark.appmsg.AppMsg; - -import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; @@ -47,7 +37,6 @@ import java.util.regex.Matcher; public class DecryptActivity extends DrawerActivity { /* Intents */ - // without permission public static final String ACTION_DECRYPT = Constants.INTENT_PREFIX + "DECRYPT"; /* EXTRA keys for input */ @@ -64,9 +53,7 @@ public class DecryptActivity extends DrawerActivity { private static final int PAGER_TAB_MESSAGE = 0; private static final int PAGER_TAB_FILE = 1; - private void initView() { - // Pager mViewPager = (ViewPager) findViewById(R.id.decrypt_pager); mPagerTabStrip = (PagerTabStrip) findViewById(R.id.decrypt_pager_tab_strip); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java index 069a5a584..a929047b8 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Dominik Schürmann + * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,20 +49,19 @@ import java.io.File; public class DecryptFileFragment extends DecryptFragment { public static final String ARG_FILENAME = "filename"; + private static final int RESULT_CODE_FILE = 0x00007003; + + // view private EditText mFilename; private CheckBox mDeleteAfter; private BootstrapButton mBrowse; private BootstrapButton mDecryptButton; - private String mInputFilename = null; private String mOutputFilename = null; private FileDialogFragment mFileDialog; - private static final int RESULT_CODE_FILE = 0x00007003; - - /** * Inflate the layout for this fragment */ @@ -72,14 +71,14 @@ public class DecryptFileFragment extends DecryptFragment { mFilename = (EditText) view.findViewById(R.id.decrypt_file_filename); mBrowse = (BootstrapButton) view.findViewById(R.id.decrypt_file_browse); + mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption); + mDecryptButton = (BootstrapButton) view.findViewById(R.id.decrypt_file_action_decrypt); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*", RESULT_CODE_FILE); } }); - mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption); - mDecryptButton = (BootstrapButton) view.findViewById(R.id.decrypt_file_action_decrypt); mDecryptButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -87,12 +86,17 @@ public class DecryptFileFragment extends DecryptFragment { } }); + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + String filename = getArguments().getString(ARG_FILENAME); if (filename != null) { mFilename.setText(filename); } - - return view; } private void guessOutputFilename() { @@ -193,14 +197,12 @@ public class DecryptFileFragment extends DecryptFragment { // get returned data bundle Bundle returnData = message.getData(); - PgpDecryptVerifyResult decryptVerifyResult = returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT); if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) { showPassphraseDialog(decryptVerifyResult.getKeyIdPassphraseNeeded()); } else { - if (mDeleteAfter.isChecked()) { // Create and show dialog to delete original file DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment @@ -208,13 +210,11 @@ public class DecryptFileFragment extends DecryptFragment { deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); } - OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult(); // display signature result in activity onSignatureResult(signatureResult); } - } } }; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 4c0c80742..fd7aef353 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Dominik Schürmann + * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -187,6 +187,10 @@ public class DecryptFragment extends Fragment { } } + /** + * Should be overridden by MessageFragment and FileFragment to start actual decryption + * @param passphrase + */ protected void decryptStart(String passphrase) { } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java index 3a8aaec42..94034b356 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Dominik Schürmann + * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,7 +50,6 @@ public class DecryptMessageFragment extends DecryptFragment { private BootstrapButton mDecryptButton; private BootstrapButton mDecryptFromCLipboardButton; - /** * Inflate the layout for this fragment */ @@ -61,14 +60,12 @@ public class DecryptMessageFragment extends DecryptFragment { mMessage = (EditText) view.findViewById(R.id.message); mDecryptButton = (BootstrapButton) view.findViewById(R.id.action_decrypt); mDecryptFromCLipboardButton = (BootstrapButton) view.findViewById(R.id.action_decrypt_from_clipboard); - mDecryptButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { decryptStart(null); } }); - mDecryptFromCLipboardButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -93,6 +90,7 @@ public class DecryptMessageFragment extends DecryptFragment { private void decryptFromClipboard() { CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); + // only decrypt if clipboard content is available and a pgp message or cleartext signature if (clipboardText != null) { Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText); if (!matcher.matches()) { @@ -146,14 +144,12 @@ public class DecryptMessageFragment extends DecryptFragment { // get returned data bundle Bundle returnData = message.getData(); - PgpDecryptVerifyResult decryptVerifyResult = returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT); if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) { showPassphraseDialog(decryptVerifyResult.getKeyIdPassphraseNeeded()); } else { - AppMsg.makeText(getActivity(), R.string.decryption_successful, AppMsg.STYLE_INFO).show(); @@ -162,13 +158,11 @@ public class DecryptMessageFragment extends DecryptFragment { mMessage.setText(new String(decryptedMessage)); mMessage.setHorizontallyScrolling(false); - OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult(); // display signature result in activity onSignatureResult(signatureResult); } - } } }; @@ -184,5 +178,4 @@ public class DecryptMessageFragment extends DecryptFragment { getActivity().startService(intent); } - } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index cc8224f06..47c4347f6 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -17,49 +17,25 @@ package org.sufficientlysecure.keychain.ui; -import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.*; -import com.beardedhen.androidbootstrap.BootstrapButton; -import com.beardedhen.androidbootstrap.FontAwesomeText; -import com.devspark.appmsg.AppMsg; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; +import android.support.v4.view.PagerTabStrip; +import android.support.v4.view.ViewPager; +import android.widget.Toast; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.FileHelper; -import org.sufficientlysecure.keychain.helper.Preferences; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; -import org.sufficientlysecure.keychain.util.Choice; +import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; -import java.io.File; -import java.util.Vector; - -public class EncryptActivity extends DrawerActivity { +public class EncryptActivity extends DrawerActivity implements + EncryptSymmetricFragment.OnSymmetricKeySelection, + EncryptAsymmetricFragment.OnAsymmetricKeySelection, + EncryptActivityInterface { /* Intents */ public static final String ACTION_ENCRYPT = Constants.INTENT_PREFIX + "ENCRYPT"; @@ -74,52 +50,100 @@ public class EncryptActivity extends DrawerActivity { public static final String EXTRA_SIGNATURE_KEY_ID = "signature_key_id"; public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryption_key_ids"; + // view + ViewPager mViewPagerMode; + PagerTabStrip mPagerTabStripMode; + PagerTabStripAdapter mTabsAdapterMode; + ViewPager mViewPagerContent; + PagerTabStrip mPagerTabStripContent; + PagerTabStripAdapter mTabsAdapterContent; + + // tabs + Bundle mAsymmetricFragmentBundle = new Bundle(); + Bundle mSymmetricFragmentBundle = new Bundle(); + Bundle mMessageFragmentBundle = new Bundle(); + Bundle mFileFragmentBundle = new Bundle(); + int mSwitchToMode = PAGER_MODE_ASYMMETRIC; + int mSwitchToContent = PAGER_CONTENT_MESSAGE; + + private static final int PAGER_MODE_ASYMMETRIC = 0; + private static final int PAGER_MODE_SYMMETRIC = 1; + private static final int PAGER_CONTENT_MESSAGE = 0; + private static final int PAGER_CONTENT_FILE = 1; + + // model private long mEncryptionKeyIds[] = null; + private long mSigningKeyId = Id.key.none; + private String mPassphrase; + private String mPassphraseAgain; - private EditText mMessage = null; - private BootstrapButton mSelectKeysButton = null; + private boolean mAsciiArmorDemand = false; + private boolean mOverrideAsciiArmor = false; - private CheckBox mSign = null; - private TextView mMainUserId = null; - private TextView mMainUserIdRest = null; + private boolean mGenerateSignature = false; - private ViewFlipper mSource = null; - private TextView mSourceLabel = null; - private ImageView mSourcePrevious = null; - private ImageView mSourceNext = null; - private ViewFlipper mMode = null; - private TextView mModeLabel = null; - private ImageView mModePrevious = null; - private ImageView mModeNext = null; + @Override + public void onSigningKeySelected(long signingKeyId) { + mSigningKeyId = signingKeyId; + } - private int mEncryptTarget; + @Override + public void onEncryptionKeysSelected(long[] encryptionKeyIds) { + mEncryptionKeyIds = encryptionKeyIds; + } - private EditText mPassphrase = null; - private EditText mPassphraseAgain = null; - private CheckBox mAsciiArmor = null; - private Spinner mFileCompression = null; + @Override + public void onPassphraseUpdate(String passphrase) { + mPassphrase = passphrase; + } - private EditText mFilename = null; - private CheckBox mDeleteAfter = null; - private CheckBox mShareAfter = null; - private BootstrapButton mBrowse = null; + @Override + public void onPassphraseAgainUpdate(String passphrase) { + mPassphraseAgain = passphrase; + } - private String mInputFilename = null; - private String mOutputFilename = null; + @Override + public boolean isModeSymmetric() { + if (PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem()) { + return true; + } else { + return false; + } + } - private boolean mAsciiArmorDemand = false; - private boolean mOverrideAsciiArmor = false; + @Override + public long getSignatureKey() { + return mSigningKeyId; + } - private boolean mGenerateSignature = false; + @Override + public long[] getEncryptionKeys() { + return mEncryptionKeyIds; + } - private long mSecretKeyId = Id.key.none; + @Override + public String getPassphrase() { + return mPassphrase; + } - private FileDialogFragment mFileDialog; + @Override + public String getPassphraseAgain() { + return mPassphraseAgain; + } - private BootstrapButton mEncryptShare; - private BootstrapButton mEncryptClipboard; - private BootstrapButton mEncryptFile; + + private void initView() { + mViewPagerMode = (ViewPager) findViewById(R.id.encrypt_pager_mode); + mPagerTabStripMode = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_mode); + mViewPagerContent = (ViewPager) findViewById(R.id.encrypt_pager_content); + mPagerTabStripContent = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_content); + + mTabsAdapterMode = new PagerTabStripAdapter(this); + mViewPagerMode.setAdapter(mTabsAdapterMode); + mTabsAdapterContent = new PagerTabStripAdapter(this); + mViewPagerContent.setAdapter(mTabsAdapterContent); + } @Override public void onCreate(Bundle savedInstanceState) { @@ -137,11 +161,13 @@ public class EncryptActivity extends DrawerActivity { // Handle intent actions handleActions(getIntent()); - updateView(); - updateSource(); - updateMode(); + mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class, mAsymmetricFragmentBundle, getString(R.string.label_asymmetric)); + mTabsAdapterMode.addTab(EncryptSymmetricFragment.class, mSymmetricFragmentBundle, getString(R.string.label_symmetric)); + mViewPagerMode.setCurrentItem(mSwitchToMode); - updateActionBarButtons(); + mTabsAdapterContent.addTab(EncryptMessageFragment.class, mMessageFragmentBundle, getString(R.string.label_message)); + mTabsAdapterContent.addTab(EncryptFileFragment.class, mFileFragmentBundle, getString(R.string.label_file)); + mViewPagerContent.setCurrentItem(mSwitchToContent); } /** @@ -183,8 +209,7 @@ public class EncryptActivity extends DrawerActivity { if (extras.containsKey(EXTRA_ASCII_ARMOR)) { mAsciiArmorDemand = extras.getBoolean(EXTRA_ASCII_ARMOR, true); - mOverrideAsciiArmor = true; - mAsciiArmor.setChecked(mAsciiArmorDemand); + mFileFragmentBundle.putBoolean(EncryptFileFragment.ARG_ASCII_ARMOR, mAsciiArmorDemand); } String textData = extras.getString(EXTRA_TEXT); @@ -193,20 +218,17 @@ public class EncryptActivity extends DrawerActivity { long[] encryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); // preselect keys given by intent - preselectKeys(signatureKeyId, encryptionKeyIds); + mAsymmetricFragmentBundle.putLongArray(EncryptAsymmetricFragment.ARG_ENCRYPTION_KEY_IDS, encryptionKeyIds); + mAsymmetricFragmentBundle.putLong(EncryptAsymmetricFragment.ARG_SIGNATURE_KEY_ID, signatureKeyId); + mSwitchToMode = PAGER_MODE_ASYMMETRIC; /** * Main Actions */ if (ACTION_ENCRYPT.equals(action) && textData != null) { // encrypt text based on given extra - - mMessage.setText(textData); - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceMessage) { - mSource.showNext(); - } + mMessageFragmentBundle.putString(EncryptMessageFragment.ARG_TEXT, textData); + mSwitchToContent = PAGER_CONTENT_MESSAGE; } else if (ACTION_ENCRYPT.equals(action) && uri != null) { // encrypt file based on Uri @@ -214,14 +236,8 @@ public class EncryptActivity extends DrawerActivity { String path = FileHelper.getPath(this, uri); if (path != null) { - mInputFilename = path; - mFilename.setText(mInputFilename); - - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceFile) { - mSource.showNext(); - } + mFileFragmentBundle.putString(EncryptFileFragment.ARG_FILENAME, path); + mSwitchToContent = PAGER_CONTENT_FILE; } else { Log.e(Constants.TAG, "Direct binary data without actual file in filesystem is not supported by Intents. Please use the Remote Service API!"); @@ -236,740 +252,51 @@ public class EncryptActivity extends DrawerActivity { } } - /** - * If an Intent gives a signatureKeyId and/or encryptionKeyIds, preselect those! - * - * @param preselectedSignatureKeyId - * @param preselectedEncryptionKeyIds - */ - private void preselectKeys(long preselectedSignatureKeyId, long[] preselectedEncryptionKeyIds) { - if (preselectedSignatureKeyId != 0) { - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, - preselectedSignatureKeyId); - PGPSecretKey masterKey = null; - if (keyRing != null) { - masterKey = PgpKeyHelper.getMasterKey(keyRing); - if (masterKey != null) { - Vector signKeys = PgpKeyHelper.getUsableSigningKeys(keyRing); - if (signKeys.size() > 0) { - mSecretKeyId = masterKey.getKeyID(); - } - } - } - } - - if (preselectedEncryptionKeyIds != null) { - Vector goodIds = new Vector(); - for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) { - PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this, - preselectedEncryptionKeyIds[i]); - PGPPublicKey masterKey = null; - if (keyRing == null) { - continue; - } - masterKey = PgpKeyHelper.getMasterKey(keyRing); - if (masterKey == null) { - continue; - } - Vector encryptKeys = PgpKeyHelper.getUsableEncryptKeys(keyRing); - if (encryptKeys.size() == 0) { - continue; - } - goodIds.add(masterKey.getKeyID()); - } - if (goodIds.size() > 0) { - mEncryptionKeyIds = new long[goodIds.size()]; - for (int i = 0; i < goodIds.size(); ++i) { - mEncryptionKeyIds[i] = goodIds.get(i); - } - } - } - } - - /** - * Guess output filename based on input path - * - * @param path - * @return Suggestion for output filename - */ - private String guessOutputFilename(String path) { - // output in the same directory but with additional ending - File file = new File(path); - String ending = (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); - String outputFilename = file.getParent() + File.separator + file.getName() + ending; - - return outputFilename; - } - - private void updateSource() { - switch (mSource.getCurrentView().getId()) { - case R.id.sourceFile: { - mSourceLabel.setText(R.string.label_file); - break; - } - - case R.id.sourceMessage: { - mSourceLabel.setText(R.string.label_message); - break; - } - - default: { - break; - } - } - updateActionBarButtons(); - } - - /** - * Update ActionBar buttons based on current selection in view - */ - private void updateActionBarButtons() { - switch (mSource.getCurrentView().getId()) { - case R.id.sourceFile: { - mEncryptShare.setVisibility(View.GONE); - mEncryptClipboard.setVisibility(View.GONE); - mEncryptFile.setVisibility(View.VISIBLE); - break; - } - - case R.id.sourceMessage: { - mSourceLabel.setText(R.string.label_message); - - mEncryptShare.setVisibility(View.VISIBLE); - mEncryptClipboard.setVisibility(View.VISIBLE); - mEncryptFile.setVisibility(View.GONE); - - if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { - mEncryptShare.setEnabled(true); - mEncryptClipboard.setEnabled(true); - } else { - if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { - if (mSecretKeyId == 0) { - mEncryptShare.setEnabled(false); - mEncryptClipboard.setEnabled(false); - } else { - mEncryptShare.setEnabled(true); - mEncryptClipboard.setEnabled(true); - } - } else { - mEncryptShare.setEnabled(true); - mEncryptClipboard.setEnabled(true); - } - } - break; - } - - default: { - break; - } - } - - } - - private void updateMode() { - switch (mMode.getCurrentView().getId()) { - case R.id.modeAsymmetric: { - mModeLabel.setText(R.string.label_asymmetric); - break; - } - - case R.id.modeSymmetric: { - mModeLabel.setText(R.string.label_symmetric); - break; - } - - default: { - break; - } - } - updateActionBarButtons(); - } - - private void encryptToClipboardClicked() { - mEncryptTarget = Id.target.clipboard; - initiateEncryption(); - } - - private void encryptClicked() { - Log.d(Constants.TAG, "encryptClicked invoked!"); - - if (mSource.getCurrentView().getId() == R.id.sourceFile) { - mEncryptTarget = Id.target.file; - } else { - mEncryptTarget = Id.target.email; - } - initiateEncryption(); - } - - private void initiateEncryption() { - if (mEncryptTarget == Id.target.file) { - String currentFilename = mFilename.getText().toString(); - if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - mInputFilename = mFilename.getText().toString(); - } - - mOutputFilename = guessOutputFilename(mInputFilename); - - if (mInputFilename.equals("")) { - AppMsg.makeText(this, R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); - return; - } - - if (!mInputFilename.startsWith("content")) { - File file = new File(mInputFilename); - if (!file.exists() || !file.isFile()) { - AppMsg.makeText( - this, - getString(R.string.error_message, - getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT) - .show(); - return; - } - } - } - - // symmetric encryption - if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { - boolean gotPassPhrase = false; - String passphrase = mPassphrase.getText().toString(); - String passphraseAgain = mPassphraseAgain.getText().toString(); - if (!passphrase.equals(passphraseAgain)) { - AppMsg.makeText(this, R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show(); - return; - } - - gotPassPhrase = (passphrase.length() != 0); - if (!gotPassPhrase) { - AppMsg.makeText(this, R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT) - .show(); - return; - } - } else { - boolean encryptIt = (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0); - // for now require at least one form of encryption for files - if (!encryptIt && mEncryptTarget == Id.target.file) { - AppMsg.makeText(this, R.string.select_encryption_key, AppMsg.STYLE_ALERT).show(); - return; - } - - if (!encryptIt && mSecretKeyId == 0) { - AppMsg.makeText(this, R.string.select_encryption_or_signature_key, - AppMsg.STYLE_ALERT).show(); - return; - } - - if (mSecretKeyId != 0 - && PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) { - showPassphraseDialog(); - - return; - } - } - - if (mEncryptTarget == Id.target.file) { - showOutputFileDialog(); - } else { - encryptStart(); - } - } - - /** - * Shows passphrase dialog to cache a new passphrase the user enters for using it later for - * encryption - */ - private void showPassphraseDialog() { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - if (mEncryptTarget == Id.target.file) { - showOutputFileDialog(); - } else { - encryptStart(); - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - try { - PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance( - EncryptActivity.this, messenger, mSecretKeyId); - - passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); - } catch (PgpGeneralException e) { - Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); - // send message to handler to start encryption directly - returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); - } - } - - private void showOutputFileDialog() { - // Message is received after file is selected - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - encryptStart(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - mFileDialog = FileDialogFragment.newInstance(messenger, - getString(R.string.title_encrypt_to_file), - getString(R.string.specify_file_to_encrypt_to), mOutputFilename, null); - - mFileDialog.show(getSupportFragmentManager(), "fileDialog"); - } - - private void encryptStart() { - // Send all information needed to service to edit key in other thread - Intent intent = new Intent(this, KeychainIntentService.class); - - // fill values for this action - Bundle data = new Bundle(); - - boolean useAsciiArmor = true; - long encryptionKeyIds[] = null; - int compressionId = 0; - boolean signOnly = false; - long mSecretKeyIdToPass = 0; - - if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { - Log.d(Constants.TAG, "Symmetric encryption enabled!"); - String passphrase = mPassphrase.getText().toString(); - if (passphrase.length() == 0) { - passphrase = null; - } - data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase); - } else { - mSecretKeyIdToPass = mSecretKeyId; - encryptionKeyIds = mEncryptionKeyIds; - signOnly = (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0); - } - - intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN); - // choose default settings, target and data bundle by target - if (mEncryptTarget == Id.target.file) { - useAsciiArmor = mAsciiArmor.isChecked(); - compressionId = ((Choice) mFileCompression.getSelectedItem()).getId(); - - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); - - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename); - - data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); - data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); - - } else { - useAsciiArmor = true; - compressionId = Preferences.getPreferences(this).getDefaultMessageCompression(); - - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); - - String message = mMessage.getText().toString(); - if (signOnly) { - message = fixBadCharactersForGmail(message); - } - data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, message.getBytes()); - } - - if (mOverrideAsciiArmor) { - useAsciiArmor = mAsciiArmorDemand; - } - - data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyIdToPass); - data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor); - data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, encryptionKeyIds); - data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); - data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature); - data.putBoolean(KeychainIntentService.ENCRYPT_SIGN_ONLY, signOnly); - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Message is received after encrypting is done in KeychainIntentService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - // get returned data bundle - Bundle data = message.getData(); - - String output; - switch (mEncryptTarget) { - case Id.target.clipboard: - output = data.getString(KeychainIntentService.RESULT_ENCRYPTED_STRING); - Log.d(Constants.TAG, "output: " + output); - ClipboardReflection.copyToClipboard(EncryptActivity.this, output); - AppMsg.makeText(EncryptActivity.this, - R.string.encryption_to_clipboard_successful, AppMsg.STYLE_INFO) - .show(); - break; - - case Id.target.email: - - output = data.getString(KeychainIntentService.RESULT_ENCRYPTED_STRING); - Log.d(Constants.TAG, "output: " + output); - - Intent sendIntent = new Intent(Intent.ACTION_SEND); - - // Type is set to text/plain so that encrypted messages can - // be sent with Whatsapp, Hangouts, SMS etc... - sendIntent.setType("text/plain"); - - sendIntent.putExtra(Intent.EXTRA_TEXT, output); - startActivity(Intent.createChooser(sendIntent, - getString(R.string.title_send_email))); - break; - - case Id.target.file: - AppMsg.makeText(EncryptActivity.this, R.string.encryption_successful, - AppMsg.STYLE_INFO).show(); - - if (mDeleteAfter.isChecked()) { - // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); - deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); - } - - if (mShareAfter.isChecked()) { - // Share encrypted file - Intent sendFileIntent = new Intent(Intent.ACTION_SEND); - sendFileIntent.setType("*/*"); - sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); - startActivity(Intent.createChooser(sendFileIntent, - getString(R.string.title_send_file))); - } - break; - - default: - // shouldn't happen - break; - - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog(this); - - // start service with intent - startService(intent); - } - - /** - * Fixes bad message characters for gmail - * - * @param message - * @return - */ - private String fixBadCharactersForGmail(String message) { - // fix the message a bit, trailing spaces and newlines break stuff, - // because GMail sends as HTML and such things fuck up the - // signature, - // TODO: things like "<" and ">" also fuck up the signature - message = message.replaceAll(" +\n", "\n"); - message = message.replaceAll("\n\n+", "\n\n"); - message = message.replaceFirst("^\n+", ""); - // make sure there'll be exactly one newline at the end - message = message.replaceFirst("\n*$", "\n"); - - return message; - } - - private void initView() { - mSource = (ViewFlipper) findViewById(R.id.source); - mSourceLabel = (TextView) findViewById(R.id.sourceLabel); - mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious); - mSourceNext = (ImageView) findViewById(R.id.sourceNext); - - mSourcePrevious.setClickable(true); - mSourcePrevious.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_out)); - mSource.showPrevious(); - updateSource(); - } - }); - - mSourceNext.setClickable(true); - OnClickListener nextSourceClickListener = new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_out)); - mSource.showNext(); - updateSource(); - } - }; - mSourceNext.setOnClickListener(nextSourceClickListener); - - mSourceLabel.setClickable(true); - mSourceLabel.setOnClickListener(nextSourceClickListener); - - mMode = (ViewFlipper) findViewById(R.id.mode); - mModeLabel = (TextView) findViewById(R.id.modeLabel); - mModePrevious = (ImageView) findViewById(R.id.modePrevious); - mModeNext = (ImageView) findViewById(R.id.modeNext); - - mModePrevious.setClickable(true); - mModePrevious.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_in)); - mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_out)); - mMode.showPrevious(); - updateMode(); - } - }); - - OnClickListener nextModeClickListener = new OnClickListener() { - public void onClick(View v) { - mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_in)); - mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_out)); - mMode.showNext(); - updateMode(); - } - }; - mModeNext.setOnClickListener(nextModeClickListener); - - mModeLabel.setClickable(true); - mModeLabel.setOnClickListener(nextModeClickListener); - - mMessage = (EditText) findViewById(R.id.message); - mSelectKeysButton = (BootstrapButton) findViewById(R.id.btn_selectEncryptKeys); - mSign = (CheckBox) findViewById(R.id.sign); - mMainUserId = (TextView) findViewById(R.id.mainUserId); - mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); - - mPassphrase = (EditText) findViewById(R.id.passphrase); - mPassphraseAgain = (EditText) findViewById(R.id.passphraseAgain); - - // measure the height of the source_file view and set the message view's min height to that, - // so it fills mSource fully... bit of a hack. - View tmp = findViewById(R.id.sourceFile); - tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - int height = tmp.getMeasuredHeight(); - mMessage.setMinimumHeight(height); - - mFilename = (EditText) findViewById(R.id.filename); - mBrowse = (BootstrapButton) findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - FileHelper.openFile(EncryptActivity.this, mFilename.getText().toString(), "*/*", - Id.request.filename); - } - }); - - - - - - - mFileCompression = (Spinner) findViewById(R.id.fileCompression); - Choice[] choices = new Choice[]{ - new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" - + getString(R.string.compression_fast) + ")"), - new Choice(Id.choice.compression.zip, "ZIP (" - + getString(R.string.compression_fast) + ")"), - new Choice(Id.choice.compression.zlib, "ZLIB (" - + getString(R.string.compression_fast) + ")"), - new Choice(Id.choice.compression.bzip2, "BZIP2 (" - + getString(R.string.compression_very_slow) + ")"), }; - ArrayAdapter adapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mFileCompression.setAdapter(adapter); - - int defaultFileCompression = Preferences.getPreferences(this).getDefaultFileCompression(); - for (int i = 0; i < choices.length; ++i) { - if (choices[i].getId() == defaultFileCompression) { - mFileCompression.setSelection(i); - break; - } - } - - mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption); - mShareAfter = (CheckBox) findViewById(R.id.shareAfterEncryption); - - mAsciiArmor = (CheckBox) findViewById(R.id.asciiArmour); - mAsciiArmor.setChecked(Preferences.getPreferences(this).getDefaultAsciiArmour()); - - mSelectKeysButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - selectPublicKeys(); - } - }); - - mSign.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - CheckBox checkBox = (CheckBox) v; - if (checkBox.isChecked()) { - selectSecretKey(); - } else { - mSecretKeyId = Id.key.none; - updateView(); - } - } - }); - - mEncryptClipboard = (BootstrapButton) findViewById(R.id.action_encrypt_clipboard); - mEncryptClipboard.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - encryptToClipboardClicked(); - } - }); - mEncryptShare = (BootstrapButton) findViewById(R.id.action_encrypt_share); - mEncryptShare.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - encryptClicked(); - } - }); - mEncryptFile = (BootstrapButton) findViewById(R.id.action_encrypt_file); - mEncryptFile.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - encryptClicked(); - } - }); - } - - private void updateView() { - if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { - mSelectKeysButton.setText(getString(R.string.select_keys_button_default)); - } else { - mSelectKeysButton.setText(getResources().getQuantityString( - R.plurals.select_keys_button, mEncryptionKeyIds.length, - mEncryptionKeyIds.length)); - } - - if (mSecretKeyId == Id.key.none) { - mSign.setChecked(false); - mMainUserId.setText(""); - mMainUserIdRest.setText(""); - } else { - String uid = getResources().getString(R.string.user_id_no_name); - String uidExtra = ""; - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, - mSecretKeyId); - if (keyRing != null) { - PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing); - if (key != null) { - String userId = PgpKeyHelper.getMainUserIdSafe(this, key); - String chunks[] = userId.split(" <", 2); - uid = chunks[0]; - if (chunks.length > 1) { - uidExtra = "<" + chunks[1]; - } - } - } - mMainUserId.setText(uid); - mMainUserIdRest.setText(uidExtra); - mSign.setChecked(true); - } - - updateActionBarButtons(); - } - - private void selectPublicKeys() { - Intent intent = new Intent(this, SelectPublicKeyActivity.class); - Vector keyIds = new Vector(); - if (mSecretKeyId != 0) { - keyIds.add(mSecretKeyId); - } - if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) { - for (int i = 0; i < mEncryptionKeyIds.length; ++i) { - keyIds.add(mEncryptionKeyIds[i]); - } - } - long[] initialKeyIds = null; - if (keyIds.size() > 0) { - initialKeyIds = new long[keyIds.size()]; - for (int i = 0; i < keyIds.size(); ++i) { - initialKeyIds[i] = keyIds.get(i); - } - } - intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds); - startActivityForResult(intent, Id.request.public_keys); - } - - private void selectSecretKey() { - Intent intent = new Intent(this, SelectSecretKeyActivity.class); - startActivityForResult(intent, Id.request.secret_keys); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.filename: { - if (resultCode == RESULT_OK && data != null) { - try { - String path = FileHelper.getPath(this, data.getData()); - Log.d(Constants.TAG, "path=" + path); - - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); - } - } - return; - } - - case Id.request.public_keys: { - if (resultCode == RESULT_OK) { - Bundle bundle = data.getExtras(); - mEncryptionKeyIds = bundle - .getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS); - } - updateView(); - break; - } - - case Id.request.secret_keys: { - if (resultCode == RESULT_OK) { - Uri uri_master_key = data.getData(); - mSecretKeyId = Long.valueOf(uri_master_key.getLastPathSegment()); - } else { - mSecretKeyId = Id.key.none; - } - updateView(); - break; - } - - default: { - break; - } - } - - super.onActivityResult(requestCode, resultCode, data); - } +// /** +// * Update ActionBar buttons based on current selection in view +// */ +// private void updateActionBarButtons() { +// switch (mSource.getCurrentView().getId()) { +// case R.id.sourceFile: { +// mEncryptShare.setVisibility(View.GONE); +// mEncryptClipboard.setVisibility(View.GONE); +// mEncryptFile.setVisibility(View.VISIBLE); +// break; +// } +// +// case R.id.sourceMessage: { +// mSourceLabel.setText(R.string.label_message); +// +// mEncryptShare.setVisibility(View.VISIBLE); +// mEncryptClipboard.setVisibility(View.VISIBLE); +// mEncryptFile.setVisibility(View.GONE); +// +// if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { +// mEncryptShare.setEnabled(true); +// mEncryptClipboard.setEnabled(true); +// } else { +// if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { +// if (mSecretKeyId == 0) { +// mEncryptShare.setEnabled(false); +// mEncryptClipboard.setEnabled(false); +// } else { +// mEncryptShare.setEnabled(true); +// mEncryptClipboard.setEnabled(true); +// } +// } else { +// mEncryptShare.setEnabled(true); +// mEncryptClipboard.setEnabled(true); +// } +// } +// break; +// } +// +// default: { +// break; +// } +// } +// +// } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java new file mode 100644 index 000000000..8fbabe9c0 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java @@ -0,0 +1,14 @@ +package org.sufficientlysecure.keychain.ui; + + +public interface EncryptActivityInterface { + + public boolean isModeSymmetric(); + + public long getSignatureKey(); + public long[] getEncryptionKeys(); + + public String getPassphrase(); + public String getPassphraseAgain(); + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java new file mode 100644 index 000000000..b88dafd4a --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import com.beardedhen.androidbootstrap.BootstrapButton; + +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.ProviderHelper; + +import java.util.Vector; + +public class EncryptAsymmetricFragment extends Fragment { + public static final String ARG_SIGNATURE_KEY_ID = "signature_key_id"; + public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; + + public static final int RESULT_CODE_PUBLIC_KEYS = 0x00007001; + public static final int RESULT_CODE_SECRET_KEYS = 0x00007002; + + OnAsymmetricKeySelection mKeySelectionListener; + + // view + private BootstrapButton mSelectKeysButton; + private CheckBox mSign; + private TextView mMainUserId; + private TextView mMainUserIdRest; + + // model + private long mSecretKeyId = Id.key.none; + private long mEncryptionKeyIds[] = null; + + // Container Activity must implement this interface + public interface OnAsymmetricKeySelection { + public void onSigningKeySelected(long signingKeyId); + + public void onEncryptionKeysSelected(long[] encryptionKeyIds); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mKeySelectionListener = (OnAsymmetricKeySelection) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement OnAsymmetricKeySelection"); + } + } + + private void setSignatureKeyId(long signatureKeyId) { + mSecretKeyId = signatureKeyId; + mKeySelectionListener.onSigningKeySelected(signatureKeyId); + } + + private void setEncryptionKeyIds(long[] encryptionKeyIds) { + mEncryptionKeyIds = encryptionKeyIds; + mKeySelectionListener.onEncryptionKeysSelected(encryptionKeyIds); + } + + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false); + + mSelectKeysButton = (BootstrapButton) view.findViewById(R.id.btn_selectEncryptKeys); + mSign = (CheckBox) view.findViewById(R.id.sign); + mMainUserId = (TextView) view.findViewById(R.id.mainUserId); + mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mSelectKeysButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + selectPublicKeys(); + } + }); + mSign.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + CheckBox checkBox = (CheckBox) v; + if (checkBox.isChecked()) { + selectSecretKey(); + } else { + setSignatureKeyId(Id.key.none); + updateView(); + } + } + }); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + long signatureKeyId = getArguments().getLong(ARG_SIGNATURE_KEY_ID); + long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS); + + // preselect keys given by arguments (given by Intent to EncryptActivity) + preselectKeys(signatureKeyId, encryptionKeyIds); + } + + /** + * If an Intent gives a signatureKeyId and/or encryptionKeyIds, preselect those! + * + * @param preselectedSignatureKeyId + * @param preselectedEncryptionKeyIds + */ + private void preselectKeys(long preselectedSignatureKeyId, long[] preselectedEncryptionKeyIds) { + if (preselectedSignatureKeyId != 0) { + PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(getActivity(), + preselectedSignatureKeyId); + PGPSecretKey masterKey; + if (keyRing != null) { + masterKey = PgpKeyHelper.getMasterKey(keyRing); + if (masterKey != null) { + Vector signKeys = PgpKeyHelper.getUsableSigningKeys(keyRing); + if (signKeys.size() > 0) { + setSignatureKeyId(masterKey.getKeyID()); + } + } + } + } + + if (preselectedEncryptionKeyIds != null) { + Vector goodIds = new Vector(); + for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) { + PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(getActivity(), + preselectedEncryptionKeyIds[i]); + PGPPublicKey masterKey; + if (keyRing == null) { + continue; + } + masterKey = PgpKeyHelper.getMasterKey(keyRing); + if (masterKey == null) { + continue; + } + Vector encryptKeys = PgpKeyHelper.getUsableEncryptKeys(keyRing); + if (encryptKeys.size() == 0) { + continue; + } + goodIds.add(masterKey.getKeyID()); + } + if (goodIds.size() > 0) { + long[] keyIds = new long[goodIds.size()]; + for (int i = 0; i < goodIds.size(); ++i) { + keyIds[i] = goodIds.get(i); + } + setEncryptionKeyIds(keyIds); + } + } + } + + private void updateView() { + if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { + mSelectKeysButton.setText(getString(R.string.select_keys_button_default)); + } else { + mSelectKeysButton.setText(getResources().getQuantityString( + R.plurals.select_keys_button, mEncryptionKeyIds.length, + mEncryptionKeyIds.length)); + } + + if (mSecretKeyId == Id.key.none) { + mSign.setChecked(false); + mMainUserId.setText(""); + mMainUserIdRest.setText(""); + } else { + String uid = getResources().getString(R.string.user_id_no_name); + String uidExtra = ""; + // TODO: make it nice and use helper! + PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(getActivity(), + mSecretKeyId); + if (keyRing != null) { + PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing); + if (key != null) { + String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key); + String chunks[] = userId.split(" <", 2); + uid = chunks[0]; + if (chunks.length > 1) { + uidExtra = "<" + chunks[1]; + } + } + } + mMainUserId.setText(uid); + mMainUserIdRest.setText(uidExtra); + mSign.setChecked(true); + } + +//TODO +// updateActionBarButtons(); + } + + private void selectPublicKeys() { + Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class); + Vector keyIds = new Vector(); + if (mSecretKeyId != 0) { + keyIds.add(mSecretKeyId); + } + if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) { + for (int i = 0; i < mEncryptionKeyIds.length; ++i) { + keyIds.add(mEncryptionKeyIds[i]); + } + } + long[] initialKeyIds = null; + if (keyIds.size() > 0) { + initialKeyIds = new long[keyIds.size()]; + for (int i = 0; i < keyIds.size(); ++i) { + initialKeyIds[i] = keyIds.get(i); + } + } + intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds); + startActivityForResult(intent, Id.request.public_keys); + } + + private void selectSecretKey() { + Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class); + startActivityForResult(intent, Id.request.secret_keys); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + + case RESULT_CODE_PUBLIC_KEYS: { + if (resultCode == Activity.RESULT_OK) { + Bundle bundle = data.getExtras(); + setEncryptionKeyIds(bundle + .getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS)); + } + updateView(); + break; + } + + case RESULT_CODE_SECRET_KEYS: { + if (resultCode == Activity.RESULT_OK) { + Uri uri_master_key = data.getData(); + setSignatureKeyId(Long.valueOf(uri_master_key.getLastPathSegment())); + } else { + setSignatureKeyId(Id.key.none); + } + updateView(); + break; + } + + default: { + break; + } + } + + super.onActivityResult(requestCode, resultCode, data); + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java new file mode 100644 index 000000000..734941ca7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; + +import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.FileHelper; +import org.sufficientlysecure.keychain.helper.Preferences; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; +import org.sufficientlysecure.keychain.util.Choice; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.File; + +public class EncryptFileFragment extends Fragment { + public static final String ARG_FILENAME = "filename"; + public static final String ARG_ASCII_ARMOR = "ascii_armor"; + + private static final int RESULT_CODE_FILE = 0x00007003; + + private EncryptActivityInterface mEncryptInterface; + + // view + private CheckBox mAsciiArmor = null; + private Spinner mFileCompression = null; + private EditText mFilename = null; + private CheckBox mDeleteAfter = null; + private CheckBox mShareAfter = null; + private BootstrapButton mBrowse = null; + private BootstrapButton mEncryptFile; + + private FileDialogFragment mFileDialog; + + // model + private String mInputFilename = null; + private String mOutputFilename = null; + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mEncryptInterface = (EncryptActivityInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface"); + } + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.encrypt_file_fragment, container, false); + + mEncryptFile = (BootstrapButton) view.findViewById(R.id.action_encrypt_file); + mEncryptFile.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + encryptClicked(); + } + }); + + mFilename = (EditText) view.findViewById(R.id.filename); + mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse); + mBrowse.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*", + Id.request.filename); + } + }); + + mFileCompression = (Spinner) view.findViewById(R.id.fileCompression); + Choice[] choices = new Choice[]{ + new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" + + getString(R.string.compression_fast) + ")"), + new Choice(Id.choice.compression.zip, "ZIP (" + + getString(R.string.compression_fast) + ")"), + new Choice(Id.choice.compression.zlib, "ZLIB (" + + getString(R.string.compression_fast) + ")"), + new Choice(Id.choice.compression.bzip2, "BZIP2 (" + + getString(R.string.compression_very_slow) + ")"),}; + ArrayAdapter adapter = new ArrayAdapter(getActivity(), + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mFileCompression.setAdapter(adapter); + + int defaultFileCompression = Preferences.getPreferences(getActivity()).getDefaultFileCompression(); + for (int i = 0; i < choices.length; ++i) { + if (choices[i].getId() == defaultFileCompression) { + mFileCompression.setSelection(i); + break; + } + } + + mDeleteAfter = (CheckBox) view.findViewById(R.id.deleteAfterEncryption); + mShareAfter = (CheckBox) view.findViewById(R.id.shareAfterEncryption); + + mAsciiArmor = (CheckBox) view.findViewById(R.id.asciiArmour); + mAsciiArmor.setChecked(Preferences.getPreferences(getActivity()).getDefaultAsciiArmour()); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + String filename = getArguments().getString(ARG_FILENAME); + if (filename != null) { + mFilename.setText(filename); + } + boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR); + if (asciiArmor) { + mAsciiArmor.setChecked(asciiArmor); + } + } + + /** + * Guess output filename based on input path + * + * @param path + * @return Suggestion for output filename + */ + private String guessOutputFilename(String path) { + // output in the same directory but with additional ending + File file = new File(path); + String ending = (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); + String outputFilename = file.getParent() + File.separator + file.getName() + ending; + + return outputFilename; + } + + private void showOutputFileDialog() { + // Message is received after file is selected + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == FileDialogFragment.MESSAGE_OKAY) { + Bundle data = message.getData(); + mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + encryptStart(); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + mFileDialog = FileDialogFragment.newInstance(messenger, + getString(R.string.title_encrypt_to_file), + getString(R.string.specify_file_to_encrypt_to), mOutputFilename, null); + + mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog"); + } + + private void encryptClicked() { + String currentFilename = mFilename.getText().toString(); + if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { + mInputFilename = mFilename.getText().toString(); + } + + mOutputFilename = guessOutputFilename(mInputFilename); + + if (mInputFilename.equals("")) { + AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); + return; + } + + if (!mInputFilename.startsWith("content")) { + File file = new File(mInputFilename); + if (!file.exists() || !file.isFile()) { + AppMsg.makeText( + getActivity(), + getString(R.string.error_message, + getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT) + .show(); + return; + } + } + + if (mEncryptInterface.isModeSymmetric()) { + // symmetric encryption + + if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) { + AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show(); + return; + } + + boolean gotPassPhrase = (mEncryptInterface.getPassphrase().length() != 0); + if (!gotPassPhrase) { + AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT) + .show(); + return; + } + } else { + // asymmetric encryption + + boolean encryptIt = (mEncryptInterface.getEncryptionKeys() != null + && mEncryptInterface.getEncryptionKeys().length > 0); + // for now require at least one form of encryption for files + if (!encryptIt) { + AppMsg.makeText(getActivity(), R.string.select_encryption_key, AppMsg.STYLE_ALERT).show(); + return; + } + + if (!encryptIt && mEncryptInterface.getSignatureKey() == 0) { + AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key, + AppMsg.STYLE_ALERT).show(); + return; + } + + if (mEncryptInterface.getSignatureKey() != 0 + && PassphraseCacheService.getCachedPassphrase(getActivity(), mEncryptInterface.getSignatureKey()) == null) { + showPassphraseDialog(); + + return; + } + } + + showOutputFileDialog(); + } + + private void encryptStart() { + // Send all information needed to service to edit key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + Bundle data = new Bundle(); + + boolean useAsciiArmor = true; + long encryptionKeyIds[] = null; + int compressionId = 0; + boolean signOnly = false; + long mSecretKeyIdToPass = 0; + + if (mEncryptInterface.isModeSymmetric()) { + Log.d(Constants.TAG, "Symmetric encryption enabled!"); + String passphrase = mEncryptInterface.getPassphrase(); + if (passphrase.length() == 0) { + passphrase = null; + } + data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase); + } else { + mSecretKeyIdToPass = mEncryptInterface.getSignatureKey(); + encryptionKeyIds = mEncryptInterface.getEncryptionKeys(); + signOnly = (mEncryptInterface.getEncryptionKeys() == null + || mEncryptInterface.getEncryptionKeys().length == 0); + } + + intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN); + + // choose default settings, target and data bundle by target + useAsciiArmor = mAsciiArmor.isChecked(); + compressionId = ((Choice) mFileCompression.getSelectedItem()).getId(); + + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); + + Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" + + mOutputFilename); + + data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); + data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + + + data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyIdToPass); + data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor); + data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, encryptionKeyIds); + data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); +// data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature); + data.putBoolean(KeychainIntentService.ENCRYPT_SIGN_ONLY, signOnly); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after encrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), + getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle data = message.getData(); + + String output; + AppMsg.makeText(getActivity(), R.string.encryption_successful, + AppMsg.STYLE_INFO).show(); + + if (mDeleteAfter.isChecked()) { + // Create and show dialog to delete original file + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment + .newInstance(mInputFilename); + deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + } + + if (mShareAfter.isChecked()) { + // Share encrypted file + Intent sendFileIntent = new Intent(Intent.ACTION_SEND); + sendFileIntent.setType("*/*"); + sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); + startActivity(Intent.createChooser(sendFileIntent, + getString(R.string.title_send_file))); + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case RESULT_CODE_FILE: { + if (resultCode == Activity.RESULT_OK && data != null) { + try { + String path = FileHelper.getPath(getActivity(), data.getData()); + Log.d(Constants.TAG, "path=" + path); + + mFilename.setText(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + } + } + return; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + + break; + } + } + } + + /** + * Shows passphrase dialog to cache a new passphrase the user enters for using it later for + * encryption + */ + private void showPassphraseDialog() { + // Message is received after passphrase is cached + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + showOutputFileDialog(); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + try { + PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance( + getActivity(), messenger, mEncryptInterface.getSignatureKey()); + + passphraseDialog.show(getActivity().getSupportFragmentManager(), "passphraseDialog"); + } catch (PgpGeneralException e) { + Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); + // send message to handler to start encryption directly + returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); + } + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java new file mode 100644 index 000000000..7f1d7311d --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.helper.Preferences; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; +import org.sufficientlysecure.keychain.util.Log; + +public class EncryptMessageFragment extends Fragment { + public static final String ARG_TEXT = "text"; + + private EditText mMessage = null; + private BootstrapButton mEncryptShare; + private BootstrapButton mEncryptClipboard; + + private EncryptActivityInterface mEncryptInterface; + + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mEncryptInterface = (EncryptActivityInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface"); + } + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.encrypt_message_fragment, container, false); + + mMessage = (EditText) view.findViewById(R.id.message); + mEncryptClipboard = (BootstrapButton) view.findViewById(R.id.action_encrypt_clipboard); + mEncryptShare = (BootstrapButton) view.findViewById(R.id.action_encrypt_share); + mEncryptClipboard.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + encryptClicked(true); + } + }); + mEncryptShare.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + encryptClicked(false); + } + }); + + return view; + } + + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + String text = getArguments().getString(ARG_TEXT); + if (text != null) { + mMessage.setText(text); + } + } + + /** + * Fixes bad message characters for gmail + * + * @param message + * @return + */ + private String fixBadCharactersForGmail(String message) { + // fix the message a bit, trailing spaces and newlines break stuff, + // because GMail sends as HTML and such things fuck up the + // signature, + // TODO: things like "<" and ">" also fuck up the signature + message = message.replaceAll(" +\n", "\n"); + message = message.replaceAll("\n\n+", "\n\n"); + message = message.replaceFirst("^\n+", ""); + // make sure there'll be exactly one newline at the end + message = message.replaceFirst("\n*$", "\n"); + + return message; + } + + private void encryptClicked(boolean toClipboard) { + if (mEncryptInterface.isModeSymmetric()) { + // symmetric encryption + + boolean gotPassPhrase = false; + if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) { + AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show(); + return; + } + + gotPassPhrase = (mEncryptInterface.getPassphrase().length() != 0); + if (!gotPassPhrase) { + AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT) + .show(); + return; + } + } else { + // asymmetric encryption + + boolean encryptIt = (mEncryptInterface.getEncryptionKeys() != null + && mEncryptInterface.getEncryptionKeys().length > 0); + + if (!encryptIt && mEncryptInterface.getSignatureKey() == 0) { + AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key, + AppMsg.STYLE_ALERT).show(); + return; + } + + if (mEncryptInterface.getSignatureKey() != 0 + && PassphraseCacheService.getCachedPassphrase(getActivity(), mEncryptInterface.getSignatureKey()) == null) { + showPassphraseDialog(toClipboard); + + return; + } + } + + encryptStart(toClipboard); + } + + private void encryptStart(final boolean toClipboard) { + // Send all information needed to service to edit key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + Bundle data = new Bundle(); + + long encryptionKeyIds[] = null; + int compressionId = 0; + boolean signOnly = false; + long mSecretKeyIdToPass = 0; + + if (mEncryptInterface.isModeSymmetric()) { + Log.d(Constants.TAG, "Symmetric encryption enabled!"); + String passphrase = mEncryptInterface.getPassphrase(); + if (passphrase.length() == 0) { + passphrase = null; + } + data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase); + } else { + mSecretKeyIdToPass = mEncryptInterface.getSignatureKey(); + encryptionKeyIds = mEncryptInterface.getEncryptionKeys(); + signOnly = (mEncryptInterface.getEncryptionKeys() == null + || mEncryptInterface.getEncryptionKeys().length == 0); + } + + intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN); + + // choose default settings, target and data bundle by target + compressionId = Preferences.getPreferences(getActivity()).getDefaultMessageCompression(); + + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); + + String message = mMessage.getText().toString(); + if (signOnly) { + message = fixBadCharactersForGmail(message); + } + data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, message.getBytes()); + + data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyIdToPass); + data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, true); + data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, encryptionKeyIds); + data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); +// data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature); + data.putBoolean(KeychainIntentService.ENCRYPT_SIGN_ONLY, signOnly); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after encrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), + getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle data = message.getData(); + + String output; + if (toClipboard) { + output = data.getString(KeychainIntentService.RESULT_ENCRYPTED_STRING); + Log.d(Constants.TAG, "output: " + output); + ClipboardReflection.copyToClipboard(getActivity(), output); + AppMsg.makeText(getActivity(), + R.string.encryption_to_clipboard_successful, AppMsg.STYLE_INFO) + .show(); + } else { + output = data.getString(KeychainIntentService.RESULT_ENCRYPTED_STRING); + Log.d(Constants.TAG, "output: " + output); + + Intent sendIntent = new Intent(Intent.ACTION_SEND); + + // Type is set to text/plain so that encrypted messages can + // be sent with Whatsapp, Hangouts, SMS etc... + sendIntent.setType("text/plain"); + + sendIntent.putExtra(Intent.EXTRA_TEXT, output); + startActivity(Intent.createChooser(sendIntent, + getString(R.string.title_send_email))); + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } + + /** + * Shows passphrase dialog to cache a new passphrase the user enters for using it later for + * encryption + */ + private void showPassphraseDialog(final boolean toClipboard) { + // Message is received after passphrase is cached + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + encryptStart(toClipboard); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + try { + PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance( + getActivity(), messenger, mEncryptInterface.getSignatureKey()); + + passphraseDialog.show(getActivity().getSupportFragmentManager(), "passphraseDialog"); + } catch (PgpGeneralException e) { + Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); + // send message to handler to start encryption directly + returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); + } + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java new file mode 100644 index 000000000..f23cc565a --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; + +public class EncryptSymmetricFragment extends Fragment { + + OnSymmetricKeySelection mPassphraseUpdateListener; + + private EditText mPassphrase; + private EditText mPassphraseAgain; + + // Container Activity must implement this interface + public interface OnSymmetricKeySelection { + public void onPassphraseUpdate(String passphrase); + + public void onPassphraseAgainUpdate(String passphrase); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mPassphraseUpdateListener = (OnSymmetricKeySelection) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement OnSymmetricKeySelection"); + } + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.encrypt_symmetric_fragment, container, false); + + mPassphrase = (EditText) view.findViewById(R.id.passphrase); + mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain); + mPassphrase.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + mPassphraseUpdateListener.onPassphraseUpdate(s.toString()); + } + }); + mPassphraseAgain.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + mPassphraseUpdateListener.onPassphraseAgainUpdate(s.toString()); + } + }); + + return view; + } +} diff --git a/OpenPGP-Keychain/src/main/res/layout/decrypt_file_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/decrypt_file_fragment.xml index e7d08f0fa..4515c8092 100644 --- a/OpenPGP-Keychain/src/main/res/layout/decrypt_file_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/decrypt_file_fragment.xml @@ -13,8 +13,7 @@ + android:orientation="horizontal"> diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_content.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_content.xml index 1dba66cfa..e719d07e1 100644 --- a/OpenPGP-Keychain/src/main/res/layout/encrypt_content.xml +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_content.xml @@ -1,318 +1,37 @@ - + + + android:layout_height="150dp"> - + android:layout_gravity="top" + android:background="@color/emphasis" + android:textColor="#fff" /> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + - - - - + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_file_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_file_fragment.xml index 24a67acb7..09df8ae28 100644 --- a/OpenPGP-Keychain/src/main/res/layout/encrypt_file_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_file_fragment.xml @@ -59,7 +59,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/section_encrypt_and_or_sign" - android:layout_above="@+id/decrypt_file_action_decrypt" + android:layout_above="@+id/action_encrypt_file" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" /> @@ -68,10 +68,12 @@ android:layout_width="match_parent" android:layout_height="60dp" android:padding="4dp" - android:visibility="gone" android:text="@string/btn_encrypt_file" bootstrapbutton:bb_icon_left="fa-lock" - bootstrapbutton:bb_type="info" /> + bootstrapbutton:bb_type="info" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" /> \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/values/strings.xml b/OpenPGP-Keychain/src/main/res/values/strings.xml index 5d325d5ec..8ae43c8ea 100644 --- a/OpenPGP-Keychain/src/main/res/values/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values/strings.xml @@ -135,8 +135,6 @@ Name Comment Email - Sign User Id - Sign email Upload key to selected keyserver after certification Fingerprint Select -- cgit v1.2.3