diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure')
24 files changed, 640 insertions, 935 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index b6832a9c7..6a9656b28 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -108,6 +108,10 @@ public final class Constants { // keyserver sync settings public static final String SYNC_CONTACTS = "syncContacts"; public static final String SYNC_KEYSERVER = "syncKeyserver"; + // other settings + public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm"; + public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities"; + public static final String EXPERIMENTAL_ENABLE_KEYBASE = "experimentalEnableKeybase"; public static final class Theme { public static final String LIGHT = "light"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java index 30f37dd4f..8f1abde83 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java @@ -43,7 +43,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.provider.ProviderHelper; @@ -141,7 +141,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat } } - PgpDecryptVerify op = new PgpDecryptVerify(mContext, mProviderHelper, mProgressable); + PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable); PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes) .setSignedLiteralData(true) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 1e51403fc..dd30156f9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -79,9 +79,9 @@ import java.security.SignatureException; import java.util.Date; import java.util.Iterator; -public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> { +public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInputParcel> { - public PgpDecryptVerify(Context context, ProviderHelper providerHelper, Progressable progressable) { + public PgpDecryptVerifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { super(context, providerHelper, progressable); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index e67f43c4c..cc4a08fb4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -39,7 +39,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; @@ -534,7 +534,7 @@ public class OpenPgpService extends RemoteService { byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE); - PgpDecryptVerify op = new PgpDecryptVerify(this, mProviderHelper, null); + PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mProviderHelper, null); long inputLength = inputStream.available(); InputData inputData = new InputData(inputStream, inputLength); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java index dca2a08c2..eff27f112 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -40,7 +40,7 @@ import org.sufficientlysecure.keychain.operations.PromoteKeyOperation; import org.sufficientlysecure.keychain.operations.RevokeOperation; import org.sufficientlysecure.keychain.operations.SignEncryptOperation; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; @@ -111,7 +111,7 @@ public class KeychainService extends Service implements Progressable { op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); } else if (inputParcel instanceof PgpDecryptVerifyInputParcel) { - op = new PgpDecryptVerify(outerThis, new ProviderHelper(outerThis), outerThis); + op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else if (inputParcel instanceof SaveKeyringParcel) { op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java index 2d9ac6ee3..a3ea8ad9a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java @@ -18,7 +18,11 @@ package org.sufficientlysecure.keychain.ui; +import java.io.File; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; import android.app.Activity; import android.content.ContentResolver; @@ -47,7 +51,20 @@ public class BackupFragment extends Fragment { private int mIndex; static final int REQUEST_REPEAT_PASSPHRASE = 1; + private ExportHelper mExportHelper; + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + // we won't get attached to a non-fragment activity, so the cast should be safe + mExportHelper = new ExportHelper((FragmentActivity) activity); + } + + @Override + public void onDetach() { + super.onDetach(); + mExportHelper = null; + } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -80,8 +97,7 @@ public class BackupFragment extends Fragment { } if (!includeSecretKeys) { - ExportHelper exportHelper = new ExportHelper(activity); - exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, false); + startBackup(false); return; } @@ -136,8 +152,7 @@ public class BackupFragment extends Fragment { return; } - ExportHelper exportHelper = new ExportHelper(activity); - exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); + startBackup(true); } }.execute(activity.getContentResolver()); @@ -167,8 +182,19 @@ public class BackupFragment extends Fragment { return; } - ExportHelper exportHelper = new ExportHelper(getActivity()); - exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); + startBackup(true); } } + + private void startBackup(boolean exportSecret) { + File filename; + String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); + if (exportSecret) { + filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".asc"); + } else { + filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".pub.asc"); + } + mExportHelper.showExportKeysDialog(null, filename, exportSecret); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java index 016ab5f3c..c5528e40b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java @@ -30,6 +30,8 @@ public class CertifyFingerprintActivity extends BaseActivity { protected Uri mDataUri; + public static final String EXTRA_ENABLE_WORD_CONFIRM = "enable_word_confirm"; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -40,6 +42,7 @@ public class CertifyFingerprintActivity extends BaseActivity { finish(); return; } + boolean enableWordConfirm = getIntent().getBooleanExtra(EXTRA_ENABLE_WORD_CONFIRM, false); setFullScreenDialogClose(new View.OnClickListener() { @Override @@ -50,7 +53,7 @@ public class CertifyFingerprintActivity extends BaseActivity { Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); - startFragment(savedInstanceState, mDataUri); + startFragment(savedInstanceState, mDataUri, enableWordConfirm); } @Override @@ -58,7 +61,7 @@ public class CertifyFingerprintActivity extends BaseActivity { setContentView(R.layout.certify_fingerprint_activity); } - private void startFragment(Bundle savedInstanceState, Uri dataUri) { + private void startFragment(Bundle savedInstanceState, Uri dataUri, boolean enableWordConfirm) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -67,7 +70,7 @@ public class CertifyFingerprintActivity extends BaseActivity { } // Create an instance of the fragment - CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri); + CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri, enableWordConfirm); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java index a6b8a0e85..552fa34c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.database.Cursor; +import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager; @@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.util.ExperimentalWordConfirm; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -44,23 +46,24 @@ public class CertifyFingerprintFragment extends LoaderFragment implements static final int REQUEST_CERTIFY = 1; public static final String ARG_DATA_URI = "uri"; + public static final String ARG_ENABLE_WORD_CONFIRM = "enable_word_confirm"; private TextView mFingerprint; + private TextView mIntro; private static final int LOADER_ID_UNIFIED = 0; private Uri mDataUri; - - private View mActionNo; - private View mActionYes; + private boolean mEnableWordConfirm; /** * Creates new instance of this fragment */ - public static CertifyFingerprintFragment newInstance(Uri dataUri) { + public static CertifyFingerprintFragment newInstance(Uri dataUri, boolean enableWordConfirm) { CertifyFingerprintFragment frag = new CertifyFingerprintFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); + args.putBoolean(ARG_ENABLE_WORD_CONFIRM, enableWordConfirm); frag.setArguments(args); @@ -72,18 +75,19 @@ public class CertifyFingerprintFragment extends LoaderFragment implements View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer()); - mActionNo = view.findViewById(R.id.certify_fingerprint_button_no); - mActionYes = view.findViewById(R.id.certify_fingerprint_button_yes); + View actionNo = view.findViewById(R.id.certify_fingerprint_button_no); + View actionYes = view.findViewById(R.id.certify_fingerprint_button_yes); mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint); + mIntro = (TextView) view.findViewById(R.id.certify_fingerprint_intro); - mActionNo.setOnClickListener(new View.OnClickListener() { + actionNo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getActivity().finish(); } }); - mActionYes.setOnClickListener(new View.OnClickListener() { + actionYes.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { certify(mDataUri); @@ -103,6 +107,11 @@ public class CertifyFingerprintFragment extends LoaderFragment implements getActivity().finish(); return; } + mEnableWordConfirm = getArguments().getBoolean(ARG_ENABLE_WORD_CONFIRM); + + if (mEnableWordConfirm) { + mIntro.setText(R.string.certify_fingerprint_text_words); + } loadData(dataUri); } @@ -149,10 +158,13 @@ public class CertifyFingerprintFragment extends LoaderFragment implements switch (loader.getId()) { case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { - byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); - mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); + + if (mEnableWordConfirm) { + displayWordConfirm(fingerprintBlob); + } else { + displayHexConfirm(fingerprintBlob); + } break; } @@ -162,6 +174,19 @@ public class CertifyFingerprintFragment extends LoaderFragment implements setContentShown(true); } + private void displayHexConfirm(byte[] fingerprintBlob) { + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); + mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); + } + + private void displayWordConfirm(byte[] fingerprintBlob) { + String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob); + + mFingerprint.setTextSize(24); + mFingerprint.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + mFingerprint.setText(fingerprint); + } + /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index f57d2d056..26e56280a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -36,7 +36,6 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.support.v7.widget.DefaultItemAnimator; @@ -222,18 +221,6 @@ public class DecryptListFragment } } - private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - File file = new File(inputUri.getPath()); - File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; - File targetFile = new File(parentDir, originalFilename); - FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), - getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); - } else { - FileHelper.saveDocument(this, mimeType, originalFilename, REQUEST_CODE_OUTPUT); - } - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { @@ -534,7 +521,8 @@ public class DecryptListFragment return true; } mCurrentInputUri = model.mInputUri; - askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType()); + FileHelper.saveDocument(this, metadata.getFilename(), model.mInputUri, metadata.getMimeType(), + R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT); return true; case R.id.decrypt_delete: deleteFile(activity, model.mInputUri); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java index fc72a6c9f..84660ca7a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -83,11 +83,7 @@ public class EncryptDecryptOverviewFragment extends Fragment { mDecryptFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - FileHelper.openDocument(EncryptDecryptOverviewFragment.this, "*/*", REQUEST_CODE_INPUT); - } else { - FileHelper.openFile(EncryptDecryptOverviewFragment.this, null, "*/*", REQUEST_CODE_INPUT); - } + FileHelper.openDocument(EncryptDecryptOverviewFragment.this, null, "*/*", false, REQUEST_CODE_INPUT); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 63d37f296..8572a5712 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Date; @@ -196,13 +195,9 @@ public class EncryptFilesFragment } private void addInputUri() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT); - } else { - FileHelper.openFile(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ? - null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri, - "*/*", REQUEST_CODE_INPUT); - } + FileHelper.openDocument(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ? + null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri, + "*/*", true, REQUEST_CODE_INPUT); } private void addInputUri(Uri inputUri) { @@ -230,19 +225,8 @@ public class EncryptFilesFragment (mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri)) + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); Uri inputUri = model.inputUri; - saveDocumentIntent(targetName, inputUri); - } - - private void saveDocumentIntent(String targetName, Uri inputUri) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - File file = new File(inputUri.getPath()); - File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; - File targetFile = new File(parentDir, targetName); - FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file), - getString(R.string.specify_file_to_encrypt_to), targetFile, REQUEST_CODE_OUTPUT); - } else { - FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT); - } + FileHelper.saveDocument(this, targetName, inputUri, + R.string.title_encrypt_to_file, R.string.specify_file_to_encrypt_to, REQUEST_CODE_OUTPUT); } public void addFile(Intent data) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index 538fa16c7..746c75600 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -64,8 +64,8 @@ public class ImportKeysFileFragment extends Fragment { // open .asc or .gpg files // setting it to text/plain prevents Cyanogenmod's file manager from selecting asc // or gpg types! - FileHelper.openFile(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR), - "*/*", REQUEST_CODE_FILE); + FileHelper.openDocument(ImportKeysFileFragment.this, + Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 1cd1a3099..4de83337e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -252,7 +252,7 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe String message = this.getString(R.string.specify_file_to_export_log_to); - FileHelper.saveFile(new FileHelper.FileDialogCallback() { + FileHelper.saveDocumentDialog(new FileHelper.FileDialogCallback() { @Override public void onFileSelected(File file, boolean checked) { writeToLogFile(mResult.getLog(), file); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index d7224bd04..881fb05aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -280,51 +280,42 @@ public class PassphraseDialogActivity extends FragmentActivity { mPassphraseText.setText(message); - if (keyType == CanonicalizedSecretKey.SecretKeyType.PATTERN) { - // start pattern dialog and show progress circle here... -// Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class); -// patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123"); -// startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN); - mInput.setVisibility(View.INVISIBLE); - mProgress.setVisibility(View.VISIBLE); - } else { - // Hack to open keyboard. - // This is the only method that I found to work across all Android versions - // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ - // Notes: * onCreateView can't be used because we want to add buttons to the dialog - // * opening in onActivityCreated does not work on Android 4.4 - mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mPassphraseEditText.post(new Runnable() { - @Override - public void run() { - if (getActivity() == null || mPassphraseEditText == null) { - return; - } - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT); + // Hack to open keyboard. + // This is the only method that I found to work across all Android versions + // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ + // Notes: * onCreateView can't be used because we want to add buttons to the dialog + // * opening in onActivityCreated does not work on Android 4.4 + mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + mPassphraseEditText.post(new Runnable() { + @Override + public void run() { + if (getActivity() == null || mPassphraseEditText == null) { + return; } - }); - } - }); - mPassphraseEditText.requestFocus(); - - mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); - mPassphraseEditText.setOnEditorActionListener(this); - - if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) - || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { - mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER); - mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); - } else { - mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + InputMethodManager imm = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT); + } + }); } + }); + mPassphraseEditText.requestFocus(); + mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); + mPassphraseEditText.setOnEditorActionListener(this); + + if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) + || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { + mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER); mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); + } else { + mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } + mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); + AlertDialog dialog = alert.create(); dialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java deleted file mode 100644 index e55494145..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * 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 <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui; - -import android.annotation.TargetApi; -import android.support.v7.app.AlertDialog; -import android.app.PendingIntent; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.nfc.FormatException; -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; -import android.nfc.NfcAdapter; -import android.nfc.Tag; -import android.nfc.tech.Ndef; -import android.os.Build; -import android.os.Bundle; -import android.provider.Settings; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.FragmentTransaction; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.util.Log; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.List; - -@TargetApi(Build.VERSION_CODES.HONEYCOMB) -public class PassphraseWizardActivity extends FragmentActivity { -//public class PassphraseWizardActivity extends FragmentActivity implements LockPatternView.OnPatternListener { - //create or authenticate - public String selectedAction; - //for lockpattern - public static char[] pattern; - private static String passphrase = ""; - //nfc string - private static byte[] output = new byte[8]; - - public static final String CREATE_METHOD = "create"; - public static final String AUTHENTICATION = "authenticate"; - - NfcAdapter adapter; - PendingIntent pendingIntent; - IntentFilter writeTagFilters[]; - boolean writeMode; - Tag myTag; - boolean writeNFC = false; - boolean readNFC = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getActionBar() != null) { - getActionBar().setTitle(R.string.unlock_method); - } - - selectedAction = getIntent().getAction(); - if (savedInstanceState == null) { - SelectMethods selectMethods = new SelectMethods(); - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.add(R.id.fragmentContainer, selectMethods).commit(); - } - setContentView(R.layout.passphrase_wizard); - - adapter = NfcAdapter.getDefaultAdapter(this); - if (adapter != null) { - pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, PassphraseWizardActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); - IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); - tagDetected.addCategory(Intent.CATEGORY_DEFAULT); - writeTagFilters = new IntentFilter[]{tagDetected}; - } - } - - public void noPassphrase(View view) { - passphrase = ""; - Toast.makeText(this, R.string.no_passphrase_set, Toast.LENGTH_SHORT).show(); - this.finish(); - } - - public void passphrase(View view) { - Passphrase passphrase = new Passphrase(); - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.fragmentContainer, passphrase).addToBackStack(null).commit(); - } - - public void startLockpattern(View view) { - if (getActionBar() != null) { - getActionBar().setTitle(R.string.draw_lockpattern); - } -// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction); -// LockPatternFragment lpf = LockPatternFragment.newInstance("asd"); - -// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); -// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit(); - } - - public void cancel(View view) { - this.finish(); - } - - public void savePassphrase(View view) { - EditText passphrase = (EditText) findViewById(R.id.passphrase); - passphrase.setError(null); - String pw = passphrase.getText().toString(); - //check and save passphrase - if (selectedAction.equals(CREATE_METHOD)) { - EditText passphraseAgain = (EditText) findViewById(R.id.passphraseAgain); - passphraseAgain.setError(null); - String pwAgain = passphraseAgain.getText().toString(); - - if (!TextUtils.isEmpty(pw)) { - if (!TextUtils.isEmpty(pwAgain)) { - if (pw.equals(pwAgain)) { - PassphraseWizardActivity.passphrase = pw; - Toast.makeText(this, getString(R.string.passphrase_saved), Toast.LENGTH_SHORT).show(); - this.finish(); - } else { - passphrase.setError(getString(R.string.passphrase_invalid)); - passphrase.requestFocus(); - } - } else { - passphraseAgain.setError(getString(R.string.missing_passphrase)); - passphraseAgain.requestFocus(); - } - } else { - passphrase.setError(getString(R.string.missing_passphrase)); - passphrase.requestFocus(); - } - } - //check for right passphrase - if (selectedAction.equals(AUTHENTICATION)) { - if (pw.equals(PassphraseWizardActivity.passphrase)) { - Toast.makeText(this, getString(R.string.unlocked), Toast.LENGTH_SHORT).show(); - this.finish(); - } else { - passphrase.setError(getString(R.string.passphrase_invalid)); - passphrase.requestFocus(); - } - } - } - - public void NFC(View view) { - if (adapter != null) { - if (getActionBar() != null) { - getActionBar().setTitle(R.string.nfc_title); - } - NFCFragment nfc = new NFCFragment(); - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.fragmentContainer, nfc).addToBackStack(null).commit(); - - //if you want to create a new method or just authenticate - if (CREATE_METHOD.equals(selectedAction)) { - writeNFC = true; - } else if (AUTHENTICATION.equals(selectedAction)) { - readNFC = true; - } - - if (!adapter.isEnabled()) { - showAlertDialog(getString(R.string.enable_nfc), true); - } - } else { - showAlertDialog(getString(R.string.no_nfc_support), false); - } - } - - @Override - protected void onNewIntent(Intent intent) { - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { - myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - - if (writeNFC && CREATE_METHOD.equals(selectedAction)) { - //write new password on NFC tag - try { - if (myTag != null) { - write(myTag); - writeNFC = false; //just write once - Toast.makeText(this, R.string.nfc_write_succesful, Toast.LENGTH_SHORT).show(); - //advance to lockpattern -// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction); -// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); -// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit(); - } - } catch (IOException | FormatException e) { - Log.e(Constants.TAG, "Failed to write on NFC tag", e); - } - - } else if (readNFC && AUTHENTICATION.equals(selectedAction)) { - //read pw from NFC tag - try { - if (myTag != null) { - //if tag detected, read tag - String pwtag = read(myTag); - if (output != null && pwtag.equals(output.toString())) { - - //passwort matches, go to next view - Toast.makeText(this, R.string.passphrases_match + "!", Toast.LENGTH_SHORT).show(); - -// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction); -// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); -// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit(); - readNFC = false; //just once - } else { - //passwort doesnt match - TextView nfc = (TextView) findViewById(R.id.nfcText); - nfc.setText(R.string.nfc_wrong_tag); - } - } - } catch (IOException | FormatException e) { - Log.e(Constants.TAG, "Failed to read NFC tag", e); - } - } - } - } - - private void write(Tag tag) throws IOException, FormatException { - //generate new random key and write them on the tag - SecureRandom sr = new SecureRandom(); - sr.nextBytes(output); - NdefRecord[] records = {createRecord(output.toString())}; - NdefMessage message = new NdefMessage(records); - Ndef ndef = Ndef.get(tag); - ndef.connect(); - ndef.writeNdefMessage(message); - ndef.close(); - } - - private String read(Tag tag) throws IOException, FormatException { - //read string from tag - String password = null; - Ndef ndef = Ndef.get(tag); - ndef.connect(); - NdefMessage ndefMessage = ndef.getCachedNdefMessage(); - - NdefRecord[] records = ndefMessage.getRecords(); - for (NdefRecord ndefRecord : records) { - if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) { - try { - password = readText(ndefRecord); - } catch (UnsupportedEncodingException e) { - Log.e(Constants.TAG, "Failed to read password from tag", e); - } - } - } - ndef.close(); - return password; - } - - private String readText(NdefRecord record) throws UnsupportedEncodingException { - //low-level method for reading nfc - byte[] payload = record.getPayload(); - String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16"; - int languageCodeLength = payload[0] & 0063; - return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); - } - - private NdefRecord createRecord(String text) throws UnsupportedEncodingException { - //low-level method for writing nfc - String lang = "en"; - byte[] textBytes = text.getBytes(); - byte[] langBytes = lang.getBytes("US-ASCII"); - int langLength = langBytes.length; - int textLength = textBytes.length; - byte[] payload = new byte[1 + langLength + textLength]; - - // set status byte (see NDEF spec for actual bits) - payload[0] = (byte) langLength; - // copy langbytes and textbytes into payload - System.arraycopy(langBytes, 0, payload, 1, langLength); - System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength); - return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload); - } - - public void showAlertDialog(String message, boolean nfc) { - //This method shows an AlertDialog - AlertDialog.Builder alert = new AlertDialog.Builder(this); - alert.setTitle("Information").setMessage(message).setPositiveButton("Ok", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - } - } - ); - if (nfc) { - - alert.setNeutralButton(R.string.nfc_settings, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialogInterface, int i) { - startActivity(new Intent(Settings.ACTION_NFC_SETTINGS)); - } - } - ); - } - alert.show(); - } - - @Override - public void onPause() { - //pause this app and free nfc intent - super.onPause(); - if (adapter != null) { - WriteModeOff(); - } - } - - @Override - public void onResume() { - //resume this app and get nfc intent - super.onResume(); - if (adapter != null) { - WriteModeOn(); - } - } - - private void WriteModeOn() { - //enable nfc for this view - writeMode = true; - adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null); - } - - private void WriteModeOff() { - //disable nfc for this view - writeMode = false; - adapter.disableForegroundDispatch(this); - } - - public static class SelectMethods extends Fragment { -// private OnFragmentInteractionListener mListener; - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - */ - public SelectMethods() { - - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public void onResume() { - super.onResume(); - if (getActivity().getActionBar() != null) { - getActivity().getActionBar().setTitle(R.string.unlock_method); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.passphrase_wizard_fragment_select_methods, container, false); - } - -// @Override -// public void onAttach(Activity activity) { -// super.onAttach(activity); -// try { -// mListener = (OnFragmentInteractionListener) activity; -// } catch (ClassCastException e) { -// throw new ClassCastException(activity.toString() -// + " must implement OnFragmentInteractionListener"); -// } -// } -// -// @Override -// public void onDetach() { -// super.onDetach(); -// mListener = null; -// } - - /** - * This interface must be implemented by activities that contain this - * fragment to allow an interaction in this fragment to be communicated - * to the activity and potentially other fragments contained in that - * activity. - * <p/> - * See the Android Training lesson <a href= - * "http://developer.android.com/training/basics/fragments/communicating.html" - * >Communicating with Other Fragments</a> for more information. - */ -// public static interface OnFragmentInteractionListener { -// public void onFragmentInteraction(Uri uri); -// } - - } - - - // /** -// * A simple {@link android.support.v4.app.Fragment} subclass. -// * Activities that contain this fragment must implement the -// * {@link com.haibison.android.lockpattern.Passphrase.OnFragmentInteractionListener} interface -// * to handle interaction events. -// */ - public static class Passphrase extends Fragment { - -// private OnFragmentInteractionListener mListener; - - public Passphrase() { - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.passphrase_wizard_fragment_passphrase, container, false); - EditText passphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain); - TextView passphraseText = (TextView) view.findViewById(R.id.passphraseText); - TextView passphraseTextAgain = (TextView) view.findViewById(R.id.passphraseTextAgain); - String selectedAction = getActivity().getIntent().getAction(); - if (selectedAction.equals(AUTHENTICATION)) { - passphraseAgain.setVisibility(View.GONE); - passphraseTextAgain.setVisibility(View.GONE); - passphraseText.setText(R.string.enter_passphrase); -// getActivity().getActionBar().setTitle(R.string.enter_passphrase); - } else if (selectedAction.equals(CREATE_METHOD)) { - passphraseAgain.setVisibility(View.VISIBLE); - passphraseTextAgain.setVisibility(View.VISIBLE); - passphraseText.setText(R.string.passphrase); -// getActivity().getActionBar().setTitle(R.string.set_passphrase); - } - return view; - } - -// @Override -// public void onAttach(Activity activity) { -// super.onAttach(activity); -// try { -// mListener = (OnFragmentInteractionListener) activity; -// } catch (ClassCastException e) { -// throw new ClassCastException(activity.toString() -// + " must implement OnFragmentInteractionListener"); -// } -// } -// -// @Override -// public void onDetach() { -// super.onDetach(); -// mListener = null; -// } - -// /** -// * This interface must be implemented by activities that contain this -// * fragment to allow an interaction in this fragment to be communicated -// * to the activity and potentially other fragments contained in that -// * activity. -// * <p/> -// * See the Android Training lesson <a href= -// * "http://developer.android.com/training/basics/fragments/communicating.html" -// * >Communicating with Other Fragments</a> for more information. -// */ -// public interface OnFragmentInteractionListener { -// public void onFragmentInteraction(Uri uri); -// } - } - - - /** - * A simple {@link android.support.v4.app.Fragment} subclass. - * Activities that contain this fragment must implement the - * interface - * to handle interaction events. - * Use the method to - * create an instance of this fragment. - */ - public static class NFCFragment extends Fragment { - // TODO: Rename parameter arguments, choose names that match - // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER - private static final String ARG_PARAM1 = "param1"; - private static final String ARG_PARAM2 = "param2"; - - // TODO: Rename and change types of parameters - private String mParam1; - private String mParam2; - -// private OnFragmentInteractionListener mListener; - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param param1 Parameter 1. - * @param param2 Parameter 2. - * @return A new instance of fragment SelectMethods. - */ - // TODO: Rename and change types and number of parameters - public static NFCFragment newInstance(String param1, String param2) { - NFCFragment fragment = new NFCFragment(); - Bundle args = new Bundle(); - args.putString(ARG_PARAM1, param1); - args.putString(ARG_PARAM2, param2); - fragment.setArguments(args); - return fragment; - } - - public NFCFragment() { - // Required empty public constructor - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - mParam1 = getArguments().getString(ARG_PARAM1); - mParam2 = getArguments().getString(ARG_PARAM2); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.passphrase_wizard_fragment_nfc, container, false); - } - -// // TODO: Rename method, update argument and hook method into UI event -// public void onButtonPressed(Uri uri) { -// if (mListener != null) { -// mListener.onFragmentInteraction(uri); -// } -// } - -// @Override -// public void onAttach(Activity activity) { -// super.onAttach(activity); -// try { -// mListener = (OnFragmentInteractionListener) activity; -// } catch (ClassCastException e) { -// throw new ClassCastException(activity.toString() -// + " must implement OnFragmentInteractionListener"); -// } -// } - - -// @Override -// public void onDetach() { -// super.onDetach(); -// mListener = null; -// } - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index 842059da5..4077f1c84 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -32,6 +32,7 @@ import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; import android.provider.ContactsContract; import android.support.v7.widget.Toolbar; import android.text.TextUtils; @@ -93,14 +94,14 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } }); initializeSearchKeyserver( - (CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER) + (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER) ); initializeSearchKeybase( - (CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE) + (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE) ); } else if (action != null && action.equals(ACTION_PREFS_ADV)) { - addPreferencesFromResource(R.xml.adv_preferences); + addPreferencesFromResource(R.xml.passphrase_preferences); initializePassphraseCacheSubs( (CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS)); @@ -194,10 +195,10 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } }); initializeSearchKeyserver( - (CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER) + (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER) ); initializeSearchKeybase( - (CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE) + (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE) ); } @@ -221,14 +222,14 @@ public class SettingsActivity extends AppCompatPreferenceActivity { /** * This fragment shows the PIN/password preferences */ - public static class AdvancedPrefsFragment extends PreferenceFragment { + public static class PassphrasePrefsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.adv_preferences); + addPreferencesFromResource(R.xml.passphrase_preferences); initializePassphraseCacheSubs( (CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS)); @@ -255,8 +256,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } public static class Initializer { - private CheckBoxPreference mUseTor; - private CheckBoxPreference mUseNormalProxy; + private SwitchPreference mUseTor; + private SwitchPreference mUseNormalProxy; private EditTextPreference mProxyHost; private EditTextPreference mProxyPort; private ListPreference mProxyType; @@ -292,8 +293,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity { mActivity.addPreferencesFromResource(R.xml.proxy_prefs); } - mUseTor = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY); - mUseNormalProxy = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY); + mUseTor = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY); + mUseNormalProxy = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY); mProxyHost = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_HOST); mProxyPort = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_PORT); mProxyType = (ListPreference) automaticallyFindPreference(Constants.Pref.PROXY_TYPE); @@ -472,7 +473,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { /** * This fragment shows the keyserver/contacts sync preferences */ - public static class SyncSettingsFragment extends PreferenceFragment { + public static class SyncPrefsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { @@ -491,19 +492,19 @@ public class SettingsActivity extends AppCompatPreferenceActivity { final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0]; // for keyserver sync initializeSyncCheckBox( - (CheckBoxPreference) findPreference(Constants.Pref.SYNC_KEYSERVER), + (SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER), account, Constants.PROVIDER_AUTHORITY ); // for contacts sync initializeSyncCheckBox( - (CheckBoxPreference) findPreference(Constants.Pref.SYNC_CONTACTS), + (SwitchPreference) findPreference(Constants.Pref.SYNC_CONTACTS), account, ContactsContract.AUTHORITY ); } - private void initializeSyncCheckBox(final CheckBoxPreference syncCheckBox, + private void initializeSyncCheckBox(final SwitchPreference syncCheckBox, final Account account, final String authority) { boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority); @@ -528,7 +529,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { }); } - private void setSummary(CheckBoxPreference syncCheckBox, String authority, + private void setSummary(SwitchPreference syncCheckBox, String authority, boolean checked) { switch (authority) { case Constants.PROVIDER_AUTHORITY: { @@ -551,12 +552,39 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } } + /** + * This fragment shows experimental features + */ + public static class ExperimentalPrefsFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.experimental_preferences); + + initializeExperimentalEnableWordConfirm( + (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM)); + + initializeExperimentalEnableLinkedIdentities( + (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES)); + + initializeExperimentalEnableKeybase( + (SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_KEYBASE)); + + initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); + + } + } + protected boolean isValidFragment(String fragmentName) { - return AdvancedPrefsFragment.class.getName().equals(fragmentName) + return PassphrasePrefsFragment.class.getName().equals(fragmentName) || CloudSearchPrefsFragment.class.getName().equals(fragmentName) || ProxyPrefsFragment.class.getName().equals(fragmentName) || GuiPrefsFragment.class.getName().equals(fragmentName) - || SyncSettingsFragment.class.getName().equals(fragmentName) + || SyncPrefsFragment.class.getName().equals(fragmentName) + || ExperimentalPrefsFragment.class.getName().equals(fragmentName) || super.isValidFragment(fragmentName); } @@ -601,7 +629,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { }); } - private static void initializeSearchKeyserver(final CheckBoxPreference mSearchKeyserver) { + private static void initializeSearchKeyserver(final SwitchPreference mSearchKeyserver) { Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs(); mSearchKeyserver.setChecked(prefs.searchKeyserver); mSearchKeyserver.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @@ -614,7 +642,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { }); } - private static void initializeSearchKeybase(final CheckBoxPreference mSearchKeybase) { + private static void initializeSearchKeybase(final SwitchPreference mSearchKeybase) { Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs(); mSearchKeybase.setChecked(prefs.searchKeybase); mSearchKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @@ -656,4 +684,37 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } }); } + + private static void initializeExperimentalEnableWordConfirm(final SwitchPreference mExperimentalEnableWordConfirm) { + mExperimentalEnableWordConfirm.setChecked(sPreferences.getExperimentalEnableWordConfirm()); + mExperimentalEnableWordConfirm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mExperimentalEnableWordConfirm.setChecked((Boolean) newValue); + sPreferences.setExperimentalEnableWordConfirm((Boolean) newValue); + return false; + } + }); + } + + private static void initializeExperimentalEnableLinkedIdentities(final SwitchPreference mExperimentalEnableLinkedIdentities) { + mExperimentalEnableLinkedIdentities.setChecked(sPreferences.getExperimentalEnableLinkedIdentities()); + mExperimentalEnableLinkedIdentities.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mExperimentalEnableLinkedIdentities.setChecked((Boolean) newValue); + sPreferences.setExperimentalEnableLinkedIdentities((Boolean) newValue); + return false; + } + }); + } + + private static void initializeExperimentalEnableKeybase(final SwitchPreference mExperimentalKeybase) { + mExperimentalKeybase.setChecked(sPreferences.getExperimentalEnableKeybase()); + mExperimentalKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mExperimentalKeybase.setChecked((Boolean) newValue); + sPreferences.setExperimentalEnableKeybase((Boolean) newValue); + return false; + } + }); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 1d0e085da..06126ebc4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -32,6 +32,9 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.provider.ContactsContract; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CollapsingToolbarLayout; +import android.support.design.widget.FloatingActionButton; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; import android.support.v4.app.LoaderManager; @@ -45,13 +48,13 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; -import com.getbase.floatingactionbutton.FloatingActionButton; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -80,6 +83,7 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; import org.sufficientlysecure.keychain.util.Preferences; +import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -107,16 +111,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements private ArrayList<ParcelableKeyRing> mKeyList; private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper; - private TextView mName; private TextView mStatusText; private ImageView mStatusImage; - private RelativeLayout mBigToolbar; + private AppBarLayout mAppBarLayout; + private CollapsingToolbarLayout mCollapsingToolbarLayout; private ImageButton mActionEncryptFile; private ImageButton mActionEncryptText; private ImageButton mActionNfc; private FloatingActionButton mFab; private ImageView mPhoto; + private FrameLayout mPhotoLayout; private ImageView mQrCode; private CardView mQrCodeLayout; @@ -156,16 +161,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements setTitle(null); - mName = (TextView) findViewById(R.id.view_key_name); mStatusText = (TextView) findViewById(R.id.view_key_status); mStatusImage = (ImageView) findViewById(R.id.view_key_status_image); - mBigToolbar = (RelativeLayout) findViewById(R.id.toolbar_big); + mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar_layout); + mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); mActionEncryptFile = (ImageButton) findViewById(R.id.view_key_action_encrypt_files); mActionEncryptText = (ImageButton) findViewById(R.id.view_key_action_encrypt_text); mActionNfc = (ImageButton) findViewById(R.id.view_key_action_nfc); mFab = (FloatingActionButton) findViewById(R.id.fab); mPhoto = (ImageView) findViewById(R.id.view_key_photo); + mPhotoLayout = (FrameLayout) findViewById(R.id.view_key_photo_layout); mQrCode = (ImageView) findViewById(R.id.view_key_qr_code); mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout); @@ -294,6 +300,13 @@ public class ViewKeyActivity extends BaseNfcActivity implements .replace(R.id.view_key_fragment, frag) .commit(); + if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) { + final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(mDataUri); + manager.beginTransaction() + .replace(R.id.view_key_keybase_fragment, keybaseFrag) + .commit(); + } + // need to postpone loading of the yubikey fragment until after mMasterKeyId // is available, but we mark here that this should be done mShowYubikeyAfterCreation = true; @@ -349,7 +362,11 @@ public class ViewKeyActivity extends BaseNfcActivity implements return true; } case R.id.menu_key_view_certify_fingerprint: { - certifyFingeprint(mDataUri); + certifyFingeprint(mDataUri, false); + return true; + } + case R.id.menu_key_view_certify_fingerprint_word: { + certifyFingeprint(mDataUri, true); return true; } } @@ -364,6 +381,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements exportKey.setVisible(mIsSecret); MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked); + MenuItem certifyFingerprintWord = menu.findItem(R.id.menu_key_view_certify_fingerprint_word); + certifyFingerprintWord.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked + && Preferences.getPreferences(this).getExperimentalEnableWordConfirm()); return true; } @@ -375,9 +395,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements startActivityForResult(scanQrCode, REQUEST_QR_FINGERPRINT); } - private void certifyFingeprint(Uri dataUri) { + private void certifyFingeprint(Uri dataUri, boolean enableWordConfirm) { Intent intent = new Intent(this, CertifyFingerprintActivity.class); intent.setData(dataUri); + intent.putExtra(CertifyFingerprintActivity.EXTRA_ENABLE_WORD_CONFIRM, enableWordConfirm); startActivityForResult(intent, REQUEST_CERTIFY); } @@ -413,7 +434,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements private void backupToFile() { new ExportHelper(this).showExportKeysDialog( - mMasterKeyId, Constants.Path.APP_DIR_FILE, true); + mMasterKeyId, new File(Constants.Path.APP_DIR, + KeyFormattingUtils.convertKeyIdToHex(mMasterKeyId) + ".sec.asc"), true); } private void deleteKey() { @@ -728,9 +750,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements // get name, email, and comment from USER_ID KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); if (mainUserId.name != null) { - mName.setText(mainUserId.name); + mCollapsingToolbarLayout.setTitle(mainUserId.name); } else { - mName.setText(R.string.user_id_no_name); + mCollapsingToolbarLayout.setTitle(getString(R.string.user_id_no_name)); } mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); @@ -767,8 +789,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements } protected void onPostExecute(Bitmap photo) { + if (photo == null) { + return; + } + mPhoto.setImageBitmap(photo); - mPhoto.setVisibility(View.VISIBLE); + mPhotoLayout.setVisibility(View.VISIBLE); } }; @@ -781,9 +807,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements State.REVOKED, R.color.icons, true); color = getResources().getColor(R.color.key_flag_red); - mActionEncryptFile.setVisibility(View.GONE); - mActionEncryptText.setVisibility(View.GONE); - mActionNfc.setVisibility(View.GONE); + mActionEncryptFile.setVisibility(View.INVISIBLE); + mActionEncryptText.setVisibility(View.INVISIBLE); + mActionNfc.setVisibility(View.INVISIBLE); mFab.setVisibility(View.GONE); mQrCodeLayout.setVisibility(View.GONE); } else if (mIsExpired) { @@ -797,9 +823,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements State.EXPIRED, R.color.icons, true); color = getResources().getColor(R.color.key_flag_red); - mActionEncryptFile.setVisibility(View.GONE); - mActionEncryptText.setVisibility(View.GONE); - mActionNfc.setVisibility(View.GONE); + mActionEncryptFile.setVisibility(View.INVISIBLE); + mActionEncryptText.setVisibility(View.INVISIBLE); + mActionNfc.setVisibility(View.INVISIBLE); mFab.setVisibility(View.GONE); mQrCodeLayout.setVisibility(View.GONE); } else if (mIsSecret) { @@ -814,15 +840,15 @@ public class ViewKeyActivity extends BaseNfcActivity implements mQrCodeLayout.setVisibility(View.VISIBLE); // and place leftOf qr code - RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams) - mName.getLayoutParams(); - // remove right margin - nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - nameParams.setMarginEnd(0); - } - nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); - mName.setLayoutParams(nameParams); +// RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams) +// mName.getLayoutParams(); +// // remove right margin +// nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); +// if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { +// nameParams.setMarginEnd(0); +// } +// nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); +// mName.setLayoutParams(nameParams); RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams) mStatusText.getLayoutParams(); @@ -844,7 +870,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements } mFab.setVisibility(View.VISIBLE); // noinspection deprecation (no getDrawable with theme at current minApi level 15!) - mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); + mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); } else { mActionEncryptFile.setVisibility(View.VISIBLE); mActionEncryptText.setVisibility(View.VISIBLE); @@ -872,22 +898,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements } if (mPreviousColor == 0 || mPreviousColor == color) { - mStatusBar.setBackgroundColor(getStatusBarBackgroundColor(color)); - mBigToolbar.setBackgroundColor(color); + mAppBarLayout.setBackgroundColor(color); + mCollapsingToolbarLayout.setContentScrimColor(color); + mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); mPreviousColor = color; } else { - ObjectAnimator colorFade1 = - ObjectAnimator.ofObject(mStatusBar, "backgroundColor", - new ArgbEvaluator(), mPreviousColor, - getStatusBarBackgroundColor(color)); - ObjectAnimator colorFade2 = - ObjectAnimator.ofObject(mBigToolbar, "backgroundColor", + ObjectAnimator colorFade = + ObjectAnimator.ofObject(mAppBarLayout, "backgroundColor", new ArgbEvaluator(), mPreviousColor, color); + mCollapsingToolbarLayout.setContentScrimColor(color); + mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); - colorFade1.setDuration(1200); - colorFade2.setDuration(1200); - colorFade1.start(); - colorFade2.start(); + colorFade.setDuration(1200); + colorFade.start(); mPreviousColor = color; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index 673092e61..edd9feec9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -57,7 +57,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements public static final int TAB_IDENTITIES = 1; public static final int TAB_SUBKEYS = 2; public static final int TAB_CERTS = 3; - public static final int TAB_KEYBASE = 4; // view private ViewPager mViewPager; @@ -140,11 +139,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements adapter.addTab(ViewKeyAdvCertsFragment.class, certsBundle, getString(R.string.key_view_tab_certs)); - Bundle trustBundle = new Bundle(); - trustBundle.putParcelable(ViewKeyTrustFragment.ARG_DATA_URI, dataUri); - adapter.addTab(ViewKeyTrustFragment.class, - trustBundle, getString(R.string.key_view_tab_keybase)); - // update layout after operations mSlidingTabLayout.setViewPager(mViewPager); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java index 150acdc90..266633061 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java @@ -59,14 +59,12 @@ import java.util.ArrayList; import java.util.Hashtable; import java.util.List; -public class ViewKeyTrustFragment extends LoaderFragment implements +public class ViewKeyKeybaseFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks<Cursor>, CryptoOperationHelper.Callback<KeybaseVerificationParcel, KeybaseVerificationResult> { public static final String ARG_DATA_URI = "uri"; - private View mStartSearch; - private TextView mTrustReadout; private TextView mReportHeader; private TableLayout mProofListing; private LayoutInflater mInflater; @@ -86,15 +84,25 @@ public class ViewKeyTrustFragment extends LoaderFragment implements private CryptoOperationHelper<KeybaseVerificationParcel, KeybaseVerificationResult> mKeybaseOpHelper; + /** + * Creates new instance of this fragment + */ + public static ViewKeyKeybaseFragment newInstance(Uri dataUri) { + ViewKeyKeybaseFragment frag = new ViewKeyKeybaseFragment(); + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, getContainer()); mInflater = inflater; - mTrustReadout = (TextView) view.findViewById(R.id.view_key_trust_readout); - mStartSearch = view.findViewById(R.id.view_key_trust_search_cloud); - mStartSearch.setEnabled(false); mReportHeader = (TextView) view.findViewById(R.id.view_key_trust_cloud_narrative); mProofListing = (TableLayout) view.findViewById(R.id.view_key_proof_list); mProofVerifyHeader = view.findViewById(R.id.view_key_proof_verify_header); @@ -157,83 +165,45 @@ public class ViewKeyTrustFragment extends LoaderFragment implements } boolean nothingSpecial = true; - StringBuilder message = new StringBuilder(); // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) if (data.moveToFirst()) { - if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) { - message.append(getString(R.string.key_trust_it_is_yours)).append("\n"); - nothingSpecial = false; - } else if (data.getInt(INDEX_VERIFIED) != 0) { - message.append(getString(R.string.key_trust_already_verified)).append("\n"); - nothingSpecial = false; - } + final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT); + final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); - // If this key is revoked, don’t trust it! - if (data.getInt(INDEX_TRUST_IS_REVOKED) != 0) { - message.append(getString(R.string.key_trust_revoked)). - append(getString(R.string.key_trust_old_keys)); + startSearch(fingerprint); + } - nothingSpecial = false; - } else { - if (data.getInt(INDEX_TRUST_IS_EXPIRED) != 0) { + setContentShown(true); + } - // if expired, don’t trust it! - message.append(getString(R.string.key_trust_expired)). - append(getString(R.string.key_trust_old_keys)); + private void startSearch(final String fingerprint) { + final Preferences.ProxyPrefs proxyPrefs = + Preferences.getPreferences(getActivity()).getProxyPrefs(); - nothingSpecial = false; - } + OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() { + @Override + public void onOrbotStarted() { + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); } - if (nothingSpecial) { - message.append(getString(R.string.key_trust_maybe)); + @Override + public void onNeutralButton() { + new DescribeKey(ParcelableProxy.getForNoProxy()) + .execute(fingerprint); } - final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT); - final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); - if (fingerprint != null) { + @Override + public void onCancel() { - mStartSearch.setEnabled(true); - mStartSearch.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - final Preferences.ProxyPrefs proxyPrefs = - Preferences.getPreferences(getActivity()).getProxyPrefs(); - - OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() { - @Override - public void onOrbotStarted() { - mStartSearch.setEnabled(false); - new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); - } - - @Override - public void onNeutralButton() { - mStartSearch.setEnabled(false); - new DescribeKey(ParcelableProxy.getForNoProxy()) - .execute(fingerprint); - } - - @Override - public void onCancel() { - - } - }; - - if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) { - mStartSearch.setEnabled(false); - new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); - } - } - }); } - } + }; - mTrustReadout.setText(message); - setContentShown(true); + if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) { + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); + } } /** @@ -299,17 +269,16 @@ public class ViewKeyTrustFragment extends LoaderFragment implements return new ResultPage(getString(R.string.key_trust_results_prefix), proofList); } - private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks,String proofType){ + private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks, String proofType) { //Formatting SpannableStringBuilder with String.format() causes the links to stop working. //This method is to insert the links while reserving the links SpannableStringBuilder ssb = new SpannableStringBuilder(); ssb.append(proofType); - if(proofType.contains("%s")){ + if (proofType.contains("%s")) { int i = proofType.indexOf("%s"); - ssb.replace(i,i+2,proofLinks); - } - else ssb.append(proofLinks); + ssb.replace(i, i + 2, proofLinks); + } else ssb.append(proofLinks); return ssb; } @@ -343,7 +312,6 @@ public class ViewKeyTrustFragment extends LoaderFragment implements result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence); } - mStartSearch.setVisibility(View.GONE); mReportHeader.setVisibility(View.VISIBLE); mProofListing.setVisibility(View.VISIBLE); mReportHeader.setText(result.mHeader); @@ -358,22 +326,35 @@ public class ViewKeyTrustFragment extends LoaderFragment implements text.setMovementMethod(LinkMovementMethod.getInstance()); mProofListing.addView(row); } - - // mSearchReport.loadDataWithBaseURL("file:///android_res/drawable/", s, "text/html", "UTF-8", null); } } private String getProofNarrative(int proofType) { int stringIndex; switch (proofType) { - case Proof.PROOF_TYPE_TWITTER: stringIndex = R.string.keybase_narrative_twitter; break; - case Proof.PROOF_TYPE_GITHUB: stringIndex = R.string.keybase_narrative_github; break; - case Proof.PROOF_TYPE_DNS: stringIndex = R.string.keybase_narrative_dns; break; - case Proof.PROOF_TYPE_WEB_SITE: stringIndex = R.string.keybase_narrative_web_site; break; - case Proof.PROOF_TYPE_HACKERNEWS: stringIndex = R.string.keybase_narrative_hackernews; break; - case Proof.PROOF_TYPE_COINBASE: stringIndex = R.string.keybase_narrative_coinbase; break; - case Proof.PROOF_TYPE_REDDIT: stringIndex = R.string.keybase_narrative_reddit; break; - default: stringIndex = R.string.keybase_narrative_unknown; + case Proof.PROOF_TYPE_TWITTER: + stringIndex = R.string.keybase_narrative_twitter; + break; + case Proof.PROOF_TYPE_GITHUB: + stringIndex = R.string.keybase_narrative_github; + break; + case Proof.PROOF_TYPE_DNS: + stringIndex = R.string.keybase_narrative_dns; + break; + case Proof.PROOF_TYPE_WEB_SITE: + stringIndex = R.string.keybase_narrative_web_site; + break; + case Proof.PROOF_TYPE_HACKERNEWS: + stringIndex = R.string.keybase_narrative_hackernews; + break; + case Proof.PROOF_TYPE_COINBASE: + stringIndex = R.string.keybase_narrative_coinbase; + break; + case Proof.PROOF_TYPE_REDDIT: + stringIndex = R.string.keybase_narrative_reddit; + break; + default: + stringIndex = R.string.keybase_narrative_unknown; } return getActivity().getString(stringIndex); } @@ -390,14 +371,22 @@ public class ViewKeyTrustFragment extends LoaderFragment implements // which proofs do we have working verifiers for? private boolean haveProofFor(int proofType) { switch (proofType) { - case Proof.PROOF_TYPE_TWITTER: return true; - case Proof.PROOF_TYPE_GITHUB: return true; - case Proof.PROOF_TYPE_DNS: return true; - case Proof.PROOF_TYPE_WEB_SITE: return true; - case Proof.PROOF_TYPE_HACKERNEWS: return true; - case Proof.PROOF_TYPE_COINBASE: return true; - case Proof.PROOF_TYPE_REDDIT: return true; - default: return false; + case Proof.PROOF_TYPE_TWITTER: + return true; + case Proof.PROOF_TYPE_GITHUB: + return true; + case Proof.PROOF_TYPE_DNS: + return true; + case Proof.PROOF_TYPE_WEB_SITE: + return true; + case Proof.PROOF_TYPE_HACKERNEWS: + return true; + case Proof.PROOF_TYPE_COINBASE: + return true; + case Proof.PROOF_TYPE_REDDIT: + return true; + default: + return false; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 5ef8618ce..84774ae5e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.Messenger; @@ -119,14 +120,19 @@ public class FileDialogFragment extends DialogFragment { mFilename = (EditText) view.findViewById(R.id.input); mFilename.setText(mFile.getName()); mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - // only .asc or .gpg files - // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc - // or gpg types! - FileHelper.openFile(FileDialogFragment.this, Uri.fromFile(mFile), "*/*", REQUEST_CODE); - } - }); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + mBrowse.setVisibility(View.GONE); + } else { + mBrowse.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + // only .asc or .gpg files + // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc + // or gpg types! + FileHelper.saveDocumentKitKat( + FileDialogFragment.this, "*/*", mFile.getName(), REQUEST_CODE); + } + }); + } mCheckBox = (CheckBox) view.findViewById(R.id.checkbox); if (checkboxText == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java new file mode 100644 index 000000000..43ccac24f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.util; + +import android.content.Context; + +import org.spongycastle.util.Arrays; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.BitSet; + +public class ExperimentalWordConfirm { + + public static String getWords(Context context, byte[] fingerprintBlob) { + ArrayList<String> words = new ArrayList<>(); + + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader( + context.getAssets().open("word_confirm_list.txt"), + "UTF-8" + )); + + String line = reader.readLine(); + while (line != null) { + words.add(line); + + line = reader.readLine(); + } + } catch (IOException e) { + throw new RuntimeException("IOException", e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException ignored) { + } + } + } + + String fingerprint = ""; + + // NOTE: 160 bit SHA-1 truncated to 156 bit + byte[] fingerprintBlobTruncated = Arrays.copyOfRange(fingerprintBlob, 0, 156 / 8); + + // TODO: implement key stretching to minimize fp length? + + // BitSet bits = BitSet.valueOf(fingerprintBlob); // min API 19 and little endian! + BitSet bits = bitSetToByteArray(fingerprintBlobTruncated); + Log.d(Constants.TAG, "bits: " + bits.toString()); + + final int CHUNK_SIZE = 13; + final int LAST_CHUNK_INDEX = fingerprintBlobTruncated.length * 8 / CHUNK_SIZE; // 12 + Log.d(Constants.TAG, "LAST_CHUNK_INDEX: " + LAST_CHUNK_INDEX); + + int from = 0; + int to = CHUNK_SIZE; + for (int i = 0; i < (LAST_CHUNK_INDEX + 1); i++) { + Log.d(Constants.TAG, "from: " + from + " to: " + to); + + BitSet setIndex = bits.get(from, to); + int wordIndex = (int) bitSetToLong(setIndex); + // int wordIndex = (int) setIndex.toLongArray()[0]; // min API 19 + + fingerprint += words.get(wordIndex); + + if (i != LAST_CHUNK_INDEX) { + // line break every 3 words + if (to % (CHUNK_SIZE * 3) == 0) { + fingerprint += "\n"; + } else { + fingerprint += " "; + } + } + + from = to; + to += CHUNK_SIZE; + } + + return fingerprint; + } + + /** + * Returns a BitSet containing the values in bytes. + * BIG ENDIAN! + */ + private static BitSet bitSetToByteArray(byte[] bytes) { + int arrayLength = bytes.length * 8; + BitSet bits = new BitSet(); + + for (int i = 0; i < arrayLength; i++) { + if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) { + bits.set(i); + } + } + return bits; + } + + private static long bitSetToLong(BitSet bits) { + long value = 0L; + for (int i = 0; i < bits.length(); ++i) { + value += bits.get(i) ? (1L << i) : 0L; + } + return value; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java index 72620e712..45dc33906 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java @@ -69,7 +69,7 @@ public class ExportHelper : R.string.specify_backup_dest_single); } - FileHelper.saveFile(new FileHelper.FileDialogCallback() { + FileHelper.saveDocumentDialog(new FileHelper.FileDialogCallback() { @Override public void onFileSelected(File file, boolean checked) { mExportFile = file; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java index 4a00f46cb..9fb362412 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java @@ -28,16 +28,19 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.net.Uri; import android.os.Build; +import android.os.Build.VERSION_CODES; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.provider.DocumentsContract; import android.provider.OpenableColumns; +import android.support.annotation.StringRes; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.widget.Toast; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; @@ -46,42 +49,95 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.text.DecimalFormat; + +/** This class offers a number of helper functions for saving documents. + * + * There are three entry points here: openDocument, saveDocument and + * saveDocumentDialog. Each behaves a little differently depending on whether + * the Android version used is pre or post KitKat. + * + * - openDocument queries for a document for reading. Used in "open encrypted + * file" ui flow. On pre-kitkat, this relies on an external file manager, + * and will fail with a toast message if none is installed. + * + * - saveDocument queries for a document name for saving. on pre-kitkat, this + * shows a dialog where a filename can be input. on kitkat and up, it + * directly triggers a "save document" intent. Used in "save encrypted file" + * ui flow. + * + * - saveDocumentDialog queries for a document. this shows a dialog on all + * versions of android. the browse button opens an external browser on + * pre-kitkat or the "save document" intent on post-kitkat devices. Used in + * "backup key" ui flow. + * + * It is noteworthy that the "saveDocument" call is essentially substituted + * by the "saveDocumentDialog" on pre-kitkat devices. + * + */ public class FileHelper { - /** - * Checks if external storage is mounted if file is located on external storage - * - * @return true if storage is mounted - */ - public static boolean isStorageMounted(String file) { - if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - return false; - } + public static void openDocument(Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + openDocumentPreKitKat(fragment, last, mimeType, multiple, requestCode); + } else { + openDocumentKitKat(fragment, mimeType, multiple, requestCode); } + } - return true; + public static void saveDocument(Fragment fragment, String targetName, Uri inputUri, + @StringRes int title, @StringRes int message, int requestCode) { + saveDocument(fragment, targetName, inputUri, "*/*", title, message, requestCode); } - /** - * Opens the preferred installed file manager on Android and shows a toast if no manager is - * installed. - * - * @param last default selected Uri, not supported by all file managers - * @param mimeType can be text/plain for example - * @param requestCode requestCode used to identify the result coming back from file manager to - * onActivityResult() in your activity - */ - public static void openFile(Fragment fragment, Uri last, String mimeType, int requestCode) { + public static void saveDocument(Fragment fragment, String targetName, Uri inputUri, String mimeType, + @StringRes int title, @StringRes int message, int requestCode) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + saveDocumentDialog(fragment, targetName, inputUri, title, message, requestCode); + } else { + saveDocumentKitKat(fragment, mimeType, targetName, requestCode); + } + } + + public static void saveDocumentDialog(final Fragment fragment, String targetName, Uri inputUri, + @StringRes int title, @StringRes int message, final int requestCode) { + + saveDocumentDialog(fragment, targetName, inputUri, title, message, new FileDialogCallback() { + // is this a good idea? seems hacky... + @Override + public void onFileSelected(File file, boolean checked) { + Intent intent = new Intent(); + intent.setData(Uri.fromFile(file)); + fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent); + } + }); + } + + public static void saveDocumentDialog(final Fragment fragment, String targetName, Uri inputUri, + @StringRes int title, @StringRes int message, FileDialogCallback callback) { + + File file = inputUri == null ? null : new File(inputUri.getPath()); + File parentDir = file != null && file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; + File targetFile = new File(parentDir, targetName); + saveDocumentDialog(callback, fragment.getActivity().getSupportFragmentManager(), + fragment.getString(title), fragment.getString(message), targetFile, null); + + } + + /** Opens the preferred installed file manager on Android and shows a toast + * if no manager is installed. */ + private static void openDocumentPreKitKat( + Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); - + if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple); + } intent.setData(last); intent.setType(mimeType); @@ -92,11 +148,34 @@ public class FileHelper { Toast.makeText(fragment.getActivity(), R.string.no_filemanager_installed, Toast.LENGTH_SHORT).show(); } + + } + + /** Opens the storage browser on Android 4.4 or later for opening a file */ + @TargetApi(Build.VERSION_CODES.KITKAT) + private static void openDocumentKitKat(Fragment fragment, String mimeType, boolean multiple, int requestCode) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(mimeType); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple); + fragment.startActivityForResult(intent, requestCode); + } + + /** Opens the storage browser on Android 4.4 or later for saving a file. */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static void saveDocumentKitKat(Fragment fragment, String mimeType, String suggestedName, int requestCode) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(mimeType); + intent.putExtra("android.content.extra.SHOW_ADVANCED", true); // Note: This is not documented, but works + intent.putExtra(Intent.EXTRA_TITLE, suggestedName); + fragment.startActivityForResult(intent, requestCode); } - public static void saveFile(final FileDialogCallback callback, final FragmentManager fragmentManager, - final String title, final String message, final File defaultFile, - final String checkMsg) { + public static void saveDocumentDialog( + final FileDialogCallback callback, final FragmentManager fragmentManager, + final String title, final String message, final File defaultFile, + final String checkMsg) { // Message is received after file is selected Handler returnHandler = new Handler() { @Override @@ -123,61 +202,6 @@ public class FileHelper { }); } - public static void saveFile(Fragment fragment, String title, String message, File defaultFile, int requestCode) { - saveFile(fragment, title, message, defaultFile, requestCode, null); - } - - public static void saveFile(final Fragment fragment, String title, String message, File defaultFile, - final int requestCode, String checkMsg) { - saveFile(new FileDialogCallback() { - @Override - public void onFileSelected(File file, boolean checked) { - Intent intent = new Intent(); - intent.setData(Uri.fromFile(file)); - fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent); - } - }, fragment.getActivity().getSupportFragmentManager(), title, message, defaultFile, checkMsg); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void openDocument(Fragment fragment, String mimeType, int requestCode) { - openDocument(fragment, mimeType, false, requestCode); - } - - /** - * Opens the storage browser on Android 4.4 or later for opening a file - * - * @param mimeType can be text/plain for example - * @param multiple allow file chooser to return multiple files - * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your - */ - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void openDocument(Fragment fragment, String mimeType, boolean multiple, int requestCode) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType(mimeType); - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple); - - fragment.startActivityForResult(intent, requestCode); - } - - /** - * Opens the storage browser on Android 4.4 or later for saving a file - * - * @param mimeType can be text/plain for example - * @param suggestedName a filename desirable for the file to be saved - * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your - */ - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void saveDocument(Fragment fragment, String mimeType, String suggestedName, int requestCode) { - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType(mimeType); - intent.putExtra("android.content.extra.SHOW_ADVANCED", true); // Note: This is not documented, but works - intent.putExtra(Intent.EXTRA_TITLE, suggestedName); - fragment.startActivityForResult(intent, requestCode); - } - public static String getFilename(Context context, Uri uri) { String filename = null; try { @@ -298,6 +322,17 @@ public class FileHelper { } } + /** Checks if external storage is mounted if file is located on external storage. */ + public static boolean isStorageMounted(String file) { + if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + return false; + } + } + + return true; + } + public interface FileDialogCallback { void onFileSelected(File file, boolean checked); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index 60d0e6ac1..4ef215036 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -25,7 +25,6 @@ import android.content.res.Resources; import android.preference.PreferenceManager; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.Pref; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; import java.net.Proxy; @@ -335,7 +334,7 @@ public class Preferences { } } - // proxy preference functions ends here + // cloud prefs public CloudSearchPrefs getCloudSearchPrefs() { return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true), @@ -360,6 +359,38 @@ public class Preferences { } } + // experimental prefs + + public void setExperimentalEnableWordConfirm(boolean enableWordConfirm) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM, enableWordConfirm); + editor.commit(); + } + + public boolean getExperimentalEnableWordConfirm() { + return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM, false); + } + + public void setExperimentalEnableLinkedIdentities(boolean enableLinkedIdentities) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, enableLinkedIdentities); + editor.commit(); + } + + public boolean getExperimentalEnableLinkedIdentities() { + return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES, false); + } + + public void setExperimentalEnableKeybase(boolean enableKeybase) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, enableKeybase); + editor.commit(); + } + + public boolean getExperimentalEnableKeybase() { + return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, false); + } + public void upgradePreferences(Context context) { if (mSharedPreferences.getInt(Constants.Pref.PREF_DEFAULT_VERSION, 0) != Constants.Defaults.PREF_VERSION) { |