diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java index bd9d70ff2..c293cae9a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java @@ -3,6 +3,7 @@ package org.sufficientlysecure.keychain.livedata; import android.content.Context; import android.net.Uri; +import android.os.SystemClock; import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; @@ -10,6 +11,7 @@ import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; public class GenericLiveData extends AsyncTaskLiveData { private GenericDataLoader genericDataLoader; + private Long minLoadTime; public GenericLiveData(Context context, GenericDataLoader genericDataLoader) { super(context, null); @@ -26,12 +28,30 @@ public class GenericLiveData extends AsyncTaskLiveData { this.genericDataLoader = genericDataLoader; } + public void setMinLoadTime(Long minLoadTime) { + this.minLoadTime = minLoadTime; + } + @Override protected T asyncLoadData() { - return genericDataLoader.loadData(); + long startTime = SystemClock.elapsedRealtime(); + + T result = genericDataLoader.loadData(); + + try { + long elapsedTime = SystemClock.elapsedRealtime() - startTime; + if (minLoadTime != null && elapsedTime < minLoadTime) { + Thread.sleep(minLoadTime - elapsedTime); + } + } catch (InterruptedException e) { + // nvm + } + + return result; } public interface GenericDataLoader { T loadData(); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java index 72c5a81ce..4b34a13c2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java @@ -19,11 +19,12 @@ package org.sufficientlysecure.keychain.ui.token; import java.util.List; +import java.util.Objects; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; -import android.content.DialogInterface; +import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -58,8 +59,8 @@ import org.sufficientlysecure.keychain.service.input.SecurityTokenChangePinParce import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; -import org.sufficientlysecure.keychain.ui.SecurityTokenOperationActivity; import org.sufficientlysecure.keychain.ui.SecurityTokenChangePinOperationActivity; +import org.sufficientlysecure.keychain.ui.SecurityTokenOperationActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.AbstractCallback; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; @@ -106,13 +107,16 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur super.onCreate(savedInstanceState); Bundle args = getArguments(); - SecurityTokenInfo tokenInfo = args.getParcelable(ARG_TOKEN_INFO); + SecurityTokenInfo tokenInfo = Objects.requireNonNull(args).getParcelable(ARG_TOKEN_INFO); - presenter = new ManageSecurityTokenPresenter(getContext(), getLoaderManager(), tokenInfo); + ManageSecurityTokenViewModel viewModel = ViewModelProviders.of(this).get(ManageSecurityTokenViewModel.class); + viewModel.setTokenInfo(requireContext(), tokenInfo); + + presenter = new ManageSecurityTokenPresenter(requireContext(), this, viewModel); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { this.layoutInflater = inflater; View view = inflater.inflate(R.layout.create_security_token_import_fragment, container, false); @@ -180,7 +184,7 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur @Override public void finishAndShowKey(long masterKeyId) { - Activity activity = getActivity(); + Activity activity = requireActivity(); Intent viewKeyIntent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), masterKeyId); if (activity instanceof CreateKeyActivity) { @@ -321,12 +325,7 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur .setTitle(R.string.token_reset_confirm_title) .setMessage(R.string.token_reset_confirm_message) .setNegativeButton(R.string.button_cancel, null) - .setPositiveButton(R.string.token_reset_confirm_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - presenter.onClickConfirmReset(); - } - }).show(); + .setPositiveButton(R.string.token_reset_confirm_ok, (dialog, which) -> presenter.onClickConfirmReset()).show(); } @Override @@ -338,7 +337,7 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur @Override public void startCreateKeyForToken(SecurityTokenInfo tokenInfo) { - CreateKeyActivity activity = (CreateKeyActivity) getActivity(); + CreateKeyActivity activity = (CreateKeyActivity) requireActivity(); activity.startCreateKeyForSecurityToken(tokenInfo); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java index 4504ee992..db4400711 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java @@ -18,13 +18,10 @@ package org.sufficientlysecure.keychain.ui.token; +import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.net.Uri; -import android.os.Bundle; import android.os.Handler; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; import org.sufficientlysecure.keychain.operations.results.GenericOperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; @@ -34,26 +31,15 @@ import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpPresenter; import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpView; import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenFragment.StatusLine; -import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.ContentUriRetrievalLoader; -import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.KeyRetrievalResult; -import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.KeyserverRetrievalLoader; -import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.LocalKeyLookupLoader; -import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.UriKeyRetrievalLoader; +import org.sufficientlysecure.keychain.ui.token.PublicKeyRetriever.KeyRetrievalResult; import org.sufficientlysecure.keychain.ui.util.PermissionsUtil; class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { - private static final int LOADER_LOCAL = 0; - private static final int LOADER_URI = 1; - private static final int LOADER_KEYSERVER = 2; - private static final int LOADER_CONTENT_URI = 3; - private static final String ARG_CONTENT_URI = "content_uri"; - - private final Context context; - private final LoaderManager loaderManager; - private SecurityTokenInfo tokenInfo; + private final ManageSecurityTokenViewModel viewModel; + private final LifecycleOwner lifecycleOwner; private ManageSecurityTokenMvpView view; @@ -69,12 +55,13 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { private OperationLog log; private Uri selectedContentUri; - ManageSecurityTokenPresenter(Context context, LoaderManager loaderManager, SecurityTokenInfo tokenInfo) { + ManageSecurityTokenPresenter(Context context, LifecycleOwner lifecycleOwner, ManageSecurityTokenViewModel viewModel) { this.context = context.getApplicationContext(); - this.loaderManager = loaderManager; - this.tokenInfo = tokenInfo; + this.lifecycleOwner = lifecycleOwner; + this.viewModel = viewModel; this.log = new OperationLog(); + viewModel.resetLiveData(lifecycleOwner); } @Override @@ -86,10 +73,7 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { public void detach() { this.view = null; - loaderManager.destroyLoader(LOADER_LOCAL); - loaderManager.destroyLoader(LOADER_URI); - loaderManager.destroyLoader(LOADER_KEYSERVER); - loaderManager.destroyLoader(LOADER_CONTENT_URI); + viewModel.resetLiveData(lifecycleOwner); } @Override @@ -110,6 +94,8 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { searchedAtUri = false; searchedKeyservers = false; + viewModel.resetLiveData(lifecycleOwner); + view.hideAction(); view.resetStatusLines(); continueSearch(); @@ -117,8 +103,8 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { private void continueSearch() { if (!checkedKeyStatus) { - boolean keyIsLocked = tokenInfo.getVerifyRetries() == 0; - boolean keyIsEmpty = tokenInfo.isEmpty(); + boolean keyIsLocked = viewModel.tokenInfo.getVerifyRetries() == 0; + boolean keyIsEmpty = viewModel.tokenInfo.isEmpty(); if (keyIsLocked || keyIsEmpty) { // the "checking key status" is fake: we only do it if we already know the key is locked view.statusLineAdd(StatusLine.CHECK_KEY); @@ -131,19 +117,19 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { if (!searchedLocally) { view.statusLineAdd(StatusLine.SEARCH_LOCAL); - loaderManager.restartLoader(LOADER_LOCAL, null, loaderCallbacks); + viewModel.getKeyRetrievalLocal(context).observe(lifecycleOwner, this::processLocalResult); return; } if (!searchedAtUri) { view.statusLineAdd(StatusLine.SEARCH_URI); - loaderManager.restartLoader(LOADER_URI, null, loaderCallbacks); + viewModel.getKeyRetrievalUri(context).observe(lifecycleOwner, this::processUriResult); return; } if (!searchedKeyservers) { view.statusLineAdd(StatusLine.SEARCH_KEYSERVER); - loaderManager.restartLoader(LOADER_KEYSERVER, null, loaderCallbacks); + viewModel.getKeyRetrievalKeyserver(context).observe(lifecycleOwner, this::processKeyserverResult); return; } @@ -151,21 +137,18 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { } private void delayPerformKeyCheck() { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (view == null) { - return; - } - - performKeyCheck(); + new Handler().postDelayed(() -> { + if (view == null) { + return; } + + performKeyCheck(); }, 1000); } private void performKeyCheck() { - boolean keyIsEmpty = tokenInfo.isEmpty(); - boolean putKeyIsSupported = tokenInfo.isPutKeySupported(); + boolean keyIsEmpty = viewModel.tokenInfo.isEmpty(); + boolean putKeyIsSupported = viewModel.tokenInfo.isPutKeySupported(); if (keyIsEmpty && !putKeyIsSupported) { view.statusLineOk(); @@ -174,7 +157,7 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { } if (keyIsEmpty) { - boolean tokenIsAdminLocked = tokenInfo.getVerifyAdminRetries() == 0; + boolean tokenIsAdminLocked = viewModel.tokenInfo.getVerifyAdminRetries() == 0; if (tokenIsAdminLocked) { view.statusLineError(); view.showActionLocked(0); @@ -186,11 +169,11 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { return; } - boolean keyIsLocked = tokenInfo.getVerifyRetries() == 0; + boolean keyIsLocked = viewModel.tokenInfo.getVerifyRetries() == 0; if (keyIsLocked) { view.statusLineError(); - int unlockAttemptsLeft = tokenInfo.getVerifyAdminRetries(); + int unlockAttemptsLeft = viewModel.tokenInfo.getVerifyAdminRetries(); view.showActionLocked(unlockAttemptsLeft); return; } @@ -212,7 +195,7 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { return; } - if (tokenInfo.getVerifyAdminRetries() == 0) { + if (viewModel.tokenInfo.getVerifyAdminRetries() == 0) { view.showErrorCannotUnlock(); return; } @@ -230,63 +213,37 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { view.showErrorCannotUnlock(); } - private LoaderCallbacks loaderCallbacks = new LoaderCallbacks() { - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_LOCAL: - return new LocalKeyLookupLoader(context, tokenInfo.getFingerprints()); - case LOADER_URI: - return new UriKeyRetrievalLoader(context, tokenInfo.getUrl(), tokenInfo.getFingerprints()); - case LOADER_KEYSERVER: - return new KeyserverRetrievalLoader(context, tokenInfo.getFingerprints()); - case LOADER_CONTENT_URI: - return new ContentUriRetrievalLoader(context, tokenInfo.getFingerprints(), - args.getParcelable(ARG_CONTENT_URI)); - } - throw new IllegalArgumentException("called with unknown loader id!"); - } + private void processLocalResult(KeyRetrievalResult result) { + searchedLocally = true; + processResult(result); + } - @Override - public void onLoadFinished(Loader loader, KeyRetrievalResult data) { - switch (loader.getId()) { - case LOADER_LOCAL: { - searchedLocally = true; - break; - } - case LOADER_URI: { - searchedAtUri = true; - break; - } - case LOADER_KEYSERVER: { - searchedKeyservers = true; - break; - } - case LOADER_CONTENT_URI: { - // nothing to do here - break; - } - default: { - throw new IllegalArgumentException("called with unknown loader id!"); - } - } - log.add(data.getOperationResult(), 0); + private void processUriResult(KeyRetrievalResult result) { + searchedAtUri = true; + processResult(result); + } - if (data.isSuccess()) { - processResult(data); - } else { - continueSearchAfterError(); - } - } + private void processKeyserverResult(KeyRetrievalResult result) { + searchedKeyservers = true; + processResult(result); + } - @Override - public void onLoaderReset(Loader loader) { - - } - }; + private void processContentUriResult(KeyRetrievalResult result) { + processResult(result); + } private void processResult(KeyRetrievalResult result) { + log.add(result.getOperationResult(), 0); + + if (result.isSuccess()) { + processResultSuccess(result); + } else { + continueSearchAfterError(); + } + } + + private void processResultSuccess(KeyRetrievalResult result) { view.statusLineOk(); byte[] importKeyData = result.getKeyData(); @@ -310,7 +267,7 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { } private void promoteKeyWithTokenInfo(Long masterKeyId) { - view.operationPromote(masterKeyId, tokenInfo.getAid(), tokenInfo.getFingerprints()); + view.operationPromote(masterKeyId, viewModel.tokenInfo.getAid(), viewModel.tokenInfo.getFingerprints()); } @Override @@ -363,8 +320,8 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { @Override public void onClickResetToken() { - if (!tokenInfo.isResetSupported()) { - TokenType tokenType = tokenInfo.getTokenType(); + if (!viewModel.tokenInfo.isResetSupported()) { + TokenType tokenType = viewModel.tokenInfo.getTokenType(); boolean isGnukOrNitrokeyStart = tokenType == TokenType.GNUK_OLD || tokenType == TokenType.NITROKEY_START_OLD; view.showErrorCannotReset(isGnukOrNitrokeyStart); @@ -381,33 +338,33 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { @Override public void onSecurityTokenResetSuccess(SecurityTokenInfo tokenInfo) { - this.tokenInfo = tokenInfo; + viewModel.setTokenInfo(context, tokenInfo); resetAndContinueSearch(); } @Override public void onSecurityTokenResetCanceled(SecurityTokenInfo tokenInfo) { if (tokenInfo != null) { - this.tokenInfo = tokenInfo; + viewModel.setTokenInfo(context, tokenInfo); resetAndContinueSearch(); } } @Override public void onClickSetupToken() { - view.startCreateKeyForToken(tokenInfo); + view.startCreateKeyForToken(viewModel.tokenInfo); } @Override public void onSecurityTokenChangePinSuccess(SecurityTokenInfo tokenInfo) { - this.tokenInfo = tokenInfo; + viewModel.setTokenInfo(context, tokenInfo); resetAndContinueSearch(); } @Override public void onSecurityTokenChangePinCanceled(SecurityTokenInfo tokenInfo) { if (tokenInfo != null) { - this.tokenInfo = tokenInfo; + viewModel.setTokenInfo(context, tokenInfo); resetAndContinueSearch(); } } @@ -433,9 +390,7 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { view.resetStatusLines(); view.statusLineAdd(StatusLine.SEARCH_CONTENT_URI); - Bundle args = new Bundle(); - args.putParcelable(ARG_CONTENT_URI, contentUri); - loaderManager.restartLoader(LOADER_CONTENT_URI, args, loaderCallbacks); + viewModel.getKeyRetrievalContentUri(context, contentUri).observe(lifecycleOwner, this::processContentUriResult); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenViewModel.java new file mode 100644 index 000000000..4509e9d85 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenViewModel.java @@ -0,0 +1,82 @@ +package org.sufficientlysecure.keychain.ui.token; + + +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.content.Context; +import android.net.Uri; + +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo; +import org.sufficientlysecure.keychain.ui.token.PublicKeyRetriever.KeyRetrievalResult; + + +public class ManageSecurityTokenViewModel extends ViewModel { + private static final long MIN_OPERATION_TIME_MILLIS = 700; + + SecurityTokenInfo tokenInfo; + + private GenericLiveData keyRetrievalLocal; + private GenericLiveData keyRetrievalUri; + private GenericLiveData keyRetrievalKeyserver; + private GenericLiveData keyRetrievalContentUri; + + private PublicKeyRetriever publicKeyRetriever; + + void setTokenInfo(Context context, SecurityTokenInfo tokenInfo) { + this.tokenInfo = tokenInfo; + this.publicKeyRetriever = new PublicKeyRetriever(context, tokenInfo); + } + + public LiveData getKeyRetrievalLocal(Context context) { + if (keyRetrievalLocal == null) { + keyRetrievalLocal = new GenericLiveData<>(context, publicKeyRetriever::retrieveLocal); + keyRetrievalLocal.setMinLoadTime(MIN_OPERATION_TIME_MILLIS); + } + return keyRetrievalLocal; + } + + public LiveData getKeyRetrievalUri(Context context) { + if (keyRetrievalUri == null) { + keyRetrievalUri = new GenericLiveData<>(context, publicKeyRetriever::retrieveUri); + keyRetrievalUri.setMinLoadTime(MIN_OPERATION_TIME_MILLIS); + } + return keyRetrievalUri; + } + + public LiveData getKeyRetrievalKeyserver(Context context) { + if (keyRetrievalKeyserver == null) { + keyRetrievalKeyserver = new GenericLiveData<>(context, publicKeyRetriever::retrieveKeyserver); + keyRetrievalKeyserver.setMinLoadTime(MIN_OPERATION_TIME_MILLIS); + } + return keyRetrievalKeyserver; + } + + public LiveData getKeyRetrievalContentUri(Context context, Uri uri) { + if (keyRetrievalContentUri == null) { + keyRetrievalContentUri = new GenericLiveData<>(context, () -> publicKeyRetriever.retrieveContentUri(uri)); + keyRetrievalContentUri.setMinLoadTime(MIN_OPERATION_TIME_MILLIS); + } + return keyRetrievalContentUri; + } + + public void resetLiveData(LifecycleOwner lifecycleOwner) { + if (keyRetrievalLocal != null) { + keyRetrievalLocal.removeObservers(lifecycleOwner); + keyRetrievalLocal = null; + } + if (keyRetrievalKeyserver != null) { + keyRetrievalKeyserver.removeObservers(lifecycleOwner); + keyRetrievalKeyserver = null; + } + if (keyRetrievalUri != null) { + keyRetrievalUri.removeObservers(lifecycleOwner); + keyRetrievalUri = null; + } + if (keyRetrievalContentUri != null) { + keyRetrievalContentUri.removeObservers(lifecycleOwner); + keyRetrievalContentUri = null; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java deleted file mode 100644 index 1257a232e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.token; - - -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -import android.content.ContentResolver; -import android.content.Context; -import android.net.Uri; -import android.os.SystemClock; -import android.support.annotation.Nullable; -import android.support.v4.content.AsyncTaskLoader; -import android.text.TextUtils; - -import com.google.auto.value.AutoValue; -import okhttp3.Call; -import okhttp3.HttpUrl; -import okhttp3.Request.Builder; -import okhttp3.Response; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserverClient; -import org.sufficientlysecure.keychain.keyimport.KeyserverClient.QueryFailedException; -import org.sufficientlysecure.keychain.keyimport.KeyserverClient.QueryNotFoundException; -import org.sufficientlysecure.keychain.network.OkHttpClientFactory; -import org.sufficientlysecure.keychain.operations.results.GenericOperationResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.daos.KeyRepository; -import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.KeyRetrievalResult; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ParcelableProxy; -import org.sufficientlysecure.keychain.util.Preferences; -import timber.log.Timber; - - -public abstract class PublicKeyRetrievalLoader extends AsyncTaskLoader { - private static final long MIN_OPERATION_TIME_MILLIS = 1500; - - - private KeyRetrievalResult cachedResult; - - protected final List fingerprints; - - - private PublicKeyRetrievalLoader(Context context, List fingerprints) { - super(context); - - this.fingerprints = fingerprints; - } - - @Override - protected KeyRetrievalResult onLoadInBackground() { - long startTime = SystemClock.elapsedRealtime(); - - KeyRetrievalResult keyRetrievalResult = super.onLoadInBackground(); - - try { - long elapsedTime = SystemClock.elapsedRealtime() - startTime; - if (elapsedTime < MIN_OPERATION_TIME_MILLIS) { - Thread.sleep(MIN_OPERATION_TIME_MILLIS - elapsedTime); - } - } catch (InterruptedException e) { - // nvm - } - - return keyRetrievalResult; - } - - static class LocalKeyLookupLoader extends PublicKeyRetrievalLoader { - private final KeyRepository keyRepository; - - LocalKeyLookupLoader(Context context, List fingerprints) { - super(context, fingerprints); - - this.keyRepository = KeyRepository.create(context); - } - - @Override - public KeyRetrievalResult loadInBackground() { - OperationLog log = new OperationLog(); - log.add(LogType.MSG_RET_LOCAL_START, 0); - - for (byte[] fingerprint : fingerprints) { - long keyId = KeyFormattingUtils.getKeyIdFromFingerprint(fingerprint); - if (keyId == 0L) { - continue; - } - - log.add(LogType.MSG_RET_LOCAL_SEARCH, 1, KeyFormattingUtils.convertKeyIdToHex(keyId)); - try { - Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(keyId); - if (masterKeyId == null) { - log.add(LogType.MSG_RET_LOCAL_NOT_FOUND, 2); - continue; - } - - // TODO check fingerprint - // if (!Arrays.equals(fingerprints, cachedPublicKeyRing.getFingerprint())) { - // log.add(LogType.MSG_RET_LOCAL_FP_MISMATCH, 1); - // return KeyRetrievalResult.createWithError(log); - // } else { - // log.add(LogType.MSG_RET_LOCAL_FP_MATCH, 1); - // } - - switch (keyRepository.getSecretKeyType(keyId)) { - case PASSPHRASE: - case PASSPHRASE_EMPTY: { - log.add(LogType.MSG_RET_LOCAL_SECRET, 1); - log.add(LogType.MSG_RET_LOCAL_OK, 1); - return KeyRetrievalResult.createWithMasterKeyIdAndSecretAvailable(log, masterKeyId); - } - - case GNU_DUMMY: - case DIVERT_TO_CARD: - case UNAVAILABLE: { - log.add(LogType.MSG_RET_LOCAL_OK, 1); - return KeyRetrievalResult.createWithMasterKeyId(log, masterKeyId); - } - - default: { - throw new IllegalStateException("Unhandled SecretKeyType!"); - } - } - } catch (NotFoundException e) { - log.add(LogType.MSG_RET_LOCAL_NOT_FOUND, 2); - } - } - - log.add(LogType.MSG_RET_LOCAL_NONE_FOUND, 1); - return KeyRetrievalResult.createWithError(log); - } - } - - static class UriKeyRetrievalLoader extends PublicKeyRetrievalLoader { - private final String tokenUri; - - UriKeyRetrievalLoader(Context context, String tokenUri, List fingerprints) { - super(context, fingerprints); - - this.tokenUri = tokenUri; - } - - @Override - public KeyRetrievalResult loadInBackground() { - OperationLog log = new OperationLog(); - - try { - log.add(LogType.MSG_RET_URI_START, 0); - if (TextUtils.isEmpty(tokenUri)) { - log.add(LogType.MSG_RET_URI_NULL, 1); - return KeyRetrievalResult.createWithError(log); - } - - log.add(LogType.MSG_RET_URI_FETCHING, 1, tokenUri); - - HttpUrl httpUrl = HttpUrl.parse(tokenUri); - if (httpUrl == null) { - log.add(LogType.MSG_RET_URI_ERROR_PARSE, 1); - return KeyRetrievalResult.createWithError(log); - } - - Call call = OkHttpClientFactory.getSimpleClient().newCall(new Builder().url(httpUrl).build()); - Response execute = call.execute(); - if (!execute.isSuccessful()) { - log.add(LogType.MSG_RET_URI_ERROR_FETCH, 1); - } - - IteratorWithIOThrow uncachedKeyRingIterator = UncachedKeyRing.fromStream( - execute.body().byteStream()); - while (uncachedKeyRingIterator.hasNext()) { - UncachedKeyRing keyRing = uncachedKeyRingIterator.next(); - log.add(LogType.MSG_RET_URI_TEST, 1, KeyFormattingUtils.convertKeyIdToHex(keyRing.getMasterKeyId())); - if (keyRing.containsKeyWithAnyFingerprint(fingerprints)) { - log.add(LogType.MSG_RET_URI_OK, 1); - return KeyRetrievalResult.createWithKeyringdata(log, keyRing.getMasterKeyId(), keyRing.getEncoded()); - } - } - - log.add(LogType.MSG_RET_URI_ERROR_NO_MATCH, 1); - } catch (IOException e) { - log.add(LogType.MSG_RET_URI_ERROR_FETCH, 1); - Timber.e(e, "error retrieving key from uri"); - } - - return KeyRetrievalResult.createWithError(log); - } - } - - static class KeyserverRetrievalLoader extends PublicKeyRetrievalLoader { - KeyserverRetrievalLoader(Context context, List fingerprints) { - super(context, fingerprints); - } - - @Override - public KeyRetrievalResult loadInBackground() { - OperationLog log = new OperationLog(); - - HkpKeyserverAddress preferredKeyserver = Preferences.getPreferences(getContext()).getPreferredKeyserver(); - ParcelableProxy parcelableProxy = Preferences.getPreferences(getContext()).getParcelableProxy(); - - HkpKeyserverClient keyserverClient = HkpKeyserverClient.fromHkpKeyserverAddress(preferredKeyserver); - - try { - log.add(LogType.MSG_RET_KS_START, 0); - - String keyString = keyserverClient.get( - "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprints.get(0)), parcelableProxy); - UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(keyString.getBytes()); - - if (!keyRing.containsKeyWithAnyFingerprint(fingerprints)) { - log.add(LogType.MSG_RET_KS_FP_MISMATCH, 1); - return KeyRetrievalResult.createWithError(log); - } else { - log.add(LogType.MSG_RET_KS_FP_MATCH, 1); - } - - log.add(LogType.MSG_RET_KS_OK, 1); - return KeyRetrievalResult.createWithKeyringdata(log, keyRing.getMasterKeyId(), keyRing.getEncoded()); - } catch (QueryNotFoundException e) { - log.add(LogType.MSG_RET_KS_ERROR_NOT_FOUND, 1); - } catch (QueryFailedException | IOException | PgpGeneralException e) { - log.add(LogType.MSG_RET_KS_ERROR, 1); - Timber.e(e, "error retrieving key from keyserver"); - } - - return KeyRetrievalResult.createWithError(log); - } - } - - static class ContentUriRetrievalLoader extends PublicKeyRetrievalLoader { - private final ContentResolver contentResolver; - private final Uri uri; - - ContentUriRetrievalLoader(Context context, List fingerprints, Uri uri) { - super(context, fingerprints); - - this.uri = uri; - this.contentResolver = context.getContentResolver(); - } - - @Override - public KeyRetrievalResult loadInBackground() { - OperationLog log = new OperationLog(); - - try { - log.add(LogType.MSG_RET_CURI_START, 0); - - log.add(LogType.MSG_RET_CURI_OPEN, 1, uri.toString()); - InputStream is = contentResolver.openInputStream(uri); - if (is == null) { - log.add(LogType.MSG_RET_CURI_ERROR_NOT_FOUND, 1); - return KeyRetrievalResult.createWithError(log); - } - - IteratorWithIOThrow uncachedKeyRingIterator = UncachedKeyRing.fromStream(is); - while (uncachedKeyRingIterator.hasNext()) { - UncachedKeyRing keyRing = uncachedKeyRingIterator.next(); - log.add(LogType.MSG_RET_CURI_FOUND, 1, KeyFormattingUtils.convertKeyIdToHex(keyRing.getMasterKeyId())); - if (keyRing.containsKeyWithAnyFingerprint(fingerprints)) { - log.add(LogType.MSG_RET_CURI_OK, 1); - return KeyRetrievalResult.createWithKeyringdata(log, keyRing.getMasterKeyId(), keyRing.getEncoded()); - } else { - log.add(LogType.MSG_RET_CURI_MISMATCH, 1); - } - } - log.add(LogType.MSG_RET_CURI_ERROR_NO_MATCH, 1); - } catch (IOException e) { - Timber.e(e, "error reading keyring from file"); - log.add(LogType.MSG_RET_CURI_ERROR_IO, 1); - } - - return KeyRetrievalResult.createWithError(log); - } - } - - @Override - public void deliverResult(KeyRetrievalResult result) { - cachedResult = result; - - if (isStarted()) { - super.deliverResult(result); - } - } - - @Override - protected void onStartLoading() { - if (cachedResult != null) { - deliverResult(cachedResult); - } - - if (takeContentChanged() || cachedResult == null) { - forceLoad(); - } - } - - @AutoValue - static abstract class KeyRetrievalResult { - abstract GenericOperationResult getOperationResult(); - - @Nullable - abstract Long getMasterKeyId(); - @Nullable - @SuppressWarnings("mutable") - abstract byte[] getKeyData(); - abstract boolean isSecretKeyAvailable(); - - boolean isSuccess() { - return getMasterKeyId() != null || getKeyData() != null; - } - - static KeyRetrievalResult createWithError(OperationLog log) { - return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult( - new GenericOperationResult(GenericOperationResult.RESULT_ERROR, log), - null, null, false); - } - - static KeyRetrievalResult createWithKeyringdata(OperationLog log, long masterKeyId, byte[] keyringData) { - return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult( - new GenericOperationResult(GenericOperationResult.RESULT_OK, log), - masterKeyId, keyringData, false); - } - - static KeyRetrievalResult createWithMasterKeyIdAndSecretAvailable(OperationLog log, long masterKeyId) { - return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult( - new GenericOperationResult(GenericOperationResult.RESULT_OK, log), - masterKeyId, null, true); - } - - static KeyRetrievalResult createWithMasterKeyId(OperationLog log, long masterKeyId) { - return new AutoValue_PublicKeyRetrievalLoader_KeyRetrievalResult( - new GenericOperationResult(GenericOperationResult.RESULT_OK, log), - masterKeyId, null, false); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetriever.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetriever.java new file mode 100644 index 000000000..0a4c26740 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetriever.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.token; + + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import android.content.Context; +import android.net.Uri; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.google.auto.value.AutoValue; +import okhttp3.Call; +import okhttp3.HttpUrl; +import okhttp3.Request.Builder; +import okhttp3.Response; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserverClient; +import org.sufficientlysecure.keychain.keyimport.KeyserverClient.QueryFailedException; +import org.sufficientlysecure.keychain.keyimport.KeyserverClient.QueryNotFoundException; +import org.sufficientlysecure.keychain.network.OkHttpClientFactory; +import org.sufficientlysecure.keychain.operations.results.GenericOperationResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; +import timber.log.Timber; + + +public class PublicKeyRetriever { + private final Context context; + private final List fingerprints; + private final SecurityTokenInfo securityTokenInfo; + + + PublicKeyRetriever(Context context, SecurityTokenInfo securityTokenInfo) { + this.context = context; + this.fingerprints = securityTokenInfo.getFingerprints(); + this.securityTokenInfo = securityTokenInfo; + } + + public KeyRetrievalResult retrieveLocal() { + KeyRepository keyRepository = KeyRepository.create(context); + OperationLog log = new OperationLog(); + log.add(LogType.MSG_RET_LOCAL_START, 0); + + for (byte[] fingerprint : fingerprints) { + long keyId = KeyFormattingUtils.getKeyIdFromFingerprint(fingerprint); + if (keyId == 0L) { + continue; + } + + log.add(LogType.MSG_RET_LOCAL_SEARCH, 1, KeyFormattingUtils.convertKeyIdToHex(keyId)); + try { + Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(keyId); + if (masterKeyId == null) { + log.add(LogType.MSG_RET_LOCAL_NOT_FOUND, 2); + continue; + } + + // TODO check fingerprint + // if (!Arrays.equals(fingerprints, cachedPublicKeyRing.getFingerprint())) { + // log.add(LogType.MSG_RET_LOCAL_FP_MISMATCH, 1); + // return KeyRetrievalResult.createWithError(log); + // } else { + // log.add(LogType.MSG_RET_LOCAL_FP_MATCH, 1); + // } + + switch (keyRepository.getSecretKeyType(keyId)) { + case PASSPHRASE: + case PASSPHRASE_EMPTY: { + log.add(LogType.MSG_RET_LOCAL_SECRET, 1); + log.add(LogType.MSG_RET_LOCAL_OK, 1); + return KeyRetrievalResult.createWithMasterKeyIdAndSecretAvailable(log, masterKeyId); + } + + case GNU_DUMMY: + case DIVERT_TO_CARD: + case UNAVAILABLE: { + log.add(LogType.MSG_RET_LOCAL_OK, 1); + return KeyRetrievalResult.createWithMasterKeyId(log, masterKeyId); + } + + default: { + throw new IllegalStateException("Unhandled SecretKeyType!"); + } + } + } catch (NotFoundException e) { + log.add(LogType.MSG_RET_LOCAL_NOT_FOUND, 2); + } + } + + log.add(LogType.MSG_RET_LOCAL_NONE_FOUND, 1); + return KeyRetrievalResult.createWithError(log); + } + + public KeyRetrievalResult retrieveUri() { + OperationLog log = new OperationLog(); + + String tokenUri = securityTokenInfo.getUrl(); + + try { + log.add(LogType.MSG_RET_URI_START, 0); + if (TextUtils.isEmpty(tokenUri)) { + log.add(LogType.MSG_RET_URI_NULL, 1); + return KeyRetrievalResult.createWithError(log); + } + + log.add(LogType.MSG_RET_URI_FETCHING, 1, tokenUri); + + HttpUrl httpUrl = HttpUrl.parse(tokenUri); + if (httpUrl == null) { + log.add(LogType.MSG_RET_URI_ERROR_PARSE, 1); + return KeyRetrievalResult.createWithError(log); + } + + Call call = OkHttpClientFactory.getSimpleClient().newCall(new Builder().url(httpUrl).build()); + Response execute = call.execute(); + if (!execute.isSuccessful()) { + log.add(LogType.MSG_RET_URI_ERROR_FETCH, 1); + } + + IteratorWithIOThrow uncachedKeyRingIterator = UncachedKeyRing.fromStream( + execute.body().byteStream()); + while (uncachedKeyRingIterator.hasNext()) { + UncachedKeyRing keyRing = uncachedKeyRingIterator.next(); + log.add(LogType.MSG_RET_URI_TEST, 1, KeyFormattingUtils.convertKeyIdToHex(keyRing.getMasterKeyId())); + if (keyRing.containsKeyWithAnyFingerprint(fingerprints)) { + log.add(LogType.MSG_RET_URI_OK, 1); + return KeyRetrievalResult.createWithKeyringdata(log, keyRing.getMasterKeyId(), keyRing.getEncoded()); + } + } + + log.add(LogType.MSG_RET_URI_ERROR_NO_MATCH, 1); + } catch (IOException e) { + log.add(LogType.MSG_RET_URI_ERROR_FETCH, 1); + Timber.e(e, "error retrieving key from uri"); + } + + return KeyRetrievalResult.createWithError(log); + } + + public KeyRetrievalResult retrieveKeyserver() { + OperationLog log = new OperationLog(); + + HkpKeyserverAddress preferredKeyserver = Preferences.getPreferences(context).getPreferredKeyserver(); + ParcelableProxy parcelableProxy = Preferences.getPreferences(context).getParcelableProxy(); + + HkpKeyserverClient keyserverClient = HkpKeyserverClient.fromHkpKeyserverAddress(preferredKeyserver); + + try { + log.add(LogType.MSG_RET_KS_START, 0); + + String keyString = keyserverClient.get( + "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprints.get(0)), parcelableProxy); + UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(keyString.getBytes()); + + if (!keyRing.containsKeyWithAnyFingerprint(fingerprints)) { + log.add(LogType.MSG_RET_KS_FP_MISMATCH, 1); + return KeyRetrievalResult.createWithError(log); + } else { + log.add(LogType.MSG_RET_KS_FP_MATCH, 1); + } + + log.add(LogType.MSG_RET_KS_OK, 1); + return KeyRetrievalResult.createWithKeyringdata(log, keyRing.getMasterKeyId(), keyRing.getEncoded()); + } catch (QueryNotFoundException e) { + log.add(LogType.MSG_RET_KS_ERROR_NOT_FOUND, 1); + } catch (QueryFailedException | IOException | PgpGeneralException e) { + log.add(LogType.MSG_RET_KS_ERROR, 1); + Timber.e(e, "error retrieving key from keyserver"); + } + + return KeyRetrievalResult.createWithError(log); + } + + public KeyRetrievalResult retrieveContentUri(Uri uri) { + OperationLog log = new OperationLog(); + + try { + log.add(LogType.MSG_RET_CURI_START, 0); + + log.add(LogType.MSG_RET_CURI_OPEN, 1, uri.toString()); + InputStream is = context.getContentResolver().openInputStream(uri); + if (is == null) { + log.add(LogType.MSG_RET_CURI_ERROR_NOT_FOUND, 1); + return KeyRetrievalResult.createWithError(log); + } + + IteratorWithIOThrow uncachedKeyRingIterator = UncachedKeyRing.fromStream(is); + while (uncachedKeyRingIterator.hasNext()) { + UncachedKeyRing keyRing = uncachedKeyRingIterator.next(); + log.add(LogType.MSG_RET_CURI_FOUND, 1, KeyFormattingUtils.convertKeyIdToHex(keyRing.getMasterKeyId())); + if (keyRing.containsKeyWithAnyFingerprint(fingerprints)) { + log.add(LogType.MSG_RET_CURI_OK, 1); + return KeyRetrievalResult.createWithKeyringdata(log, keyRing.getMasterKeyId(), keyRing.getEncoded()); + } else { + log.add(LogType.MSG_RET_CURI_MISMATCH, 1); + } + } + log.add(LogType.MSG_RET_CURI_ERROR_NO_MATCH, 1); + } catch (IOException e) { + Timber.e(e, "error reading keyring from file"); + log.add(LogType.MSG_RET_CURI_ERROR_IO, 1); + } + + return KeyRetrievalResult.createWithError(log); + } + + @AutoValue + static abstract class KeyRetrievalResult { + abstract GenericOperationResult getOperationResult(); + + @Nullable + abstract Long getMasterKeyId(); + @Nullable + @SuppressWarnings("mutable") + abstract byte[] getKeyData(); + abstract boolean isSecretKeyAvailable(); + + boolean isSuccess() { + return getMasterKeyId() != null || getKeyData() != null; + } + + static KeyRetrievalResult createWithError(OperationLog log) { + return new AutoValue_PublicKeyRetriever_KeyRetrievalResult( + new GenericOperationResult(GenericOperationResult.RESULT_ERROR, log), + null, null, false); + } + + static KeyRetrievalResult createWithKeyringdata(OperationLog log, long masterKeyId, byte[] keyringData) { + return new AutoValue_PublicKeyRetriever_KeyRetrievalResult( + new GenericOperationResult(GenericOperationResult.RESULT_OK, log), + masterKeyId, keyringData, false); + } + + static KeyRetrievalResult createWithMasterKeyIdAndSecretAvailable(OperationLog log, long masterKeyId) { + return new AutoValue_PublicKeyRetriever_KeyRetrievalResult( + new GenericOperationResult(GenericOperationResult.RESULT_OK, log), + masterKeyId, null, true); + } + + static KeyRetrievalResult createWithMasterKeyId(OperationLog log, long masterKeyId) { + return new AutoValue_PublicKeyRetriever_KeyRetrievalResult( + new GenericOperationResult(GenericOperationResult.RESULT_OK, log), + masterKeyId, null, false); + } + } +}