diff options
Diffstat (limited to 'OpenKeychain/src/main/java')
73 files changed, 4187 insertions, 2610 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/AppCompatPreferenceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/AppCompatPreferenceActivity.java new file mode 100644 index 000000000..5200b8ced --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/AppCompatPreferenceActivity.java @@ -0,0 +1,127 @@ + +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.compatibility; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls + * to be used with AppCompat. + * <p/> + * This technique can be used with an {@link android.app.Activity} class, not just + * {@link android.preference.PreferenceActivity}. + */ +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java index c0221fad3..649cede10 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java @@ -45,6 +45,7 @@ public class CloudSearch { } final ImportKeysList results = new ImportKeysList(servers.size()); + ArrayList<Thread> searchThreads = new ArrayList<>(); for (final Keyserver keyserver : servers) { Runnable r = new Runnable() { @Override @@ -57,19 +58,25 @@ public class CloudSearch { results.finishedAdding(); // notifies if all searchers done } }; - new Thread(r).start(); + Thread searchThread = new Thread(r); + searchThreads.add(searchThread); + searchThread.start(); } // wait for either all the searches to come back, or 10 seconds - synchronized(results) { + synchronized (results) { try { results.wait(10 * SECONDS); + for (Thread thread : searchThreads) { + // kill threads that haven't returned yet + thread.interrupt(); + } } catch (InterruptedException e) { } } if (results.outstandingSuppliers() > 0) { - String message = "Launched " + servers.size() + " cloud searchers, but" + + String message = "Launched " + servers.size() + " cloud searchers, but" + results.outstandingSuppliers() + "failed to complete."; problems.add(new Keyserver.QueryFailedException(message)); } 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 439260b74..0806e6a16 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -58,12 +58,13 @@ import java.util.concurrent.atomic.AtomicBoolean; * @see CertifyActionsParcel * */ -public class CertifyOperation extends BaseOperation { +public class CertifyOperation extends BaseOperation<CertifyActionsParcel> { public CertifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) { super(context, providerHelper, progressable, cancelled); } + @Override public CertifyResult execute(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput) { OperationLog log = new OperationLog(); @@ -86,8 +87,10 @@ public class CertifyOperation extends BaseOperation { case PATTERN: case PASSPHRASE: if (!cryptoInput.hasPassphrase()) { - return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase( - certificationKey.getKeyId(), certificationKey.getKeyId(), null)); + return new CertifyResult(log, + RequiredInputParcel.createRequiredSignPassphrase( + certificationKey.getKeyId(), certificationKey.getKeyId(), null) + ); } // certification is always with the master key id, so use that one passphrase = cryptoInput.getPassphrase(); @@ -185,10 +188,10 @@ public class CertifyOperation extends BaseOperation { } HkpKeyserver keyServer = null; - ImportExportOperation importExportOperation = null; + ExportOperation exportOperation = null; if (parcel.keyServerUri != null) { keyServer = new HkpKeyserver(parcel.keyServerUri); - importExportOperation = new ImportExportOperation(mContext, mProviderHelper, mProgressable); + exportOperation = new ExportOperation(mContext, mProviderHelper, mProgressable); } // Write all certified keys into the database @@ -206,10 +209,10 @@ public class CertifyOperation extends BaseOperation { mProviderHelper.clearLog(); SaveKeyringResult result = mProviderHelper.savePublicKeyRing(certifiedKey); - if (importExportOperation != null) { + if (exportOperation != null) { // TODO use subresult, get rid of try/catch! try { - importExportOperation.uploadKeyRingToServer(keyServer, certifiedKey); + exportOperation.uploadKeyRingToServer(keyServer, certifiedKey); uploadOk += 1; } catch (AddKeyException e) { Log.e(Constants.TAG, "error uploading key", e); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java new file mode 100644 index 000000000..bda574e0a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ConsolidateOperation.java @@ -0,0 +1,46 @@ +/* + * 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.operations; + +import android.content.Context; + +import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; + +public class ConsolidateOperation extends BaseOperation<ConsolidateInputParcel> { + + public ConsolidateOperation(Context context, ProviderHelper providerHelper, Progressable + progressable) { + super(context, providerHelper, progressable); + } + + @Override + public ConsolidateResult execute(ConsolidateInputParcel consolidateInputParcel, + CryptoInputParcel cryptoInputParcel) { + if (consolidateInputParcel.mConsolidateRecovery) { + return mProviderHelper.consolidateDatabaseStep2(mProgressable); + } else { + return mProviderHelper.consolidateDatabaseStep1(mProgressable); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java index 5ef04ab05..50b2ef69b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java @@ -27,6 +27,8 @@ import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; +import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; /** An operation which implements a high level keyring delete operation. @@ -37,13 +39,18 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; * a list. * */ -public class DeleteOperation extends BaseOperation { +public class DeleteOperation extends BaseOperation<DeleteKeyringParcel> { public DeleteOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { super(context, providerHelper, progressable); } - public DeleteResult execute(long[] masterKeyIds, boolean isSecret) { + @Override + public DeleteResult execute(DeleteKeyringParcel deleteKeyringParcel, + CryptoInputParcel cryptoInputParcel) { + + long[] masterKeyIds = deleteKeyringParcel.mMasterKeyIds; + boolean isSecret = deleteKeyringParcel.mIsSecret; OperationLog log = new OperationLog(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java new file mode 100644 index 000000000..01a45bc79 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> + * + * 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 android.content.Context; +import android.database.Cursor; +import android.net.Uri; + +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.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.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.ExportKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * 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. + * TODO rework uploadKeyRingToServer + */ +public class ExportOperation extends BaseOperation<ExportKeyringParcel> { + + public ExportOperation(Context context, ProviderHelper providerHelper, Progressable + progressable) { + super(context, providerHelper, progressable); + } + + public ExportOperation(Context context, ProviderHelper providerHelper, + Progressable progressable, AtomicBoolean cancelled) { + super(context, providerHelper, progressable, cancelled); + } + + public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) + throws AddKeyException { + uploadKeyRingToServer(server, keyring.getUncachedKeyRing()); + } + + public void uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring) throws + AddKeyException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ArmoredOutputStream aos = null; + try { + aos = new ArmoredOutputStream(bos); + keyring.encode(aos); + aos.close(); + + String armoredKey = bos.toString("UTF-8"); + server.add(armoredKey); + } catch (IOException e) { + Log.e(Constants.TAG, "IOException", e); + throw new AddKeyException(); + } finally { + 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. + } + } + } + + public ExportResult exportToFile(long[] masterKeyIds, boolean exportSecret, String outputFile) { + + OperationLog log = new OperationLog(); + if (masterKeyIds != null) { + log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length); + } else { + log.add(LogType.MSG_EXPORT_ALL, 0); + } + + // 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); + } + + // check if storage is ready + if (!FileHelper.isStorageMounted(outputFile)) { + log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1); + 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(); + } + return result; + } finally { + outStream.close(); + } + } catch (IOException e) { + log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log); + } + + } + + public ExportResult exportToUri(long[] masterKeyIds, boolean exportSecret, Uri outputUri) { + + OperationLog log = new OperationLog(); + if (masterKeyIds != null) { + log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length); + } else { + log.add(LogType.MSG_EXPORT_ALL, 0); + } + + // 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); + } + + 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); + } + + } + + ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret, + OutputStream outStream) { + + /* 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); + 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, ids[] = null; + + if (masterKeyIds != null) { + // generate placeholders and string selection args + ids = new String[masterKeyIds.length]; + StringBuilder placeholders = new StringBuilder("?"); + for (int i = 0; i < masterKeyIds.length; i++) { + ids[i] = Long.toString(masterKeyIds[i]); + if (i != 0) { + placeholders.append(",?"); + } + } + + // put together selection string + selection = Tables.KEY_RINGS_PUBLIC + "." + KeyRings.MASTER_KEY_ID + + " IN (" + placeholders + ")"; + } + + cursor = mProviderHelper.getContentResolver().query( + KeyRings.buildUnifiedKeyRingsUri(), new String[]{ + KeyRings.MASTER_KEY_ID, KeyRings.PUBKEY_DATA, + KeyRings.PRIVKEY_DATA, KeyRings.HAS_ANY_SECRET + }, selection, ids, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + ); + + if (cursor == null || !cursor.moveToFirst()) { + log.add(LogType.MSG_EXPORT_ERROR_DB, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret); + } + + int numKeys = cursor.getCount(); + + 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); + + 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(); + } + } + } + + updateProgress(progress++, numKeys); + + cursor.moveToNext(); + } + + updateProgress(R.string.progress_done, numKeys, numKeys); + + } catch (IOException e) { + log.add(LogType.MSG_EXPORT_ERROR_IO, 1); + return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret); + } finally { + // Make sure the stream is closed + if (outStream != null) try { + outStream.close(); + } catch (Exception e) { + Log.e(Constants.TAG, "error closing stream", e); + } + if (cursor != null) { + cursor.close(); + } + } + + + log.add(LogType.MSG_EXPORT_SUCCESS, 1); + return new ExportResult(ExportResult.RESULT_OK, log, okPublic, okSecret); + + } + + public ExportResult execute(ExportKeyringParcel exportInput, CryptoInputParcel cryptoInput) { + switch (exportInput.mExportType) { + case UPLOAD_KEYSERVER: { + HkpKeyserver hkpKeyserver = new HkpKeyserver(exportInput.mKeyserver); + try { + CanonicalizedPublicKeyRing keyring + = mProviderHelper.getCanonicalizedPublicKeyRing( + exportInput.mCanonicalizedPublicKeyringUri); + uploadKeyRingToServer(hkpKeyserver, keyring); + // TODO: replace with proper log + return new ExportResult(ExportResult.RESULT_OK, new OperationLog()); + } catch (Exception e) { + return new ExportResult(ExportResult.RESULT_ERROR, new OperationLog()); + // TODO: Implement better exception handling, replace with log + } + } + case EXPORT_FILE: { + return exportToFile(exportInput.mMasterKeyIds, exportInput.mExportSecret, + exportInput.mOutputFile); + } + case EXPORT_URI: { + return exportToUri(exportInput.mMasterKeyIds, exportInput.mExportSecret, + exportInput.mOutputUri); + } + default: { // can't happen + return null; + } + } + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java index cdda42dae..ace059dac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -19,57 +19,50 @@ package org.sufficientlysecure.keychain.operations; import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -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.KeybaseKeyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver; -import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; -import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; 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.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.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.ProgressScaler; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -/** An operation class which implements high level import and export +/** + * An operation class which implements high level import * operations. - * * This class receives a source and/or destination of keys as input and performs - * all steps for this import or export. - * + * all steps for this import. * For the import operation, the only valid source is an Iterator of * ParcelableKeyRing, each of which must contain either a single * keyring encoded as bytes, or a unique reference to a keyring @@ -78,72 +71,57 @@ import java.util.concurrent.atomic.AtomicBoolean; * secret keys, because some implementations (notably Symantec PGP Desktop) do * not include self certificates for user ids in the secret keyring. The import * method here will generally import keyrings in the order given by the - * iterator. so this should be ensured beforehand. - * @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. - * - * TODO rework uploadKeyRingToServer + * iterator, so this should be ensured beforehand. * + * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries() */ -public class ImportExportOperation extends BaseOperation { +public class ImportOperation extends BaseOperation<ImportKeyringParcel> { - public ImportExportOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { + public ImportOperation(Context context, ProviderHelper providerHelper, Progressable + progressable) { super(context, providerHelper, progressable); } - public ImportExportOperation(Context context, ProviderHelper providerHelper, - Progressable progressable, AtomicBoolean cancelled) { + public ImportOperation(Context context, ProviderHelper providerHelper, + Progressable progressable, AtomicBoolean cancelled) { super(context, providerHelper, progressable, cancelled); } - public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) throws AddKeyException { - uploadKeyRingToServer(server, keyring.getUncachedKeyRing()); + // Overloaded functions for using progressable supplied in constructor during import + public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num, + String keyServerUri) { + return serialKeyRingImport(entries, num, keyServerUri, mProgressable); } - public void uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring) throws AddKeyException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ArmoredOutputStream aos = null; - try { - aos = new ArmoredOutputStream(bos); - keyring.encode(aos); - aos.close(); + public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries, + String keyServerUri) { + + Iterator<ParcelableKeyRing> it = entries.iterator(); + int numEntries = entries.size(); + + return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable); - String armoredKey = bos.toString("UTF-8"); - server.add(armoredKey); - } catch (IOException e) { - Log.e(Constants.TAG, "IOException", e); - throw new AddKeyException(); - } finally { - 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. - } - } } - public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries, String keyServerUri) { + public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries, String keyServerUri, + Progressable progressable) { Iterator<ParcelableKeyRing> it = entries.iterator(); int numEntries = entries.size(); - return importKeyRings(it, numEntries, keyServerUri); + return serialKeyRingImport(it, numEntries, keyServerUri, progressable); } - public ImportKeyResult importKeyRings(ParcelableFileCache<ParcelableKeyRing> cache, String keyServerUri) { + public ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache, + String keyServerUri) { // get entries from cached file try { IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); int numEntries = it.getSize(); - return importKeyRings(it, numEntries, keyServerUri); + return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable); } catch (IOException e) { // Special treatment here, we need a lot @@ -157,15 +135,18 @@ public class ImportExportOperation extends BaseOperation { } /** - * Since the introduction of multithreaded import, we expect calling functions to handle the key sync i,e - * ContactSyncAdapterService.requestSync() + * Since the introduction of multithreaded import, we expect calling functions to handle the + * key sync i,eContactSyncAdapterService.requestSync() * - * @param entries keys to import - * @param num number of keys to import + * @param entries keys to import + * @param num number of keys to import * @param keyServerUri contains uri of keyserver to import from, if it is an import from cloud + * @param progressable Allows multi-threaded import to supply a progressable that ignores the + * progress of a single key being imported * @return */ - public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num, String keyServerUri) { + public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num, + String keyServerUri, Progressable progressable) { updateProgress(R.string.progress_importing, 0, 100); OperationLog log = new OperationLog(); @@ -208,7 +189,8 @@ public class ImportExportOperation extends BaseOperation { else { // We fetch from keyservers first, because we tend to get more certificates - // from there, so the number of certificates which are merged in later is smaller. + // from there, so the number of certificates which are merged in later is + // smaller. // If we have a keyServerUri and a fingerprint or at least a keyId, // download from HKP @@ -224,7 +206,8 @@ public class ImportExportOperation extends BaseOperation { byte[] data; // Download by fingerprint, or keyId - whichever is available if (entry.mExpectedFingerprint != null) { - log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" + entry.mExpectedFingerprint.substring(24)); + log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" + + entry.mExpectedFingerprint.substring(24)); data = keyServer.get("0x" + entry.mExpectedFingerprint).getBytes(); } else { log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, entry.mKeyIdHex); @@ -302,10 +285,12 @@ public class ImportExportOperation extends BaseOperation { mProviderHelper.clearLog(); if (key.isSecret()) { result = mProviderHelper.saveSecretKeyRing(key, - new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100)); + new ProgressScaler(progressable, (int) (position * progSteps), + (int) ((position + 1) * progSteps), 100)); } else { result = mProviderHelper.savePublicKeyRing(key, - new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100)); + new ProgressScaler(progressable, (int) (position * progSteps), + (int) ((position + 1) * progSteps), 100)); } if (!result.success()) { badKeys += 1; @@ -333,7 +318,7 @@ public class ImportExportOperation extends BaseOperation { // Special: consolidate on secret key import (cannot be cancelled!) if (secret > 0) { setPreventCancel(); - ConsolidateResult result = mProviderHelper.consolidateDatabaseStep1(mProgressable); + ConsolidateResult result = mProviderHelper.consolidateDatabaseStep1(progressable); log.add(result, 1); } @@ -376,7 +361,7 @@ public class ImportExportOperation extends BaseOperation { } // Final log entry, it's easier to do this individually - if ( (newKeys > 0 || updatedKeys > 0) && badKeys > 0) { + if ((newKeys > 0 || updatedKeys > 0) && badKeys > 0) { log.add(LogType.MSG_IMPORT_PARTIAL, 1); } else if (newKeys > 0 || updatedKeys > 0) { log.add(LogType.MSG_IMPORT_SUCCESS, 1); @@ -388,206 +373,193 @@ public class ImportExportOperation extends BaseOperation { importedMasterKeyIdsArray); } - public ExportResult exportToFile(long[] masterKeyIds, boolean exportSecret, String outputFile) { + @Override + public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) { + return importKeys(importInput.mKeyList, importInput.mKeyserver); + } - OperationLog log = new OperationLog(); - if (masterKeyIds != null) { - log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length); - } else { - log.add(LogType.MSG_EXPORT_ALL, 0); - } + public ImportKeyResult importKeys(ArrayList<ParcelableKeyRing> keyList, String keyServer) { - // 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); - } + ImportKeyResult result; - // 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 (keyList == null) {// import from file, do serially + ParcelableFileCache<ParcelableKeyRing> cache = new ParcelableFileCache<>(mContext, + "key_import.pcl"); - try { - OutputStream outStream = new FileOutputStream(outputFile); - try { - ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream); - if (result.cancelled()) { - //noinspection ResultOfMethodCallIgnored - new File(outputFile).delete(); - } - return result; - } finally { - outStream.close(); + result = serialKeyRingImport(cache, keyServer); + } else { + // if there is more than one key with the same fingerprint, we do a serial import to + // prevent + // https://github.com/open-keychain/open-keychain/issues/1221 + HashSet<String> keyFingerprintSet = new HashSet<>(); + for (int i = 0; i < keyList.size(); i++) { + keyFingerprintSet.add(keyList.get(i).mExpectedFingerprint); + } + if (keyFingerprintSet.size() == keyList.size()) { + // all keys have unique fingerprints + result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer); + } else { + result = serialKeyRingImport(keyList, keyServer); } - } catch (IOException e) { - log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log); } + ContactSyncAdapterService.requestSync(); + return result; } - public ExportResult exportToUri(long[] masterKeyIds, boolean exportSecret, Uri outputUri) { + private ImportKeyResult multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator, + int totKeys, final String keyServer) { + Log.d(Constants.TAG, "Multi-threaded key import starting"); + if (keyListIterator != null) { + KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable); - OperationLog log = new OperationLog(); - if (masterKeyIds != null) { - log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length); - } else { - log.add(LogType.MSG_EXPORT_ALL, 0); - } + final ProgressScaler ignoreProgressable = new ProgressScaler(); - // 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); - } + final int maxThreads = 200; + ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads, + 30L, TimeUnit.SECONDS, + new SynchronousQueue<Runnable>()); - 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); - } + ExecutorCompletionService<ImportKeyResult> importCompletionService = + new ExecutorCompletionService(importExecutor); - } + while (keyListIterator.hasNext()) { // submit all key rings to be imported - ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret, - OutputStream outStream) { + final ParcelableKeyRing pkRing = keyListIterator.next(); - /* 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); - return new ExportResult(ExportResult.RESULT_ERROR, log); - } - */ + Callable<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult> + () { - if ( ! BufferedOutputStream.class.isInstance(outStream)) { - outStream = new BufferedOutputStream(outStream); - } + @Override + public ImportKeyResult call() { - int okSecret = 0, okPublic = 0, progress = 0; + ArrayList<ParcelableKeyRing> list = new ArrayList<>(); + list.add(pkRing); - Cursor cursor = null; - try { + return serialKeyRingImport(list, keyServer, ignoreProgressable); + } + }; - String selection = null, ids[] = null; + importCompletionService.submit(importOperationCallable); + } - if (masterKeyIds != null) { - // generate placeholders and string selection args - ids = new String[masterKeyIds.length]; - StringBuilder placeholders = new StringBuilder("?"); - for (int i = 0; i < masterKeyIds.length; i++) { - ids[i] = Long.toString(masterKeyIds[i]); - if (i != 0) { - placeholders.append(",?"); + while (!accumulator.isImportFinished()) { // accumulate the results of each import + try { + accumulator.accumulateKeyImport(importCompletionService.take().get()); + } catch (InterruptedException | ExecutionException e) { + Log.e(Constants.TAG, "A key could not be imported during multi-threaded " + + "import", e); + // do nothing? + if (e instanceof ExecutionException) { + // Since serialKeyRingImport does not throw any exceptions, this is what + // would have happened if + // we were importing the key on this thread + throw new RuntimeException(); } } - - // put together selection string - selection = Tables.KEY_RINGS_PUBLIC + "." + KeyRings.MASTER_KEY_ID - + " IN (" + placeholders + ")"; } + return accumulator.getConsolidatedResult(); + } + return null; // TODO: Decide if we should just crash instead of returning null + } - cursor = mProviderHelper.getContentResolver().query( - KeyRings.buildUnifiedKeyRingsUri(), new String[]{ - KeyRings.MASTER_KEY_ID, KeyRings.PUBKEY_DATA, - KeyRings.PRIVKEY_DATA, KeyRings.HAS_ANY_SECRET - }, selection, ids, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID - ); - - if (cursor == null || !cursor.moveToFirst()) { - log.add(LogType.MSG_EXPORT_ERROR_DB, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret); - } + /** + * Used to accumulate the results of individual key imports + */ + private class KeyImportAccumulator { + private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog(); + Progressable mProgressable; + private int mTotalKeys; + private int mImportedKeys = 0; + ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>(); + private int mBadKeys = 0; + private int mNewKeys = 0; + private int mUpdatedKeys = 0; + private int mSecret = 0; + private int mResultType = 0; + + /** + * Accumulates keyring imports and updates the progressable whenever a new key is imported. + * Also sets the progress to 0 on instantiation. + * + * @param totalKeys total number of keys to be imported + * @param externalProgressable the external progressable to be updated every time a key + * is imported + */ + public KeyImportAccumulator(int totalKeys, Progressable externalProgressable) { + mTotalKeys = totalKeys; + mProgressable = externalProgressable; + mProgressable.setProgress(0, totalKeys); + } - int numKeys = cursor.getCount(); + public int getTotalKeys() { + return mTotalKeys; + } - updateProgress( - mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, - numKeys), 0, numKeys); + public int getImportedKeys() { + return mImportedKeys; + } - // For each public masterKey id - while (!cursor.isAfterLast()) { + public synchronized void accumulateKeyImport(ImportKeyResult result) { + mImportedKeys++; - long keyId = cursor.getLong(0); - ArmoredOutputStream arOutStream = null; + mProgressable.setProgress(mImportedKeys, mTotalKeys); - // Create an output stream - try { - arOutStream = new ArmoredOutputStream(outStream); + mImportLog.addAll(result.getLog().toList());//accumulates log + mBadKeys += result.mBadKeys; + mNewKeys += result.mNewKeys; + mUpdatedKeys += result.mUpdatedKeys; + mSecret += result.mSecret; - log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId)); + long[] masterKeyIds = result.getImportedMasterKeyIds(); + for (long masterKeyId : masterKeyIds) { + mImportedMasterKeyIds.add(masterKeyId); + } - byte[] data = cursor.getBlob(1); - CanonicalizedKeyRing ring = - UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true); - ring.encode(arOutStream); + // if any key import has been cancelled, set result type to cancelled + // resultType is added to in getConsolidatedKayImport to account for remaining factors + mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED; + } - 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; + /** + * returns accumulated result of all imports so far + */ + public ImportKeyResult getConsolidatedResult() { + + // adding required information to mResultType + // special case,no keys requested for import + if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) { + mResultType = ImportKeyResult.RESULT_FAIL_NOTHING; + } else { + if (mNewKeys > 0) { + mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS; } - - 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(); - } + if (mUpdatedKeys > 0) { + mResultType |= ImportKeyResult.RESULT_OK_UPDATED; + } + if (mBadKeys > 0) { + mResultType |= ImportKeyResult.RESULT_WITH_ERRORS; + if (mNewKeys == 0 && mUpdatedKeys == 0) { + mResultType |= ImportKeyResult.RESULT_ERROR; } } - - updateProgress(progress++, numKeys); - - cursor.moveToNext(); + if (mImportLog.containsWarnings()) { + mResultType |= ImportKeyResult.RESULT_WARNINGS; + } } - updateProgress(R.string.progress_done, numKeys, numKeys); - - } catch (IOException e) { - log.add(LogType.MSG_EXPORT_ERROR_IO, 1); - return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret); - } finally { - // Make sure the stream is closed - if (outStream != null) try { - outStream.close(); - } catch (Exception e) { - Log.e(Constants.TAG, "error closing stream", e); - } - if (cursor != null) { - cursor.close(); + long masterKeyIds[] = new long[mImportedMasterKeyIds.size()]; + for (int i = 0; i < masterKeyIds.length; i++) { + masterKeyIds[i] = mImportedMasterKeyIds.get(i); } - } - - log.add(LogType.MSG_EXPORT_SUCCESS, 1); - return new ExportResult(ExportResult.RESULT_OK, log, okPublic, okSecret); + return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys, + mSecret, masterKeyIds); + } + public boolean isImportFinished() { + return mTotalKeys == mImportedKeys; + } } -} +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java new file mode 100644 index 000000000..57b99951d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java @@ -0,0 +1,158 @@ +/* + * 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.operations; + +import android.content.Context; + +import com.textuality.keybase.lib.Proof; +import com.textuality.keybase.lib.prover.Prover; +import de.measite.minidns.Client; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Question; +import de.measite.minidns.Record; +import de.measite.minidns.record.Data; +import de.measite.minidns.record.TXT; +import org.json.JSONObject; +import org.spongycastle.openpgp.PGPUtil; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificationParcel> { + + public KeybaseVerificationOperation(Context context, ProviderHelper providerHelper, + Progressable progressable) { + super(context, providerHelper, progressable); + } + + @Override + public KeybaseVerificationResult execute(KeybaseVerificationParcel keybaseInput, + CryptoInputParcel cryptoInput) { + + String requiredFingerprint = keybaseInput.mRequiredFingerprint; + + OperationResult.OperationLog log = new OperationResult.OperationLog(); + log.add(OperationResult.LogType.MSG_KEYBASE_VERIFICATION, 0, requiredFingerprint); + + try { + String keybaseProof = keybaseInput.mKeybaseProof; + Proof proof = new Proof(new JSONObject(keybaseProof)); + mProgressable.setProgress(R.string.keybase_message_fetching_data, 0, 100); + + Prover prover = Prover.findProverFor(proof); + + if (prover == null) { + log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_NO_PROVER, 1, + proof.getPrettyName()); + return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); + } + + if (!prover.fetchProofData()) { + log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FETCH_PROOF, 1); + return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); + } + + if (!prover.checkFingerprint(requiredFingerprint)) { + log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FINGERPRINT_MISMATCH, 1); + return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); + } + + String domain = prover.dnsTxtCheckRequired(); + if (domain != null) { + DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT)); + if (dnsQuery == null) { + log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_DNS_FAIL, 1); + log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 2, + getFlattenedProverLog(prover)); + return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); + } + Record[] records = dnsQuery.getAnswers(); + List<List<byte[]>> extents = new ArrayList<List<byte[]>>(); + for (Record r : records) { + Data d = r.getPayload(); + if (d instanceof TXT) { + extents.add(((TXT) d).getExtents()); + } + } + if (!prover.checkDnsTxt(extents)) { + log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 1, + getFlattenedProverLog(prover)); + return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); + } + } + + byte[] messageBytes = prover.getPgpMessage().getBytes(); + if (prover.rawMessageCheckRequired()) { + InputStream messageByteStream = PGPUtil.getDecoderStream(new + ByteArrayInputStream + (messageBytes)); + if (!prover.checkRawMessageBytes(messageByteStream)) { + log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 1, + getFlattenedProverLog(prover)); + return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); + } + } + + PgpDecryptVerify op = new PgpDecryptVerify(mContext, mProviderHelper, mProgressable); + + PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes) + .setSignedLiteralData(true) + .setRequiredSignerFingerprint(requiredFingerprint); + + DecryptVerifyResult decryptVerifyResult = op.execute(input, new CryptoInputParcel()); + + if (!decryptVerifyResult.success()) { + log.add(decryptVerifyResult, 1); + return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); + } + + if (!prover.validate(new String(decryptVerifyResult.getOutputBytes()))) { + log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH, 1); + return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); + } + + return new KeybaseVerificationResult(OperationResult.RESULT_OK, log, prover); + } catch (Exception e) { + // just adds the passed parameter, in this case e.getMessage() + log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 1, e.getMessage()); + return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log); + } + } + + private String getFlattenedProverLog(Prover prover) { + String log = ""; + for (String line : prover.getLog()) { + log += line + "\n"; + } + return log; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java index 558756378..6291f14a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -29,13 +29,13 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; +import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ProgressScaler; -import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; /** An operation which promotes a public key ring to a secret one. @@ -45,13 +45,24 @@ import java.util.concurrent.atomic.AtomicBoolean; * without secret key material, using a GNU_DUMMY s2k type. * */ -public class PromoteKeyOperation extends BaseOperation { +public class PromoteKeyOperation extends BaseOperation<PromoteKeyringParcel> { public PromoteKeyOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) { super(context, providerHelper, progressable, cancelled); } + @Override + public PromoteKeyResult execute(PromoteKeyringParcel promoteKeyringParcel, + CryptoInputParcel cryptoInputParcel) { + // Input + long masterKeyId = promoteKeyringParcel.mKeyRingId; + byte[] cardAid = promoteKeyringParcel.mCardAid; + long[] subKeyIds = promoteKeyringParcel.mSubKeyIds; + + return execute(masterKeyId, cardAid, subKeyIds); + } + public PromoteKeyResult execute(long masterKeyId, byte[] cardAid, long[] subKeyIds) { OperationLog log = new OperationLog(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java index 7c58d62f8..8fe5b86c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java @@ -25,7 +25,6 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java new file mode 100644 index 000000000..420cbbf01 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/KeybaseVerificationResult.java @@ -0,0 +1,78 @@ +/* + * 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.operations.results; + +import android.os.Parcel; +import android.os.Parcelable; +import com.textuality.keybase.lib.KeybaseException; +import com.textuality.keybase.lib.prover.Prover; + +public class KeybaseVerificationResult extends OperationResult implements Parcelable { + public final String mProofUrl; + public final String mPresenceUrl; + public final String mPresenceLabel; + + public KeybaseVerificationResult(int result, OperationLog log) { + super(result, log); + mProofUrl = null; + mPresenceLabel = null; + mPresenceUrl = null; + } + + public KeybaseVerificationResult(int result, OperationLog log, Prover prover) + throws KeybaseException { + super(result, log); + mProofUrl = prover.getProofUrl(); + mPresenceUrl = prover.getPresenceUrl(); + mPresenceLabel = prover.getPresenceLabel(); + } + + protected KeybaseVerificationResult(Parcel in) { + super(in); + mProofUrl = in.readString(); + mPresenceUrl = in.readString(); + mPresenceLabel = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mProofUrl); + dest.writeString(mPresenceUrl); + dest.writeString(mPresenceLabel); + } + + public static final Parcelable.Creator<KeybaseVerificationResult> CREATOR = new Parcelable.Creator<KeybaseVerificationResult>() { + @Override + public KeybaseVerificationResult createFromParcel(Parcel in) { + return new KeybaseVerificationResult(in); + } + + @Override + public KeybaseVerificationResult[] newArray(int size) { + return new KeybaseVerificationResult[size]; + } + }; +}
\ No newline at end of file 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 c9e427462..f0561bef2 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 @@ -750,7 +750,19 @@ public abstract class OperationResult implements Parcelable { MSG_DEL_OK (LogLevel.OK, R.plurals.msg_del_ok), MSG_DEL_FAIL (LogLevel.WARN, R.plurals.msg_del_fail), - //export log + // keybase verification + MSG_KEYBASE_VERIFICATION(LogLevel.START, R.string.msg_keybase_verification), + + MSG_KEYBASE_ERROR_NO_PROVER(LogLevel.ERROR, R.string.msg_keybase_error_no_prover), + MSG_KEYBASE_ERROR_FETCH_PROOF(LogLevel.ERROR, R.string.msg_keybase_error_fetching_evidence), + MSG_KEYBASE_ERROR_FINGERPRINT_MISMATCH(LogLevel.ERROR, + R.string.msg_keybase_error_key_mismatch), + MSG_KEYBASE_ERROR_DNS_FAIL(LogLevel.ERROR, R.string.msg_keybase_error_dns_fail), + MSG_KEYBASE_ERROR_SPECIFIC(LogLevel.ERROR, R.string.msg_keybase_error_specific), + MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH(LogLevel.ERROR, + R.string.msg_keybase_error_msg_payload_mismatch), + + // export log MSG_EXPORT_LOG(LogLevel.START,R.string.msg_export_log_start), MSG_EXPORT_LOG_EXPORT_ERROR_NO_FILE(LogLevel.ERROR,R.string.msg_export_log_error_no_file), MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN(LogLevel.ERROR,R.string.msg_export_log_error_fopen), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 7a4c85178..a6d260d22 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -938,7 +938,14 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> log.add(LogType.MSG_DC_OK, indent); + OpenPgpMetadata metadata = new OpenPgpMetadata( + "", + "text/plain", + -1, + clearText.length); + DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); + result.setDecryptMetadata(metadata); result.setSignatureResult(signatureResultBuilder.build()); return result; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index bf7014853..590c58f97 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -32,7 +32,7 @@ import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.operations.ImportExportOperation; +import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -1248,9 +1248,9 @@ public class ProviderHelper { // 3. Re-Import secret keyrings from cache if (numSecrets > 0) { - ImportKeyResult result = new ImportExportOperation(mContext, this, + ImportKeyResult result = new ImportOperation(mContext, this, new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport)) - .importKeyRings(itSecrets, numSecrets, null); + .serialKeyRingImport(itSecrets, numSecrets, null); log.add(result, indent); } else { log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent); @@ -1276,9 +1276,9 @@ public class ProviderHelper { // 4. Re-Import public keyrings from cache if (numPublics > 0) { - ImportKeyResult result = new ImportExportOperation(mContext, this, + ImportKeyResult result = new ImportOperation(mContext, this, new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport)) - .importKeyRings(itPublics, numPublics, null); + .serialKeyRingImport(itPublics, numPublics, null); log.add(result, indent); } else { log.add(LogType.MSG_CON_REIMPORT_PUBLIC_SKIP, indent); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 0d2d3256d..ac66bd097 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -167,7 +167,7 @@ public class OpenPgpService extends RemoteService { Intent data, RequiredInputParcel requiredInput) { switch (requiredInput.mType) { - case NFC_KEYTOCARD: + case NFC_MOVE_KEY_TO_CARD: case NFC_DECRYPT: case NFC_SIGN: { // build PendingIntent for YubiKey NFC operations diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java new file mode 100644 index 000000000..15d109814 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java @@ -0,0 +1,58 @@ +/* + * 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 ConsolidateInputParcel implements Parcelable { + + public boolean mConsolidateRecovery; + + public ConsolidateInputParcel(boolean consolidateRecovery) { + mConsolidateRecovery = consolidateRecovery; + } + + protected ConsolidateInputParcel(Parcel in) { + mConsolidateRecovery = in.readByte() != 0x00; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (mConsolidateRecovery ? 0x01 : 0x00)); + } + + public static final Parcelable.Creator<ConsolidateInputParcel> CREATOR = new Parcelable.Creator<ConsolidateInputParcel>() { + @Override + public ConsolidateInputParcel createFromParcel(Parcel in) { + return new ConsolidateInputParcel(in); + } + + @Override + public ConsolidateInputParcel[] newArray(int size) { + return new ConsolidateInputParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java new file mode 100644 index 000000000..b412a6e2b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java @@ -0,0 +1,63 @@ +/* + * 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 DeleteKeyringParcel implements Parcelable { + + public long[] mMasterKeyIds; + public boolean mIsSecret; + + public DeleteKeyringParcel(long[] masterKeyIds, boolean isSecret) { + mMasterKeyIds = masterKeyIds; + mIsSecret = isSecret; + } + + protected DeleteKeyringParcel(Parcel in) { + mIsSecret = in.readByte() != 0x00; + mMasterKeyIds = in.createLongArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (mIsSecret ? 0x01 : 0x00)); + dest.writeLongArray(mMasterKeyIds); + } + + public static final Parcelable.Creator<DeleteKeyringParcel> CREATOR = new Parcelable.Creator<DeleteKeyringParcel>() { + @Override + public DeleteKeyringParcel createFromParcel(Parcel in) { + return new DeleteKeyringParcel(in); + } + + @Override + public DeleteKeyringParcel[] newArray(int size) { + return new DeleteKeyringParcel[size]; + } + }; +} + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java new file mode 100644 index 000000000..ef5b48df3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java @@ -0,0 +1,99 @@ +/* + * 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.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +public class ExportKeyringParcel implements Parcelable { + public String mKeyserver; + public Uri mCanonicalizedPublicKeyringUri; + + 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(long[] masterKeyIds, boolean exportSecret, String outputFile) { + mExportType = ExportType.EXPORT_FILE; + mMasterKeyIds = masterKeyIds; + mExportSecret = exportSecret; + mOutputFile = outputFile; + } + + public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, Uri outputUri) { + mExportType = ExportType.EXPORT_URI; + mMasterKeyIds = masterKeyIds; + mExportSecret = exportSecret; + mOutputUri = outputUri; + } + + protected ExportKeyringParcel(Parcel in) { + mKeyserver = in.readString(); + mCanonicalizedPublicKeyringUri = (Uri) in.readValue(Uri.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(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mKeyserver); + dest.writeValue(mCanonicalizedPublicKeyringUri); + dest.writeByte((byte) (mExportSecret ? 0x01 : 0x00)); + dest.writeString(mOutputFile); + dest.writeValue(mOutputUri); + dest.writeValue(mExportType); + dest.writeLongArray(mMasterKeyIds); + } + + public static final Parcelable.Creator<ExportKeyringParcel> CREATOR = new Parcelable.Creator<ExportKeyringParcel>() { + @Override + public ExportKeyringParcel createFromParcel(Parcel in) { + return new ExportKeyringParcel(in); + } + + @Override + public ExportKeyringParcel[] newArray(int size) { + return new ExportKeyringParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java new file mode 100644 index 000000000..a41dd71cb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * 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; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; + +import java.util.ArrayList; + +public class ImportKeyringParcel implements Parcelable { + // if null, keys are expected to be read from a cache file in ImportExportOperations + public ArrayList<ParcelableKeyRing> mKeyList; + public String mKeyserver; // must be set if keys are to be imported from a keyserver + + public ImportKeyringParcel (ArrayList<ParcelableKeyRing> keyList, String keyserver) { + mKeyList = keyList; + mKeyserver = keyserver; + } + + protected ImportKeyringParcel(Parcel in) { + if (in.readByte() == 0x01) { + mKeyList = new ArrayList<>(); + in.readList(mKeyList, ParcelableKeyRing.class.getClassLoader()); + } else { + mKeyList = null; + } + mKeyserver = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (mKeyList == null) { + dest.writeByte((byte) (0x00)); + } else { + dest.writeByte((byte) (0x01)); + dest.writeList(mKeyList); + } + dest.writeString(mKeyserver); + } + + public static final Parcelable.Creator<ImportKeyringParcel> CREATOR = new Parcelable.Creator<ImportKeyringParcel>() { + @Override + public ImportKeyringParcel createFromParcel(Parcel in) { + return new ImportKeyringParcel(in); + } + + @Override + public ImportKeyringParcel[] newArray(int size) { + return new ImportKeyringParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java new file mode 100644 index 000000000..1872191af --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java @@ -0,0 +1,62 @@ +/* + * 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 KeybaseVerificationParcel implements Parcelable { + + public String mKeybaseProof; + public String mRequiredFingerprint; + + public KeybaseVerificationParcel(String keybaseProof, String requiredFingerprint) { + mKeybaseProof = keybaseProof; + mRequiredFingerprint = requiredFingerprint; + } + + protected KeybaseVerificationParcel(Parcel in) { + mKeybaseProof = in.readString(); + mRequiredFingerprint = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mKeybaseProof); + dest.writeString(mRequiredFingerprint); + } + + public static final Parcelable.Creator<KeybaseVerificationParcel> CREATOR = new Parcelable.Creator<KeybaseVerificationParcel>() { + @Override + public KeybaseVerificationParcel createFromParcel(Parcel in) { + return new KeybaseVerificationParcel(in); + } + + @Override + public KeybaseVerificationParcel[] newArray(int size) { + return new KeybaseVerificationParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainNewService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainNewService.java deleted file mode 100644 index 9e33a1421..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainNewService.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> - * 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.service; - - -import java.util.concurrent.atomic.AtomicBoolean; - -import android.app.Service; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.Parcelable; -import android.os.RemoteException; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.operations.BaseOperation; -import org.sufficientlysecure.keychain.operations.CertifyOperation; -import org.sufficientlysecure.keychain.operations.EditKeyOperation; -import org.sufficientlysecure.keychain.operations.SignEncryptOperation; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; -import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.util.Log; - -/** - * This Service contains all important long lasting operations for OpenKeychain. It receives Intents with - * data from the activities or other apps, executes them, and stops itself after doing them. - */ -public class KeychainNewService extends Service implements Progressable { - - // messenger for communication (hack) - public static final String EXTRA_MESSENGER = "messenger"; - - // extras for operation - public static final String EXTRA_OPERATION_INPUT = "op_input"; - public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; - - // this attribute can possibly merged with the one above? not sure... - private AtomicBoolean mActionCanceled = new AtomicBoolean(false); - - ThreadLocal<Messenger> mMessenger = new ThreadLocal<>(); - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - /** - * This is run on the main thread, we need to spawn a runnable which runs on another thread for the actual operation - */ - @Override - public int onStartCommand(final Intent intent, int flags, int startId) { - - Runnable actionRunnable = new Runnable() { - @Override - public void run() { - // We have not been cancelled! (yet) - mActionCanceled.set(false); - - Bundle extras = intent.getExtras(); - - // Set messenger for communication (for this particular thread) - mMessenger.set(extras.<Messenger>getParcelable(EXTRA_MESSENGER)); - - // Input - Parcelable inputParcel = extras.getParcelable(EXTRA_OPERATION_INPUT); - CryptoInputParcel cryptoInput = extras.getParcelable(EXTRA_CRYPTO_INPUT); - - // Operation - BaseOperation op; - - // just for brevity - KeychainNewService outerThis = KeychainNewService.this; - if (inputParcel instanceof SignEncryptParcel) { - op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); - } else if (inputParcel instanceof PgpDecryptVerifyInputParcel) { - op = new PgpDecryptVerify(outerThis, new ProviderHelper(outerThis), outerThis); - } else if (inputParcel instanceof SaveKeyringParcel) { - op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); - } else if (inputParcel instanceof CertifyAction) { - op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); - } else { - return; - } - - @SuppressWarnings("unchecked") // this is unchecked, we make sure it's the correct op above! - OperationResult result = op.execute(inputParcel, cryptoInput); - - sendMessageToHandler(MessageStatus.OKAY, result); - - } - }; - - Thread actionThread = new Thread(actionRunnable); - actionThread.start(); - - return START_NOT_STICKY; - } - - private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) { - - Message msg = Message.obtain(); - assert msg != null; - msg.arg1 = status.ordinal(); - if (arg2 != null) { - msg.arg2 = arg2; - } - if (data != null) { - msg.setData(data); - } - - try { - mMessenger.get().send(msg); - } catch (RemoteException e) { - Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); - } catch (NullPointerException e) { - Log.w(Constants.TAG, "Messenger is null!", e); - } - } - - private void sendMessageToHandler(MessageStatus status, OperationResult data) { - Bundle bundle = new Bundle(); - bundle.putParcelable(OperationResult.EXTRA_RESULT, data); - sendMessageToHandler(status, null, bundle); - } - - private void sendMessageToHandler(MessageStatus status) { - sendMessageToHandler(status, null, null); - } - - /** - * Set progress of ProgressDialog by sending message to handler on UI thread - */ - @Override - public void setProgress(String message, int progress, int max) { - Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max=" - + max); - - Bundle data = new Bundle(); - if (message != null) { - data.putString(ServiceProgressHandler.DATA_MESSAGE, message); - } - data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress); - data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max); - - sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data); - } - - @Override - public void setProgress(int resourceId, int progress, int max) { - setProgress(getString(resourceId), progress, max); - } - - @Override - public void setProgress(int progress, int max) { - setProgress(null, progress, max); - } - - @Override - public void setPreventCancel() { - sendMessageToHandler(MessageStatus.PREVENT_CANCEL); - } - -} 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 ba877c2a2..1cd76b462 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -20,70 +20,35 @@ package org.sufficientlysecure.keychain.service; import android.app.Service; import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.Message; import android.os.Messenger; +import android.os.Parcelable; import android.os.RemoteException; -import com.textuality.keybase.lib.Proof; -import com.textuality.keybase.lib.prover.Prover; - -import org.json.JSONObject; -import org.spongycastle.openpgp.PGPUtil; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.keyimport.Keyserver; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.BaseOperation; import org.sufficientlysecure.keychain.operations.CertifyOperation; +import org.sufficientlysecure.keychain.operations.ConsolidateOperation; import org.sufficientlysecure.keychain.operations.DeleteOperation; import org.sufficientlysecure.keychain.operations.EditKeyOperation; -import org.sufficientlysecure.keychain.operations.ImportExportOperation; +import org.sufficientlysecure.keychain.operations.ExportOperation; +import org.sufficientlysecure.keychain.operations.ImportOperation; +import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation; import org.sufficientlysecure.keychain.operations.PromoteKeyOperation; import org.sufficientlysecure.keychain.operations.SignEncryptOperation; -import org.sufficientlysecure.keychain.operations.results.CertifyResult; -import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.operations.results.DeleteResult; -import org.sufficientlysecure.keychain.operations.results.ExportResult; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; -import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.ParcelableFileCache; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import de.measite.minidns.Client; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.Question; -import de.measite.minidns.Record; -import de.measite.minidns.record.Data; -import de.measite.minidns.record.TXT; +import java.util.concurrent.atomic.AtomicBoolean; /** * This Service contains all important long lasting operations for OpenKeychain. It receives Intents with @@ -91,75 +56,19 @@ import de.measite.minidns.record.TXT; */ public class KeychainService extends Service implements Progressable { - /* extras that can be given by intent */ + // messenger for communication (hack) public static final String EXTRA_MESSENGER = "messenger"; - public static final String EXTRA_DATA = "data"; - - /* possible actions */ - - public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF"; - - public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING"; - - public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING"; - - public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING"; - public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING"; - - public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING"; - - public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE"; - public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE"; - - public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL"; - - /* keys for data bundle */ - - // keybase proof - public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint"; - public static final String KEYBASE_PROOF = "keybase_proof"; - - // save keyring - public static final String EDIT_KEYRING_PARCEL = "save_parcel"; - public static final String EDIT_KEYRING_PASSPHRASE = "passphrase"; + // extras for operation + public static final String EXTRA_OPERATION_INPUT = "op_input"; public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; - // delete keyring(s) - public static final String DELETE_KEY_LIST = "delete_list"; - public static final String DELETE_IS_SECRET = "delete_is_secret"; - - // import key - public static final String IMPORT_KEY_LIST = "import_key_list"; - public static final String IMPORT_KEY_SERVER = "import_key_server"; - - // export key - public static final String EXPORT_FILENAME = "export_filename"; - public static final String EXPORT_URI = "export_uri"; - public static final String EXPORT_SECRET = "export_secret"; - public static final String EXPORT_ALL = "export_all"; - public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id"; - - // upload key - public static final String UPLOAD_KEY_SERVER = "upload_key_server"; - - // promote key - public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id"; - public static final String PROMOTE_CARD_AID = "promote_card_aid"; - public static final String PROMOTE_SUBKEY_IDS = "promote_fingerprints"; - - // consolidate - public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; - - Messenger mMessenger; + public static final String ACTION_CANCEL = "action_cancel"; // this attribute can possibly merged with the one above? not sure... private AtomicBoolean mActionCanceled = new AtomicBoolean(false); - - private KeyImportAccumulator mKeyImportAccumulator; - - private KeychainService mKeychainService; + ThreadLocal<Messenger> mMessenger = new ThreadLocal<>(); @Override public IBinder onBind(Intent intent) { @@ -171,9 +80,8 @@ public class KeychainService extends Service implements Progressable { */ @Override public int onStartCommand(final Intent intent, int flags, int startId) { - mKeychainService = this; - if (ACTION_CANCEL.equals(intent.getAction())) { + if (intent.getAction() != null && intent.getAction().equals(ACTION_CANCEL)) { mActionCanceled.set(true); return START_NOT_STICKY; } @@ -185,315 +93,62 @@ public class KeychainService extends Service implements Progressable { mActionCanceled.set(false); Bundle extras = intent.getExtras(); - if (extras == null) { - Log.e(Constants.TAG, "Extras bundle is null!"); - return; - } - if (!(extras.containsKey(EXTRA_MESSENGER) || extras.containsKey(EXTRA_DATA) || (intent - .getAction() == null))) { - Log.e(Constants.TAG, - "Extra bundle must contain a messenger, a data bundle, and an action!"); + // Set messenger for communication (for this particular thread) + mMessenger.set(extras.<Messenger>getParcelable(EXTRA_MESSENGER)); + + // Input + Parcelable inputParcel = extras.getParcelable(EXTRA_OPERATION_INPUT); + CryptoInputParcel cryptoInput = extras.getParcelable(EXTRA_CRYPTO_INPUT); + + // Operation + BaseOperation op; + + // just for brevity + KeychainService outerThis = KeychainService.this; + if (inputParcel instanceof SignEncryptParcel) { + op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis), + outerThis, mActionCanceled); + } else if (inputParcel instanceof PgpDecryptVerifyInputParcel) { + op = new PgpDecryptVerify(outerThis, new ProviderHelper(outerThis), outerThis); + } else if (inputParcel instanceof SaveKeyringParcel) { + op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, + mActionCanceled); + } else if (inputParcel instanceof CertifyActionsParcel) { + op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis, + mActionCanceled); + } else if (inputParcel instanceof DeleteKeyringParcel) { + op = new DeleteOperation(outerThis, new ProviderHelper(outerThis), outerThis); + } else if (inputParcel instanceof PromoteKeyringParcel) { + op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis), + outerThis, mActionCanceled); + } else if (inputParcel instanceof ImportKeyringParcel) { + 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 ConsolidateInputParcel) { + op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis), + outerThis); + } else if (inputParcel instanceof KeybaseVerificationParcel) { + op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis), + outerThis); + } else { return; } - Uri dataUri = intent.getData(); - - mMessenger = (Messenger) extras.get(EXTRA_MESSENGER); - Bundle data = extras.getBundle(EXTRA_DATA); - if (data == null) { - Log.e(Constants.TAG, "data extra is null!"); - return; - } + @SuppressWarnings("unchecked") // this is unchecked, we make sure it's the correct op above! + OperationResult result = op.execute(inputParcel, cryptoInput); + sendMessageToHandler(MessageStatus.OKAY, result); - Log.logDebugBundle(data, "EXTRA_DATA"); - - ProviderHelper providerHelper = new ProviderHelper(mKeychainService); - - String action = intent.getAction(); - - // executeServiceMethod action from extra bundle - switch (action) { - case ACTION_CONSOLIDATE: { - - // Operation - ConsolidateResult result; - if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) { - result = providerHelper.consolidateDatabaseStep2(mKeychainService); - } else { - result = providerHelper.consolidateDatabaseStep1(mKeychainService); - } - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_VERIFY_KEYBASE_PROOF: { - - try { - Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF))); - setProgress(R.string.keybase_message_fetching_data, 0, 100); - - Prover prover = Prover.findProverFor(proof); - - if (prover == null) { - sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof - .getPrettyName()); - return; - } - - if (!prover.fetchProofData()) { - sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence)); - return; - } - String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT); - if (!prover.checkFingerprint(requiredFingerprint)) { - sendProofError(getString(R.string.keybase_key_mismatch)); - return; - } - - String domain = prover.dnsTxtCheckRequired(); - if (domain != null) { - DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT)); - if (dnsQuery == null) { - sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure)); - return; - } - Record[] records = dnsQuery.getAnswers(); - List<List<byte[]>> extents = new ArrayList<List<byte[]>>(); - for (Record r : records) { - Data d = r.getPayload(); - if (d instanceof TXT) { - extents.add(((TXT) d).getExtents()); - } - } - if (!prover.checkDnsTxt(extents)) { - sendProofError(prover.getLog(), null); - return; - } - } - - byte[] messageBytes = prover.getPgpMessage().getBytes(); - if (prover.rawMessageCheckRequired()) { - InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream - (messageBytes)); - if (!prover.checkRawMessageBytes(messageByteStream)) { - sendProofError(prover.getLog(), null); - return; - } - } - - PgpDecryptVerify op = new PgpDecryptVerify(mKeychainService, providerHelper, - mKeychainService); - - PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes) - .setSignedLiteralData(true) - .setRequiredSignerFingerprint(requiredFingerprint); - - DecryptVerifyResult decryptVerifyResult = op.execute(input, new CryptoInputParcel()); - - if (!decryptVerifyResult.success()) { - OperationLog log = decryptVerifyResult.getLog(); - OperationResult.LogEntryParcel lastEntry = null; - for (OperationResult.LogEntryParcel entry : log) { - lastEntry = entry; - } - sendProofError(getString(lastEntry.mType.getMsgId())); - return; - } - - if (!prover.validate(new String(decryptVerifyResult.getOutputBytes()))) { - sendProofError(getString(R.string.keybase_message_payload_mismatch)); - return; - } - - Bundle resultData = new Bundle(); - resultData.putString(ServiceProgressHandler.DATA_MESSAGE, "OK"); - - // these help the handler construct a useful human-readable message - resultData.putString(ServiceProgressHandler.KEYBASE_PROOF_URL, prover.getProofUrl()); - resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl()); - resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL, prover - .getPresenceLabel()); - sendMessageToHandler(MessageStatus.OKAY, resultData); - } catch (Exception e) { - sendErrorToHandler(e); - } - - break; - } - case ACTION_DELETE: { - - // Input - long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST); - boolean isSecret = data.getBoolean(DELETE_IS_SECRET); - - // Operation - DeleteOperation op = new DeleteOperation(mKeychainService, providerHelper, mKeychainService); - DeleteResult result = op.execute(masterKeyIds, isSecret); - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_EDIT_KEYRING: { - - // Input - SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL); - CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); - - // Operation - EditKeyOperation op = new EditKeyOperation(mKeychainService, providerHelper, - mKeychainService, mActionCanceled); - OperationResult result = op.execute(saveParcel, cryptoInput); - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_PROMOTE_KEYRING: { - - // Input - long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID); - byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID); - long[] subKeyIds = data.getLongArray(PROMOTE_SUBKEY_IDS); - - // Operation - PromoteKeyOperation op = new PromoteKeyOperation( - mKeychainService, providerHelper, mKeychainService, - mActionCanceled); - PromoteKeyResult result = op.execute(keyRingId, cardAid, subKeyIds); - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_EXPORT_KEYRING: { - - // Input - boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); - String outputFile = data.getString(EXPORT_FILENAME); - Uri outputUri = data.getParcelable(EXPORT_URI); - - boolean exportAll = data.getBoolean(EXPORT_ALL); - long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); - - // Operation - ImportExportOperation importExportOperation = new ImportExportOperation( - mKeychainService, providerHelper, mKeychainService); - ExportResult result; - if (outputFile != null) { - result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile); - } else { - result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri); - } - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_IMPORT_KEYRING: { - - // Input - String keyServer = data.getString(IMPORT_KEY_SERVER); - ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST); - - // either keyList or cache must be null, no guarantees otherwise - if (keyList == null) {// import from file, do serially - serialKeyImport(null, keyServer, providerHelper); - } else { - // if there is more than one key with the same fingerprint, we do a serial import to prevent - // https://github.com/open-keychain/open-keychain/issues/1221 - HashSet<String> keyFingerprintSet = new HashSet<>(); - for (int i = 0; i < keyList.size(); i++) { - keyFingerprintSet.add(keyList.get(i).mExpectedFingerprint); - } - if (keyFingerprintSet.size() == keyList.size()) { - // all keys have unique fingerprints - multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer); - } else { - serialKeyImport(keyList, keyServer, providerHelper); - } - } - - break; - } - case ACTION_UPLOAD_KEYRING: { - try { - - // Input - String keyServer = data.getString(UPLOAD_KEY_SERVER); - // and dataUri! - - // Operation - HkpKeyserver server = new HkpKeyserver(keyServer); - - CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri); - ImportExportOperation importExportOperation = new ImportExportOperation(mKeychainService, - providerHelper, mKeychainService); - - try { - importExportOperation.uploadKeyRingToServer(server, keyring); - } catch (Keyserver.AddKeyException e) { - throw new PgpGeneralException("Unable to export key to selected server"); - } - - sendMessageToHandler(MessageStatus.OKAY); - } catch (Exception e) { - sendErrorToHandler(e); - } - break; - } - } - if (!intent.getAction().equals(ACTION_IMPORT_KEYRING)) { - // import keyring handles stopping service on its own - stopSelf(); - } } }; Thread actionThread = new Thread(actionRunnable); actionThread.start(); - - return START_NOT_STICKY; - } - - private void sendProofError(List<String> log, String label) { - String msg = null; - label = (label == null) ? "" : label + ": "; - for (String m : log) { - Log.e(Constants.TAG, label + m); - msg = m; - } - sendProofError(label + msg); - } - private void sendProofError(String msg) { - Bundle bundle = new Bundle(); - bundle.putString(ServiceProgressHandler.DATA_ERROR, msg); - sendMessageToHandler(MessageStatus.OKAY, bundle); - } - - private void sendErrorToHandler(Exception e) { - // TODO: Implement a better exception handling here - // contextualize the exception, if necessary - String message; - if (e instanceof PgpGeneralMsgIdException) { - e = ((PgpGeneralMsgIdException) e).getContextualized(mKeychainService); - message = e.getMessage(); - } else { - message = e.getMessage(); - } - Log.d(Constants.TAG, "KeychainService Exception: ", e); - - Bundle data = new Bundle(); - data.putString(ServiceProgressHandler.DATA_ERROR, message); - sendMessageToHandler(MessageStatus.EXCEPTION, null, data); + return START_NOT_STICKY; } private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) { @@ -509,7 +164,7 @@ public class KeychainService extends Service implements Progressable { } try { - mMessenger.send(msg); + mMessenger.get().send(msg); } catch (RemoteException e) { Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); } catch (NullPointerException e) { @@ -523,10 +178,6 @@ public class KeychainService extends Service implements Progressable { sendMessageToHandler(status, null, bundle); } - private void sendMessageToHandler(MessageStatus status, Bundle data) { - sendMessageToHandler(status, null, data); - } - private void sendMessageToHandler(MessageStatus status) { sendMessageToHandler(status, null, null); } @@ -564,214 +215,4 @@ public class KeychainService extends Service implements Progressable { sendMessageToHandler(MessageStatus.PREVENT_CANCEL); } - public void serialKeyImport(ArrayList<ParcelableKeyRing> keyList, final String keyServer, - ProviderHelper providerHelper) { - Log.d(Constants.TAG, "serial key import starting"); - ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<>(mKeychainService, "key_import.pcl"); - - // Operation - ImportExportOperation importExportOperation = new ImportExportOperation( - mKeychainService, providerHelper, mKeychainService, - mActionCanceled); - // Either list or cache must be null, no guarantees otherwise. - ImportKeyResult result = keyList != null - ? importExportOperation.importKeyRings(keyList, keyServer) - : importExportOperation.importKeyRings(cache, keyServer); - - ContactSyncAdapterService.requestSync(); - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - stopSelf(); - } - - public void multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator, int totKeys, final String - keyServer) { - Log.d(Constants.TAG, "Multi-threaded key import starting"); - if (keyListIterator != null) { - mKeyImportAccumulator = new KeyImportAccumulator(totKeys, mKeychainService); - setProgress(0, totKeys); - - final int maxThreads = 200; - ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads, - 30L, TimeUnit.SECONDS, - new SynchronousQueue<Runnable>()); - - while (keyListIterator.hasNext()) { - - final ParcelableKeyRing pkRing = keyListIterator.next(); - - Runnable importOperationRunnable = new Runnable() { - - @Override - public void run() { - ImportKeyResult result = null; - try { - ImportExportOperation importExportOperation = new ImportExportOperation( - mKeychainService, - new ProviderHelper(mKeychainService), - mKeyImportAccumulator.getImportProgressable(), - mActionCanceled); - - ArrayList<ParcelableKeyRing> list = new ArrayList<>(); - list.add(pkRing); - - result = importExportOperation.importKeyRings(list, - keyServer); - } finally { - // in the off-chance that importKeyRings does something to crash the - // thread before it can call singleKeyRingImportCompleted, our imported - // key count will go wrong. This will cause the service to never die, - // and the progress dialog to stay displayed. The finally block was - // originally meant to ensure singleKeyRingImportCompleted was called, - // and checks for null were to be introduced, but in such a scenario, - // knowing an uncaught error exists in importKeyRings is more important. - - // if a null gets passed, something wrong is happening. We want a crash. - - mKeyImportAccumulator.singleKeyRingImportCompleted(result); - } - } - }; - - importExecutor.execute(importOperationRunnable); - } - } - } - - /** - * Used to accumulate the results of individual key imports - */ - private class KeyImportAccumulator { - private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog(); - private int mTotalKeys; - private int mImportedKeys = 0; - private Progressable mInternalProgressable; - ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>(); - private int mBadKeys = 0; - private int mNewKeys = 0; - private int mUpdatedKeys = 0; - private int mSecret = 0; - private int mResultType = 0; - - /** - * meant to be used with a service due to stopSelf() in singleKeyRingImportCompleted. Remove this if - * generalising. - * - * @param totalKeys total number of keys to be imported - * @param externalProgressable the external progressable to be updated every time a key is imported - */ - public KeyImportAccumulator(int totalKeys, Progressable externalProgressable) { - mTotalKeys = totalKeys; - // ignore updates from ImportExportOperation for now - mInternalProgressable = new Progressable() { - @Override - public void setProgress(String message, int current, int total) { - - } - - @Override - public void setProgress(int resourceId, int current, int total) { - - } - - @Override - public void setProgress(int current, int total) { - - } - - @Override - public void setPreventCancel() { - - } - }; - } - - private synchronized void singleKeyRingImportCompleted(ImportKeyResult result) { - // increase imported key count and accumulate log and bad, new etc. key counts from result - mKeyImportAccumulator.accumulateKeyImport(result); - - setProgress(mKeyImportAccumulator.getImportedKeys(), mKeyImportAccumulator.getTotalKeys()); - - if (mKeyImportAccumulator.isImportFinished()) { - ContactSyncAdapterService.requestSync(); - - sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY, - mKeyImportAccumulator.getConsolidatedImportKeyResult()); - - stopSelf();//we're done here - } - } - - public Progressable getImportProgressable() { - return mInternalProgressable; - } - - public int getTotalKeys() { - return mTotalKeys; - } - - public int getImportedKeys() { - return mImportedKeys; - } - - public synchronized void accumulateKeyImport(ImportKeyResult result) { - mImportedKeys++; - mImportLog.addAll(result.getLog().toList());//accumulates log - mBadKeys += result.mBadKeys; - mNewKeys += result.mNewKeys; - mUpdatedKeys += result.mUpdatedKeys; - mSecret += result.mSecret; - - long[] masterKeyIds = result.getImportedMasterKeyIds(); - for (long masterKeyId : masterKeyIds) { - mImportedMasterKeyIds.add(masterKeyId); - } - - // if any key import has been cancelled, set result type to cancelled - // resultType is added to in getConsolidatedKayImport to account for remaining factors - mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED; - } - - /** - * returns accumulated result of all imports so far - */ - public ImportKeyResult getConsolidatedImportKeyResult() { - - // adding required information to mResultType - // special case,no keys requested for import - if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) { - mResultType = ImportKeyResult.RESULT_FAIL_NOTHING; - } else { - if (mNewKeys > 0) { - mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS; - } - if (mUpdatedKeys > 0) { - mResultType |= ImportKeyResult.RESULT_OK_UPDATED; - } - if (mBadKeys > 0) { - mResultType |= ImportKeyResult.RESULT_WITH_ERRORS; - if (mNewKeys == 0 && mUpdatedKeys == 0) { - mResultType |= ImportKeyResult.RESULT_ERROR; - } - } - if (mImportLog.containsWarnings()) { - mResultType |= ImportKeyResult.RESULT_WARNINGS; - } - } - - long masterKeyIds[] = new long[mImportedMasterKeyIds.size()]; - for (int i = 0; i < masterKeyIds.length; i++) { - masterKeyIds[i] = mImportedMasterKeyIds.get(i); - } - - return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys, - mSecret, masterKeyIds); - } - - public boolean isImportFinished() { - return mTotalKeys == mImportedKeys; - } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index a0b470add..7c0b7eaef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -459,11 +459,16 @@ public class PassphraseCacheService extends Service { * Called when one specific passphrase for keyId timed out */ private void timeout(long keyId) { + CachedPassphrase cPass = mPassphraseCache.get(keyId); - // clean internal char[] from memory! - cPass.getPassphrase().removeFromMemory(); - // remove passphrase object - mPassphraseCache.remove(keyId); + if (cPass != null) { + if (cPass.getPassphrase() != null) { + // clean internal char[] from memory! + cPass.getPassphrase().removeFromMemory(); + } + // remove passphrase object + mPassphraseCache.remove(keyId); + } Log.d(Constants.TAG, "PassphraseCacheService Timeout of keyId " + keyId + ", removed from memory!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java new file mode 100644 index 000000000..d268c3694 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java @@ -0,0 +1,66 @@ +/* + * 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 PromoteKeyringParcel implements Parcelable { + + public long mKeyRingId; + public byte[] mCardAid; + public long[] mSubKeyIds; + + public PromoteKeyringParcel(long keyRingId, byte[] cardAid, long[] subKeyIds) { + mKeyRingId = keyRingId; + mCardAid = cardAid; + mSubKeyIds = subKeyIds; + } + + protected PromoteKeyringParcel(Parcel in) { + mKeyRingId = in.readLong(); + mCardAid = in.createByteArray(); + mSubKeyIds = in.createLongArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mKeyRingId); + dest.writeByteArray(mCardAid); + dest.writeLongArray(mSubKeyIds); + } + + public static final Parcelable.Creator<PromoteKeyringParcel> CREATOR = new Parcelable.Creator<PromoteKeyringParcel>() { + @Override + public PromoteKeyringParcel createFromParcel(Parcel in) { + return new PromoteKeyringParcel(in); + } + + @Override + public PromoteKeyringParcel[] newArray(int size) { + return new PromoteKeyringParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java index 989b0c4bd..d294e5057 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java @@ -64,6 +64,8 @@ public class ServiceProgressHandler extends Handler { public static final String KEYBASE_PRESENCE_URL = "keybase_presence_url"; public static final String KEYBASE_PRESENCE_LABEL = "keybase_presence_label"; + public static final String TAG_PROGRESS_DIALOG = "progressDialog"; + FragmentActivity mActivity; public ServiceProgressHandler(FragmentActivity activity) { @@ -88,7 +90,7 @@ public class ServiceProgressHandler extends Handler { Handler handler = new Handler(); handler.post(new Runnable() { public void run() { - frag.show(manager, "progressDialog"); + frag.show(manager, TAG_PROGRESS_DIALOG); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 930c2ee4f..efe844145 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -12,7 +12,7 @@ import android.os.Parcelable; public class RequiredInputParcel implements Parcelable { public enum RequiredInputType { - PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_KEYTOCARD + PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD } public Date mSignatureTime; @@ -226,7 +226,7 @@ public class RequiredInputParcel implements Parcelable { ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0)); // We need to pass in a subkey here... - return new RequiredInputParcel(RequiredInputType.NFC_KEYTOCARD, + return new RequiredInputParcel(RequiredInputType.NFC_MOVE_KEY_TO_CARD, inputHashes, null, null, mMasterKeyId, buf.getLong()); } @@ -241,7 +241,7 @@ public class RequiredInputParcel implements Parcelable { if (!mMasterKeyId.equals(input.mMasterKeyId)) { throw new AssertionError("Master keys must match, this is a programming error!"); } - if (input.mType != RequiredInputType.NFC_KEYTOCARD) { + if (input.mType != RequiredInputType.NFC_MOVE_KEY_TO_CARD) { throw new AssertionError("Operation types must match, this is a programming error!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index e39a3a0bf..891c2268c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -19,15 +19,12 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Intent; import android.database.Cursor; import android.database.MatrixCursor; import android.graphics.PorterDuff; import android.net.Uri; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.os.Parcel; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; @@ -52,15 +49,11 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; @@ -302,7 +295,7 @@ public class CertifyKeyFragment } @Override - protected CertifyActionsParcel createOperationInput() { + public CertifyActionsParcel createOperationInput() { // Bail out if there is not at least one user id selected ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions(); @@ -325,7 +318,7 @@ public class CertifyKeyFragment } @Override - protected void onCryptoOperationSuccess(CertifyResult result) { + public void onCryptoOperationSuccess(CertifyResult result) { Intent intent = new Intent(); intent.putExtra(CertifyResult.EXTRA_RESULT, result); getActivity().setResult(Activity.RESULT_OK, intent); @@ -333,7 +326,7 @@ public class CertifyKeyFragment } @Override - protected void onCryptoOperationCancelled() { + public void onCryptoOperationCancelled() { super.onCryptoOperationCancelled(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java index 6a9bb7b11..7a473e49f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java @@ -17,25 +17,27 @@ package org.sufficientlysecure.keychain.ui; -import android.app.ProgressDialog; import android.content.Intent; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.FragmentActivity; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; +import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; /** * We can not directly create a dialog on the application context. * This activity encapsulates a DialogFragment to emulate a dialog. */ -public class ConsolidateDialogActivity extends FragmentActivity { +public class ConsolidateDialogActivity extends FragmentActivity + implements CryptoOperationHelper.Callback<ConsolidateInputParcel, ConsolidateResult> { public static final String EXTRA_CONSOLIDATE_RECOVERY = "consolidate_recovery"; + private CryptoOperationHelper<ConsolidateInputParcel, ConsolidateResult> mConsolidateOpHelper; + private boolean mRecovery; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -48,55 +50,45 @@ public class ConsolidateDialogActivity extends FragmentActivity { } private void consolidateRecovery(boolean recovery) { - // Message is received after importing is done in KeychainService - ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - /* don't care about the results (for now?) - - // get returned data bundle - Bundle returnData = message.getInputData(); - if (returnData == null) { - return; - } - final ConsolidateResult result = - returnData.getParcelable(KeychainService.RESULT_CONSOLIDATE); - if (result == null) { - return; - } - result.createNotify(ConsolidateDialogActivity.this).show(); - */ - - ConsolidateDialogActivity.this.finish(); - } - } - }; - - // Send all information needed to service to import key in other thread - Intent intent = new Intent(this, KeychainService.class); - intent.setAction(KeychainService.ACTION_CONSOLIDATE); - - // fill values for this action - Bundle data = new Bundle(); - data.putBoolean(KeychainService.CONSOLIDATE_RECOVERY, recovery); - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog( - getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL, false - ); - - // start service with intent - startService(intent); + + mRecovery = recovery; + + mConsolidateOpHelper = new CryptoOperationHelper<>(this, this, R.string.progress_importing); + mConsolidateOpHelper.cryptoOperation(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (mConsolidateOpHelper != null) { + mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data); + } } + @Override + public ConsolidateInputParcel createOperationInput() { + return new ConsolidateInputParcel(mRecovery); + } + + @Override + public void onCryptoOperationSuccess(ConsolidateResult result) { + // don't care about result (for now?) + ConsolidateDialogActivity.this.finish(); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(ConsolidateResult result) { + // don't care about result (for now?) + ConsolidateDialogActivity.this.finish(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 68a809b69..36ab62cb4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -17,9 +17,8 @@ package org.sufficientlysecure.keychain.ui; -import android.app.AlertDialog; -import android.content.DialogInterface; import android.content.Intent; +import android.nfc.NfcAdapter; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; @@ -43,7 +42,9 @@ public class CreateKeyActivity extends BaseNfcActivity { public static final String EXTRA_FIRST_TIME = "first_time"; public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails"; public static final String EXTRA_PASSPHRASE = "passphrase"; - public static final String EXTRA_USE_SMART_CARD_SETTINGS = "use_smart_card_settings"; + public static final String EXTRA_CREATE_YUBI_KEY = "create_yubi_key"; + public static final String EXTRA_YUBI_KEY_PIN = "yubi_key_pin"; + public static final String EXTRA_YUBI_KEY_ADMIN_PIN = "yubi_key_admin_pin"; public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; public static final String EXTRA_NFC_AID = "nfc_aid"; @@ -56,7 +57,9 @@ public class CreateKeyActivity extends BaseNfcActivity { ArrayList<String> mAdditionalEmails; Passphrase mPassphrase; boolean mFirstTime; - boolean mUseSmartCardSettings; + boolean mCreateYubiKey; + String mYubiKeyPin; + String mYubiKeyAdminPin; Fragment mCurrentFragment; @@ -64,6 +67,23 @@ public class CreateKeyActivity extends BaseNfcActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // React on NDEF_DISCOVERED from Manifest + // NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity + if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { + try { + handleTagDiscoveredIntent(getIntent()); + } catch (CardException e) { + handleNfcError(e); + } catch (IOException e) { + handleNfcError(e); + } + + setTitle(R.string.title_manage_my_keys); + + // done + return; + } + // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { // Restore value of members from saved state @@ -72,7 +92,9 @@ public class CreateKeyActivity extends BaseNfcActivity { mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS); mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE); mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME); - mUseSmartCardSettings = savedInstanceState.getBoolean(EXTRA_USE_SMART_CARD_SETTINGS); + mCreateYubiKey = savedInstanceState.getBoolean(EXTRA_CREATE_YUBI_KEY); + mYubiKeyPin = savedInstanceState.getString(EXTRA_YUBI_KEY_PIN); + mYubiKeyAdminPin = savedInstanceState.getString(EXTRA_YUBI_KEY_ADMIN_PIN); mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); } else { @@ -82,24 +104,32 @@ public class CreateKeyActivity extends BaseNfcActivity { mName = intent.getStringExtra(EXTRA_NAME); mEmail = intent.getStringExtra(EXTRA_EMAIL); mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); - mUseSmartCardSettings = intent.getBooleanExtra(EXTRA_USE_SMART_CARD_SETTINGS, false); + mCreateYubiKey = intent.getBooleanExtra(EXTRA_CREATE_YUBI_KEY, false); if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) { byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); - Fragment frag2 = CreateKeyYubiKeyImportFragment.createInstance( - nfcFingerprints, nfcAid, nfcUserId); - loadFragment(frag2, FragAction.START); + if (containsKeys(nfcFingerprints)) { + Fragment frag = CreateYubiKeyImportFragment.newInstance( + nfcFingerprints, nfcAid, nfcUserId); + loadFragment(frag, FragAction.START); + + setTitle(R.string.title_import_keys); + } else { + Fragment frag = CreateYubiKeyBlankFragment.newInstance(); + loadFragment(frag, FragAction.START); + setTitle(R.string.title_manage_my_keys); + } - setTitle(R.string.title_import_keys); + // done return; - } else { - CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance(); - loadFragment(frag, FragAction.START); } + // normal key creation + CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance(); + loadFragment(frag, FragAction.START); } if (mFirstTime) { @@ -122,16 +152,7 @@ public class CreateKeyActivity extends BaseNfcActivity { byte[] nfcAid = nfcGetAid(); String userId = nfcGetUserId(); - // If all fingerprint bytes are 0, the card contains no keys. - boolean cardContainsKeys = false; - for (byte b : scannedFingerprints) { - if (b != 0) { - cardContainsKeys = true; - break; - } - } - - if (cardContainsKeys) { + if (containsKeys(scannedFingerprints)) { try { long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints); CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); @@ -146,25 +167,29 @@ public class CreateKeyActivity extends BaseNfcActivity { finish(); } catch (PgpKeyNotFoundException e) { - Fragment frag = CreateKeyYubiKeyImportFragment.createInstance( + Fragment frag = CreateYubiKeyImportFragment.newInstance( scannedFingerprints, nfcAid, userId); loadFragment(frag, FragAction.TO_RIGHT); } } else { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.first_time_blank_smartcard_title) - .setMessage(R.string.first_time_blank_smartcard_message) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int button) { - CreateKeyActivity.this.mUseSmartCardSettings = true; - } - }) - .setNegativeButton(android.R.string.no, null).show(); + Fragment frag = CreateYubiKeyBlankFragment.newInstance(); + loadFragment(frag, FragAction.TO_RIGHT); } } + private boolean containsKeys(byte[] scannedFingerprints) { + // If all fingerprint bytes are 0, the card contains no keys. + boolean cardContainsKeys = false; + for (byte b : scannedFingerprints) { + if (b != 0) { + cardContainsKeys = true; + break; + } + } + return cardContainsKeys; + } + @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -174,7 +199,9 @@ public class CreateKeyActivity extends BaseNfcActivity { outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails); outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase); outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime); - outState.putBoolean(EXTRA_USE_SMART_CARD_SETTINGS, mUseSmartCardSettings); + outState.putBoolean(EXTRA_CREATE_YUBI_KEY, mCreateYubiKey); + outState.putString(EXTRA_YUBI_KEY_PIN, mYubiKeyPin); + outState.putString(EXTRA_YUBI_KEY_ADMIN_PIN, mYubiKeyAdminPin); } @Override @@ -182,7 +209,7 @@ public class CreateKeyActivity extends BaseNfcActivity { setContentView(R.layout.create_key_activity); } - public static enum FragAction { + public enum FragAction { START, TO_RIGHT, TO_LEFT diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 597f04d6b..69dd232cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -29,6 +30,7 @@ import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; @@ -37,9 +39,9 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.dialog.AddEmailDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.EmailEditText; +import org.sufficientlysecure.keychain.util.Passphrase; import java.util.ArrayList; import java.util.List; @@ -201,7 +203,7 @@ public class CreateKeyEmailFragment extends Fragment { Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { - if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { + if (message.what == AddEmailDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL); @@ -232,11 +234,38 @@ public class CreateKeyEmailFragment extends Fragment { mCreateKeyActivity.mEmail = mEmailEdit.getText().toString(); mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails(); - CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance(); - mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + CreateKeyActivity createKeyActivity = ((CreateKeyActivity) getActivity()); + + if (createKeyActivity.mCreateYubiKey) { + hideKeyboard(); + + // set empty passphrase + createKeyActivity.mPassphrase = new Passphrase(); + + CreateYubiKeyPinFragment frag = CreateYubiKeyPinFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } else { + CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } } } + private void hideKeyboard() { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + private ArrayList<String> getAdditionalEmails() { ArrayList<String> emails = new ArrayList<>(); for (EmailAdapter.ViewModel holder : mAdditionalEmailModels) { 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 ebbd01afe..8c7abb874 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,12 +18,10 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -35,15 +33,19 @@ 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.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.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +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.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; @@ -63,6 +65,10 @@ public class CreateKeyFinalFragment extends Fragment { SaveKeyringParcel mSaveKeyringParcel; + private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper; + private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mCreateOpHelper; + private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mMoveToCardOpHelper; + public static CreateKeyFinalFragment newInstance() { CreateKeyFinalFragment frag = new CreateKeyFinalFragment(); @@ -129,11 +135,26 @@ public class CreateKeyFinalFragment extends Fragment { } }); + // If this is a debug build, don't upload by default + if (Constants.DEBUG) { + mUploadCheckbox.setChecked(false); + } + return view; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mCreateOpHelper != null) { + mCreateOpHelper.handleActivityResult(requestCode, resultCode, data); + } + if (mMoveToCardOpHelper != null) { + mMoveToCardOpHelper.handleActivityResult(requestCode, resultCode, data); + } + if (mUploadOpHelper != null) { + mUploadOpHelper.handleActivityResult(requestCode, resultCode, data); + } + switch (requestCode) { case REQUEST_EDIT_KEY: { if (resultCode == Activity.RESULT_OK) { @@ -156,7 +177,8 @@ public class CreateKeyFinalFragment extends Fragment { if (mSaveKeyringParcel == null) { mSaveKeyringParcel = new SaveKeyringParcel(); - if (createKeyActivity.mUseSmartCardSettings) { + + if (createKeyActivity.mCreateYubiKey) { mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L)); mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, @@ -164,6 +186,7 @@ public class CreateKeyFinalFragment extends Fragment { mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 2048, null, KeyFlags.AUTHENTICATION, 0L)); mEditText.setText(R.string.create_key_custom); + mEditButton.setEnabled(false); } else { mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); @@ -192,115 +215,184 @@ public class CreateKeyFinalFragment extends Fragment { } } - private void createKey() { - Intent intent = new Intent(getActivity(), KeychainService.class); - intent.setAction(KeychainService.ACTION_EDIT_KEYRING); + final CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); - ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { + CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> createKeyCallback + = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - if (returnData == null) { - return; - } - final EditKeyResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - if (result == null) { - Log.e(Constants.TAG, "result == null"); - return; - } - - if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) { - // result will be displayed after upload - uploadKey(result); - } else { - Intent data = new Intent(); - data.putExtra(OperationResult.EXTRA_RESULT, result); - getActivity().setResult(Activity.RESULT_OK, data); - getActivity().finish(); - } + public SaveKeyringParcel createOperationInput() { + return mSaveKeyringParcel; + } + + @Override + public void onCryptoOperationSuccess(EditKeyResult result) { + + if (createKeyActivity.mCreateYubiKey) { + moveToCard(result); + return; + } + + if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) { + // result will be displayed after upload + uploadKey(result); + return; } + + Intent data = new Intent(); + data.putExtra(OperationResult.EXTRA_RESULT, result); + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(EditKeyResult result) { + result.createNotify(getActivity()).show(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; } }; - // fill values for this action - Bundle data = new Bundle(); + mCreateOpHelper = new CryptoOperationHelper<>(this, createKeyCallback, + R.string.progress_building_key); + mCreateOpHelper.cryptoOperation(); + } - // get selected key entries - data.putParcelable(KeychainService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel); + private void moveToCard(final EditKeyResult saveKeyResult) { + CachedPublicKeyRing key = (new ProviderHelper(getActivity())) + .getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); - intent.putExtra(KeychainService.EXTRA_DATA, data); + final SaveKeyringParcel changeKeyringParcel; + try { + changeKeyringParcel = new SaveKeyringParcel(key.getMasterKeyId(), key.getFingerprint()); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "Key that should be moved to YubiKey not found in database!"); + return; + } - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); + Cursor cursor = getActivity().getContentResolver().query( + KeychainContract.Keys.buildKeysUri(changeKeyringParcel.mMasterKeyId), + new String[]{KeychainContract.Keys.KEY_ID,}, null, null, null + ); + try { + while (cursor != null && cursor.moveToNext()) { + long subkeyId = cursor.getLong(0); + changeKeyringParcel.getOrCreateSubkeyChange(subkeyId).mMoveKeyToCard = true; + } + } finally { + if (cursor != null) { + cursor.close(); + } + } - saveHandler.showProgressDialog(getString(R.string.progress_building_key), - ProgressDialog.STYLE_HORIZONTAL, false); + CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> callback + = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { - getActivity().startService(intent); - } + @Override + public SaveKeyringParcel createOperationInput() { + return changeKeyringParcel; + } - // TODO move into EditKeyOperation - private void uploadKey(final EditKeyResult saveKeyResult) { - // Send all information needed to service to upload key in other thread - final Intent intent = new Intent(getActivity(), KeychainService.class); + @Override + public void onCryptoOperationSuccess(EditKeyResult result) { + handleResult(result); + } - intent.setAction(KeychainService.ACTION_UPLOAD_KEYRING); + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(EditKeyResult result) { + handleResult(result); + } + + public void handleResult(EditKeyResult result) { + // merge logs of createKey with moveToCard + saveKeyResult.getLog().add(result, 0); + + if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) { + // result will be displayed after upload + uploadKey(saveKeyResult); + return; + } + + Intent data = new Intent(); + data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult); + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + + + mMoveToCardOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_modify); + mMoveToCardOpHelper.cryptoOperation(); + } + private void uploadKey(final EditKeyResult saveKeyResult) { // set data uri as path to keyring - Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( + final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( saveKeyResult.mMasterKeyId); - intent.setData(blobUri); + // upload to favorite keyserver + final String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); - // fill values for this action - Bundle data = new Bundle(); + CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> callback + = new CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult>() { - // upload to favorite keyserver - String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); - data.putString(KeychainService.UPLOAD_KEY_SERVER, keyserver); + @Override + public ExportKeyringParcel createOperationInput() { + return new ExportKeyringParcel(keyserver, blobUri); + } - intent.putExtra(KeychainService.EXTRA_DATA, data); + @Override + public void onCryptoOperationSuccess(ExportResult result) { + handleResult(result); + } - ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // TODO: upload operation needs a result! - // TODO: then combine these results - //if (result.getResult() == OperationResultParcel.RESULT_OK) { - //Notify.create(getActivity(), R.string.key_send_success, - //Notify.Style.OK).show(); - - Intent data = new Intent(); - data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult); - getActivity().setResult(Activity.RESULT_OK, data); - getActivity().finish(); - } + public void onCryptoOperationCancelled() { + } - }; - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); + @Override + public void onCryptoOperationError(ExportResult result) { + handleResult(result); + } - // show progress dialog - saveHandler.showProgressDialog( - getString(R.string.progress_uploading), - ProgressDialog.STYLE_HORIZONTAL, false); + public void handleResult(ExportResult result) { + // TODO: ExportOperation UPLOAD_KEYSERVER needs logs! + // TODO: then merge logs here! + //saveKeyResult.getLog().add(result, 0); + + Intent data = new Intent(); + data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult); + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; - // start service with intent - getActivity().startService(intent); + mUploadOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_uploading); + mUploadOpHelper.cryptoOperation(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java index 1a844e6e4..cd97ef108 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -81,7 +81,7 @@ public class CreateKeyStartFragment extends Fragment { mYubiKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - CreateKeyYubiKeyWaitFragment frag = new CreateKeyYubiKeyWaitFragment(); + CreateYubiKeyWaitFragment frag = new CreateYubiKeyWaitFragment(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java new file mode 100644 index 000000000..5b13dc88e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; + +public class CreateYubiKeyBlankFragment extends Fragment { + + CreateKeyActivity mCreateKeyActivity; + View mBackButton; + View mNextButton; + + /** + * Creates new instance of this fragment + */ + public static CreateYubiKeyBlankFragment newInstance() { + CreateYubiKeyBlankFragment frag = new CreateYubiKeyBlankFragment(); + + Bundle args = new Bundle(); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubi_key_blank_fragment, container, false); + + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (getFragmentManager().getBackStackEntryCount() == 0) { + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } else { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + private void nextClicked() { + mCreateKeyActivity.mCreateYubiKey = true; + + CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java index 2ab8c5967..9897fa237 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java @@ -22,11 +22,8 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Intent; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -37,18 +34,19 @@ import android.widget.TextView; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; -public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListenerFragment { +public class CreateYubiKeyImportFragment + extends CryptoOperationFragment<ImportKeyringParcel, ImportKeyResult> + implements NfcListenerFragment { private static final String ARG_FINGERPRINT = "fingerprint"; public static final String ARG_AID = "aid"; @@ -64,9 +62,13 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe private TextView vSerNo; private TextView vUserId; - public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) { + // for CryptoOperationFragment key import + private String mKeyserver; + private ArrayList<ParcelableKeyRing> mKeyList; - CreateKeyYubiKeyImportFragment frag = new CreateKeyYubiKeyImportFragment(); + public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) { + + CreateYubiKeyImportFragment frag = new CreateYubiKeyImportFragment(); Bundle args = new Bundle(); args.putByteArray(ARG_FINGERPRINT, scannedFingerprints); @@ -95,7 +97,7 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.create_yubikey_import_fragment, container, false); + View view = inflater.inflate(R.layout.create_yubi_key_import_fragment, container, false); vSerNo = (TextView) view.findViewById(R.id.yubikey_serno); vUserId = (TextView) view.findViewById(R.id.yubikey_userid); @@ -175,77 +177,20 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe public void importKey() { - // Message is received after decrypting is done in KeychainService - ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - - ImportKeyResult result = - returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - - long[] masterKeyIds = result.getImportedMasterKeyIds(); - - // TODO handle masterKeyIds.length != 1...? sorta outlandish scenario - - if (!result.success() || masterKeyIds.length == 0) { - result.createNotify(getActivity()).show(); - return; - } - - Intent intent = new Intent(getActivity(), ViewKeyActivity.class); - // use the imported masterKeyId, not the one from the yubikey, because - // that one might* just have been a subkey of the imported key - intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0])); - intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); - startActivity(intent); - getActivity().finish(); - - } - - } - }; - - // Send all information needed to service to decrypt in other thread - Intent intent = new Intent(getActivity(), KeychainService.class); - - // fill values for this action - Bundle data = new Bundle(); - - intent.setAction(KeychainService.ACTION_IMPORT_KEYRING); - ArrayList<ParcelableKeyRing> keyList = new ArrayList<>(); keyList.add(new ParcelableKeyRing(mNfcFingerprint, null, null)); - data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keyList); + mKeyList = keyList; { Preferences prefs = Preferences.getPreferences(getActivity()); Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); + mKeyserver = cloudPrefs.keyserver; } - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); + super.setProgressMessageResource(R.string.progress_importing); - saveHandler.showProgressDialog( - getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL, false - ); - - // start service with intent - getActivity().startService(intent); + super.cryptoOperation(); } @@ -264,4 +209,29 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe refreshSearch(); } + + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(mKeyList, mKeyserver); + } + + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + long[] masterKeyIds = result.getImportedMasterKeyIds(); + if (masterKeyIds.length == 0) { + super.onCryptoOperationError(result); + return; + } + + Intent intent = new Intent(getActivity(), ViewKeyActivity.class); + // use the imported masterKeyId, not the one from the yubikey, because + // that one might* just have been a subkey of the imported key + intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0])); + intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + startActivity(intent); + getActivity().finish(); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java new file mode 100644 index 000000000..8744762fe --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; + +import java.security.SecureRandom; + +public class CreateYubiKeyPinFragment extends Fragment { + + // view + CreateKeyActivity mCreateKeyActivity; + TextView mPin; + TextView mAdminPin; + View mBackButton; + View mNextButton; + + /** + * Creates new instance of this fragment + */ + public static CreateYubiKeyPinFragment newInstance() { + CreateYubiKeyPinFragment frag = new CreateYubiKeyPinFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubi_key_pin_fragment, container, false); + + mPin = (TextView) view.findViewById(R.id.create_yubi_key_pin); + mAdminPin = (TextView) view.findViewById(R.id.create_yubi_key_admin_pin); + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + if (mCreateKeyActivity.mYubiKeyPin == null) { + new AsyncTask<Void, Void, Pair<String, String>>() { + @Override + protected Pair<String, String> doInBackground(Void... unused) { + SecureRandom secureRandom = new SecureRandom(); + // min = 6, we choose 6 + String pin = "" + secureRandom.nextInt(999999); + // min = 8, we choose 10, but 6 are equals the PIN + String adminPin = pin + secureRandom.nextInt(9999); + + return new Pair<>(pin, adminPin); + } + + @Override + protected void onPostExecute(Pair<String, String> pair) { + mCreateKeyActivity.mYubiKeyPin = pair.first; + mCreateKeyActivity.mYubiKeyAdminPin = pair.second; + + mPin.setText(mCreateKeyActivity.mYubiKeyPin); + mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin); + } + }.execute(); + } else { + mPin.setText(mCreateKeyActivity.mYubiKeyPin); + mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin); + } + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + back(); + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + + private void nextClicked() { + // save state +// mCreateKeyActivity.mPassphrase = new Passphrase(mPassphraseEdit); + + CreateYubiKeyPinRepeatFragment frag = CreateYubiKeyPinRepeatFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + + private void back() { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java new file mode 100644 index 000000000..dc437577a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; + +public class CreateYubiKeyPinRepeatFragment extends Fragment { + + // view + CreateKeyActivity mCreateKeyActivity; + EditText mPin; + EditText mAdminPin; + View mBackButton; + View mNextButton; + + /** + * Creates new instance of this fragment + */ + public static CreateYubiKeyPinRepeatFragment newInstance() { + CreateYubiKeyPinRepeatFragment frag = new CreateYubiKeyPinRepeatFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + /** + * Checks if text of given EditText is not empty. If it is empty an error is + * set and the EditText gets the focus. + * + * @param context + * @param editText + * @return true if EditText is not empty + */ + private static boolean isEditTextNotEmpty(Context context, EditText editText) { + boolean output = true; + if (editText.getText().length() == 0) { + editText.setError(context.getString(R.string.create_key_empty)); + editText.requestFocus(); + output = false; + } else { + editText.setError(null); + } + + return output; + } + + private static boolean checkPin(Context context, EditText editText1, String pin) { + boolean output = editText1.getText().toString().equals(pin); + + if (!output) { + editText1.setError(context.getString(R.string.create_key_yubi_key_pin_not_correct)); + editText1.requestFocus(); + } else { + editText1.setError(null); + } + + return output; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubi_key_pin_repeat_fragment, container, false); + + mPin = (EditText) view.findViewById(R.id.create_yubi_key_pin_repeat); + mAdminPin = (EditText) view.findViewById(R.id.create_yubi_key_admin_pin_repeat); + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + mPin.requestFocus(); + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + back(); + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + private void back() { + hideKeyboard(); + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + + private void nextClicked() { + if (isEditTextNotEmpty(getActivity(), mPin) + && checkPin(getActivity(), mPin, mCreateKeyActivity.mYubiKeyPin) + && isEditTextNotEmpty(getActivity(), mAdminPin) + && checkPin(getActivity(), mAdminPin, mCreateKeyActivity.mYubiKeyAdminPin)) { + + CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); + hideKeyboard(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + } + + private void hideKeyboard() { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java index 0b8586c0a..d45195512 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java @@ -28,14 +28,14 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -public class CreateKeyYubiKeyWaitFragment extends Fragment { +public class CreateYubiKeyWaitFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.create_yubikey_wait_fragment, container, false); + View view = inflater.inflate(R.layout.create_yubi_key_wait_fragment, container, false); mBackButton = view.findViewById(R.id.create_key_back_button); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 4375be740..04f54f151 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -120,7 +120,14 @@ public class DecryptActivity extends BaseActivity { case ACTION_DECRYPT_FROM_CLIPBOARD: { ClipboardManager clipMan = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + if (clipMan == null) { + break; + } + ClipData clip = clipMan.getPrimaryClip(); + if (clip == null) { + break; + } // check if data is available as uri Uri uri = null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index c8ae867b2..4eb8cd5e8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.support.v4.app.Fragment; +import android.os.Parcelable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -45,14 +46,14 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -78,6 +79,8 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. private DecryptVerifyResult mDecryptVerifyResult; private ViewAnimator mOverlayAnimator; + private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mImportOpHelper; + @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -135,43 +138,15 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. private void lookupUnknownKey(long unknownKeyId) { - // Message is received after importing is done in KeychainService - ServiceProgressHandler serviceHandler = new ServiceProgressHandler(getActivity()) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - - if (returnData == null) { - return; - } - - final ImportKeyResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - - Activity activity = getActivity(); - if (result != null && activity != null) { - result.createNotify(activity).show(); - } - - getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this); - } - } - }; - - // fill values for this action - Bundle data = new Bundle(); + final ArrayList<ParcelableKeyRing> keyList; + final String keyserver; // search config { Preferences prefs = Preferences.getPreferences(getActivity()); Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); + keyserver = cloudPrefs.keyserver; } { @@ -180,19 +155,43 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); - data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, selectedEntries); + keyList = selectedEntries; } - // Send all information needed to service to query keys in other thread - Intent intent = new Intent(getActivity(), KeychainService.class); - intent.setAction(KeychainService.ACTION_IMPORT_KEYRING); - intent.putExtra(KeychainService.EXTRA_DATA, data); + CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> callback + = new CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult>() { + + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(keyList, keyserver); + } + + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + result.createNotify(getActivity()).show(); + + getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this); + } + + @Override + public void onCryptoOperationCancelled() { + // do nothing + } + + @Override + public void onCryptoOperationError(ImportKeyResult result) { + result.createNotify(getActivity()).show(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(serviceHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); + mImportOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_importing); - getActivity().startService(intent); + mImportOpHelper.cryptoOperation(); } private void showKey(long keyId) { @@ -457,4 +456,11 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. protected abstract void onVerifyLoaded(boolean hideErrorOverlay); + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mImportOpHelper != null) { + mImportOpHelper.handleActivityResult(requestCode, resultCode, data); + } + super.onActivityResult(requestCode, resultCode, data); + } } 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 d2bff8336..96767463e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -254,18 +254,12 @@ public class DecryptListFragment } @Override - protected boolean onCryptoSetProgress(String msg, int progress, int max) { + public boolean onCryptoSetProgress(String msg, int progress, int max) { mAdapter.setProgress(mCurrentInputUri, progress, max, msg); return true; } - - @Override - protected void dismissProgress() { - // progress shown inline, so never mind - } - @Override - protected void onCryptoOperationError(DecryptVerifyResult result) { + public void onCryptoOperationError(DecryptVerifyResult result) { final Uri uri = mCurrentInputUri; mCurrentInputUri = null; @@ -275,7 +269,7 @@ public class DecryptListFragment } @Override - protected void onCryptoOperationSuccess(DecryptVerifyResult result) { + public void onCryptoOperationSuccess(DecryptVerifyResult result) { Uri uri = mCurrentInputUri; mCurrentInputUri = null; @@ -439,7 +433,7 @@ public class DecryptListFragment } @Override - protected PgpDecryptVerifyInputParcel createOperationInput() { + public PgpDecryptVerifyInputParcel createOperationInput() { if (mCurrentInputUri == null) { if (mPendingInputUris.isEmpty()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 48aa3016d..1363d44f2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -26,7 +25,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; -import android.os.Parcelable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -40,9 +38,6 @@ import android.widget.ListView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; -import org.sufficientlysecure.keychain.operations.results.InputPendingResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.SingletonResult; @@ -54,8 +49,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; @@ -65,7 +58,12 @@ import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; -import org.sufficientlysecure.keychain.ui.dialog.*; +import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; @@ -609,12 +607,12 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, } @Override - protected SaveKeyringParcel createOperationInput() { + public SaveKeyringParcel createOperationInput() { return mSaveKeyringParcel; } @Override - protected void onCryptoOperationSuccess(OperationResult result) { + public void onCryptoOperationSuccess(OperationResult result) { // if good -> finish, return result to showkey and display there! Intent intent = new Intent(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java index 590d02c6f..779b22535 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -29,6 +29,7 @@ import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -37,6 +38,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; import org.sufficientlysecure.keychain.util.FileHelper; @@ -92,15 +94,31 @@ public class EncryptDecryptOverviewFragment extends Fragment { mDecryptFromClipboard.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Intent clipboardDecrypt = new Intent(getActivity(), DecryptActivity.class); - clipboardDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_FROM_CLIPBOARD); - startActivityForResult(clipboardDecrypt, 0); + decryptFromClipboard(); } }); return view; } + private void decryptFromClipboard() { + + Activity activity = getActivity(); + if (activity == null) { + return; + } + + final CharSequence clipboardText = ClipboardReflection.getClipboardText(activity); + if (clipboardText == null || TextUtils.isEmpty(clipboardText)) { + Notify.create(activity, R.string.error_clipboard_empty, Style.ERROR).show(); + return; + } + + Intent clipboardDecrypt = new Intent(getActivity(), DecryptActivity.class); + clipboardDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_FROM_CLIPBOARD); + startActivityForResult(clipboardDecrypt, 0); + } + @Override public void onResume() { super.onResume(); 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 d7c6b2049..215af5885 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -386,7 +386,7 @@ public class EncryptFilesFragment } @Override - protected void onCryptoOperationSuccess(final SignEncryptResult result) { + public void onCryptoOperationSuccess(final SignEncryptResult result) { if (mDeleteAfterEncrypt) { // TODO make behavior coherent here @@ -485,7 +485,7 @@ public class EncryptFilesFragment } - protected SignEncryptParcel createOperationInput() { + public SignEncryptParcel createOperationInput() { SignEncryptParcel actionsParcel = getCachedActionsParcel(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java index e0629eb73..886c52651 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java @@ -202,7 +202,7 @@ public class EncryptTextFragment } - protected SignEncryptParcel createOperationInput() { + public SignEncryptParcel createOperationInput() { if (mMessage == null || mMessage.isEmpty()) { Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR) @@ -316,7 +316,7 @@ public class EncryptTextFragment } @Override - protected void onCryptoOperationSuccess(SignEncryptResult result) { + public void onCryptoOperationSuccess(SignEncryptResult result) { if (mShareAfterEncrypt) { // Share encrypted message/file 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 07ab88b02..ba4f759e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -17,12 +17,10 @@ package org.sufficientlysecure.keychain.ui; -import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Message; -import android.os.Messenger; import android.support.v4.app.Fragment; import android.view.View; import android.view.View.OnClickListener; @@ -35,9 +33,10 @@ 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.KeychainService; +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; import org.sufficientlysecure.keychain.util.Log; @@ -47,7 +46,8 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize import java.io.IOException; import java.util.ArrayList; -public class ImportKeysActivity extends BaseNfcActivity { +public class ImportKeysActivity extends BaseNfcActivity + implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER; @@ -82,6 +82,12 @@ public class ImportKeysActivity extends BaseNfcActivity { private Fragment mTopFragment; private View mImportButton; + // for CryptoOperationHelper.Callback + private String mKeyserver; + private ArrayList<ParcelableKeyRing> mKeyList; + + private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -388,23 +394,9 @@ public class ImportKeysActivity extends BaseNfcActivity { return; } - ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - ImportKeysActivity.this.handleMessage(message); - } - }; - - // Send all information needed to service to import key in other thread - Intent intent = new Intent(this, KeychainService.class); - - intent.setAction(KeychainService.ACTION_IMPORT_KEYRING); - - // fill values for this action - Bundle data = new Bundle(); + mOperationHelper = new CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult>( + this, this, R.string.progress_importing + ); ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState(); if (ls instanceof ImportKeysListFragment.BytesLoaderState) { @@ -423,30 +415,18 @@ public class ImportKeysActivity extends BaseNfcActivity { new ParcelableFileCache<>(this, "key_import.pcl"); cache.writeCache(selectedEntries); - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(serviceHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - // show progress dialog - serviceHandler.showProgressDialog( - getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL, - true - ); + mKeyList = null; + mKeyserver = null; + mOperationHelper.cryptoOperation(); - // start service with intent - startService(intent); } catch (IOException e) { Log.e(Constants.TAG, "Problem writing cache file", e); Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR) .show((ViewGroup) findViewById(R.id.import_snackbar)); } } else if (ls instanceof ImportKeysListFragment.CloudLoaderState) { - ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls; - - data.putString(KeychainService.IMPORT_KEY_SERVER, sls.mCloudPrefs.keyserver); + ImportKeysListFragment.CloudLoaderState sls = + (ImportKeysListFragment.CloudLoaderState) ls; // get selected key entries ArrayList<ParcelableKeyRing> keys = new ArrayList<>(); @@ -459,22 +439,11 @@ public class ImportKeysActivity extends BaseNfcActivity { ); } } - data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keys); - - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(serviceHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - // show progress dialog - serviceHandler.showProgressDialog( - getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL, true - ); + mKeyList = keys; + mKeyserver = sls.mCloudPrefs.keyserver; + mOperationHelper.cryptoOperation(); - // start service with intent - startService(intent); } } @@ -485,4 +454,57 @@ public class ImportKeysActivity extends BaseNfcActivity { // either way, finish afterwards finish(); } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mOperationHelper == null || + !mOperationHelper.handleActivityResult(requestCode, resultCode, data)) { + 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())) { + 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)); + } + // methods from CryptoOperationHelper.Callback + + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(mKeyList, mKeyserver); + } + + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + handleResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + // do nothing + } + + @Override + public void onCryptoOperationError(ImportKeyResult result) { + handleResult(result); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java index 9f3beff43..da713d0d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; -import android.app.ProgressDialog; import android.content.Intent; import android.content.pm.ActivityInfo; import android.net.Uri; @@ -26,8 +25,6 @@ import android.nfc.NdefMessage; import android.nfc.NfcAdapter; import android.os.Build; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.os.Parcelable; import android.support.v4.app.FragmentActivity; import android.widget.Toast; @@ -43,8 +40,8 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.SingletonResult; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; @@ -55,7 +52,8 @@ import java.util.Locale; /** * Proxy activity (just a transparent content view) to scan QR Codes using the Barcode Scanner app */ -public class ImportKeysProxyActivity extends FragmentActivity { +public class ImportKeysProxyActivity extends FragmentActivity + implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { public static final String ACTION_QR_CODE_API = OpenKeychainIntents.IMPORT_KEY_FROM_QR_CODE; // implies activity returns scanned fingerprint as extra and does not import @@ -64,6 +62,11 @@ public class ImportKeysProxyActivity extends FragmentActivity { public static final String EXTRA_FINGERPRINT = "fingerprint"; + // for CryptoOperationHelper + private String mKeyserver; + private ArrayList<ParcelableKeyRing> mKeyList; + private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mImportOpHelper; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -106,6 +109,10 @@ public class ImportKeysProxyActivity extends FragmentActivity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mImportOpHelper != null) { + mImportOpHelper.cryptoOperation(); + } + if (requestCode == IntentIntegratorSupportV4.REQUEST_CODE) { IntentResult scanResult = IntentIntegratorSupportV4.parseActivityResult(requestCode, resultCode, data); @@ -205,75 +212,56 @@ public class ImportKeysProxyActivity extends FragmentActivity { private void startImportService(ArrayList<ParcelableKeyRing> keyRings) { - // Message is received after importing is done in KeychainService - ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - if (returnData == null) { - finish(); - return; - } - final ImportKeyResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - if (result == null) { - Log.e(Constants.TAG, "result == null"); - finish(); - return; - } - - if (!result.success()) { - // only return if no success... - Intent data = new Intent(); - data.putExtras(returnData); - returnResult(data); - return; - } - - Intent certifyIntent = new Intent(ImportKeysProxyActivity.this, - CertifyKeyActivity.class); - certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result); - certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, - result.getImportedMasterKeyIds()); - startActivityForResult(certifyIntent, 0); - } - } - }; - - // fill values for this action - Bundle data = new Bundle(); - // search config { Preferences prefs = Preferences.getPreferences(this); Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); + mKeyserver = cloudPrefs.keyserver; } - data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keyRings); + mKeyList = keyRings; + + mImportOpHelper = new CryptoOperationHelper<>(this, this, R.string.progress_importing); + + mImportOpHelper.cryptoOperation(); + } + + + // CryptoOperationHelper.Callback methods - // Send all information needed to service to query keys in other thread - Intent intent = new Intent(this, KeychainService.class); - intent.setAction(KeychainService.ACTION_IMPORT_KEYRING); - intent.putExtra(KeychainService.EXTRA_DATA, data); + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(mKeyList, mKeyserver); + } - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(serviceHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + Intent certifyIntent = new Intent(this, CertifyKeyActivity.class); + certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result); + certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, + result.getImportedMasterKeyIds()); + startActivityForResult(certifyIntent, 0); + } - // show progress dialog - serviceHandler.showProgressDialog( - getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL, true); + @Override + public void onCryptoOperationCancelled() { - // start service with intent - startService(intent); + } + + @Override + public void onCryptoOperationError(ImportKeyResult result) { + Bundle returnData = new Bundle(); + returnData.putParcelable(OperationResult.EXTRA_RESULT, result); + Intent data = new Intent(); + data.putExtras(returnData); + returnResult(data); + return; + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 780558b27..e038cf948 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -64,9 +63,10 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; @@ -90,7 +90,8 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersListView; */ public class KeyListFragment extends LoaderFragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, - LoaderManager.LoaderCallbacks<Cursor>, FabContainer { + LoaderManager.LoaderCallbacks<Cursor>, FabContainer, + CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { static final int REQUEST_REPEAT_PASSPHRASE = 1; static final int REQUEST_ACTION = 2; @@ -107,6 +108,14 @@ public class KeyListFragment extends LoaderFragment private FloatingActionsMenu mFab; + // for CryptoOperationHelper import + private ArrayList<ParcelableKeyRing> mKeyList; + private String mKeyserver; + private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mImportOpHelper; + + // for ConsolidateOperation + private CryptoOperationHelper<ConsolidateInputParcel, ConsolidateResult> mConsolidateOpHelper; + // This ids for multiple key export. private ArrayList<Long> mIdsForRepeatAskPassphrase; // This index for remembering the number of master key. @@ -580,112 +589,59 @@ public class KeyListFragment extends LoaderFragment ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); keyList.add(keyEntry); } + mKeyList = keyList; } finally { cursor.close(); } - ServiceProgressHandler serviceHandler = new ServiceProgressHandler(getActivity()) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == 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; - } - - result.createNotify(getActivity()).show(); - } - } - }; - - // Send all information needed to service to query keys in other thread - Intent intent = new Intent(getActivity(), KeychainService.class); - intent.setAction(KeychainService.ACTION_IMPORT_KEYRING); - - // fill values for this action - Bundle data = new Bundle(); - // search config { Preferences prefs = Preferences.getPreferences(getActivity()); Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); + mKeyserver = cloudPrefs.keyserver; } - data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keyList); - - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(serviceHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - // show progress dialog - serviceHandler.showProgressDialog( - getString(R.string.progress_updating), - ProgressDialog.STYLE_HORIZONTAL, true); - - // start service with intent - getActivity().startService(intent); + mImportOpHelper = new CryptoOperationHelper<>(this, + this, R.string.progress_updating); + mImportOpHelper.cryptoOperation(); } private void consolidate() { - // Message is received after importing is done in KeychainService - ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - if (returnData == null) { - return; - } - final ConsolidateResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - if (result == null) { - return; - } - result.createNotify(getActivity()).show(); - } + CryptoOperationHelper.Callback<ConsolidateInputParcel, ConsolidateResult> callback + = new CryptoOperationHelper.Callback<ConsolidateInputParcel, ConsolidateResult>() { + + @Override + public ConsolidateInputParcel createOperationInput() { + return new ConsolidateInputParcel(false); // we want to perform a full consolidate } - }; - // Send all information needed to service to import key in other thread - Intent intent = new Intent(getActivity(), KeychainService.class); + @Override + public void onCryptoOperationSuccess(ConsolidateResult result) { + result.createNotify(getActivity()).show(); + } - intent.setAction(KeychainService.ACTION_CONSOLIDATE); + @Override + public void onCryptoOperationCancelled() { - // fill values for this action - Bundle data = new Bundle(); + } - intent.putExtra(KeychainService.EXTRA_DATA, data); + @Override + public void onCryptoOperationError(ConsolidateResult result) { + result.createNotify(getActivity()).show(); + } - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; - // show progress dialog - saveHandler.showProgressDialog( - getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL, false); + mConsolidateOpHelper = + new CryptoOperationHelper<>(this, callback, R.string.progress_importing); - // start service with intent - getActivity().startService(intent); + mConsolidateOpHelper.cryptoOperation(); } private void showMultiExportDialog(long[] masterKeyIds) { @@ -724,6 +680,14 @@ public class KeyListFragment extends LoaderFragment @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mImportOpHelper != null) { + mImportOpHelper.handleActivityResult(requestCode, resultCode, data); + } + + if (mConsolidateOpHelper != null) { + mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data); + } + if (requestCode == REQUEST_REPEAT_PASSPHRASE) { if (resultCode != Activity.RESULT_OK) { return; @@ -769,6 +733,32 @@ public class KeyListFragment extends LoaderFragment anim.start(); } + // CryptoOperationHelper.Callback methods + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(mKeyList, mKeyserver); + } + + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + result.createNotify(getActivity()).show(); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(ImportKeyResult result) { + result.createNotify(getActivity()).show(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter { private HashMap<Integer, Boolean> mSelection = new HashMap<>(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index c5fc9abe0..addfb6a23 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -63,7 +63,7 @@ public class NfcOperationActivity extends BaseNfcActivity { mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT); // obtain passphrase for this subkey - if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_KEYTOCARD) { + if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD) { obtainYubiKeyPin(mRequiredInput); } } @@ -96,7 +96,7 @@ public class NfcOperationActivity extends BaseNfcActivity { } break; } - case NFC_KEYTOCARD: { + case NFC_MOVE_KEY_TO_CARD: { ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index 7408135ae..a1cb77546 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -18,15 +18,11 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; -import android.app.ProgressDialog; import android.content.Intent; import android.graphics.PorterDuff; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.support.v4.app.FragmentActivity; import android.view.View; import android.widget.ImageView; import android.widget.NumberPicker; @@ -38,9 +34,9 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainService; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; @@ -52,7 +48,8 @@ import edu.cmu.cylab.starslinger.exchange.ExchangeActivity; import edu.cmu.cylab.starslinger.exchange.ExchangeConfig; @TargetApi(Build.VERSION_CODES.HONEYCOMB) -public class SafeSlingerActivity extends BaseActivity { +public class SafeSlingerActivity extends BaseActivity + implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { private static final int REQUEST_CODE_SAFE_SLINGER = 211; @@ -61,6 +58,12 @@ public class SafeSlingerActivity extends BaseActivity { private long mMasterKeyId; private int mSelectedNumber = 2; + // for CryptoOperationHelper + private ArrayList<ParcelableKeyRing> mKeyList; + private String mKeyserver; + private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper; + + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -116,65 +119,17 @@ public class SafeSlingerActivity extends BaseActivity { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mOperationHelper != null) { + mOperationHelper.handleActivityResult(requestCode, resultCode, data); + } + if (requestCode == REQUEST_CODE_SAFE_SLINGER) { if (resultCode == ExchangeActivity.RESULT_EXCHANGE_CANCELED) { return; } - final FragmentActivity activity = SafeSlingerActivity.this; - - // Message is received after importing is done in KeychainService - ServiceProgressHandler saveHandler = new ServiceProgressHandler(activity) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == 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; - } - - if (!result.success()) { -// result.createNotify(activity).show(); - // only return if no success... - Intent data = new Intent(); - data.putExtras(returnData); - setResult(RESULT_OK, data); - finish(); - return; - } - -// if (mExchangeMasterKeyId == null) { -// return; -// } - - Intent certifyIntent = new Intent(activity, CertifyKeyActivity.class); - certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result); - certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds()); - certifyIntent.putExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, mMasterKeyId); - startActivityForResult(certifyIntent, 0); - -// mExchangeMasterKeyId = null; - } - } - }; - Log.d(Constants.TAG, "importKeys started"); - // Send all information needed to service to import key in other thread - Intent intent = new Intent(activity, KeychainService.class); - - intent.setAction(KeychainService.ACTION_IMPORT_KEYRING); - // instead of giving the entries by Intent extra, cache them into a // file to prevent Java Binder problems on heavy imports // read FileImportCache for more info. @@ -185,28 +140,18 @@ public class SafeSlingerActivity extends BaseActivity { // We parcel this iteratively into a file - anything we can // display here, we should be able to import. ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<>(activity, "key_import.pcl"); + new ParcelableFileCache<>(this, "key_import.pcl"); cache.writeCache(it.size(), it.iterator()); - // fill values for this action - Bundle bundle = new Bundle(); - intent.putExtra(KeychainService.EXTRA_DATA, bundle); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog( - getString(R.string.progress_importing), - ProgressDialog.STYLE_HORIZONTAL, true - ); + mOperationHelper = + new CryptoOperationHelper(this, this, R.string.progress_importing); - // start service with intent - activity.startService(intent); + mKeyList = null; + mKeyserver = null; + mOperationHelper.cryptoOperation(); } catch (IOException e) { Log.e(Constants.TAG, "Problem writing cache file", e); - Notify.create(activity, "Problem writing cache file!", Notify.Style.ERROR).show(); + Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR).show(); } } else { // give everything else down to KeyListActivity! @@ -233,4 +178,39 @@ public class SafeSlingerActivity extends BaseActivity { return list; } + // CryptoOperationHelper.Callback functions + + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(mKeyList, mKeyserver); + } + + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + Intent certifyIntent = new Intent(this, CertifyKeyActivity.class); + certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result); + certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds()); + certifyIntent.putExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, mMasterKeyId); + startActivityForResult(certifyIntent, 0); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(ImportKeyResult result) { + Bundle returnData = new Bundle(); + returnData.putParcelable(OperationResult.EXTRA_RESULT, result); + Intent data = new Intent(); + data.putExtras(returnData); + setResult(RESULT_OK, data); + finish(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index 442bdf8f7..6fc0aaac6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> * * This program is free software: you can redistribute it and/or modify @@ -17,14 +18,11 @@ package org.sufficientlysecure.keychain.ui; -import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.Preference; -import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; import android.support.v7.widget.Toolbar; @@ -32,15 +30,16 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; -import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity; import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference; +import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import java.util.List; -public class SettingsActivity extends PreferenceActivity { +public class SettingsActivity extends AppCompatPreferenceActivity { public static final String ACTION_PREFS_CLOUD = "org.sufficientlysecure.keychain.ui.PREFS_CLOUD"; public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV"; @@ -91,22 +90,6 @@ public class SettingsActivity extends PreferenceActivity { initializePassphraseCacheTtl( (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); - int[] valueIds = new int[]{ - CompressionAlgorithmTags.UNCOMPRESSED, - CompressionAlgorithmTags.ZIP, - CompressionAlgorithmTags.ZLIB, - CompressionAlgorithmTags.BZIP2, - }; - String[] entries = new String[]{ - getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")", - "ZIP (" + getString(R.string.compression_fast) + ")", - "ZLIB (" + getString(R.string.compression_fast) + ")", - "BZIP2 (" + getString(R.string.compression_very_slow) + ")",}; - String[] values = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - initializeUseDefaultYubiKeyPin( (CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN)); @@ -122,13 +105,14 @@ public class SettingsActivity extends PreferenceActivity { private void setupToolbar() { ViewGroup root = (ViewGroup) findViewById(android.R.id.content); LinearLayout content = (LinearLayout) root.getChildAt(0); - LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.preference_toolbar_activity, null); + LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.preference_toolbar, null); root.removeAllViews(); toolbarContainer.addView(content); root.addView(toolbarContainer); Toolbar toolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar); + toolbar.setTitle(R.string.title_preferences); toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp)); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @@ -141,34 +125,13 @@ public class SettingsActivity extends PreferenceActivity { } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_KEYSERVER_PREF: { - if (resultCode == RESULT_CANCELED || data == null) { - return; - } - String servers[] = data - .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS); - sPreferences.setKeyServers(servers); - mKeyServerPreference.setSummary(keyserverSummary(this)); - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - break; - } - } - } - - @Override public void onBuildHeaders(List<Header> target) { super.onBuildHeaders(target); loadHeadersFromResource(R.xml.preference_headers, target); } /** - * This fragment shows the Cloud Search preferences in android 3.0+ + * This fragment shows the Cloud Search preferences */ public static class CloudSearchPrefsFragment extends PreferenceFragment { @@ -207,12 +170,7 @@ public class SettingsActivity extends PreferenceActivity { public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_KEYSERVER_PREF: { - if (resultCode == RESULT_CANCELED || data == null) { - return; - } - String servers[] = data - .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS); - sPreferences.setKeyServers(servers); + // update preference, in case it changed mKeyServerPreference.setSummary(keyserverSummary(getActivity())); break; } @@ -226,7 +184,7 @@ public class SettingsActivity extends PreferenceActivity { } /** - * This fragment shows the advanced preferences in android 3.0+ + * This fragment shows the PIN/password preferences */ public static class AdvancedPrefsFragment extends PreferenceFragment { @@ -243,25 +201,6 @@ public class SettingsActivity extends PreferenceActivity { initializePassphraseCacheTtl( (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); - int[] valueIds = new int[]{ - CompressionAlgorithmTags.UNCOMPRESSED, - CompressionAlgorithmTags.ZIP, - CompressionAlgorithmTags.ZLIB, - CompressionAlgorithmTags.BZIP2, - }; - - String[] entries = new String[]{ - getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")", - "ZIP (" + getString(R.string.compression_fast) + ")", - "ZLIB (" + getString(R.string.compression_fast) + ")", - "BZIP2 (" + getString(R.string.compression_very_slow) + ")", - }; - - String[] values = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - initializeUseDefaultYubiKeyPin( (CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN)); @@ -270,7 +209,6 @@ public class SettingsActivity extends PreferenceActivity { } } - @TargetApi(Build.VERSION_CODES.KITKAT) protected boolean isValidFragment(String fragmentName) { return AdvancedPrefsFragment.class.getName().equals(fragmentName) || CloudSearchPrefsFragment.class.getName().equals(fragmentName) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java index dc203756f..f61ada84f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java @@ -17,89 +17,23 @@ package org.sufficientlysecure.keychain.ui; -import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.ui.dialog.AddKeyserverDialogFragment; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.widget.Editor; -import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; -import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor; -import java.util.Vector; - -public class SettingsKeyServerActivity extends BaseActivity implements OnClickListener, - EditorListener { +public class SettingsKeyServerActivity extends BaseActivity { public static final String EXTRA_KEY_SERVERS = "key_servers"; - private LayoutInflater mInflater; - private ViewGroup mEditors; - private View mAdd; - private View mRotate; - private TextView mTitle; - private TextView mSummary; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Inflate a "Done"/"Cancel" custom action bar view - setFullScreenDialogDoneClose(R.string.btn_save, - new View.OnClickListener() { - @Override - public void onClick(View v) { - okClicked(); - } - }, - new View.OnClickListener() { - @Override - public void onClick(View v) { - cancelClicked(); - } - }); - - mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - mTitle = (TextView) findViewById(R.id.title); - mSummary = (TextView) findViewById(R.id.summary); - mSummary.setText(getText(R.string.label_first_keyserver_is_used)); - - mTitle.setText(R.string.label_keyservers); - - mEditors = (ViewGroup) findViewById(R.id.editors); - mAdd = findViewById(R.id.add); - mAdd.setOnClickListener(this); - - mRotate = findViewById(R.id.rotate); - mRotate.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - Vector<String> servers = serverList(); - String first = servers.get(0); - if (first != null) { - servers.remove(0); - servers.add(first); - String[] dummy = {}; - makeServerList(servers.toArray(dummy)); - } - } - }); - Intent intent = getIntent(); String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS); - makeServerList(servers); + loadFragment(savedInstanceState, servers); } @Override @@ -107,118 +41,22 @@ public class SettingsKeyServerActivity extends BaseActivity implements OnClickLi setContentView(R.layout.key_server_preference); } - private void makeServerList(String[] servers) { - if (servers != null) { - mEditors.removeAllViews(); - for (String serv : servers) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate( - R.layout.key_server_editor, mEditors, false); - view.setEditorListener(this); - view.setValue(serv); - mEditors.addView(view); - } + private void loadFragment(Bundle savedInstanceState, String[] keyservers) { + // 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 (savedInstanceState != null) { + return; } - } - - public void onDeleted(Editor editor, boolean wasNewItem) { - // nothing to do - } - @Override - public void onEdited() { + SettingsKeyserverFragment fragment = SettingsKeyserverFragment.newInstance(keyservers); - } - - // button to add keyserver clicked - public void onClick(View v) { - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - Bundle data = message.getData(); - switch (message.what) { - case AddKeyserverDialogFragment.MESSAGE_OKAY: { - boolean verified = data.getBoolean(AddKeyserverDialogFragment.MESSAGE_VERIFIED); - if (verified) { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_verified, Notify.Style.OK).show(); - } else { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_without_verification, - Notify.Style.WARN).show(); - } - String keyserver = data.getString(AddKeyserverDialogFragment.MESSAGE_KEYSERVER); - addKeyserver(keyserver); - break; - } - case AddKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: { - AddKeyserverDialogFragment.FailureReason failureReason = - (AddKeyserverDialogFragment.FailureReason) data.getSerializable( - AddKeyserverDialogFragment.MESSAGE_FAILURE_REASON); - switch (failureReason) { - case CONNECTION_FAILED: { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_connection_failed, - Notify.Style.ERROR).show(); - break; - } - case INVALID_URL: { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_invalid_url, - Notify.Style.ERROR).show(); - break; - } - } - break; - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - AddKeyserverDialogFragment dialogFragment = AddKeyserverDialogFragment - .newInstance(messenger); - dialogFragment.show(getSupportFragmentManager(), "addKeyserverDialog"); - } - - public void addKeyserver(String keyserverUrl) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, - mEditors, false); - view.setEditorListener(this); - view.setValue(keyserverUrl); - mEditors.addView(view); - } - - private void cancelClicked() { - setResult(RESULT_CANCELED, null); - finish(); - } - - private Vector<String> serverList() { - Vector<String> servers = new Vector<>(); - for (int i = 0; i < mEditors.getChildCount(); ++i) { - KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); - String tmp = editor.getValue(); - if (tmp.length() > 0) { - servers.add(tmp); - } - } - return servers; - } - - private void okClicked() { - Intent data = new Intent(); - Vector<String> servers = new Vector<>(); - for (int i = 0; i < mEditors.getChildCount(); ++i) { - KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); - String tmp = editor.getValue(); - if (tmp.length() > 0) { - servers.add(tmp); - } - } - String[] dummy = new String[0]; - data.putExtra(EXTRA_KEY_SERVERS, servers.toArray(dummy)); - setResult(RESULT_OK, data); - finish(); + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.keyserver_settings_fragment_container, fragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java new file mode 100644 index 000000000..2ae64d90b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * 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.ui; + +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.support.v4.view.MotionEventCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment; +import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter; +import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder; +import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; +import org.sufficientlysecure.keychain.util.Preferences; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SettingsKeyserverFragment extends Fragment implements RecyclerItemClickListener.OnItemClickListener { + + private static final String ARG_KEYSERVER_ARRAY = "arg_keyserver_array"; + private ItemTouchHelper mItemTouchHelper; + + private ArrayList<String> mKeyservers; + private KeyserverListAdapter mAdapter; + + public static SettingsKeyserverFragment newInstance(String[] keyservers) { + Bundle args = new Bundle(); + args.putStringArray(ARG_KEYSERVER_ARRAY, keyservers); + + SettingsKeyserverFragment fragment = new SettingsKeyserverFragment(); + fragment.setArguments(args); + + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle + savedInstanceState) { + + return inflater.inflate(R.layout.settings_keyserver_fragment, null); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + String keyservers[] = getArguments().getStringArray(ARG_KEYSERVER_ARRAY); + mKeyservers = new ArrayList<>(Arrays.asList(keyservers)); + + mAdapter = new KeyserverListAdapter(mKeyservers); + + RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.keyserver_recycler_view); + // recyclerView.setHasFixedSize(true); // the size of the first item changes + recyclerView.setAdapter(mAdapter); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + + + ItemTouchHelper.Callback callback = new ItemTouchHelperDragCallback(mAdapter); + mItemTouchHelper = new ItemTouchHelper(callback); + mItemTouchHelper.attachToRecyclerView(recyclerView); + + // for clicks + recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), this)); + + // can't use item decoration because it doesn't move with drag and drop + // recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null)); + + // We have a menu item to show in action bar. + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + inflater.inflate(R.menu.keyserver_pref_menu, menu); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case R.id.menu_add_keyserver: + startAddKeyserverDialog(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + private void startAddKeyserverDialog() { + // keyserver and position have no meaning + startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.ADD, null, -1); + } + + private void startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction action, + String keyserver, final int position) { + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + Bundle data = message.getData(); + switch (message.what) { + case AddEditKeyserverDialogFragment.MESSAGE_OKAY: { + boolean deleted = + data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER_DELETED + , false); + if (deleted) { + Notify.create(getActivity(), + getActivity().getString( + R.string.keyserver_preference_deleted, mKeyservers.get(position)), + Notify.Style.OK) + .show(); + deleteKeyserver(position); + return; + } + boolean verified = + data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_VERIFIED); + if (verified) { + Notify.create(getActivity(), + R.string.add_keyserver_verified, Notify.Style.OK).show(); + } else { + Notify.create(getActivity(), + R.string.add_keyserver_without_verification, + Notify.Style.WARN).show(); + } + String keyserver = data.getString( + AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER); + + AddEditKeyserverDialogFragment.DialogAction dialogAction + = (AddEditKeyserverDialogFragment.DialogAction) data.getSerializable( + AddEditKeyserverDialogFragment.MESSAGE_DIALOG_ACTION); + switch (dialogAction) { + case ADD: + addKeyserver(keyserver); + break; + case EDIT: + editKeyserver(keyserver, position); + break; + } + break; + } + case AddEditKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: { + AddEditKeyserverDialogFragment.FailureReason failureReason = + (AddEditKeyserverDialogFragment.FailureReason) data.getSerializable( + AddEditKeyserverDialogFragment.MESSAGE_FAILURE_REASON); + switch (failureReason) { + case CONNECTION_FAILED: { + Notify.create(getActivity(), + R.string.add_keyserver_connection_failed, + Notify.Style.ERROR).show(); + break; + } + case INVALID_URL: { + Notify.create(getActivity(), + R.string.add_keyserver_invalid_url, + Notify.Style.ERROR).show(); + break; + } + } + break; + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + AddEditKeyserverDialogFragment dialogFragment = AddEditKeyserverDialogFragment + .newInstance(messenger, action, keyserver, position); + dialogFragment.show(getFragmentManager(), "addKeyserverDialog"); + } + + private void addKeyserver(String keyserver) { + mKeyservers.add(keyserver); + mAdapter.notifyItemInserted(mKeyservers.size() - 1); + saveKeyserverList(); + } + + private void editKeyserver(String newKeyserver, int position) { + mKeyservers.set(position, newKeyserver); + mAdapter.notifyItemChanged(position); + saveKeyserverList(); + } + + private void deleteKeyserver(int position) { + if (mKeyservers.size() == 1) { + Notify.create(getActivity(), R.string.keyserver_preference_cannot_delete_last, + Notify.Style.ERROR).show(); + return; + } + mKeyservers.remove(position); + // we use this + mAdapter.notifyItemRemoved(position); + if (position == 0 && mKeyservers.size() > 0) { + // if we deleted the first item, we need the adapter to redraw the new first item + mAdapter.notifyItemChanged(0); + } + saveKeyserverList(); + } + + private void saveKeyserverList() { + String servers[] = mKeyservers.toArray(new String[mKeyservers.size()]); + Preferences.getPreferences(getActivity()).setKeyServers(servers); + } + + @Override + public void onItemClick(View view, int position) { + startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.EDIT, + mKeyservers.get(position), position); + } + + public class KeyserverListAdapter extends RecyclerView.Adapter<KeyserverListAdapter.ViewHolder> + implements ItemTouchHelperAdapter { + + // to update the ViewHolder associated with first item, for when an item is deleted + private ViewHolder mFirstItem; + + private final List<String> mKeyservers; + + public KeyserverListAdapter(List<String> keyservers) { + mKeyservers = keyservers; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.settings_keyserver_item, parent, false); + ViewHolder viewHolder = new ViewHolder(view); + return viewHolder; + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int position) { + if (position == 0) { + mFirstItem = holder; + } + holder.keyserverUrl.setText(mKeyservers.get(position)); + + // Start a drag whenever the handle view it touched + holder.dragHandleView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { + mItemTouchHelper.startDrag(holder); + } + return false; + } + }); + + selectUnselectKeyserver(holder, position); + } + + private void selectUnselectKeyserver(ViewHolder holder, int position) { + + if (position == 0) { + holder.showAsSelectedKeyserver(); + } else { + holder.showAsUnselectedKeyserver(); + } + } + + @Override + public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target, + int fromPosition, int toPosition) { + Collections.swap(mKeyservers, fromPosition, toPosition); + saveKeyserverList(); + selectUnselectKeyserver((ViewHolder) target, fromPosition); + // we don't want source to change color while dragging, therefore we just set + // isSelectedKeyserver instead of selectUnselectKeyserver + ((ViewHolder) source).isSelectedKeyserver = toPosition == 0; + + notifyItemMoved(fromPosition, toPosition); + } + + @Override + public int getItemCount() { + return mKeyservers.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder implements + ItemTouchHelperViewHolder { + + public final ViewGroup outerLayout; + public final TextView selectedServerLabel; + public final TextView keyserverUrl; + public final ImageView dragHandleView; + + private boolean isSelectedKeyserver = false; + + public ViewHolder(View itemView) { + super(itemView); + outerLayout = (ViewGroup) itemView.findViewById(R.id.outer_layout); + selectedServerLabel = (TextView) itemView.findViewById( + R.id.selected_keyserver_title); + keyserverUrl = (TextView) itemView.findViewById(R.id.keyserver_tv); + dragHandleView = (ImageView) itemView.findViewById(R.id.drag_handle); + + itemView.setClickable(true); + } + + public void showAsSelectedKeyserver() { + isSelectedKeyserver = true; + selectedServerLabel.setVisibility(View.VISIBLE); + outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark)); + } + + public void showAsUnselectedKeyserver() { + isSelectedKeyserver = false; + selectedServerLabel.setVisibility(View.GONE); + outerLayout.setBackgroundColor(Color.WHITE); + } + + @Override + public void onItemSelected() { + selectedServerLabel.setVisibility(View.GONE); + itemView.setBackgroundColor(Color.LTGRAY); + } + + @Override + public void onItemClear() { + if (isSelectedKeyserver) { + showAsSelectedKeyserver(); + } else { + showAsUnselectedKeyserver(); + } + } + } + } +} 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 e4dff6083..8b49f3b96 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -17,12 +17,9 @@ package org.sufficientlysecure.keychain.ui; -import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.NavUtils; import android.view.MenuItem; import android.view.View; @@ -33,23 +30,30 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.service.KeychainService; +import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; /** * Sends the selected public key to a keyserver */ -public class UploadKeyActivity extends BaseActivity { +public class UploadKeyActivity extends BaseActivity + implements CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> { private View mUploadButton; private Spinner mKeyServerSpinner; private Uri mDataUri; + // CryptoOperationHelper.Callback vars + private String mKeyserver; + private Uri mUnifiedKeyringUri; + private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -89,51 +93,23 @@ public class UploadKeyActivity extends BaseActivity { setContentView(R.layout.upload_key_activity); } - private void uploadKey() { - // Send all information needed to service to upload key in other thread - Intent intent = new Intent(this, KeychainService.class); - - intent.setAction(KeychainService.ACTION_UPLOAD_KEYRING); + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mUploadOpHelper != null) { + mUploadOpHelper.handleActivityResult(requestCode, resultCode, data); + } + super.onActivityResult(requestCode, resultCode, data); + } - // set data uri as path to keyring + private void uploadKey() { Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); - intent.setData(blobUri); - - // fill values for this action - Bundle data = new Bundle(); + mUnifiedKeyringUri = blobUri; String server = (String) mKeyServerSpinner.getSelectedItem(); - data.putString(KeychainService.UPLOAD_KEY_SERVER, server); - - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Message is received after uploading is done in KeychainService - ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - - Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success, - Toast.LENGTH_SHORT).show(); - finish(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog( - getString(R.string.progress_uploading), - ProgressDialog.STYLE_HORIZONTAL, false); + mKeyserver = server; - // start service with intent - startService(intent); + mUploadOpHelper = new CryptoOperationHelper(this, this, R.string.progress_uploading); + mUploadOpHelper.cryptoOperation(); } @Override @@ -148,4 +124,30 @@ public class UploadKeyActivity extends BaseActivity { } return super.onOptionsItemSelected(item); } + + @Override + public ExportKeyringParcel createOperationInput() { + return new ExportKeyringParcel(mKeyserver, mUnifiedKeyringUri); + } + + @Override + public void onCryptoOperationSuccess(ExportResult result) { + Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success, + Toast.LENGTH_SHORT).show(); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(ExportResult result) { + // TODO: Implement proper log for key upload then show error + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 5f6a32e5a..d3849c892 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -65,11 +65,13 @@ import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.KeychainService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -89,7 +91,8 @@ import java.util.ArrayList; import java.util.HashMap; public class ViewKeyActivity extends BaseNfcActivity implements - LoaderManager.LoaderCallbacks<Cursor> { + LoaderManager.LoaderCallbacks<Cursor>, + CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; public static final String EXTRA_NFC_AID = "nfc_aid"; @@ -105,6 +108,11 @@ public class ViewKeyActivity extends BaseNfcActivity implements protected Uri mDataUri; + // For CryptoOperationHelper.Callback + private String mKeyserver; + private ArrayList<ParcelableKeyRing> mKeyList; + private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper; + private TextView mName; private TextView mStatusText; private ImageView mStatusImage; @@ -396,7 +404,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements private void certifyImmediate() { Intent intent = new Intent(this, CertifyKeyActivity.class); - intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] {mMasterKeyId}); + intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId}); startCertifyIntent(intent); } @@ -486,6 +494,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mOperationHelper != null) { + mOperationHelper.handleActivityResult(requestCode, resultCode, data); + } + if (requestCode == REQUEST_QR_FINGERPRINT && resultCode == Activity.RESULT_OK) { // If there is an EXTRA_RESULT, that's an error. Just show it. @@ -651,56 +663,20 @@ public class ViewKeyActivity extends BaseNfcActivity implements ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); ArrayList<ParcelableKeyRing> entries = new ArrayList<>(); entries.add(keyEntry); - - // Message is received after importing is done in KeychainService - ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - - mIsRefreshing = false; - - if (returnData == null) { - finish(); - return; - } - final ImportKeyResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - result.createNotify(ViewKeyActivity.this).show(); - } - } - }; - - // fill values for this action - Bundle data = new Bundle(); + mKeyList = entries; // search config { Preferences prefs = Preferences.getPreferences(this); Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver); + mKeyserver = cloudPrefs.keyserver; } - data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, entries); - - // Send all information needed to service to query keys in other thread - Intent intent = new Intent(this, KeychainService.class); - intent.setAction(KeychainService.ACTION_IMPORT_KEYRING); - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(serviceHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - // start service with intent - startService(intent); + mOperationHelper = new CryptoOperationHelper<>( + this, this, R.string.progress_importing); + mOperationHelper.cryptoOperation(); } private void editKey(Uri dataUri) { @@ -986,4 +962,33 @@ public class ViewKeyActivity extends BaseNfcActivity implements public void onLoaderReset(Loader<Cursor> loader) { } + + // CryptoOperationHelper.Callback functions + + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(mKeyList, mKeyserver); + } + + @Override + public void onCryptoOperationSuccess(ImportKeyResult result) { + mIsRefreshing = false; + result.createNotify(this).show(); + } + + @Override + public void onCryptoOperationCancelled() { + mIsRefreshing = false; + } + + @Override + public void onCryptoOperationError(ImportKeyResult result) { + mIsRefreshing = false; + result.createNotify(this).show(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } }
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java index c33485adc..6052eec16 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java @@ -17,15 +17,12 @@ package org.sufficientlysecure.keychain.ui; -import android.app.ProgressDialog; import android.content.Intent; import android.database.Cursor; import android.graphics.Typeface; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -48,9 +45,10 @@ import com.textuality.keybase.lib.User; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -59,7 +57,8 @@ import java.util.Hashtable; import java.util.List; public class ViewKeyTrustFragment extends LoaderFragment implements - LoaderManager.LoaderCallbacks<Cursor> { + LoaderManager.LoaderCallbacks<Cursor>, + CryptoOperationHelper.Callback<KeybaseVerificationParcel, KeybaseVerificationResult> { public static final String ARG_DATA_URI = "uri"; @@ -76,6 +75,14 @@ public class ViewKeyTrustFragment extends LoaderFragment implements // for retrieving the key we’re working on private Uri mDataUri; + private Proof mProof; + + // for CryptoOperationHelper,Callback + private String mKeybaseProof; + private String mKeybaseFingerprint; + private CryptoOperationHelper<KeybaseVerificationParcel, KeybaseVerificationResult> + mKeybaseOpHelper; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); @@ -349,112 +356,130 @@ public class ViewKeyTrustFragment extends LoaderFragment implements } private void verify(final Proof proof, final String fingerprint) { - Intent intent = new Intent(getActivity(), KeychainService.class); - Bundle data = new Bundle(); - intent.setAction(KeychainService.ACTION_VERIFY_KEYBASE_PROOF); - data.putString(KeychainService.KEYBASE_PROOF, proof.toString()); - data.putString(KeychainService.KEYBASE_REQUIRED_FINGERPRINT, fingerprint); - intent.putExtra(KeychainService.EXTRA_DATA, data); + mProof = proof; + mKeybaseProof = proof.toString(); + mKeybaseFingerprint = fingerprint; mProofVerifyDetail.setVisibility(View.GONE); - // Create a new Messenger for the communication back after proof work is done - ServiceProgressHandler handler = new ServiceProgressHandler(getActivity()) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - Bundle returnData = message.getData(); - String msg = returnData.getString(ServiceProgressHandler.DATA_MESSAGE); - SpannableStringBuilder ssb = new SpannableStringBuilder(); - - if ((msg != null) && msg.equals("OK")) { - - //yay - String proofUrl = returnData.getString(ServiceProgressHandler.KEYBASE_PROOF_URL); - String presenceUrl = returnData.getString(ServiceProgressHandler.KEYBASE_PRESENCE_URL); - String presenceLabel = returnData.getString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL); - - String proofLabel; - switch (proof.getType()) { - case Proof.PROOF_TYPE_TWITTER: - proofLabel = getString(R.string.keybase_twitter_proof); - break; - case Proof.PROOF_TYPE_DNS: - proofLabel = getString(R.string.keybase_dns_proof); - break; - case Proof.PROOF_TYPE_WEB_SITE: - proofLabel = getString(R.string.keybase_web_site_proof); - break; - case Proof.PROOF_TYPE_GITHUB: - proofLabel = getString(R.string.keybase_github_proof); - break; - case Proof.PROOF_TYPE_REDDIT: - proofLabel = getString(R.string.keybase_reddit_proof); - break; - default: - proofLabel = getString(R.string.keybase_a_post); - break; - } + mKeybaseOpHelper = new CryptoOperationHelper<>(this, this, + R.string.progress_verifying_signature); + mKeybaseOpHelper.cryptoOperation(); + } - ssb.append(getString(R.string.keybase_proof_succeeded)); - StyleSpan bold = new StyleSpan(Typeface.BOLD); - ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - ssb.append("\n\n"); - int length = ssb.length(); - ssb.append(proofLabel); - if (proofUrl != null) { - URLSpan postLink = new URLSpan(proofUrl); - ssb.setSpan(postLink, length, length + proofLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (Proof.PROOF_TYPE_DNS == proof.getType()) { - ssb.append(" ").append(getString(R.string.keybase_for_the_domain)).append(" "); - } else { - ssb.append(" ").append(getString(R.string.keybase_fetched_from)).append(" "); - } - length = ssb.length(); - URLSpan presenceLink = new URLSpan(presenceUrl); - ssb.append(presenceLabel); - ssb.setSpan(presenceLink, length, length + presenceLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - if (Proof.PROOF_TYPE_REDDIT == proof.getType()) { - ssb.append(", "). - append(getString(R.string.keybase_reddit_attribution)). - append(" “").append(proof.getHandle()).append("”, "); - } - ssb.append(" ").append(getString(R.string.keybase_contained_signature)); - } else { - // verification failed! - msg = returnData.getString(ServiceProgressHandler.DATA_ERROR); - ssb.append(getString(R.string.keybase_proof_failure)); - if (msg == null) { - msg = getString(R.string.keybase_unknown_proof_failure); - } - StyleSpan bold = new StyleSpan(Typeface.BOLD); - ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - ssb.append("\n\n").append(msg); - } - mProofVerifyHeader.setVisibility(View.VISIBLE); - mProofVerifyDetail.setVisibility(View.VISIBLE); - mProofVerifyDetail.setMovementMethod(LinkMovementMethod.getInstance()); - mProofVerifyDetail.setText(ssb); - } - } - }; + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (mKeybaseOpHelper != null) { + mKeybaseOpHelper.handleActivityResult(requestCode, resultCode, data); + } + } + + // CryptoOperationHelper.Callback methods + @Override + public KeybaseVerificationParcel createOperationInput() { + return new KeybaseVerificationParcel(mKeybaseProof, mKeybaseFingerprint); + } + + @Override + public void onCryptoOperationSuccess(KeybaseVerificationResult result) { + + result.createNotify(getActivity()).show(); + + String proofUrl = result.mProofUrl; + String presenceUrl = result.mPresenceUrl; + String presenceLabel = result.mPresenceLabel; + + Proof proof = mProof; // TODO: should ideally be contained in result + + String proofLabel; + switch (proof.getType()) { + case Proof.PROOF_TYPE_TWITTER: + proofLabel = getString(R.string.keybase_twitter_proof); + break; + case Proof.PROOF_TYPE_DNS: + proofLabel = getString(R.string.keybase_dns_proof); + break; + case Proof.PROOF_TYPE_WEB_SITE: + proofLabel = getString(R.string.keybase_web_site_proof); + break; + case Proof.PROOF_TYPE_GITHUB: + proofLabel = getString(R.string.keybase_github_proof); + break; + case Proof.PROOF_TYPE_REDDIT: + proofLabel = getString(R.string.keybase_reddit_proof); + break; + default: + proofLabel = getString(R.string.keybase_a_post); + break; + } + + SpannableStringBuilder ssb = new SpannableStringBuilder(); + + ssb.append(getString(R.string.keybase_proof_succeeded)); + StyleSpan bold = new StyleSpan(Typeface.BOLD); + ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append("\n\n"); + int length = ssb.length(); + ssb.append(proofLabel); + if (proofUrl != null) { + URLSpan postLink = new URLSpan(proofUrl); + ssb.setSpan(postLink, length, length + proofLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (Proof.PROOF_TYPE_DNS == proof.getType()) { + ssb.append(" ").append(getString(R.string.keybase_for_the_domain)).append(" "); + } else { + ssb.append(" ").append(getString(R.string.keybase_fetched_from)).append(" "); + } + length = ssb.length(); + URLSpan presenceLink = new URLSpan(presenceUrl); + ssb.append(presenceLabel); + ssb.setSpan(presenceLink, length, length + presenceLabel.length(), Spanned + .SPAN_EXCLUSIVE_EXCLUSIVE); + if (Proof.PROOF_TYPE_REDDIT == proof.getType()) { + ssb.append(", "). + append(getString(R.string.keybase_reddit_attribution)). + append(" “").append(proof.getHandle()).append("”, "); + } + ssb.append(" ").append(getString(R.string.keybase_contained_signature)); + + displaySpannableResult(ssb); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(KeybaseVerificationResult result) { + + result.createNotify(getActivity()).show(); + + SpannableStringBuilder ssb = new SpannableStringBuilder(); - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(handler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); + ssb.append(getString(R.string.keybase_proof_failure)); + String msg = getString(result.getLog().getLast().mType.mMsgId); + if (msg == null) { + msg = getString(R.string.keybase_unknown_proof_failure); + } + StyleSpan bold = new StyleSpan(Typeface.BOLD); + ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append("\n\n").append(msg); + + displaySpannableResult(ssb); + } - // show progress dialog - handler.showProgressDialog( - getString(R.string.progress_verifying_signature), - ProgressDialog.STYLE_HORIZONTAL, false - ); + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } - // start service with intent - getActivity().startService(intent); + private void displaySpannableResult(SpannableStringBuilder ssb) { + mProofVerifyHeader.setVisibility(View.VISIBLE); + mProofVerifyDetail.setVisibility(View.VISIBLE); + mProofVerifyDetail.setMovementMethod(LinkMovementMethod.getInstance()); + mProofVerifyDetail.setText(ssb); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java index ab25341d3..f8c3b59ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java @@ -21,12 +21,8 @@ package org.sufficientlysecure.keychain.ui; import java.nio.ByteBuffer; import java.util.Arrays; -import android.content.Intent; import android.database.Cursor; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -39,16 +35,16 @@ import android.widget.TextView; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -public class ViewKeyYubiKeyFragment extends Fragment +public class ViewKeyYubiKeyFragment + extends CryptoOperationFragment<PromoteKeyringParcel, PromoteKeyResult> implements LoaderCallbacks<Cursor> { public static final String ARG_MASTER_KEY_ID = "master_key_id"; @@ -60,6 +56,8 @@ public class ViewKeyYubiKeyFragment extends Fragment private String mUserId; private byte[] mCardAid; private long mMasterKeyId; + private long[] mSubKeyIds; + private Button vButton; private TextView vStatus; @@ -127,50 +125,15 @@ public class ViewKeyYubiKeyFragment extends Fragment } public void promoteToSecretKey() { - - ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle returnData = message.getData(); - - PromoteKeyResult result = - returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); - - result.createNotify(getActivity()).show(); - } - - } - }; - - // Send all information needed to service to decrypt in other thread - Intent intent = new Intent(getActivity(), KeychainService.class); - - // fill values for this action - - intent.setAction(KeychainService.ACTION_PROMOTE_KEYRING); - - Bundle data = new Bundle(); - data.putLong(KeychainService.PROMOTE_MASTER_KEY_ID, mMasterKeyId); - data.putByteArray(KeychainService.PROMOTE_CARD_AID, mCardAid); long[] subKeyIds = new long[mFingerprints.length]; for (int i = 0; i < subKeyIds.length; i++) { subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]); } - data.putLongArray(KeychainService.PROMOTE_SUBKEY_IDS, subKeyIds); - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - // start service with intent - getActivity().startService(intent); + // mMasterKeyId and mCardAid are already set + mSubKeyIds = subKeyIds; + cryptoOperation(); } public static final String[] PROJECTION = new String[]{ @@ -240,4 +203,14 @@ public class ViewKeyYubiKeyFragment extends Fragment public void onLoaderReset(Loader<Cursor> loader) { } + + @Override + public PromoteKeyringParcel createOperationInput() { + return new PromoteKeyringParcel(mMasterKeyId, mCardAid, mSubKeyIds); + } + + @Override + protected void onCryptoOperationResult(PromoteKeyResult result) { + result.createNotify(getActivity()).show(); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index db88de676..93c6593ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -33,6 +33,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Highlighter; @@ -92,8 +93,8 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { } /** This method returns a list of all selected entries, with public keys sorted - * before secret keys, see ImportExportOperation for specifics. - * @see org.sufficientlysecure.keychain.operations.ImportExportOperation + * before secret keys, see ImportOperation for specifics. + * @see ImportOperation */ public ArrayList<ImportKeysListEntry> getSelectedEntries() { ArrayList<ImportKeysListEntry> result = new ArrayList<>(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java index a28a5ea59..bede16b2a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -94,7 +94,7 @@ public abstract class BaseNfcActivity extends BaseActivity { public void onNewIntent(Intent intent) { if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { try { - handleNdefDiscoveredIntent(intent); + handleTagDiscoveredIntent(intent); } catch (CardException e) { handleNfcError(e); } catch (IOException e) { @@ -269,7 +269,7 @@ public abstract class BaseNfcActivity extends BaseActivity { * This method is called by onNewIntent above upon discovery of an NFC tag. * It handles initialization and login to the application, subsequently * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then - * finishes the activity with an appropiate result. + * finishes the activity with an appropriate result. * * On general communication, see also * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx @@ -278,7 +278,7 @@ public abstract class BaseNfcActivity extends BaseActivity { * on ISO SmartCard Systems specification. * */ - protected void handleNdefDiscoveredIntent(Intent intent) throws IOException { + protected void handleTagDiscoveredIntent(Intent intent) throws IOException { Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java index 87c6ee3ca..17e4e6ede 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java @@ -36,7 +36,8 @@ public abstract class CachingCryptoOperationFragment <T extends Parcelable, S ex mCachedActionsParcel = null; } - protected abstract T createOperationInput(); + @Override + public abstract T createOperationInput(); protected T getCachedActionsParcel() { return mCachedActionsParcel; @@ -46,7 +47,7 @@ public abstract class CachingCryptoOperationFragment <T extends Parcelable, S ex mCachedActionsParcel = cachedActionsParcel; } - protected void onCryptoOperationCancelled() { + public void onCryptoOperationCancelled() { mCachedActionsParcel = null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index 764602735..115f52d56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -18,217 +18,78 @@ package org.sufficientlysecure.keychain.ui.base; -import android.app.Activity; -import android.app.ProgressDialog; import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.os.Parcelable; import android.support.v4.app.Fragment; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.InputPendingResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.service.KeychainNewService; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.ui.NfcOperationActivity; -import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; -import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; - /** * All fragments executing crypto operations need to extend this class. */ -public abstract class CryptoOperationFragment <T extends Parcelable, S extends OperationResult> - extends Fragment { - - public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; - public static final int REQUEST_CODE_NFC = 0x00008002; - - private void initiateInputActivity(RequiredInputParcel requiredInput) { - - switch (requiredInput.mType) { - case NFC_KEYTOCARD: - case NFC_DECRYPT: - case NFC_SIGN: { - Intent intent = new Intent(getActivity(), NfcOperationActivity.class); - intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); - startActivityForResult(intent, REQUEST_CODE_NFC); - return; - } - - case PASSPHRASE: - case PASSPHRASE_SYMMETRIC: { - Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); - intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput); - startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); - return; - } - } - - throw new RuntimeException("Unhandled pending result!"); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_CANCELED) { - onCryptoOperationCancelled(); - return; - } - - switch (requestCode) { - case REQUEST_CODE_PASSPHRASE: { - if (resultCode == Activity.RESULT_OK && data != null) { - CryptoInputParcel cryptoInput = - data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - cryptoOperation(cryptoInput); - return; - } - break; - } - - case REQUEST_CODE_NFC: { - if (resultCode == Activity.RESULT_OK && data != null) { - CryptoInputParcel cryptoInput = - data.getParcelableExtra(NfcOperationActivity.RESULT_DATA); - cryptoOperation(cryptoInput); - return; - } - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } - } - } +public abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult> + extends Fragment implements CryptoOperationHelper.Callback<T, S> { - protected void dismissProgress() { + private CryptoOperationHelper<T, S> mOperationHelper; - ProgressDialogFragment progressDialogFragment = - (ProgressDialogFragment) getFragmentManager().findFragmentByTag("progressDialog"); + public CryptoOperationFragment() { - if (progressDialogFragment == null) { - return; - } - - progressDialogFragment.dismissAllowingStateLoss(); + mOperationHelper = new CryptoOperationHelper<>(this, this); + } + public void setProgressMessageResource(int id) { + mOperationHelper.setProgressMessageResource(id); } - protected abstract T createOperationInput(); - protected void cryptoOperation(CryptoInputParcel cryptoInput) { - cryptoOperation(cryptoInput, true); + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mOperationHelper.handleActivityResult(requestCode, resultCode, data); } + @Override + public abstract T createOperationInput(); + protected void cryptoOperation() { cryptoOperation(new CryptoInputParcel()); } - protected void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) { - - T operationInput = createOperationInput(); - if (operationInput == null) { - return; - } - - // Send all information needed to service to edit key in other thread - Intent intent = new Intent(getActivity(), KeychainNewService.class); - - intent.putExtra(KeychainNewService.EXTRA_OPERATION_INPUT, operationInput); - intent.putExtra(KeychainNewService.EXTRA_CRYPTO_INPUT, cryptoInput); - - ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - - // get returned data bundle - Bundle returnData = message.getData(); - if (returnData == null) { - return; - } - - final OperationResult result = - returnData.getParcelable(OperationResult.EXTRA_RESULT); - - onHandleResult(result); - } - } - - @Override - protected void onSetProgress(String msg, int progress, int max) { - // allow handling of progress in fragment, or delegate upwards - if ( ! onCryptoSetProgress(msg, progress, max)) { - super.onSetProgress(msg, progress, max); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - if (showProgress) { - saveHandler.showProgressDialog( - getString(R.string.progress_building_key), - ProgressDialog.STYLE_HORIZONTAL, false); - } - - getActivity().startService(intent); - + protected void cryptoOperation(CryptoInputParcel cryptoInput) { + cryptoOperation(cryptoInput, true); } - protected void onCryptoOperationResult(S result) { - if (result.success()) { - onCryptoOperationSuccess(result); - } else { - onCryptoOperationError(result); - } + protected void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) { + mOperationHelper.cryptoOperation(cryptoInput, showProgress); } - abstract protected void onCryptoOperationSuccess(S result); + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } - protected void onCryptoOperationError(S result) { + @Override + public void onCryptoOperationError(S result) { + onCryptoOperationResult(result); result.createNotify(getActivity()).show(); } - protected void onCryptoOperationCancelled() { + @Override + public void onCryptoOperationCancelled() { } - public void onHandleResult(OperationResult result) { - - if (result instanceof InputPendingResult) { - InputPendingResult pendingResult = (InputPendingResult) result; - if (pendingResult.isPending()) { - RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel(); - initiateInputActivity(requiredInput); - return; - } - } - - dismissProgress(); - - try { - // noinspection unchecked, because type erasure :( - onCryptoOperationResult((S) result); - } catch (ClassCastException e) { - throw new AssertionError("bad return class (" - + result.getClass().getSimpleName() + "), this is a programming error!"); - } - + @Override + public void onCryptoOperationSuccess(S result) { + onCryptoOperationResult(result); } - protected boolean onCryptoSetProgress(String msg, int progress, int max) { - return false; + /** + * + * To be overriden by subclasses, if desired. Provides a way to access the method by the + * same name in CryptoOperationHelper, if super.onCryptoOperationSuccess and + * super.onCryptoOperationError are called at the start of the respective functions in the + * subclass overriding them + * @param result + */ + protected void onCryptoOperationResult(S result) { } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java new file mode 100644 index 000000000..240dd0972 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -0,0 +1,326 @@ +/* + * 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.ui.base; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcelable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; + +import android.support.v4.app.FragmentManager; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.InputPendingResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.service.KeychainService; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.ui.NfcOperationActivity; +import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; +import org.sufficientlysecure.keychain.util.Log; + +/** + * Designed to be integrated into activities or fragments used for CryptoOperations. + * Encapsulates the execution of a crypto operation and handling of input pending cases.s + * + * @param <T> The type of input parcel sent to the operation + * @param <S> The type of result returned by the operation + */ +public class CryptoOperationHelper<T extends Parcelable, S extends OperationResult> { + + public interface Callback <T extends Parcelable, S extends OperationResult> { + T createOperationInput(); + void onCryptoOperationSuccess(S result); + void onCryptoOperationCancelled(); + void onCryptoOperationError(S result); + boolean onCryptoSetProgress(String msg, int progress, int max); + } + + public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; + public static final int REQUEST_CODE_NFC = 0x00008002; + + // keeps track of request code used to start an activity from this CryptoOperationHelper. + // this is necessary when multiple CryptoOperationHelpers are used in the same fragment/activity + // otherwise all CryptoOperationHandlers may respond to the same onActivityResult + private int mRequestedCode = -1; + + private int mProgressMessageResource; + + private FragmentActivity mActivity; + private Fragment mFragment; + private Callback<T, S> mCallback; + + private boolean mUseFragment; // short hand for mActivity == null + + /** + * If OperationHelper is being integrated into an activity + * + * @param activity + */ + public CryptoOperationHelper(FragmentActivity activity, Callback<T, S> callback, int progressMessageString) { + mActivity = activity; + mUseFragment = false; + mCallback = callback; + mProgressMessageResource = progressMessageString; + } + + /** + * if OperationHelper is being integrated into a fragment + * + * @param fragment + */ + public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback, int progressMessageString) { + mFragment = fragment; + mUseFragment = true; + mProgressMessageResource = progressMessageString; + mCallback = callback; + } + + /** + * if OperationHelper is being integrated into a fragment with default message for the progress dialog + * + * @param fragment + */ + public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback) { + mFragment = fragment; + mUseFragment = true; + mProgressMessageResource = R.string.progress_building_key; + mCallback = callback; + } + + public void setProgressMessageResource(int id) { + mProgressMessageResource = id; + } + + private void initiateInputActivity(RequiredInputParcel requiredInput) { + + Activity activity = mUseFragment ? mFragment.getActivity() : mActivity; + + switch (requiredInput.mType) { + case NFC_MOVE_KEY_TO_CARD: + case NFC_DECRYPT: + case NFC_SIGN: { + Intent intent = new Intent(activity, NfcOperationActivity.class); + intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); + mRequestedCode = REQUEST_CODE_NFC; + if (mUseFragment) { + mFragment.startActivityForResult(intent, mRequestedCode); + } else { + mActivity.startActivityForResult(intent, mRequestedCode); + } + return; + } + + case PASSPHRASE: + case PASSPHRASE_SYMMETRIC: { + Intent intent = new Intent(activity, PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput); + mRequestedCode = REQUEST_CODE_PASSPHRASE; + if (mUseFragment) { + mFragment.startActivityForResult(intent, mRequestedCode); + } else { + mActivity.startActivityForResult(intent, mRequestedCode); + } + return; + } + } + + throw new RuntimeException("Unhandled pending result!"); + } + + /** + * Attempts the result of an activity started by this helper. Returns true if requestCode is recognized, + * false otherwise. + * + * @param requestCode + * @param resultCode + * @param data + * @return true if requestCode was recognized, false otherwise + */ + public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { + Log.d(Constants.TAG, "received activity result in OperationHelper"); + + if (mRequestedCode != requestCode) { + // this wasn't meant for us to handle + return false; + } else { + mRequestedCode = -1; + } + if (resultCode == Activity.RESULT_CANCELED) { + mCallback.onCryptoOperationCancelled(); + return true; + } + + switch (requestCode) { + case REQUEST_CODE_PASSPHRASE: { + if (resultCode == Activity.RESULT_OK && data != null) { + CryptoInputParcel cryptoInput = + data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); + cryptoOperation(cryptoInput); + return true; + } + break; + } + + case REQUEST_CODE_NFC: { + if (resultCode == Activity.RESULT_OK && data != null) { + CryptoInputParcel cryptoInput = + data.getParcelableExtra(NfcOperationActivity.RESULT_DATA); + cryptoOperation(cryptoInput); + return true; + } + break; + } + + default: { + return false; + } + } + return true; + } + + protected void dismissProgress() { + FragmentManager fragmentManager = + mUseFragment ? mFragment.getFragmentManager() : + mActivity.getSupportFragmentManager(); + + if (fragmentManager == null) { // the fragment holding us has died + // fragmentManager was null when used with DialogFragments. (they close on click?) + return; + } + + ProgressDialogFragment progressDialogFragment = + (ProgressDialogFragment) fragmentManager.findFragmentByTag( + ServiceProgressHandler.TAG_PROGRESS_DIALOG); + + if (progressDialogFragment == null) { + return; + } + + progressDialogFragment.dismissAllowingStateLoss(); + + } + + public void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) { + + FragmentActivity activity = mUseFragment ? mFragment.getActivity() : mActivity; + + T operationInput = mCallback.createOperationInput(); + if (operationInput == null) { + return; + } + + // Send all information needed to service to edit key in other thread + Intent intent = new Intent(activity, KeychainService.class); + + intent.putExtra(KeychainService.EXTRA_OPERATION_INPUT, operationInput); + intent.putExtra(KeychainService.EXTRA_CRYPTO_INPUT, cryptoInput); + + ServiceProgressHandler saveHandler = new ServiceProgressHandler(activity) { + @Override + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == MessageStatus.OKAY.ordinal()) { + + // get returned data bundle + Bundle returnData = message.getData(); + if (returnData == null) { + return; + } + + final OperationResult result = + returnData.getParcelable(OperationResult.EXTRA_RESULT); + + onHandleResult(result); + } + } + + @Override + protected void onSetProgress(String msg, int progress, int max) { + // allow handling of progress in fragment, or delegate upwards + if ( ! mCallback.onCryptoSetProgress(msg, progress, max)) { + super.onSetProgress(msg, progress, max); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); + + if (showProgress) { + saveHandler.showProgressDialog( + activity.getString(mProgressMessageResource), + ProgressDialog.STYLE_HORIZONTAL, false); + } + + activity.startService(intent); + } + + public void cryptoOperation(CryptoInputParcel cryptoInputParcel) { + cryptoOperation(cryptoInputParcel, true); + } + + public void cryptoOperation() { + cryptoOperation(new CryptoInputParcel()); + } + + protected void onCryptoOperationResult(S result) { + if (result.success()) { + mCallback.onCryptoOperationSuccess(result); + } else { + mCallback.onCryptoOperationError(result); + } + } + + public void onHandleResult(OperationResult result) { + Log.d(Constants.TAG, "Handling result in OperationHelper success: " + result.success()); + + if (result instanceof InputPendingResult) { + InputPendingResult pendingResult = (InputPendingResult) result; + if (pendingResult.isPending()) { + + RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel(); + initiateInputActivity(requiredInput); + return; + } + } + + dismissProgress(); + + try { + // noinspection unchecked, because type erasure :( + onCryptoOperationResult((S) result); + } catch (ClassCastException e) { + throw new AssertionError("bad return class (" + + result.getClass().getSimpleName() + "), this is a programming error!"); + } + + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index 2c1714b67..321242b2f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -53,8 +53,11 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.TlsHelper; -public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener { - private static final String ARG_MESSENGER = "messenger"; +public class AddEditKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener { + private static final String ARG_MESSENGER = "arg_messenger"; + private static final String ARG_ACTION = "arg_dialog_action"; + private static final String ARG_POSITION = "arg_position"; + private static final String ARG_KEYSERVER = "arg_keyserver"; public static final int MESSAGE_OKAY = 1; public static final int MESSAGE_VERIFICATION_FAILED = 2; @@ -62,20 +65,37 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit public static final String MESSAGE_KEYSERVER = "new_keyserver"; public static final String MESSAGE_VERIFIED = "verified"; public static final String MESSAGE_FAILURE_REASON = "failure_reason"; + public static final String MESSAGE_KEYSERVER_DELETED = "keyserver_deleted"; + public static final String MESSAGE_DIALOG_ACTION = "message_dialog_action"; + public static final String MESSAGE_EDIT_POSITION = "keyserver_edited_position"; private Messenger mMessenger; + private DialogAction mDialogAction; + private int mPosition; + private EditText mKeyserverEditText; private CheckBox mVerifyKeyserverCheckBox; + public enum DialogAction { + ADD, + EDIT + } + public enum FailureReason { INVALID_URL, CONNECTION_FAILED } - public static AddKeyserverDialogFragment newInstance(Messenger messenger) { - AddKeyserverDialogFragment frag = new AddKeyserverDialogFragment(); + public static AddEditKeyserverDialogFragment newInstance(Messenger messenger, + DialogAction action, + String keyserver, + int position) { + AddEditKeyserverDialogFragment frag = new AddEditKeyserverDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); + args.putSerializable(ARG_ACTION, action); + args.putString(ARG_KEYSERVER, keyserver); + args.putInt(ARG_POSITION, position); frag.setArguments(args); @@ -88,11 +108,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit final Activity activity = getActivity(); mMessenger = getArguments().getParcelable(ARG_MESSENGER); + mDialogAction = (DialogAction) getArguments().getSerializable(ARG_ACTION); + mPosition = getArguments().getInt(ARG_POSITION); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); - alert.setTitle(R.string.add_keyserver_dialog_title); - LayoutInflater inflater = activity.getLayoutInflater(); View view = inflater.inflate(R.layout.add_keyserver_dialog, null); alert.setView(view); @@ -100,14 +120,26 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit mKeyserverEditText = (EditText) view.findViewById(R.id.keyserver_url_edit_text); mVerifyKeyserverCheckBox = (CheckBox) view.findViewById(R.id.verify_keyserver_checkbox); - // we don't want dialog to be dismissed on click, thereby requiring the hack seen below - // and in onStart + switch (mDialogAction) { + case ADD: { + alert.setTitle(R.string.add_keyserver_dialog_title); + break; + } + case EDIT: { + alert.setTitle(R.string.edit_keyserver_dialog_title); + mKeyserverEditText.setText(getArguments().getString(ARG_KEYSERVER)); + break; + } + } + + // we don't want dialog to be dismissed on click for keyserver addition or edit, + // thereby requiring the hack seen below and in onStart alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { // we need to have an empty listener to prevent errors on some devices as mentioned // at http://stackoverflow.com/q/13746412/3000919 - // actual listener set in onStart + // actual listener set in onStart for adding keyservers or editing them } }); @@ -119,6 +151,23 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit } }); + switch (mDialogAction) { + case EDIT: { + alert.setNeutralButton(R.string.label_keyserver_dialog_delete, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteKeyserver(mPosition); + } + }); + break; + } + case ADD: { + // do nothing + break; + } + } + // Hack to open keyboard. // This is the only method that I found to work across all Android versions // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ @@ -155,25 +204,41 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit positiveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + // behaviour same for edit and add String keyserverUrl = mKeyserverEditText.getText().toString(); if (mVerifyKeyserverCheckBox.isChecked()) { verifyConnection(keyserverUrl); } else { dismiss(); // return unverified keyserver back to activity - addKeyserver(keyserverUrl, false); + keyserverEdited(keyserverUrl, false); } } }); } } - public void addKeyserver(String keyserver, boolean verified) { + public void keyserverEdited(String keyserver, boolean verified) { dismiss(); Bundle data = new Bundle(); + data.putSerializable(MESSAGE_DIALOG_ACTION, mDialogAction); data.putString(MESSAGE_KEYSERVER, keyserver); data.putBoolean(MESSAGE_VERIFIED, verified); + if (mDialogAction == DialogAction.EDIT) { + data.putInt(MESSAGE_EDIT_POSITION, mPosition); + } + + sendMessageToHandler(MESSAGE_OKAY, data); + } + + public void deleteKeyserver(int position) { + dismiss(); + Bundle data = new Bundle(); + data.putSerializable(MESSAGE_DIALOG_ACTION, DialogAction.EDIT); + data.putInt(MESSAGE_EDIT_POSITION, position); + data.putBoolean(MESSAGE_KEYSERVER_DELETED, true); + sendMessageToHandler(MESSAGE_OKAY, data); } @@ -238,7 +303,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit protected void onPostExecute(FailureReason failureReason) { mProgressDialog.dismiss(); if (failureReason == null) { - addKeyserver(mKeyserver, true); + keyserverEdited(mKeyserver, true); } else { verificationFailed(failureReason); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 076876b5b..7fc9da9ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; @@ -33,16 +32,20 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DeleteResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainService; +import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.Log; import java.util.HashMap; -public class DeleteKeyDialogFragment extends DialogFragment { +public class DeleteKeyDialogFragment extends DialogFragment + implements CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> { private static final String ARG_MESSENGER = "messenger"; private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids"; @@ -52,6 +55,13 @@ public class DeleteKeyDialogFragment extends DialogFragment { private TextView mMainMessage; private View mInflateView; + private Messenger mMessenger; + + // for CryptoOperationHelper.Callback + private long[] mMasterKeyIds; + private boolean mHasSecret; + private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper; + /** * Creates new instance of this delete file dialog fragment */ @@ -68,9 +78,17 @@ public class DeleteKeyDialogFragment extends DialogFragment { } @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mDeleteOpHelper != null) { + mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data); + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity activity = getActivity(); - final Messenger messenger = getArguments().getParcelable(ARG_MESSENGER); + mMessenger = getArguments().getParcelable(ARG_MESSENGER); final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS); @@ -129,47 +147,16 @@ public class DeleteKeyDialogFragment extends DialogFragment { @Override public void onClick(DialogInterface dialog, int which) { - // Send all information needed to service to import key in other thread - Intent intent = new Intent(getActivity(), KeychainService.class); - - intent.setAction(KeychainService.ACTION_DELETE); - - // Message is received after importing is done in KeychainService - ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) { - @Override - public void handleMessage(Message message) { - super.handleMessage(message); - // handle messages by standard KeychainIntentServiceHandler first - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - try { - Message msg = Message.obtain(); - msg.copyFrom(message); - messenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "messenger error", e); - } - } - } - }; - - // fill values for this action - Bundle data = new Bundle(); - data.putLongArray(KeychainService.DELETE_KEY_LIST, masterKeyIds); - data.putBoolean(KeychainService.DELETE_IS_SECRET, hasSecret); - intent.putExtra(KeychainService.EXTRA_DATA, data); + mMasterKeyIds = masterKeyIds; + mHasSecret = hasSecret; - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog(getString(R.string.progress_deleting), - ProgressDialog.STYLE_HORIZONTAL, true); - - // start service with intent - getActivity().startService(intent); - - dismiss(); + mDeleteOpHelper = new CryptoOperationHelper<> + (DeleteKeyDialogFragment.this, DeleteKeyDialogFragment.this, + R.string.progress_deleting); + mDeleteOpHelper.cryptoOperation(); + // do NOT dismiss here, it'll give + // OperationHelper a null fragmentManager + // dismiss(); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @@ -183,4 +170,42 @@ public class DeleteKeyDialogFragment extends DialogFragment { return builder.show(); } + @Override + public DeleteKeyringParcel createOperationInput() { + return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret); + } + + @Override + public void onCryptoOperationSuccess(DeleteResult result) { + handleResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(DeleteResult result) { + handleResult(result); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + + public void handleResult(DeleteResult result) { + try { + Bundle data = new Bundle(); + data.putParcelable(OperationResult.EXTRA_RESULT, result); + Message msg = Message.obtain(); + msg.arg1 = ServiceProgressHandler.MessageStatus.OKAY.ordinal(); + msg.setData(data); + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "messenger error", e); + } + dismiss(); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java new file mode 100644 index 000000000..95199bcd5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.util.recyclerview; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +public class DividerItemDecoration extends RecyclerView.ItemDecoration { + + private static final int[] ATTRS = new int[]{ + android.R.attr.listDivider + }; + + public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; + + public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; + + private Drawable mDivider; + + private int mOrientation; + + public DividerItemDecoration(Context context, int orientation) { + final TypedArray a = context.obtainStyledAttributes(ATTRS); + mDivider = a.getDrawable(0); + a.recycle(); + setOrientation(orientation); + } + + public void setOrientation(int orientation) { + if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { + throw new IllegalArgumentException("invalid orientation"); + } + mOrientation = orientation; + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (mOrientation == VERTICAL_LIST) { + drawVertical(c, parent); + } else { + drawHorizontal(c, parent); + } + } + + public void drawVertical(Canvas c, RecyclerView parent) { + final int left = parent.getPaddingLeft(); + final int right = parent.getWidth() - parent.getPaddingRight(); + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int top = child.getBottom() + params.bottomMargin; + final int bottom = top + mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + public void drawHorizontal(Canvas c, RecyclerView parent) { + final int top = parent.getPaddingTop(); + final int bottom = parent.getHeight() - parent.getPaddingBottom(); + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int left = child.getRight() + params.rightMargin; + final int right = left + mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + if (mOrientation == VERTICAL_LIST) { + outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); + } else { + outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); + } + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java new file mode 100644 index 000000000..c691182bf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * 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.util.recyclerview; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}. + */ +public interface ItemTouchHelperAdapter { + + /** + * Called when an item has been dragged far enough to trigger a move. This is called every time + * an item is shifted, and <strong>not</strong> at the end of a "drop" event.<br/> + * <br/> + * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after + * adjusting the underlying data to reflect this move. + * + * @param fromPosition The start position of the moved item. + * @param toPosition Then resolved position of the moved item. + * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) + * @see RecyclerView.ViewHolder#getAdapterPosition() + */ + void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target, + int fromPosition, int toPosition); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java new file mode 100644 index 000000000..0fd24581d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * 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.util.recyclerview; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and + * swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/> + * </br/> + * Expects the <code>RecyclerView.Adapter</code> to listen for {@link + * ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> to implement + * {@link ItemTouchHelperViewHolder}. + */ +public class ItemTouchHelperDragCallback extends ItemTouchHelper.Callback { + + private final ItemTouchHelperAdapter mAdapter; + + public ItemTouchHelperDragCallback(ItemTouchHelperAdapter adapter) { + mAdapter = adapter; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return false; + } + + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + // Enable drag and swipe in both directions + final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; + final int swipeFlags = 0; + return makeMovementFlags(dragFlags, swipeFlags); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, + RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType()) { + return false; + } + + // Notify the adapter of the move + mAdapter.onItemMove(source, target, source.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) { + // we don't support swipe + } + + @Override + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { + // Let the view holder know that this item is being moved or dragged + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemSelected(); + } + + super.onSelectedChanged(viewHolder, actionState); + } + + @Override + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + // Tell the view holder it's time to restore the idle state + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemClear(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java new file mode 100644 index 000000000..97e70d71e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * 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.util.recyclerview; + +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Interface to notify an item ViewHolder of relevant callbacks from {@link + * android.support.v7.widget.helper.ItemTouchHelper.Callback}. + */ +public interface ItemTouchHelperViewHolder { + + /** + * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped. + * Implementations should update the item view to indicate it's active state. + */ + void onItemSelected(); + + + /** + * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item + * state should be cleared. + */ + void onItemClear(); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java new file mode 100644 index 000000000..7efcbb30c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Jacob Tabak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +/** + * based on http://stackoverflow.com/a/26196831/3000919 + */ +public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener { + private OnItemClickListener mListener; + private boolean mIgnoreTouch = false; + + public interface OnItemClickListener { + void onItemClick(View view, int position); + } + + GestureDetector mGestureDetector; + + public RecyclerItemClickListener(Context context, OnItemClickListener listener) { + mListener = listener; + mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(MotionEvent e) { + return true; + } + }); + } + + @Override + public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) { + if (mIgnoreTouch) { + return false; + } + View childView = view.findChildViewUnder(e.getX(), e.getY()); + if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) { + mListener.onItemClick(childView, view.getChildAdapterPosition(childView)); + return true; + } + return false; + } + + @Override + public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { + // TODO: should we move mListener.onItemClick here + } + + @Override + public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { + mIgnoreTouch = disallowIntercept; + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java deleted file mode 100644 index ec91b9fe4..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> - * - * 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.widget; - -public interface Editor { - public interface EditorListener { - public void onDeleted(Editor editor, boolean wasNewItem); - public void onEdited(); - } - - public void setEditorListener(EditorListener listener); - public boolean needsSaving(); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java deleted file mode 100644 index 3fd01958a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> - * - * 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.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; - -public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener { - private EditorListener mEditorListener = null; - - ImageButton mDeleteButton; - TextView mServer; - - public KeyServerEditor(Context context) { - super(context); - } - - public KeyServerEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mServer = (TextView) findViewById(R.id.server); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - - super.onFinishInflate(); - } - - public void setValue(String value) { - mServer.setText(value); - } - - public String getValue() { - return mServer.getText().toString().trim(); - } - - public void onClick(View v) { - final ViewGroup parent = (ViewGroup) getParent(); - if (v == mDeleteButton) { - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this, false); - } - } - } - - @Override - public boolean needsSaving() { - return false; - } - - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java index bbc08a2aa..b814f72b2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java @@ -18,15 +18,14 @@ package org.sufficientlysecure.keychain.util; import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Messenger; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.service.KeychainService; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.service.ImportKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import java.util.ArrayList; import java.util.HashSet; @@ -35,24 +34,35 @@ import java.util.Locale; import java.util.Set; public class EmailKeyHelper { + // to import keys, simply use CryptoOperationHelper with this callback + public abstract class ImportContactKeysCallback + implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { - public static void importContacts(Context context, Messenger messenger) { - importAll(context, messenger, ContactHelper.getContactMails(context)); - } + private ArrayList<ParcelableKeyRing> mKeyList; + private String mKeyserver; - public static void importAll(Context context, Messenger messenger, List<String> mails) { - // Collect all candidates as ImportKeysListEntry (set for deduplication) - Set<ImportKeysListEntry> entries = new HashSet<>(); - for (String mail : mails) { - entries.addAll(getEmailKeys(context, mail)); + public ImportContactKeysCallback(Context context, String keyserver) { + this(context, ContactHelper.getContactMails(context), keyserver); } - // Put them in a list and import - ArrayList<ParcelableKeyRing> keys = new ArrayList<>(entries.size()); - for (ImportKeysListEntry entry : entries) { - keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), null)); + public ImportContactKeysCallback(Context context, List<String> mails, String keyserver) { + Set<ImportKeysListEntry> entries = new HashSet<>(); + for (String mail : mails) { + entries.addAll(getEmailKeys(context, mail)); + } + + // Put them in a list and import + ArrayList<ParcelableKeyRing> keys = new ArrayList<>(entries.size()); + for (ImportKeysListEntry entry : entries) { + keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), null)); + } + mKeyList = keys; + mKeyserver = keyserver; + } + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(mKeyList, mKeyserver); } - importKeys(context, messenger, keys); } public static Set<ImportKeysListEntry> getEmailKeys(Context context, String mail) { @@ -78,17 +88,6 @@ public class EmailKeyHelper { return keys; } - private static void importKeys(Context context, Messenger messenger, ArrayList<ParcelableKeyRing> keys) { - Intent importIntent = new Intent(context, KeychainService.class); - importIntent.setAction(KeychainService.ACTION_IMPORT_KEYRING); - Bundle importData = new Bundle(); - importData.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keys); - importIntent.putExtra(KeychainService.EXTRA_DATA, importData); - importIntent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - context.startService(importIntent); - } - public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer) { Set<ImportKeysListEntry> keys = new HashSet<>(); try { 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 2fd09dc79..9567fc9c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java @@ -17,26 +17,26 @@ package org.sufficientlysecure.keychain.util; -import android.app.ProgressDialog; -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.FragmentActivity; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.ExportResult; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.service.ExportKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import java.io.File; -public class ExportHelper { +public class ExportHelper + implements CryptoOperationHelper.Callback <ExportKeyringParcel, ExportResult> { protected File mExportFile; FragmentActivity mActivity; + private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mExportOpHelper; + private boolean mExportSecret; + private long[] mMasterKeyIds; + public ExportHelper(FragmentActivity activity) { super(); this.mActivity = activity; @@ -71,60 +71,43 @@ public class ExportHelper { }, mActivity.getSupportFragmentManager() ,title, message, exportFile, checkMsg); } + // TODO: If ExportHelper requires pending data (see CryptoOPerationHelper), activities using + // TODO: this class should be able to call mExportOpHelper.handleActivity + /** * Export keys */ public void exportKeys(long[] masterKeyIds, boolean exportSecret) { Log.d(Constants.TAG, "exportKeys started"); + mExportSecret = exportSecret; + mMasterKeyIds = masterKeyIds; // if masterKeyIds is null it means export all - // Send all information needed to service to export key in other thread - final Intent intent = new Intent(mActivity, KeychainService.class); - - intent.setAction(KeychainService.ACTION_EXPORT_KEYRING); - - // fill values for this action - Bundle data = new Bundle(); - - data.putString(KeychainService.EXPORT_FILENAME, mExportFile.getAbsolutePath()); - data.putBoolean(KeychainService.EXPORT_SECRET, exportSecret); - - if (masterKeyIds == null) { - data.putBoolean(KeychainService.EXPORT_ALL, true); - } else { - data.putLongArray(KeychainService.EXPORT_KEY_RING_MASTER_KEY_ID, masterKeyIds); - } - - intent.putExtra(KeychainService.EXTRA_DATA, data); - - // Message is received after exporting is done in KeychainService - ServiceProgressHandler exportHandler = new ServiceProgressHandler(mActivity) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); + mExportOpHelper = new CryptoOperationHelper(mActivity, this, R.string.progress_exporting); + mExportOpHelper.cryptoOperation(); + } - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - // get returned data bundle - Bundle data = message.getData(); + @Override + public ExportKeyringParcel createOperationInput() { + return new ExportKeyringParcel(mMasterKeyIds, mExportSecret, mExportFile.getAbsolutePath()); + } - ExportResult result = data.getParcelable(ExportResult.EXTRA_RESULT); - result.createNotify(mActivity).show(); - } - } - }; + @Override + public void onCryptoOperationSuccess(ExportResult result) { + result.createNotify(mActivity).show(); + } - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(exportHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); + @Override + public void onCryptoOperationCancelled() { - // show progress dialog - exportHandler.showProgressDialog( - mActivity.getString(R.string.progress_exporting), - ProgressDialog.STYLE_HORIZONTAL, false - ); + } - // start service with intent - mActivity.startService(intent); + @Override + public void onCryptoOperationError(ExportResult result) { + result.createNotify(mActivity).show(); } + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index a5b0088c0..713d5f5ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -138,6 +138,9 @@ public class Preferences { public String[] getKeyServers() { String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS, Constants.Defaults.KEY_SERVERS); + if (rawData.equals("")) { + return new String[0]; + } Vector<String> servers = new Vector<>(); String chunks[] = rawData.split(","); for (String c : chunks) { @@ -150,7 +153,8 @@ public class Preferences { } public String getPreferredKeyserver() { - return getKeyServers()[0]; + String[] keyservers = getKeyServers(); + return keyservers.length == 0 ? null : keyservers[0]; } public void setKeyServers(String[] value) { |