diff options
Diffstat (limited to 'OpenKeychain/src/main/java')
29 files changed, 1417 insertions, 638 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index eeed24db0..e1daac874 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -17,15 +17,20 @@ package org.sufficientlysecure.keychain.operations; + +import java.net.Proxy; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + import android.content.Context; import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.operations.results.CertifyResult; -import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; @@ -40,6 +45,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; +import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; @@ -48,10 +54,6 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; -import java.net.Proxy; -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - /** * An operation which implements a high level user id certification operation. * <p/> @@ -204,23 +206,9 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { } // these variables are used inside the following loop, but they need to be created only once - HkpKeyserver keyServer = null; - ExportOperation exportOperation = null; - Proxy proxy = null; + UploadOperation uploadOperation = null; if (parcel.keyServerUri != null) { - keyServer = new HkpKeyserver(parcel.keyServerUri); - exportOperation = new ExportOperation(mContext, mProviderHelper, mProgressable); - if (cryptoInput.getParcelableProxy() == null) { - // explicit proxy not set - if (!OrbotHelper.isOrbotInRequiredState(mContext)) { - return new CertifyResult(null, - RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); - } - proxy = Preferences.getPreferences(mContext).getProxyPrefs() - .parcelableProxy.getProxy(); - } else { - proxy = cryptoInput.getParcelableProxy().getProxy(); - } + uploadOperation = new UploadOperation(mContext, mProviderHelper, mProgressable); } // Write all certified keys into the database @@ -239,11 +227,10 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { mProviderHelper.clearLog(); SaveKeyringResult result = mProviderHelper.savePublicKeyRing(certifiedKey); - if (exportOperation != null) { - ExportResult uploadResult = exportOperation.uploadKeyRingToServer( - keyServer, - certifiedKey, - proxy); + if (uploadOperation != null) { + UploadKeyringParcel uploadInput = + new UploadKeyringParcel(parcel.keyServerUri, certifiedKey.getMasterKeyId()); + UploadResult uploadResult = uploadOperation.execute(uploadInput, cryptoInput); log.add(uploadResult, 2); if (uploadResult.success()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index f5ba88502..cf8928768 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -17,17 +17,21 @@ package org.sufficientlysecure.keychain.operations; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + import android.content.Context; import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; -import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.InputPendingResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.Progressable; @@ -35,17 +39,14 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; -import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ProgressScaler; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; - /** * An operation which implements a high level key edit operation. * <p/> @@ -134,20 +135,20 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> { UncachedKeyRing ring = modifyResult.getRing(); if (saveParcel.isUpload()) { - UncachedKeyRing publicKeyRing; + byte[] keyringBytes; try { - publicKeyRing = ring.extractPublicKeyRing(); + UncachedKeyRing publicKeyRing = ring.extractPublicKeyRing(); + keyringBytes = publicKeyRing.getEncoded(); } catch (IOException e) { log.add(LogType.MSG_ED_ERROR_EXTRACTING_PUBLIC_UPLOAD, 1); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } - ExportKeyringParcel exportKeyringParcel = - new ExportKeyringParcel(saveParcel.getUploadKeyserver(), - publicKeyRing); + UploadKeyringParcel exportKeyringParcel = + new UploadKeyringParcel(saveParcel.getUploadKeyserver(), keyringBytes); - ExportResult uploadResult = - new ExportOperation(mContext, mProviderHelper, mProgressable) + UploadResult uploadResult = + new UploadOperation(mContext, mProviderHelper, mProgressable) .execute(exportKeyringParcel, cryptoInput); if (uploadResult.isPending()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java index 531ac01f2..ecff9f5ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java @@ -18,47 +18,49 @@ package org.sufficientlysecure.keychain.operations; + import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; +import java.io.DataOutputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; -import java.net.Proxy; +import java.text.SimpleDateFormat; import java.util.Collections; +import java.util.Date; +import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; import org.spongycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel; +import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + /** * An operation class which implements high level export @@ -72,6 +74,17 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; */ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { + private static final String[] PROJECTION = new String[] { + KeyRings.MASTER_KEY_ID, + KeyRings.PUBKEY_DATA, + KeyRings.PRIVKEY_DATA, + KeyRings.HAS_ANY_SECRET + }; + private static final int INDEX_MASTER_KEY_ID = 0; + private static final int INDEX_PUBKEY_DATA = 1; + private static final int INDEX_SECKEY_DATA = 2; + private static final int INDEX_HAS_ANY_SECRET = 3; + public ExportOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { super(context, providerHelper, progressable); @@ -82,234 +95,129 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { super(context, providerHelper, progressable, cancelled); } - public ExportResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring, - Proxy proxy) { - return uploadKeyRingToServer(server, keyring.getUncachedKeyRing(), proxy); - } - - public ExportResult uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring, Proxy proxy) { - mProgressable.setProgress(R.string.progress_uploading, 0, 1); + @NonNull + public ExportResult execute(@NonNull ExportKeyringParcel exportInput, @Nullable CryptoInputParcel cryptoInput) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ArmoredOutputStream aos = null; OperationLog log = new OperationLog(); - log.add(LogType.MSG_EXPORT_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex( - keyring.getPublicKey().getKeyId() - )); + if (exportInput.mMasterKeyIds != null) { + log.add(LogType.MSG_EXPORT, 0, exportInput.mMasterKeyIds.length); + } else { + log.add(LogType.MSG_EXPORT_ALL, 0); + } try { - aos = new ArmoredOutputStream(bos); - keyring.encode(aos); - aos.close(); - - String armoredKey = bos.toString("UTF-8"); - server.add(armoredKey, proxy); - log.add(LogType.MSG_EXPORT_UPLOAD_SUCCESS, 1); - return new ExportResult(ExportResult.RESULT_OK, log); - } catch (IOException e) { - Log.e(Constants.TAG, "IOException", e); + boolean nonEncryptedOutput = exportInput.mSymmetricPassphrase == null; - log.add(LogType.MSG_EXPORT_ERROR_KEY, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log); - } catch (AddKeyException e) { - Log.e(Constants.TAG, "AddKeyException", e); + Uri exportOutputUri = nonEncryptedOutput + ? exportInput.mOutputUri + : TemporaryStorageProvider.createFile(mContext); - log.add(LogType.MSG_EXPORT_ERROR_UPLOAD, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log); - } finally { - mProgressable.setProgress(R.string.progress_uploading, 1, 1); - try { - if (aos != null) { - aos.close(); - } - bos.close(); - } catch (IOException e) { - // this is just a finally thing, no matter if it doesn't work out. - } - } - } + int exportedDataSize; - public ExportResult exportToFile(long[] masterKeyIds, boolean exportSecret, String outputFile) { + { // export key data, and possibly return if we don't encrypt - OperationLog log = new OperationLog(); - if (masterKeyIds != null) { - log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length); - } else { - log.add(LogType.MSG_EXPORT_ALL, 0); - } + DataOutputStream outStream = new DataOutputStream(new BufferedOutputStream( + mContext.getContentResolver().openOutputStream(exportOutputUri))); - // do we have a file name? - if (outputFile == null) { - log.add(LogType.MSG_EXPORT_ERROR_NO_FILE, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log); - } + boolean exportSuccess = exportKeysToStream( + log, exportInput.mMasterKeyIds, exportInput.mExportSecret, outStream); - log.add(LogType.MSG_EXPORT_FILE_NAME, 1, outputFile); + exportedDataSize = outStream.size(); - // check if storage is ready - if (!FileHelper.isStorageMounted(outputFile)) { - log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log); - } + if (!exportSuccess) { + // if there was an error, it will be in the log so we just have to return + return new ExportResult(ExportResult.RESULT_ERROR, log); + } - try { - OutputStream outStream = new FileOutputStream(outputFile); - try { - ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream); - if (result.cancelled()) { - //noinspection ResultOfMethodCallIgnored - new File(outputFile).delete(); + if (nonEncryptedOutput) { + // log.add(LogType.MSG_EXPORT_NO_ENCRYPT, 1); + log.add(LogType.MSG_EXPORT_SUCCESS, 1); + return new ExportResult(ExportResult.RESULT_OK, log); } - return result; - } finally { - outStream.close(); } - } catch (IOException e) { - log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log); - } - } + PgpSignEncryptOperation pseOp = new PgpSignEncryptOperation(mContext, mProviderHelper, mProgressable, mCancelled); - public ExportResult exportToUri(long[] masterKeyIds, boolean exportSecret, Uri outputUri) { + PgpSignEncryptInputParcel inputParcel = new PgpSignEncryptInputParcel(); + inputParcel.setSymmetricPassphrase(exportInput.mSymmetricPassphrase); + inputParcel.setEnableAsciiArmorOutput(true); - OperationLog log = new OperationLog(); - if (masterKeyIds != null) { - log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length); - } else { - log.add(LogType.MSG_EXPORT_ALL, 0); - } + InputStream inStream = mContext.getContentResolver().openInputStream(exportOutputUri); - // do we have a file name? - if (outputUri == null) { - log.add(LogType.MSG_EXPORT_ERROR_NO_URI, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log); - } + String filename; + if (exportInput.mMasterKeyIds != null && exportInput.mMasterKeyIds.length == 1) { + filename = "backup_" + KeyFormattingUtils.convertKeyIdToHex(exportInput.mMasterKeyIds[0]); + filename += exportInput.mExportSecret ? ".sec.asc" : ".pub.asc"; + } else { + filename = "backup_" + new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); + filename += exportInput.mExportSecret ? ".asc" : ".pub.asc"; + } - try { - OutputStream outStream = mProviderHelper.getContentResolver().openOutputStream - (outputUri); - return exportKeyRings(log, masterKeyIds, exportSecret, outStream); - } catch (FileNotFoundException e) { - log.add(LogType.MSG_EXPORT_ERROR_URI_OPEN, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log); - } + InputData inputData = new InputData(inStream, exportedDataSize, filename); - } + OutputStream outStream = mContext.getContentResolver().openOutputStream(exportInput.mOutputUri); + outStream = new BufferedOutputStream(outStream); + + PgpSignEncryptResult encryptResult = pseOp.execute(inputParcel, new CryptoInputParcel(), inputData, outStream); + if (!encryptResult.success()) { + log.addByMerge(encryptResult, 1); + // log.add(LogType.MSG_EXPORT_ERROR_ENCRYPT, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log); + } - ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret, - OutputStream outStream) { + log.add(encryptResult, 1); + log.add(LogType.MSG_EXPORT_SUCCESS, 1); + return new ExportResult(ExportResult.RESULT_OK, log); - /* TODO isn't this checked above, with the isStorageMounted call? - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1); + } catch (FileNotFoundException e) { + log.add(LogType.MSG_EXPORT_ERROR_URI_OPEN, 1); return new ExportResult(ExportResult.RESULT_ERROR, log); - } - */ - if (!BufferedOutputStream.class.isInstance(outStream)) { - outStream = new BufferedOutputStream(outStream); } - int okSecret = 0, okPublic = 0, progress = 0; - - Cursor cursor = null; - try { + } - String selection = null, selectionArgs[] = null; + boolean exportKeysToStream(OperationLog log, long[] masterKeyIds, boolean exportSecret, OutputStream outStream) { - if (masterKeyIds != null) { - // convert long[] to String[] - selectionArgs = new String[masterKeyIds.length]; - for (int i = 0; i < masterKeyIds.length; i++) { - selectionArgs[i] = Long.toString(masterKeyIds[i]); - } + // noinspection unused TODO use these in a log entry + int okSecret = 0, okPublic = 0; - // generates ?,?,? as placeholders for selectionArgs - String placeholders = TextUtils.join(",", - Collections.nCopies(masterKeyIds.length, "?")); + int progress = 0; - // put together selection string - selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID - + " IN (" + placeholders + ")"; - } + Cursor cursor = queryForKeys(masterKeyIds); - cursor = mProviderHelper.getContentResolver().query( - KeyRings.buildUnifiedKeyRingsUri(), new String[]{ - KeyRings.MASTER_KEY_ID, KeyRings.PUBKEY_DATA, - KeyRings.PRIVKEY_DATA, KeyRings.HAS_ANY_SECRET - }, selection, selectionArgs, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID - ); + if (cursor == null || !cursor.moveToFirst()) { + log.add(LogType.MSG_EXPORT_ERROR_DB, 1); + return false; // new ExportResult(ExportResult.RESULT_ERROR, log); + } - if (cursor == null || !cursor.moveToFirst()) { - log.add(LogType.MSG_EXPORT_ERROR_DB, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret); - } + try { int numKeys = cursor.getCount(); - updateProgress( - mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, - numKeys), 0, numKeys); + updateProgress(mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, numKeys), + 0, numKeys); // For each public masterKey id while (!cursor.isAfterLast()) { - long keyId = cursor.getLong(0); - ArmoredOutputStream arOutStream = null; - - // Create an output stream - try { - arOutStream = new ArmoredOutputStream(outStream); - - log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId)); - - byte[] data = cursor.getBlob(1); - CanonicalizedKeyRing ring = - UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true); - ring.encode(arOutStream); + long keyId = cursor.getLong(INDEX_MASTER_KEY_ID); + log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId)); + if (writePublicKeyToStream(log, outStream, cursor)) { okPublic += 1; - } catch (PgpGeneralException e) { - log.add(LogType.MSG_EXPORT_ERROR_KEY, 2); - updateProgress(progress++, numKeys); - continue; - } finally { - // make sure this is closed - if (arOutStream != null) { - arOutStream.close(); - } - arOutStream = null; - } - if (exportSecret && cursor.getInt(3) > 0) { - try { - arOutStream = new ArmoredOutputStream(outStream); - - // export secret key part - log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId - (keyId)); - byte[] data = cursor.getBlob(2); - CanonicalizedKeyRing ring = - UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true); - ring.encode(arOutStream); - - okSecret += 1; - } catch (PgpGeneralException e) { - log.add(LogType.MSG_EXPORT_ERROR_KEY, 2); - updateProgress(progress++, numKeys); - continue; - } finally { - // make sure this is closed - if (arOutStream != null) { - arOutStream.close(); + boolean hasSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) > 0; + if (exportSecret && hasSecret) { + log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyId)); + if (writeSecretKeyToStream(log, outStream, cursor)) { + okSecret += 1; } } } updateProgress(progress++, numKeys); - cursor.moveToNext(); } @@ -317,7 +225,7 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { } catch (IOException e) { log.add(LogType.MSG_EXPORT_ERROR_IO, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret); + return false; // new ExportResult(ExportResult.RESULT_ERROR, log); } finally { // Make sure the stream is closed if (outStream != null) try { @@ -325,61 +233,80 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> { } catch (Exception e) { Log.e(Constants.TAG, "error closing stream", e); } - if (cursor != null) { - cursor.close(); - } + cursor.close(); } - - log.add(LogType.MSG_EXPORT_SUCCESS, 1); - return new ExportResult(ExportResult.RESULT_OK, log, okPublic, okSecret); + return true; } - @NonNull - public ExportResult execute(ExportKeyringParcel exportInput, CryptoInputParcel cryptoInput) { - switch (exportInput.mExportType) { - case UPLOAD_KEYSERVER: { - Proxy proxy; - if (cryptoInput.getParcelableProxy() == null) { - // explicit proxy not set - if (!OrbotHelper.isOrbotInRequiredState(mContext)) { - return new ExportResult(null, - RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); - } - proxy = Preferences.getPreferences(mContext).getProxyPrefs() - .parcelableProxy.getProxy(); - } else { - proxy = cryptoInput.getParcelableProxy().getProxy(); - } + private boolean writePublicKeyToStream(OperationLog log, OutputStream outStream, Cursor cursor) + throws IOException { - HkpKeyserver hkpKeyserver = new HkpKeyserver(exportInput.mKeyserver); - try { - if (exportInput.mCanonicalizedPublicKeyringUri != null) { - CanonicalizedPublicKeyRing keyring - = mProviderHelper.getCanonicalizedPublicKeyRing( - exportInput.mCanonicalizedPublicKeyringUri); - return uploadKeyRingToServer(hkpKeyserver, keyring, proxy); - } else { - return uploadKeyRingToServer(hkpKeyserver, exportInput.mUncachedKeyRing, - proxy); - } - } catch (ProviderHelper.NotFoundException e) { - Log.e(Constants.TAG, "error uploading key", e); - return new ExportResult(ExportResult.RESULT_ERROR, new OperationLog()); - } - } - case EXPORT_FILE: { - return exportToFile(exportInput.mMasterKeyIds, exportInput.mExportSecret, - exportInput.mOutputFile); + ArmoredOutputStream arOutStream = null; + + try { + arOutStream = new ArmoredOutputStream(outStream); + byte[] data = cursor.getBlob(INDEX_PUBKEY_DATA); + CanonicalizedKeyRing ring = UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true); + ring.encode(arOutStream); + + } catch (PgpGeneralException e) { + log.add(LogType.MSG_EXPORT_ERROR_KEY, 2); + } finally { + if (arOutStream != null) { + arOutStream.close(); } - case EXPORT_URI: { - return exportToUri(exportInput.mMasterKeyIds, exportInput.mExportSecret, - exportInput.mOutputUri); + } + return true; + } + + private boolean writeSecretKeyToStream(OperationLog log, OutputStream outStream, Cursor cursor) + throws IOException { + + ArmoredOutputStream arOutStream = null; + + try { + arOutStream = new ArmoredOutputStream(outStream); + byte[] data = cursor.getBlob(INDEX_SECKEY_DATA); + CanonicalizedKeyRing ring = UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true); + ring.encode(arOutStream); + + } catch (PgpGeneralException e) { + log.add(LogType.MSG_EXPORT_ERROR_KEY, 2); + } finally { + if (arOutStream != null) { + arOutStream.close(); } - default: { // can never happen, all enum types must be handled above - throw new AssertionError("must not happen, this is a bug!"); + } + return true; + } + + private Cursor queryForKeys(long[] masterKeyIds) { + + String selection = null, selectionArgs[] = null; + + if (masterKeyIds != null) { + // convert long[] to String[] + selectionArgs = new String[masterKeyIds.length]; + for (int i = 0; i < masterKeyIds.length; i++) { + selectionArgs[i] = Long.toString(masterKeyIds[i]); } + + // generates ?,?,? as placeholders for selectionArgs + String placeholders = TextUtils.join(",", + Collections.nCopies(masterKeyIds.length, "?")); + + // put together selection string + selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + + " IN (" + placeholders + ")"; } + + return mProviderHelper.getContentResolver().query( + KeyRings.buildUnifiedKeyRingsUri(), PROJECTION, selection, selectionArgs, + Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + ); + } + }
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 7ec33874f..c48ccc500 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -135,7 +135,7 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> { || contentType != null && !contentType.startsWith("multipart/") && !contentType.startsWith("text/") - && !contentType.startsWith("application/")) { + && !"application/octet-stream".equals(contentType)) { skipMimeParsing = true; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java new file mode 100644 index 000000000..499f592cf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> + * Copyright (C) 2015 Vincent Breitmoser <valodim@mugenguild.com> + * + * 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.operations; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.Proxy; +import java.util.concurrent.atomic.AtomicBoolean; + +import android.content.Context; +import android.support.annotation.NonNull; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; +import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.UploadResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.UploadKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + + +/** + * An operation class which implements high level export operations. + * This class receives a source and/or destination of keys as input and performs + * all steps for this export. + * + * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries() + * For the export operation, the input consists of a set of key ids and + * either the name of a file or an output uri to write to. + */ +public class UploadOperation extends BaseOperation<UploadKeyringParcel> { + + public UploadOperation(Context context, ProviderHelper providerHelper, Progressable + progressable) { + super(context, providerHelper, progressable); + } + + public UploadOperation(Context context, ProviderHelper providerHelper, + Progressable progressable, AtomicBoolean cancelled) { + super(context, providerHelper, progressable, cancelled); + } + + @NonNull + public UploadResult execute(UploadKeyringParcel uploadInput, CryptoInputParcel cryptoInput) { + Proxy proxy; + if (cryptoInput.getParcelableProxy() == null) { + // explicit proxy not set + if (!OrbotHelper.isOrbotInRequiredState(mContext)) { + return new UploadResult(null, RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput); + } + proxy = Preferences.getPreferences(mContext).getProxyPrefs().parcelableProxy.getProxy(); + } else { + proxy = cryptoInput.getParcelableProxy().getProxy(); + } + + HkpKeyserver hkpKeyserver = new HkpKeyserver(uploadInput.mKeyserver); + try { + CanonicalizedPublicKeyRing keyring; + if (uploadInput.mMasterKeyId != null) { + keyring = mProviderHelper.getCanonicalizedPublicKeyRing( + uploadInput.mMasterKeyId); + } else if (uploadInput.mUncachedKeyringBytes != null) { + CanonicalizedKeyRing canonicalizedRing = + UncachedKeyRing.decodeFromData(uploadInput.mUncachedKeyringBytes) + .canonicalize(new OperationLog(), 0, true); + if ( ! CanonicalizedPublicKeyRing.class.isInstance(canonicalizedRing)) { + throw new AssertionError("keyring bytes must contain public key ring!"); + } + keyring = (CanonicalizedPublicKeyRing) canonicalizedRing; + } else { + throw new AssertionError("key id or bytes must be non-null!"); + } + return uploadKeyRingToServer(hkpKeyserver, keyring, proxy); + } catch (ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, "error uploading key", e); + return new UploadResult(UploadResult.RESULT_ERROR, new OperationLog()); + } catch (IOException e) { + e.printStackTrace(); + return new UploadResult(UploadResult.RESULT_ERROR, new OperationLog()); + } catch (PgpGeneralException e) { + e.printStackTrace(); + return new UploadResult(UploadResult.RESULT_ERROR, new OperationLog()); + } + } + + UploadResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring, Proxy proxy) { + + mProgressable.setProgress(R.string.progress_uploading, 0, 1); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ArmoredOutputStream aos = null; + OperationLog log = new OperationLog(); + log.add(LogType.MSG_EXPORT_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex( + keyring.getPublicKey().getKeyId() + )); + + try { + aos = new ArmoredOutputStream(bos); + keyring.encode(aos); + aos.close(); + + String armoredKey = bos.toString("UTF-8"); + server.add(armoredKey, proxy); + + log.add(LogType.MSG_EXPORT_UPLOAD_SUCCESS, 1); + return new UploadResult(UploadResult.RESULT_OK, log); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException", e); + + log.add(LogType.MSG_EXPORT_ERROR_KEY, 1); + return new UploadResult(UploadResult.RESULT_ERROR, log); + } catch (AddKeyException e) { + Log.e(Constants.TAG, "AddKeyException", e); + + log.add(LogType.MSG_EXPORT_ERROR_UPLOAD, 1); + return new UploadResult(UploadResult.RESULT_ERROR, log); + } finally { + mProgressable.setProgress(R.string.progress_uploading, 1, 1); + try { + if (aos != null) { + aos.close(); + } + bos.close(); + } catch (IOException e) { + // this is just a finally thing, no matter if it doesn't work out. + } + } + } + +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java index e21ef949f..135f5af3d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java @@ -24,39 +24,18 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; public class ExportResult extends InputPendingResult { - final int mOkPublic, mOkSecret; - public ExportResult(int result, OperationLog log) { - this(result, log, 0, 0); - } - - public ExportResult(int result, OperationLog log, int okPublic, int okSecret) { super(result, log); - mOkPublic = okPublic; - mOkSecret = okSecret; - } - - - public ExportResult(OperationLog log, RequiredInputParcel requiredInputParcel, - CryptoInputParcel cryptoInputParcel) { - super(log, requiredInputParcel, cryptoInputParcel); - // we won't use these values - mOkPublic = -1; - mOkSecret = -1; } /** Construct from a parcel - trivial because we have no extra data. */ public ExportResult(Parcel source) { super(source); - mOkPublic = source.readInt(); - mOkSecret = source.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeInt(mOkPublic); - dest.writeInt(mOkSecret); } public static Creator<ExportResult> CREATOR = new Creator<ExportResult>() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index f959ddd76..24d1215d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -765,16 +765,11 @@ public abstract class OperationResult implements Parcelable { MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success), MSG_EXPORT (LogLevel.START, R.plurals.msg_export), - MSG_EXPORT_FILE_NAME (LogLevel.INFO, R.string.msg_export_file_name), MSG_EXPORT_UPLOAD_PUBLIC (LogLevel.START, R.string.msg_export_upload_public), MSG_EXPORT_PUBLIC (LogLevel.DEBUG, R.string.msg_export_public), MSG_EXPORT_SECRET (LogLevel.DEBUG, R.string.msg_export_secret), MSG_EXPORT_ALL (LogLevel.START, R.string.msg_export_all), - MSG_EXPORT_ERROR_NO_FILE (LogLevel.ERROR, R.string.msg_export_error_no_file), - MSG_EXPORT_ERROR_FOPEN (LogLevel.ERROR, R.string.msg_export_error_fopen), - MSG_EXPORT_ERROR_NO_URI (LogLevel.ERROR, R.string.msg_export_error_no_uri), MSG_EXPORT_ERROR_URI_OPEN (LogLevel.ERROR, R.string.msg_export_error_uri_open), - MSG_EXPORT_ERROR_STORAGE (LogLevel.ERROR, R.string.msg_export_error_storage), MSG_EXPORT_ERROR_DB (LogLevel.ERROR, R.string.msg_export_error_db), MSG_EXPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_export_error_io), MSG_EXPORT_ERROR_KEY (LogLevel.ERROR, R.string.msg_export_error_key), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java new file mode 100644 index 000000000..a88072de3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * 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.operations.results; + +import android.os.Parcel; + +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; + + +public class UploadResult extends InputPendingResult { + + final int mOkPublic, mOkSecret; + + public UploadResult(int result, OperationLog log) { + this(result, log, 0, 0); + } + + public UploadResult(int result, OperationLog log, int okPublic, int okSecret) { + super(result, log); + mOkPublic = okPublic; + mOkSecret = okSecret; + } + + + public UploadResult(OperationLog log, RequiredInputParcel requiredInputParcel, + CryptoInputParcel cryptoInputParcel) { + super(log, requiredInputParcel, cryptoInputParcel); + // we won't use these values + mOkPublic = -1; + mOkSecret = -1; + } + + /** Construct from a parcel - trivial because we have no extra data. */ + public UploadResult(Parcel source) { + super(source); + mOkPublic = source.readInt(); + mOkSecret = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mOkPublic); + dest.writeInt(mOkSecret); + } + + public static Creator<UploadResult> CREATOR = new Creator<UploadResult>() { + public UploadResult createFromParcel(final Parcel source) { + return new UploadResult(source); + } + + public UploadResult[] newArray(final int size) { + return new UploadResult[size]; + } + }; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 0709d4f62..2d28f70e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -346,14 +346,14 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp if (!"".equals(originalFilename)) { log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename); } - log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, - mimeType); log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1, new Date(literalData.getModificationTime().getTime()).toString()); // return here if we want to decrypt the metadata only if (input.isDecryptMetadataOnly()) { + log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType); + // this operation skips the entire stream to find the data length! Long originalSize = literalData.findDataLength(); @@ -387,6 +387,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition(); int length; byte[] buffer = new byte[1 << 16]; + byte[] firstBytes = new byte[48]; while ((length = dataIn.read(buffer)) > 0) { // Log.d(Constants.TAG, "read bytes: " + length); if (out != null) { @@ -396,6 +397,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp // update signature buffer if signature is also present signatureChecker.updateSignatureData(buffer, 0, length); + // note down first couple of bytes for "magic bytes" file type detection + if (alreadyWritten == 0) { + System.arraycopy(buffer, 0, firstBytes, 0, length > firstBytes.length ? firstBytes.length : length); + } + alreadyWritten += length; // noinspection ConstantConditions, TODO progress if (wholeSize > 0) { @@ -408,6 +414,17 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp } } + // special treatment to detect pgp mime types + if (matchesPrefix(firstBytes, "-----BEGIN PGP PUBLIC KEY BLOCK-----") + || matchesPrefix(firstBytes, "-----BEGIN PGP PRIVATE KEY BLOCK-----")) { + mimeType = "application/pgp-keys"; + } else if (matchesPrefix(firstBytes, "-----BEGIN PGP MESSAGE-----")) { + // this is NOT pgp/encrypted, see RFC 3156! + mimeType = "application/pgp-message"; + } + + log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType); + metadata = new OpenPgpMetadata( originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset); @@ -940,4 +957,16 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp return nl.getBytes(); } + /// Convenience method - Trivially checks if a byte array matches the bytes of a plain text string + // Assumes data.length >= needle.length() + static boolean matchesPrefix(byte[] data, String needle) { + byte[] needleBytes = needle.getBytes(); + for (int i = 0; i < needle.length(); i++) { + if (data[i] != needleBytes[i]) { + return false; + } + } + return true; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 406c64ae8..ecc190d97 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -53,6 +53,7 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.ProgressScaler; +import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; @@ -361,7 +362,7 @@ public class PgpSignEncryptOperation extends BaseOperation { long alreadyWritten = 0; int length; byte[] buffer = new byte[1 << 16]; - InputStream in = inputData.getInputStream(); + InputStream in = new BufferedInputStream(inputData.getInputStream()); while ((length = in.read(buffer)) > 0) { pOut.write(buffer, 0, length); @@ -389,7 +390,7 @@ public class PgpSignEncryptOperation extends BaseOperation { // write -----BEGIN PGP SIGNED MESSAGE----- armorOut.beginClearText(input.getSignatureHashAlgorithm()); - InputStream in = inputData.getInputStream(); + InputStream in = new BufferedInputStream(inputData.getInputStream()); final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); // update signature buffer with first line @@ -421,7 +422,7 @@ public class PgpSignEncryptOperation extends BaseOperation { updateProgress(R.string.progress_signing, 8, 100); log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent); - InputStream in = inputData.getInputStream(); + InputStream in = new BufferedInputStream(inputData.getInputStream()); // handle output stream separately for detached signatures detachedByteOut = new ByteArrayOutputStream(); @@ -458,7 +459,7 @@ public class PgpSignEncryptOperation extends BaseOperation { updateProgress(R.string.progress_signing, 8, 100); log.add(LogType.MSG_PSE_SIGNING, indent); - InputStream in = inputData.getInputStream(); + InputStream in = new BufferedInputStream(inputData.getInputStream()); if (enableCompression) { compressGen = new PGPCompressedDataGenerator(input.getCompressionAlgorithm()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 96778649c..c967a5abc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -18,6 +18,23 @@ package org.sufficientlysecure.keychain.pgp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; + import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.SignatureSubpacketTags; @@ -43,23 +60,6 @@ import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Utf8Util; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Comparator; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.TimeZone; -import java.util.TreeSet; - /** Wrapper around PGPKeyRing class, to be constructed from bytes. * * This class and its relatives UncachedPublicKey and UncachedSecretKey are @@ -78,8 +78,7 @@ import java.util.TreeSet; * @see org.sufficientlysecure.keychain.pgp.UncachedSecretKey * */ -@SuppressWarnings("unchecked") -public class UncachedKeyRing implements Serializable { +public class UncachedKeyRing { final PGPKeyRing mRing; final boolean mIsSecret; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java index 24c002bbd..9b0e5573e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java @@ -24,60 +24,31 @@ import android.os.Parcel; import android.os.Parcelable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.util.Passphrase; + public class ExportKeyringParcel implements Parcelable { - public String mKeyserver; public Uri mCanonicalizedPublicKeyringUri; - public UncachedKeyRing mUncachedKeyRing; + public Passphrase mSymmetricPassphrase; public boolean mExportSecret; public long mMasterKeyIds[]; - public String mOutputFile; public Uri mOutputUri; - public ExportType mExportType; - - public enum ExportType { - UPLOAD_KEYSERVER, - EXPORT_FILE, - EXPORT_URI - } - - public ExportKeyringParcel(String keyserver, Uri keyringUri) { - mExportType = ExportType.UPLOAD_KEYSERVER; - mKeyserver = keyserver; - mCanonicalizedPublicKeyringUri = keyringUri; - } - - public ExportKeyringParcel(String keyserver, UncachedKeyRing uncachedKeyRing) { - mExportType = ExportType.UPLOAD_KEYSERVER; - mKeyserver = keyserver; - mUncachedKeyRing = uncachedKeyRing; - } - - public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, String outputFile) { - mExportType = ExportType.EXPORT_FILE; - mMasterKeyIds = masterKeyIds; - mExportSecret = exportSecret; - mOutputFile = outputFile; - } - @SuppressWarnings("unused") // TODO: is it used? - public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, Uri outputUri) { - mExportType = ExportType.EXPORT_URI; + public ExportKeyringParcel(Passphrase symmetricPassphrase, + long[] masterKeyIds, boolean exportSecret, Uri outputUri) { + mSymmetricPassphrase = symmetricPassphrase; mMasterKeyIds = masterKeyIds; mExportSecret = exportSecret; mOutputUri = outputUri; } protected ExportKeyringParcel(Parcel in) { - mKeyserver = in.readString(); mCanonicalizedPublicKeyringUri = (Uri) in.readValue(Uri.class.getClassLoader()); - mUncachedKeyRing = (UncachedKeyRing) in.readValue(UncachedKeyRing.class.getClassLoader()); mExportSecret = in.readByte() != 0x00; - mOutputFile = in.readString(); mOutputUri = (Uri) in.readValue(Uri.class.getClassLoader()); - mExportType = (ExportType) in.readValue(ExportType.class.getClassLoader()); mMasterKeyIds = in.createLongArray(); + mSymmetricPassphrase = in.readParcelable(getClass().getClassLoader()); } @Override @@ -87,14 +58,11 @@ public class ExportKeyringParcel implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mKeyserver); dest.writeValue(mCanonicalizedPublicKeyringUri); - dest.writeValue(mUncachedKeyRing); dest.writeByte((byte) (mExportSecret ? 0x01 : 0x00)); - dest.writeString(mOutputFile); dest.writeValue(mOutputUri); - dest.writeValue(mExportType); dest.writeLongArray(mMasterKeyIds); + dest.writeParcelable(mSymmetricPassphrase, 0); } public static final Parcelable.Creator<ExportKeyringParcel> CREATOR = new Parcelable.Creator<ExportKeyringParcel>() { 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 c7ac92eef..981a76203 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.operations.InputDataOperation; import org.sufficientlysecure.keychain.operations.PromoteKeyOperation; import org.sufficientlysecure.keychain.operations.RevokeOperation; import org.sufficientlysecure.keychain.operations.SignEncryptOperation; +import org.sufficientlysecure.keychain.operations.UploadOperation; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; @@ -126,6 +127,8 @@ public class KeychainService extends Service implements Progressable { op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); } else if (inputParcel instanceof ExportKeyringParcel) { op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); + } else if (inputParcel instanceof UploadKeyringParcel) { + op = new UploadOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); } else if (inputParcel instanceof ConsolidateInputParcel) { op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else if (inputParcel instanceof KeybaseVerificationParcel) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java new file mode 100644 index 000000000..0a14f3dc6 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.service; + + +import android.os.Parcel; +import android.os.Parcelable; + + +public class UploadKeyringParcel implements Parcelable { + public String mKeyserver; + + public final Long mMasterKeyId; + public final byte[] mUncachedKeyringBytes; + + public UploadKeyringParcel(String keyserver, long masterKeyId) { + mKeyserver = keyserver; + mMasterKeyId = masterKeyId; + mUncachedKeyringBytes = null; + } + + public UploadKeyringParcel(String keyserver, byte[] uncachedKeyringBytes) { + mKeyserver = keyserver; + mMasterKeyId = null; + mUncachedKeyringBytes = uncachedKeyringBytes; + } + + protected UploadKeyringParcel(Parcel in) { + mKeyserver = in.readString(); + mMasterKeyId = in.readInt() != 0 ? in.readLong() : null; + mUncachedKeyringBytes = in.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mKeyserver); + if (mMasterKeyId != null) { + dest.writeInt(1); + dest.writeLong(mMasterKeyId); + } else { + dest.writeInt(0); + } + dest.writeByteArray(mUncachedKeyringBytes); + } + + public static final Creator<UploadKeyringParcel> CREATOR = new Creator<UploadKeyringParcel>() { + @Override + public UploadKeyringParcel createFromParcel(Parcel in) { + return new UploadKeyringParcel(in); + } + + @Override + public UploadKeyringParcel[] newArray(int size) { + return new UploadKeyringParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java new file mode 100644 index 000000000..ff120c9b5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui; + + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.view.MenuItem; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + + +public class BackupActivity extends BaseActivity { + + public static final String EXTRA_MASTER_KEY_IDS = "master_key_ids"; + public static final String EXTRA_SECRET = "export_secret"; + + @Override + protected void initLayout() { + setContentView(R.layout.backup_activity); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // noinspection ConstantConditions, we know this activity has an action bar + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + if (savedInstanceState == null) { + Intent intent = getIntent(); + boolean exportSecret = intent.getBooleanExtra(EXTRA_SECRET, false); + long[] masterKeyIds = intent.getLongArrayExtra(EXTRA_MASTER_KEY_IDS); + + Fragment frag = BackupCodeFragment.newInstance(masterKeyIds, exportSecret); + + FragmentManager fragMan = getSupportFragmentManager(); + fragMan.beginTransaction() + .setCustomAnimations(0, 0) + .replace(R.id.content_frame, frag) + .commit(); + } + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + FragmentManager fragMan = getSupportFragmentManager(); + // pop from back stack, or if nothing was on there finish activity + if ( ! fragMan.popBackStackImmediate()) { + finish(); + } + return true; + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java new file mode 100644 index 000000000..be1399a30 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java @@ -0,0 +1,550 @@ +/* + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * 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 java.io.File; +import java.io.IOException; +import java.security.SecureRandom; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Random; + +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentManager.OnBackStackChangedListener; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.widget.EditText; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; +import org.sufficientlysecure.keychain.service.ExportKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Passphrase; + + +public class BackupCodeFragment extends CryptoOperationFragment<ExportKeyringParcel,ExportResult> + implements OnBackStackChangedListener { + + public static final String ARG_BACKUP_CODE = "backup_code"; + public static final String BACK_STACK_INPUT = "state_display"; + public static final String ARG_EXPORT_SECRET = "export_secret"; + public static final String ARG_MASTER_KEY_IDS = "master_key_ids"; + + public static final String ARG_CURRENT_STATE = "current_state"; + + public static final int REQUEST_SAVE = 1; + public static final String ARG_BACK_STACK = "back_stack"; + + // argument variables + private boolean mExportSecret; + private long[] mMasterKeyIds; + String mBackupCode; + + private EditText[] mCodeEditText; + private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator; + + private Integer mBackStackLevel; + + private Uri mCachedExportUri; + private boolean mShareNotSave; + + public static BackupCodeFragment newInstance(long[] masterKeyIds, boolean exportSecret) { + BackupCodeFragment frag = new BackupCodeFragment(); + + Bundle args = new Bundle(); + args.putString(ARG_BACKUP_CODE, generateRandomCode()); + args.putLongArray(ARG_MASTER_KEY_IDS, masterKeyIds); + args.putBoolean(ARG_EXPORT_SECRET, exportSecret); + frag.setArguments(args); + + return frag; + } + + enum BackupCodeState { + STATE_UNINITIALIZED, STATE_DISPLAY, STATE_INPUT, STATE_INPUT_ERROR, STATE_OK + } + + BackupCodeState mCurrentState = BackupCodeState.STATE_UNINITIALIZED; + + void switchState(BackupCodeState state, boolean animate) { + + switch (state) { + case STATE_UNINITIALIZED: + throw new AssertionError("can't switch to uninitialized state, this is a bug!"); + + case STATE_DISPLAY: + mTitleAnimator.setDisplayedChild(0, animate); + mStatusAnimator.setDisplayedChild(0, animate); + mCodeFieldsAnimator.setDisplayedChild(0, animate); + + break; + + case STATE_INPUT: + mTitleAnimator.setDisplayedChild(1, animate); + mStatusAnimator.setDisplayedChild(1, animate); + mCodeFieldsAnimator.setDisplayedChild(1, animate); + + for (EditText editText : mCodeEditText) { + editText.setText(""); + } + + pushBackStackEntry(); + + break; + + case STATE_INPUT_ERROR: { + mTitleAnimator.setDisplayedChild(1, false); + mStatusAnimator.setDisplayedChild(2, animate); + mCodeFieldsAnimator.setDisplayedChild(1, false); + + hideKeyboard(); + + if (animate) { + @ColorInt int black = mCodeEditText[0].getCurrentTextColor(); + @ColorInt int red = getResources().getColor(R.color.android_red_dark); + animateFlashText(mCodeEditText, black, red, false); + } + + break; + } + + case STATE_OK: { + mTitleAnimator.setDisplayedChild(2, animate); + mStatusAnimator.setDisplayedChild(3, animate); + mCodeFieldsAnimator.setDisplayedChild(1, false); + + hideKeyboard(); + + for (EditText editText : mCodeEditText) { + editText.setEnabled(false); + } + + @ColorInt int green = getResources().getColor(R.color.android_green_dark); + if (animate) { + @ColorInt int black = mCodeEditText[0].getCurrentTextColor(); + animateFlashText(mCodeEditText, black, green, true); + } else { + for (TextView textView : mCodeEditText) { + textView.setTextColor(green); + } + } + + popBackStackNoAction(); + + break; + } + + } + + mCurrentState = state; + + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.backup_code_fragment, container, false); + + Bundle args = getArguments(); + mBackupCode = args.getString(ARG_BACKUP_CODE); + mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS); + mExportSecret = args.getBoolean(ARG_EXPORT_SECRET); + + mCodeEditText = new EditText[4]; + mCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1); + mCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2); + mCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3); + mCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4); + + { + TextView[] codeDisplayText = new TextView[4]; + codeDisplayText[0] = (TextView) view.findViewById(R.id.backup_code_display_1); + codeDisplayText[1] = (TextView) view.findViewById(R.id.backup_code_display_2); + codeDisplayText[2] = (TextView) view.findViewById(R.id.backup_code_display_3); + codeDisplayText[3] = (TextView) view.findViewById(R.id.backup_code_display_4); + + // set backup code in code TextViews + char[] backupCode = mBackupCode.toCharArray(); + for (int i = 0; i < codeDisplayText.length; i++) { + codeDisplayText[i].setText(backupCode, i * 7, 6); + } + + // set background to null in TextViews - this will retain padding from EditText style! + for (TextView textView : codeDisplayText) { + // noinspection deprecation, setBackground(Drawable) is API level >=16 + textView.setBackgroundDrawable(null); + } + } + + setupEditTextFocusNext(mCodeEditText); + setupEditTextSuccessListener(mCodeEditText); + + mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator); + mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator); + mCodeFieldsAnimator = (ToolableViewAnimator) view.findViewById(R.id.code_animator); + + View backupInput = view.findViewById(R.id.button_backup_input); + backupInput.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + switchState(BackupCodeState.STATE_INPUT, true); + } + }); + + view.findViewById(R.id.button_backup_save).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mShareNotSave = false; + startBackup(); + } + }); + + view.findViewById(R.id.button_backup_share).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mShareNotSave = true; + startBackup(); + } + }); + + view.findViewById(R.id.button_backup_back).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + FragmentManager fragMan = getFragmentManager(); + if (fragMan != null) { + fragMan.popBackStack(); + } + } + }); + + return view; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (savedInstanceState != null) { + int savedBackStack = savedInstanceState.getInt(ARG_BACK_STACK); + if (savedBackStack >= 0) { + mBackStackLevel = savedBackStack; + // unchecked use, we know that this one is available in onViewCreated + getFragmentManager().addOnBackStackChangedListener(this); + } + BackupCodeState savedState = BackupCodeState.values()[savedInstanceState.getInt(ARG_CURRENT_STATE)]; + switchState(savedState, false); + } else if (mCurrentState == BackupCodeState.STATE_UNINITIALIZED) { + switchState(BackupCodeState.STATE_DISPLAY, true); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(ARG_CURRENT_STATE, mCurrentState.ordinal()); + outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel); + } + + private void setupEditTextSuccessListener(final EditText[] backupCodes) { + for (EditText backupCode : backupCodes) { + + backupCode.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() > 6) { + throw new AssertionError("max length of each field is 6!"); + } + + boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT + || mCurrentState == BackupCodeState.STATE_INPUT_ERROR; + boolean partIsComplete = s.length() == 6; + if (!inInputState || !partIsComplete) { + return; + } + + checkIfCodeIsCorrect(); + } + }); + + } + } + + private void checkIfCodeIsCorrect() { + + StringBuilder backupCodeInput = new StringBuilder(26); + for (EditText editText : mCodeEditText) { + if (editText.getText().length() < 6) { + return; + } + backupCodeInput.append(editText.getText()); + backupCodeInput.append('-'); + } + backupCodeInput.deleteCharAt(backupCodeInput.length() - 1); + + // if they don't match, do nothing + if (backupCodeInput.toString().equals(mBackupCode)) { + switchState(BackupCodeState.STATE_OK, true); + return; + } + + // TODO remove debug code + if (backupCodeInput.toString().startsWith("ABC")) { + switchState(BackupCodeState.STATE_OK, true); + return; + } + + switchState(BackupCodeState.STATE_INPUT_ERROR, true); + + } + + private static void animateFlashText( + final TextView[] textViews, int color1, int color2, boolean staySecondColor) { + + ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animator) { + for (TextView textView : textViews) { + textView.setTextColor((Integer) animator.getAnimatedValue()); + } + } + }); + anim.setRepeatMode(ValueAnimator.REVERSE); + anim.setRepeatCount(staySecondColor ? 4 : 5); + anim.setDuration(180); + anim.setInterpolator(new AccelerateInterpolator()); + anim.start(); + + } + + private static void setupEditTextFocusNext(final EditText[] backupCodes) { + for (int i = 0; i < backupCodes.length -1; i++) { + + final int next = i+1; + + backupCodes[i].addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + boolean inserting = before < count; + boolean cursorAtEnd = (start + count) == 6; + + if (inserting && cursorAtEnd) { + backupCodes[next].requestFocus(); + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + + } + } + + private void pushBackStackEntry() { + if (mBackStackLevel != null) { + return; + } + FragmentManager fragMan = getFragmentManager(); + mBackStackLevel = fragMan.getBackStackEntryCount(); + fragMan.beginTransaction().addToBackStack(BACK_STACK_INPUT).commit(); + fragMan.addOnBackStackChangedListener(this); + } + + private void popBackStackNoAction() { + FragmentManager fragMan = getFragmentManager(); + fragMan.removeOnBackStackChangedListener(this); + fragMan.popBackStackImmediate(BACK_STACK_INPUT, FragmentManager.POP_BACK_STACK_INCLUSIVE); + mBackStackLevel = null; + } + + @Override + public void onBackStackChanged() { + FragmentManager fragMan = getFragmentManager(); + if (mBackStackLevel != null && fragMan.getBackStackEntryCount() == mBackStackLevel) { + fragMan.removeOnBackStackChangedListener(this); + switchState(BackupCodeState.STATE_DISPLAY, true); + mBackStackLevel = null; + } + } + + private void startBackup() { + + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + if (mCachedExportUri == null) { + mCachedExportUri = TemporaryStorageProvider.createFile(activity); + cryptoOperation(); + return; + } + + if (mShareNotSave) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("application/octet-stream"); + intent.putExtra(Intent.EXTRA_STREAM, mCachedExportUri); + startActivity(intent); + } else { + saveFile(false); + } + + } + + private void saveFile(boolean overwrite) { + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); + + // for kitkat and above, we have the document api + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + String filename = "openkeychain_backup_" + date + (mExportSecret ? ".gpg" : ".pub.gpg"); + FileHelper.saveDocument(this, "application/octet-stream", filename, REQUEST_SAVE); + return; + } + + File file = new File(Constants.Path.APP_DIR, "backup_" + date + (mExportSecret ? ".gpg" : ".pub.gpg")); + + if (!overwrite && file.exists()) { + Notify.create(activity, R.string.snack_backup_exists, Style.WARN, new ActionListener() { + @Override + public void onAction() { + saveFile(true); + } + }, R.string.snack_btn_overwrite).show(); + return; + } + + try { + FileHelper.copyUriData(activity, mCachedExportUri, Uri.fromFile(file)); + Notify.create(activity, R.string.snack_backup_saved_dir, Style.OK).show(); + } catch (IOException e) { + Notify.create(activity, R.string.snack_backup_error_saving, Style.ERROR).show(); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode != REQUEST_SAVE) { + super.onActivityResult(requestCode, resultCode, data); + return; + } + + if (resultCode != FragmentActivity.RESULT_OK) { + return; + } + + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + try { + Uri outputUri = data.getData(); + FileHelper.copyUriData(activity, mCachedExportUri, outputUri); + Notify.create(activity, R.string.snack_backup_saved, Style.OK).show(); + } catch (IOException e) { + Notify.create(activity, R.string.snack_backup_error_saving, Style.ERROR).show(); + } + } + + @Nullable + @Override + public ExportKeyringParcel createOperationInput() { + // TODO replace debug code with real thing + // return new ExportKeyringParcel(new Passphrase(mBackupCode), mMasterKeyIds, mExportSecret, mCachedExportUri); + return new ExportKeyringParcel(new Passphrase("abc"), mMasterKeyIds, mExportSecret, mCachedExportUri); + } + + @Override + public void onCryptoOperationSuccess(ExportResult result) { + startBackup(); + } + + @Override + public void onCryptoOperationError(ExportResult result) { + result.createNotify(getActivity()).show(); + mCachedExportUri = null; + } + + @Override + public void onCryptoOperationCancelled() { + mCachedExportUri = null; + } + + @NonNull + private static String generateRandomCode() { + + Random r = new SecureRandom(); + + // simple generation of a 20 character backup code + StringBuilder code = new StringBuilder(28); + for (int i = 0; i < 24; i++) { + if (i == 6 || i == 12 || i == 18) { + code.append('-'); + } + code.append((char) ('A' + r.nextInt(26))); + } + + return code.toString(); + + } + + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index 739eb3e35..b79e4454d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -37,17 +37,17 @@ import org.spongycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; -import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; @@ -69,7 +69,7 @@ public class CreateKeyFinalFragment extends Fragment { SaveKeyringParcel mSaveKeyringParcel; - private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper; + private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper; private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mCreateOpHelper; private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mMoveToCardOpHelper; @@ -407,20 +407,20 @@ public class CreateKeyFinalFragment extends Fragment { } // set data uri as path to keyring - final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(saveKeyResult.mMasterKeyId); + final long masterKeyId = saveKeyResult.mMasterKeyId; // upload to favorite keyserver final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver(); - CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> callback - = new CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult>() { + CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult> callback + = new CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult>() { @Override - public ExportKeyringParcel createOperationInput() { - return new ExportKeyringParcel(keyserver, blobUri); + public UploadKeyringParcel createOperationInput() { + return new UploadKeyringParcel(keyserver, masterKeyId); } @Override - public void onCryptoOperationSuccess(ExportResult result) { + public void onCryptoOperationSuccess(UploadResult result) { handleResult(result); } @@ -430,11 +430,11 @@ public class CreateKeyFinalFragment extends Fragment { } @Override - public void onCryptoOperationError(ExportResult result) { + public void onCryptoOperationError(UploadResult result) { handleResult(result); } - public void handleResult(ExportResult result) { + public void handleResult(UploadResult result) { saveKeyResult.getLog().add(result, 0); finishWithResult(saveKeyResult); } 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 a0650f8b1..13838e77c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import android.annotation.TargetApi; import android.app.Activity; import android.content.ClipDescription; import android.content.Context; @@ -36,6 +37,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Parcelable; import android.support.v7.widget.DefaultItemAnimator; @@ -249,6 +251,7 @@ public class DecryptListFragment } } + @TargetApi(VERSION_CODES.KITKAT) private void saveFileDialog(InputDataResult result, int index) { Activity activity = getActivity(); @@ -260,13 +263,13 @@ public class DecryptListFragment mCurrentSaveFileUri = result.getOutputUris().get(index); String filename = metadata.getFilename(); - if (filename == null) { + if (TextUtils.isEmpty(filename)) { String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType()); filename = "decrypted" + (ext != null ? "."+ext : ""); } - FileHelper.saveDocument(this, filename, metadata.getMimeType(), - REQUEST_CODE_OUTPUT); + // requires >=kitkat + FileHelper.saveDocument(this, metadata.getMimeType(), filename, REQUEST_CODE_OUTPUT); } private void saveFile(Uri saveUri) { @@ -374,6 +377,9 @@ public class DecryptListFragment if (ClipDescription.compareMimeTypes(type, "text/plain")) { // noinspection deprecation, this should be called from Context, but not available in minSdk icon = getResources().getDrawable(R.drawable.ic_chat_black_24dp); + } else if (ClipDescription.compareMimeTypes(type, "application/pgp-keys")) { + // noinspection deprecation, this should be called from Context, but not available in minSdk + icon = getResources().getDrawable(R.drawable.ic_key_plus_grey600_24dp); } else if (ClipDescription.compareMimeTypes(type, "image/*")) { int px = FormattingUtils.dpToPx(context, 32); Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px)); @@ -764,11 +770,14 @@ public class DecryptListFragment String filename; if (metadata == null) { filename = getString(R.string.filename_unknown); - } else if (TextUtils.isEmpty(metadata.getFilename())) { - filename = getString("text/plain".equals(metadata.getMimeType()) - ? R.string.filename_unknown_text : R.string.filename_unknown); - } else { + } else if ( ! TextUtils.isEmpty(metadata.getFilename())) { filename = metadata.getFilename(); + } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), "application/pgp-keys")) { + filename = getString(R.string.filename_keys); + } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), "text/plain")) { + filename = getString(R.string.filename_unknown_text); + } else { + filename = getString(R.string.filename_unknown); } fileHolder.vFilename.setText(filename); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerBackupFragment.java index a3ea8ad9a..cf47dfc94 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerBackupFragment.java @@ -18,11 +18,7 @@ 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; @@ -37,13 +33,11 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.util.ExportHelper; -public class BackupFragment extends Fragment { +public class DrawerBackupFragment extends Fragment { // This ids for multiple key export. private ArrayList<Long> mIdsForRepeatAskPassphrase; @@ -51,24 +45,10 @@ 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) { - View view = inflater.inflate(R.layout.backup_fragment, container, false); + View view = inflater.inflate(R.layout.drawer_backup_fragment, container, false); View backupAll = view.findViewById(R.id.backup_all); View backupPublicKeys = view.findViewById(R.id.backup_public_keys); @@ -187,14 +167,11 @@ public class BackupFragment extends Fragment { } 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); + + Intent intent = new Intent(getActivity(), BackupActivity.class); + intent.putExtra(BackupActivity.EXTRA_SECRET, exportSecret); + startActivity(intent); + } } 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 ebb9674bf..3d1629ffb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import android.annotation.TargetApi; import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; @@ -35,6 +36,7 @@ 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.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v7.widget.DefaultItemAnimator; @@ -216,6 +218,7 @@ public class EncryptFilesFragment mSelectedFiles.requestFocus(); } + @TargetApi(VERSION_CODES.KITKAT) private void showOutputFileDialog() { if (mFilesAdapter.getModelCount() != 1) { throw new IllegalStateException(); @@ -224,8 +227,7 @@ public class EncryptFilesFragment String targetName = (mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri)) + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); - FileHelper.saveDocument(this, targetName, - REQUEST_CODE_OUTPUT); + FileHelper.saveDocument(this, targetName, REQUEST_CODE_OUTPUT); } public void addFile(Intent data) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index c578bcf15..7f7532ddf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -21,8 +21,8 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Message; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -33,10 +33,8 @@ import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -78,10 +76,8 @@ public class ImportKeysActivity extends BaseNfcActivity public static final String EXTRA_PENDING_INTENT_DATA = "data"; private Intent mPendingIntentData; - // view - private ImportKeysListFragment mListFragment; - private Fragment mTopFragment; - private View mImportButton; + public static final String TAG_FRAG_LIST = "frag_list"; + public static final String TAG_FRAG_TOP = "frag_top"; // for CryptoOperationHelper.Callback private String mKeyserver; @@ -94,15 +90,22 @@ public class ImportKeysActivity extends BaseNfcActivity super.onCreate(savedInstanceState); setFullScreenDialogClose(Activity.RESULT_CANCELED, true); - mImportButton = findViewById(R.id.import_import); - mImportButton.setOnClickListener(new OnClickListener() { + findViewById(R.id.import_import).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - importKeys(); + importSelectedKeys(); } }); - handleActions(savedInstanceState, getIntent()); + // only used for OpenPgpService + if (getIntent().hasExtra(EXTRA_PENDING_INTENT_DATA)) { + mPendingIntentData = getIntent().getParcelableExtra(EXTRA_PENDING_INTENT_DATA); + } + + // if we aren't being restored, initialize fragments + if (savedInstanceState == null) { + handleActions(getIntent()); + } } @Override @@ -110,7 +113,7 @@ public class ImportKeysActivity extends BaseNfcActivity setContentView(R.layout.import_keys_activity); } - protected void handleActions(Bundle savedInstanceState, Intent intent) { + protected void handleActions(Intent intent) { String action = intent.getAction(); Bundle extras = intent.getExtras(); Uri dataUri = intent.getData(); @@ -120,14 +123,8 @@ public class ImportKeysActivity extends BaseNfcActivity extras = new Bundle(); } - if (action == null) { - startCloudFragment(savedInstanceState, null, false, null); - startListFragment(savedInstanceState, null, null, null, null); - return; - } - if (Intent.ACTION_VIEW.equals(action)) { - if (scheme.equals("http") || scheme.equals("https")) { + if ("http".equals(scheme) || "https".equals(scheme)) { action = ACTION_SEARCH_KEYSERVER_FROM_URL; } else { // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) @@ -135,20 +132,24 @@ public class ImportKeysActivity extends BaseNfcActivity action = ACTION_IMPORT_KEY; } } + if (action == null) { + // -> switch to default below + action = ""; + } switch (action) { case ACTION_IMPORT_KEY: { - /* Keychain's own Actions */ - startFileFragment(savedInstanceState); - if (dataUri != null) { // action: directly load data - startListFragment(savedInstanceState, null, dataUri, null, null); + startListFragment(null, dataUri, null, null); } else if (extras.containsKey(EXTRA_KEY_BYTES)) { byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES); // action: directly load data - startListFragment(savedInstanceState, importData, null, null, null); + startListFragment(importData, null, null, null); + } else { + startTopFileFragment(); + startListFragment(null, null, null, null); } break; } @@ -156,10 +157,6 @@ public class ImportKeysActivity extends BaseNfcActivity case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE: case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT: { - // only used for OpenPgpService - if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) { - mPendingIntentData = extras.getParcelable(EXTRA_PENDING_INTENT_DATA); - } if (extras.containsKey(EXTRA_QUERY) || extras.containsKey(EXTRA_KEY_ID)) { /* simple search based on query or key id */ @@ -175,10 +172,10 @@ public class ImportKeysActivity extends BaseNfcActivity if (query != null && query.length() > 0) { // display keyserver fragment with query - startCloudFragment(savedInstanceState, query, false, null); + startTopCloudFragment(query, false, null); // action: search immediately - startListFragment(savedInstanceState, null, null, query, null); + startListFragment(null, null, query, null); } else { Log.e(Constants.TAG, "Query is empty!"); return; @@ -194,10 +191,10 @@ public class ImportKeysActivity extends BaseNfcActivity String query = "0x" + fingerprint; // display keyserver fragment with query - startCloudFragment(savedInstanceState, query, true, null); + startTopCloudFragment(query, true, null); // action: search immediately - startListFragment(savedInstanceState, null, null, query, null); + startListFragment(null, null, query, null); } } else { Log.e(Constants.TAG, @@ -208,14 +205,6 @@ public class ImportKeysActivity extends BaseNfcActivity } break; } - case ACTION_IMPORT_KEY_FROM_FILE: { - // NOTE: this only displays the appropriate fragment, no actions are taken - startFileFragment(savedInstanceState); - - // no immediate actions! - startListFragment(savedInstanceState, null, null, null, null); - break; - } case ACTION_SEARCH_KEYSERVER_FROM_URL: { // need to process URL to get search query and keyserver authority String query = dataUri.getQueryParameter("search"); @@ -223,120 +212,88 @@ public class ImportKeysActivity extends BaseNfcActivity // if query not specified, we still allow users to search the keyserver in the link if (query == null) { Notify.create(this, R.string.import_url_warn_no_search_parameter, Notify.LENGTH_INDEFINITE, - Notify.Style.WARN).show(mTopFragment); + Notify.Style.WARN).show(); // we just set the keyserver - startCloudFragment(savedInstanceState, null, false, keyserver); + startTopCloudFragment(null, false, keyserver); // we don't set the keyserver for ImportKeysListFragment since // it'll be set in the cloudSearchPrefs of ImportKeysCloudFragment // which is used when the user clicks on the search button - startListFragment(savedInstanceState, null, null, null, null); + startListFragment(null, null, null, null); } else { // we allow our users to edit the query if they wish - startCloudFragment(savedInstanceState, query, false, keyserver); + startTopCloudFragment(query, false, keyserver); // search immediately - startListFragment(savedInstanceState, null, null, query, keyserver); + startListFragment(null, null, query, keyserver); } break; } + case ACTION_IMPORT_KEY_FROM_FILE: case ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN: { // NOTE: this only displays the appropriate fragment, no actions are taken - startFileFragment(savedInstanceState); - - // no immediate actions! - startListFragment(savedInstanceState, null, null, null, null); + startTopFileFragment(); + startListFragment(null, null, null, null); break; } default: { - startCloudFragment(savedInstanceState, null, false, null); - startListFragment(savedInstanceState, null, null, null, null); + startTopCloudFragment(null, false, null); + startListFragment(null, null, null, null); break; } } } + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + + // the only thing we need to take care of for restoring state is + // that the top layout is shown iff it contains a fragment + Fragment topFragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_TOP); + boolean hasTopFragment = topFragment != null; + findViewById(R.id.import_keys_top_layout).setVisibility(hasTopFragment ? View.VISIBLE : View.GONE); + } /** * if the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately * load content * - * @param savedInstanceState * @param bytes bytes containing list of keyrings to import * @param dataUri uri to file to import keyrings from * @param serverQuery query to search for on the keyserver * @param keyserver keyserver authority to search on. If null will use keyserver from * user preferences */ - private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, - String serverQuery, String keyserver) { - // 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. - if (mListFragment != null) { - return; - } - - mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, - keyserver); - - // Add the fragment to the 'fragment_container' FrameLayout - // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery, String keyserver) { + Fragment listFragment = + ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, keyserver); getSupportFragmentManager().beginTransaction() - .replace(R.id.import_keys_list_container, mListFragment) - .commitAllowingStateLoss(); - // do it immediately! - getSupportFragmentManager().executePendingTransactions(); + .replace(R.id.import_keys_list_container, listFragment, TAG_FRAG_LIST) + .commit(); } - private void startFileFragment(Bundle savedInstanceState) { - // 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. - if (mTopFragment != null) { - return; - } - - // Create an instance of the fragment - mTopFragment = ImportKeysFileFragment.newInstance(); - - // Add the fragment to the 'fragment_container' FrameLayout - // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + private void startTopFileFragment() { + findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE); + Fragment importFileFragment = ImportKeysFileFragment.newInstance(); getSupportFragmentManager().beginTransaction() - .replace(R.id.import_keys_top_container, mTopFragment) - .commitAllowingStateLoss(); - // do it immediately! - getSupportFragmentManager().executePendingTransactions(); + .replace(R.id.import_keys_top_container, importFileFragment, TAG_FRAG_TOP) + .commit(); } /** * loads the CloudFragment, which consists of the search bar, search button and settings icon * visually. * - * @param savedInstanceState * @param query search query * @param disableQueryEdit if true, user will not be able to edit the search query * @param keyserver keyserver authority to use for search, if null will use keyserver * specified in user preferences */ - - private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String - keyserver) { - // 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. - if (mTopFragment != null) { - return; - } - - // Create an instance of the fragment - mTopFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, keyserver); - - // Add the fragment to the 'fragment_container' FrameLayout - // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + private void startTopCloudFragment(String query, boolean disableQueryEdit, String keyserver) { + findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE); + Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, keyserver); getSupportFragmentManager().beginTransaction() - .replace(R.id.import_keys_top_container, mTopFragment) - .commitAllowingStateLoss(); - // do it immediately! - getSupportFragmentManager().executePendingTransactions(); + .replace(R.id.import_keys_top_container, importCloudFragment, TAG_FRAG_TOP) + .commit(); } private boolean isFingerprintValid(String fingerprint) { @@ -350,63 +307,32 @@ public class ImportKeysActivity extends BaseNfcActivity } public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) { - mListFragment.loadNew(loaderState); + FragmentManager fragMan = getSupportFragmentManager(); + ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST); + keyListFragment.loadNew(loaderState); } - private void handleMessage(Message message) { - if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - if (returnData == null) { - return; - } - final ImportKeyResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - if (result == null) { - Log.e(Constants.TAG, "result == null"); - return; - } + private void importSelectedKeys() { - if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction()) - || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) { - Intent intent = new Intent(); - intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); - ImportKeysActivity.this.setResult(RESULT_OK, intent); - ImportKeysActivity.this.finish(); - return; - } - if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) { - ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData); - ImportKeysActivity.this.finish(); - return; - } - - result.createNotify(ImportKeysActivity.this) - .show((ViewGroup) findViewById(R.id.import_snackbar)); - } - } + FragmentManager fragMan = getSupportFragmentManager(); + ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST); - /** - * Import keys with mImportData - */ - public void importKeys() { - - if (mListFragment.getSelectedEntries().size() == 0) { + if (keyListFragment.getSelectedEntries().size() == 0) { Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR) .show((ViewGroup) findViewById(R.id.import_snackbar)); return; } - mOperationHelper = new CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult>( + mOperationHelper = new CryptoOperationHelper<>( 1, this, this, R.string.progress_importing ); - ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState(); + ImportKeysListFragment.LoaderState ls = keyListFragment.getLoaderState(); if (ls instanceof ImportKeysListFragment.BytesLoaderState) { Log.d(Constants.TAG, "importKeys started"); // get DATA from selected key entries - IteratorWithSize<ParcelableKeyRing> selectedEntries = mListFragment.getSelectedData(); + IteratorWithSize<ParcelableKeyRing> selectedEntries = keyListFragment.getSelectedData(); // instead of giving the entries by Intent extra, cache them into a // file to prevent Java Binder problems on heavy imports @@ -435,7 +361,7 @@ public class ImportKeysActivity extends BaseNfcActivity ArrayList<ParcelableKeyRing> keys = new ArrayList<>(); { // change the format into ParcelableKeyRing - ArrayList<ImportKeysListEntry> entries = mListFragment.getSelectedEntries(); + ArrayList<ImportKeysListEntry> entries = keyListFragment.getSelectedEntries(); for (ImportKeysListEntry entry : entries) { keys.add(new ParcelableKeyRing( entry.getFingerprintHex(), entry.getKeyIdHex(), entry.getExtraData()) @@ -458,24 +384,28 @@ public class ImportKeysActivity extends BaseNfcActivity @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mOperationHelper == null || - !mOperationHelper.handleActivityResult(requestCode, resultCode, data)) { - super.onActivityResult(requestCode, resultCode, data); + if (mOperationHelper != null && + mOperationHelper.handleActivityResult(requestCode, resultCode, data)) { + return; } + super.onActivityResult(requestCode, resultCode, data); } public void handleResult(ImportKeyResult result) { - if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction()) - || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) { + String intentAction = getIntent().getAction(); + + if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction) + || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) { Intent intent = new Intent(); intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); - ImportKeysActivity.this.setResult(RESULT_OK, intent); - ImportKeysActivity.this.finish(); + setResult(RESULT_OK, intent); + finish(); return; } - if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) { - ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData); - ImportKeysActivity.this.finish(); + + if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(intentAction)) { + setResult(RESULT_OK, mPendingIntentData); + finish(); return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index 6f5d98afd..a5bd84d7e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -204,7 +204,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac private void onBackupSelected() { mToolbar.setTitle(R.string.nav_backup); mDrawer.setSelectionByIdentifier(ID_APPS, false); - Fragment frag = new BackupFragment(); + Fragment frag = new DrawerBackupFragment(); setFragment(frag, true); } @@ -265,7 +265,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac } else if (frag instanceof AppsListFragment) { mToolbar.setTitle(R.string.nav_apps); mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_APPS), false); - } else if (frag instanceof BackupFragment) { + } else if (frag instanceof DrawerBackupFragment) { mToolbar.setTitle(R.string.nav_backup); mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index 0415128a2..f38e4928d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; + import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -29,10 +30,12 @@ import android.widget.Spinner; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.operations.results.UploadResult; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.service.ExportKeyringParcel; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.Log; @@ -42,7 +45,7 @@ import org.sufficientlysecure.keychain.util.Preferences; * Sends the selected public key to a keyserver */ public class UploadKeyActivity extends BaseActivity - implements CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> { + implements CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult> { private View mUploadButton; private Spinner mKeyServerSpinner; @@ -50,8 +53,8 @@ public class UploadKeyActivity extends BaseActivity // CryptoOperationHelper.Callback vars private String mKeyserver; - private Uri mUnifiedKeyringUri; - private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper; + private long mMasterKeyId; + private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper; @Override protected void onCreate(Bundle savedInstanceState) { @@ -85,6 +88,16 @@ public class UploadKeyActivity extends BaseActivity finish(); return; } + + try { + mMasterKeyId = new ProviderHelper(this).getCachedPublicKeyRing( + KeyRings.buildUnifiedKeyRingUri(mDataUri)).getMasterKeyId(); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "Intent data pointed to bad key!"); + finish(); + return; + } + } @Override @@ -101,13 +114,10 @@ public class UploadKeyActivity extends BaseActivity } private void uploadKey() { - Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); - mUnifiedKeyringUri = blobUri; - String server = (String) mKeyServerSpinner.getSelectedItem(); mKeyserver = server; - mUploadOpHelper = new CryptoOperationHelper(1, this, this, R.string.progress_uploading); + mUploadOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_uploading); mUploadOpHelper.cryptoOperation(); } @@ -125,12 +135,12 @@ public class UploadKeyActivity extends BaseActivity } @Override - public ExportKeyringParcel createOperationInput() { - return new ExportKeyringParcel(mKeyserver, mUnifiedKeyringUri); + public UploadKeyringParcel createOperationInput() { + return new UploadKeyringParcel(mKeyserver, mMasterKeyId); } @Override - public void onCryptoOperationSuccess(ExportResult result) { + public void onCryptoOperationSuccess(UploadResult result) { result.createNotify(this).show(); } @@ -140,7 +150,7 @@ public class UploadKeyActivity extends BaseActivity } @Override - public void onCryptoOperationError(ExportResult result) { + public void onCryptoOperationError(UploadResult result) { result.createNotify(this).show(); } 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 de859724b..4b3889737 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -345,7 +345,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements startActivity(homeIntent); return true; } - case R.id.menu_key_view_export_file: { + case R.id.menu_key_view_backup: { startPassphraseActivity(REQUEST_BACKUP); return true; } @@ -395,7 +395,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements MenuItem editKey = menu.findItem(R.id.menu_key_view_edit); editKey.setVisible(mIsSecret); - MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file); + MenuItem exportKey = menu.findItem(R.id.menu_key_view_backup); exportKey.setVisible(mIsSecret); MenuItem addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity); @@ -455,10 +455,11 @@ public class ViewKeyActivity extends BaseNfcActivity implements startActivityForResult(intent, requestCode); } - private void backupToFile() { - new ExportHelper(this).showExportKeysDialog( - mMasterKeyId, new File(Constants.Path.APP_DIR, - KeyFormattingUtils.convertKeyIdToHex(mMasterKeyId) + ".sec.asc"), true); + private void startBackupActivity() { + Intent intent = new Intent(this, BackupActivity.class); + intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[] { mMasterKeyId }); + intent.putExtra(BackupActivity.EXTRA_SECRET, true); + startActivity(intent); } private void deleteKey() { @@ -514,7 +515,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements return; } - backupToFile(); + startBackupActivity(); return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java index 44323543f..a320ea3b2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java @@ -133,10 +133,7 @@ public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragmen String targetName = "pgpkey.txt"; // TODO: not supported on Android < 4.4 - FileHelper.saveDocument(this, - targetName, - "text/plain", - REQUEST_CODE_OUTPUT); + FileHelper.saveDocument(this, targetName, "text/plain", REQUEST_CODE_OUTPUT); } private void saveFile(Uri uri) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java index 7dfd56430..71f6ecc1a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java @@ -37,7 +37,7 @@ import org.sufficientlysecure.keychain.util.FabContainer; */ public class Notify { - public static enum Style { + public enum Style { OK (R.color.android_green_light), WARN(R.color.android_orange_light), ERROR(R.color.android_red_light); public final int mLineColor; @@ -142,6 +142,11 @@ public class Notify { return create(activity, text, LENGTH_LONG, style); } + public static Showable create(Activity activity, int textResId, Style style, + ActionListener actionListener, int actionResId) { + return create(activity, textResId, LENGTH_LONG, style, actionListener, actionResId); + } + public static Showable create(Activity activity, int textResId, int duration, Style style, ActionListener actionListener, int actionResId) { return create(activity, activity.getString(textResId), duration, style, actionListener, actionResId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java index 18e830139..a8274e45a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java @@ -31,6 +31,7 @@ import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; @@ -73,4 +74,29 @@ public class ToolableViewAnimator extends ViewAnimator { } super.addView(child, index, params); } + + @Override + public void setDisplayedChild(int whichChild) { + if (whichChild != getDisplayedChild()) { + super.setDisplayedChild(whichChild); + } + } + + public void setDisplayedChild(int whichChild, boolean animate) { + if (animate) { + setDisplayedChild(whichChild); + return; + } + + Animation savedInAnim = getInAnimation(); + Animation savedOutAnim = getOutAnimation(); + setInAnimation(null); + setOutAnimation(null); + + setDisplayedChild(whichChild); + + setInAnimation(savedInAnim); + setOutAnimation(savedOutAnim); + } + } 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 f2ce456f6..cc90c173f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java @@ -97,7 +97,7 @@ public class ExportHelper @Override public ExportKeyringParcel createOperationInput() { - return new ExportKeyringParcel(mMasterKeyIds, mExportSecret, mExportFile.getAbsolutePath()); + return new ExportKeyringParcel(null, mMasterKeyIds, mExportSecret, Uri.fromFile(mExportFile)); } @Override 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 fea3e65b6..902af3932 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java @@ -17,6 +17,16 @@ package org.sufficientlysecure.keychain.util; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.text.DecimalFormat; + import android.annotation.TargetApi; import android.content.ActivityNotFoundException; import android.content.ContentResolver; @@ -31,21 +41,11 @@ import android.os.Build.VERSION_CODES; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.OpenableColumns; -import android.support.annotation.StringRes; import android.support.v4.app.Fragment; import android.widget.Toast; import org.sufficientlysecure.keychain.R; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -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. * @@ -73,24 +73,27 @@ import java.text.DecimalFormat; */ public class FileHelper { - public static void openDocument(Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - openDocumentKitKat(fragment, mimeType, multiple, requestCode); - } else { - openDocumentPreKitKat(fragment, last, mimeType, multiple, requestCode); - } - } - + @TargetApi(VERSION_CODES.KITKAT) public static void saveDocument(Fragment fragment, String targetName, int requestCode) { saveDocument(fragment, targetName, "*/*", requestCode); } - public static void saveDocument(Fragment fragment, String targetName, String mimeType, - int requestCode) { + /** Opens the storage browser on Android 4.4 or later for saving a file. */ + @TargetApi(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 void openDocument(Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - saveDocumentKitKat(fragment, mimeType, targetName, requestCode); + openDocumentKitKat(fragment, mimeType, multiple, requestCode); } else { - throw new RuntimeException("saveDocument does not support Android < 4.4!"); + openDocumentPreKitKat(fragment, last, mimeType, multiple, requestCode); } } @@ -127,17 +130,6 @@ public class FileHelper { 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 String getFilename(Context context, Uri uri) { String filename = null; try { |