diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui')
17 files changed, 573 insertions, 243 deletions
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 a2aff2029..cf7a0b1d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -200,7 +200,7 @@ public class DecryptActivity extends BaseActivity { } // clean up ascii armored message, fixing newlines and stuff - String cleanedText = PgpHelper.getPgpContent(text); + String cleanedText = PgpHelper.getPgpMessageContent(text); if (cleanedText == null) { return null; } 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 1d2bf6b9c..c45a641e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -24,12 +24,14 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.content.ClipDescription; import android.content.Context; import android.content.Intent; import android.content.pm.LabeledIntent; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.Point; @@ -37,9 +39,12 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -91,6 +96,22 @@ import org.sufficientlysecure.keychain.util.ParcelableHashMap; import org.sufficientlysecure.keychain.util.Preferences; +/** Displays a list of decrypted inputs. + * + * This class has a complex control flow to manage its input URIs. Each URI + * which is in mInputUris is also in exactly one of mPendingInputUris, + * mCancelledInputUris, mCurrentInputUri, or a key in mInputDataResults. + * + * Processing of URIs happens using a looping approach: + * - There is always exactly one method running which works on mCurrentInputUri + * - Processing starts in cryptoOperation(), which pops a new mCurrentInputUri + * from the list of mPendingInputUris. + * - Once a mCurrentInputUri is finished processing, it should be set to null and + * control handed back to cryptoOperation() + * - Control flow can move through asynchronous calls, and resume in callbacks + * like onActivityResult() or onPermissionRequestResult(). + * + */ public class DecryptListFragment extends QueueingCryptoOperationFragment<InputDataParcel,InputDataResult> implements OnMenuItemClickListener { @@ -102,7 +123,7 @@ public class DecryptListFragment public static final String ARG_CAN_DELETE = "can_delete"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; - public static final String ARG_CURRENT_URI = "current_uri"; + private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; private ArrayList<Uri> mInputUris; private HashMap<Uri, InputDataResult> mInputDataResults; @@ -118,7 +139,7 @@ public class DecryptListFragment /** * Creates new instance of this fragment */ - public static DecryptListFragment newInstance(ArrayList<Uri> uris, boolean canDelete) { + public static DecryptListFragment newInstance(@NonNull ArrayList<Uri> uris, boolean canDelete) { DecryptListFragment frag = new DecryptListFragment(); Bundle args = new Bundle(); @@ -175,9 +196,12 @@ public class DecryptListFragment outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results)); outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults)); outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris); - outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri); outState.putBoolean(ARG_CAN_DELETE, mCanDelete); + // this does not save mCurrentInputUri - if anything is being + // processed at fragment recreation time, the operation in + // progress will be lost! + } @Override @@ -189,20 +213,21 @@ public class DecryptListFragment ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS); ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS); ParcelableHashMap<Uri,InputDataResult> results = args.getParcelable(ARG_RESULTS); - Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI); mCanDelete = args.getBoolean(ARG_CAN_DELETE, false); - displayInputUris(inputUris, currentInputUri, cancelledUris, + displayInputUris(inputUris, cancelledUris, results != null ? results.getMap() : null ); } - private void displayInputUris(ArrayList<Uri> inputUris, Uri currentInputUri, - ArrayList<Uri> cancelledUris, HashMap<Uri,InputDataResult> results) { + private void displayInputUris( + ArrayList<Uri> inputUris, + ArrayList<Uri> cancelledUris, + HashMap<Uri,InputDataResult> results) { mInputUris = inputUris; - mCurrentInputUri = currentInputUri; + mCurrentInputUri = null; mInputDataResults = results != null ? results : new HashMap<Uri,InputDataResult>(inputUris.size()); mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>(); @@ -211,30 +236,23 @@ public class DecryptListFragment for (final Uri uri : inputUris) { mAdapter.add(uri); - if (uri.equals(mCurrentInputUri)) { + boolean uriIsCancelled = mCancelledInputUris.contains(uri); + if (uriIsCancelled) { + mAdapter.setCancelled(uri, true); continue; } - if (mCancelledInputUris.contains(uri)) { - mAdapter.setCancelled(uri, new OnClickListener() { - @Override - public void onClick(View v) { - retryUri(uri); - } - }); + boolean uriHasResult = results != null && results.containsKey(uri); + if (uriHasResult) { + processResult(uri); continue; } - if (results != null && results.containsKey(uri)) { - processResult(uri); - } else { - mPendingInputUris.add(uri); - } + mPendingInputUris.add(uri); } - if (mCurrentInputUri == null) { - cryptoOperation(); - } + // check if there are any pending input uris + cryptoOperation(); } @Override @@ -364,12 +382,7 @@ public class DecryptListFragment mCurrentInputUri = null; mCancelledInputUris.add(uri); - mAdapter.setCancelled(uri, new OnClickListener() { - @Override - public void onClick(View v) { - retryUri(uri); - } - }); + mAdapter.setCancelled(uri, true); cryptoOperation(); @@ -463,8 +476,8 @@ public class DecryptListFragment mPendingInputUris.add(uri); mAdapter.resetItemData(uri); + // check if there are any pending input uris cryptoOperation(); - } public void displayBottomSheet(final InputDataResult result, final int index) { @@ -585,6 +598,11 @@ public class DecryptListFragment @Override public InputDataParcel createOperationInput() { + Activity activity = getActivity(); + if (activity == null) { + return null; + } + if (mCurrentInputUri == null) { if (mPendingInputUris.isEmpty()) { // nothing left to do @@ -594,7 +612,11 @@ public class DecryptListFragment mCurrentInputUri = mPendingInputUris.remove(0); } - Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri); + Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri); + + if ( ! checkAndRequestReadPermission(activity, mCurrentInputUri)) { + return null; + } PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel() .setAllowSymmetricDecryption(true); @@ -602,6 +624,87 @@ public class DecryptListFragment } + /** + * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. + * + * This method returns true on Android < 6, or if permission is already granted. It + * requests the permission and returns false otherwise, taking over responsibility + * for mCurrentInputUri. + * + * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html + */ + private boolean checkAndRequestReadPermission(Activity activity, final Uri uri) { + if ( ! "file".equals(uri.getScheme())) { + return true; + } + + if (Build.VERSION.SDK_INT < VERSION_CODES.M) { + return true; + } + + // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + return true; + } + + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + requestPermissions( + new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, + REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); + + return false; + + } + + @Override + public void onRequestPermissionsResult(int requestCode, + @NonNull String[] permissions, + @NonNull int[] grantResults) { + + if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + return; + } + + boolean permissionWasGranted = grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED; + + if (permissionWasGranted) { + + // permission granted -> retry all cancelled file uris + for (Uri uri : mCancelledInputUris) { + if ( ! "file".equals(uri.getScheme())) { + continue; + } + mCancelledInputUris.remove(uri); + mPendingInputUris.add(uri); + mAdapter.setCancelled(uri, false); + } + + } else { + + // permission denied -> cancel current, and all pending file uris + mCurrentInputUri = null; + for (final Uri uri : mPendingInputUris) { + if ( ! "file".equals(uri.getScheme())) { + continue; + } + mPendingInputUris.remove(uri); + mCancelledInputUris.add(uri); + mAdapter.setCancelled(uri, true); + } + + } + + // hand control flow back + cryptoOperation(); + + } + @Override public boolean onMenuItemClick(MenuItem menuItem) { if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { @@ -780,8 +883,10 @@ public class DecryptListFragment return false; } ViewModel viewModel = (ViewModel) o; - return !(mInputUri != null ? !mInputUri.equals(viewModel.mInputUri) - : viewModel.mInputUri != null); + if (mInputUri == null) { + return viewModel.mInputUri == null; + } + return mInputUri.equals(viewModel.mInputUri); } // Depends on inputUri only @@ -1017,10 +1122,19 @@ public class DecryptListFragment notifyItemChanged(pos); } - public void setCancelled(Uri uri, OnClickListener retryListener) { + public void setCancelled(final Uri uri, boolean isCancelled) { ViewModel newModel = new ViewModel(uri); int pos = mDataset.indexOf(newModel); - mDataset.get(pos).setCancelled(retryListener); + if (isCancelled) { + mDataset.get(pos).setCancelled(new OnClickListener() { + @Override + public void onClick(View v) { + retryUri(uri); + } + }); + } else { + mDataset.get(pos).setCancelled(null); + } notifyItemChanged(pos); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java index 201465b52..50dbc9a8b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.support.v4.app.FragmentTransaction; import android.widget.Toast; +import org.apache.james.mime4j.util.MimeUtil; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; @@ -59,12 +60,15 @@ public class EncryptTextActivity extends EncryptActivity { extras = new Bundle(); } + String textData = extras.getString(EXTRA_TEXT); + boolean returnProcessText = false; + // When sending to OpenKeychain Encrypt via share menu if (Intent.ACTION_SEND.equals(action) && type != null) { Log.logDebugBundle(extras, "extras"); // When sending to OpenKeychain Encrypt via share menu - if ("text/plain".equals(type)) { + if ( ! MimeUtil.isSameMimeType("text/plain", type)) { Toast.makeText(this, R.string.toast_wrong_mimetype, Toast.LENGTH_LONG).show(); finish(); return; @@ -94,12 +98,33 @@ public class EncryptTextActivity extends EncryptActivity { } // handle like normal text encryption, override action and extras to later // executeServiceMethod ACTION_ENCRYPT_TEXT in main actions - extras.putString(EXTRA_TEXT, sharedText); + textData = sharedText; } } - String textData = extras.getString(EXTRA_TEXT); + // Android 6, PROCESS_TEXT Intent + if (Intent.ACTION_PROCESS_TEXT.equals(action) && type != null) { + + String sharedText = null; + if (extras.containsKey(Intent.EXTRA_PROCESS_TEXT)) { + sharedText = extras.getString(Intent.EXTRA_PROCESS_TEXT); + returnProcessText = true; + } else if (extras.containsKey(Intent.EXTRA_PROCESS_TEXT_READONLY)) { + sharedText = extras.getString(Intent.EXTRA_PROCESS_TEXT_READONLY); + } + + if (sharedText != null) { + if (sharedText.length() > Constants.TEXT_LENGTH_LIMIT) { + sharedText = sharedText.substring(0, Constants.TEXT_LENGTH_LIMIT); + Notify.create(this, R.string.snack_shared_text_too_long, Style.WARN).show(); + } + // handle like normal text encryption, override action and extras to later + // executeServiceMethod ACTION_ENCRYPT_TEXT in main actions + textData = sharedText; + } + } + if (textData == null) { textData = ""; } @@ -107,7 +132,7 @@ public class EncryptTextActivity extends EncryptActivity { if (savedInstanceState == null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData); + EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData, returnProcessText); transaction.replace(R.id.encrypt_text_container, encryptFragment); transaction.commit(); } 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 4ce241c02..10d88253d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java @@ -56,8 +56,10 @@ public class EncryptTextFragment public static final String ARG_TEXT = "text"; public static final String ARG_USE_COMPRESSION = "use_compression"; + public static final String ARG_RETURN_PROCESS_TEXT = "return_process_text"; private boolean mShareAfterEncrypt; + private boolean mReturnProcessTextAfterEncrypt; private boolean mUseCompression; private boolean mHiddenRecipients = false; @@ -66,11 +68,12 @@ public class EncryptTextFragment /** * Creates new instance of this fragment */ - public static EncryptTextFragment newInstance(String text) { + public static EncryptTextFragment newInstance(String text, boolean returnProcessTextAfterEncrypt) { EncryptTextFragment frag = new EncryptTextFragment(); Bundle args = new Bundle(); args.putString(ARG_TEXT, text); + args.putBoolean(ARG_RETURN_PROCESS_TEXT, returnProcessTextAfterEncrypt); frag.setArguments(args); return frag; @@ -128,6 +131,7 @@ public class EncryptTextFragment super.onCreate(savedInstanceState); if (savedInstanceState == null) { mMessage = getArguments().getString(ARG_TEXT); + mReturnProcessTextAfterEncrypt = getArguments().getBoolean(ARG_RETURN_PROCESS_TEXT, false); } Preferences prefs = Preferences.getPreferences(getActivity()); @@ -151,6 +155,12 @@ public class EncryptTextFragment inflater.inflate(R.menu.encrypt_text_fragment, menu); menu.findItem(R.id.check_enable_compression).setChecked(mUseCompression); + + if (mReturnProcessTextAfterEncrypt) { + menu.findItem(R.id.encrypt_paste).setVisible(true); + menu.findItem(R.id.encrypt_copy).setVisible(false); + menu.findItem(R.id.encrypt_share).setVisible(false); + } } @Override @@ -177,6 +187,11 @@ public class EncryptTextFragment cryptoOperation(new CryptoInputParcel(new Date())); break; } + case R.id.encrypt_paste: { + hideKeyboard(); + cryptoOperation(new CryptoInputParcel(new Date())); + break; + } default: { return super.onOptionsItemSelected(item); } @@ -328,6 +343,11 @@ public class EncryptTextFragment // Share encrypted message/file startActivity(Intent.createChooser(createSendIntent(result.getResultBytes()), getString(R.string.title_share_message))); + } else if (mReturnProcessTextAfterEncrypt) { + Intent resultIntent = new Intent(); + resultIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, new String(result.getResultBytes())); + getActivity().setResult(Activity.RESULT_OK, resultIntent); + getActivity().finish(); } else { // Copy to clipboard copyToClipboard(result); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index 746c75600..8de60dfd3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -29,6 +29,9 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.Constants; 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.util.FileHelper; public class ImportKeysFileFragment extends Fragment { @@ -78,12 +81,16 @@ public class ImportKeysFileFragment extends Fragment { String sendText = ""; if (clipboardText != null) { sendText = clipboardText.toString(); + sendText = PgpHelper.getPgpKeyContent(sendText); + if (sendText == null) { + Notify.create(mImportActivity, "Bad data!", Style.ERROR).show(); + return; + } mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), null)); } } }); - return view; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 864283b0a..7aed3176c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -17,6 +17,11 @@ package org.sufficientlysecure.keychain.ui; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import android.app.Activity; import android.net.Uri; import android.os.Bundle; @@ -40,22 +45,12 @@ import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; -import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; -import java.io.ByteArrayInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - public class ImportKeysListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { @@ -180,8 +175,8 @@ public class ImportKeysListFragment extends ListFragment implements } static public class BytesLoaderState extends LoaderState { - byte[] mKeyBytes; - Uri mDataUri; + public byte[] mKeyBytes; + public Uri mDataUri; BytesLoaderState(byte[] keyBytes, Uri dataUri) { mKeyBytes = keyBytes; @@ -305,9 +300,7 @@ public class ImportKeysListFragment extends ListFragment implements onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_BYTES: { - BytesLoaderState ls = (BytesLoaderState) mLoaderState; - InputData inputData = getInputData(ls.mKeyBytes, ls.mDataUri); - return new ImportKeysListLoader(mActivity, inputData); + return new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState); } case LOADER_ID_CLOUD: { CloudLoaderState ls = (CloudLoaderState) mLoaderState; @@ -432,23 +425,4 @@ public class ImportKeysListFragment extends ListFragment implements } } - private InputData getInputData(byte[] importBytes, Uri dataUri) { - InputData inputData = null; - if (importBytes != null) { - inputData = new InputData(new ByteArrayInputStream(importBytes), importBytes.length); - } else if (dataUri != null) { - try { - InputStream inputStream = getActivity().getContentResolver().openInputStream(dataUri); - long length = FileHelper.getFileSize(getActivity(), dataUri, -1); - - inputData = new InputData(inputStream, length); - } catch (FileNotFoundException e) { - Log.e(Constants.TAG, "FileNotFoundException!", e); - return null; - } - } - - return inputData; - } - } 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 b60f3984c..45ce604c3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -87,12 +87,7 @@ public class ImportKeysProxyActivity extends FragmentActivity processScannedContent(dataUri); } else if (ACTION_SCAN_WITH_RESULT.equals(action) || ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) { - IntentIntegrator integrator = new IntentIntegrator(this); - integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES) - .setPrompt(getString(R.string.import_qr_code_text)) - .setResultDisplayDuration(0); - integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - integrator.initiateScan(); + new IntentIntegrator(this).setCaptureActivity(QrCodeCaptureActivity.class).initiateScan(); } else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { // Check to see if the Activity started due to an Android Beam if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 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 ce6994ba4..23c1250d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -30,6 +30,7 @@ import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -46,14 +47,17 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; +import android.widget.Button; import android.widget.ListView; import android.widget.TextView; +import android.widget.ViewAnimator; import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionsMenu; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.BenchmarkResult; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; @@ -61,6 +65,7 @@ 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.BenchmarkInputParcel; import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; @@ -97,6 +102,8 @@ public class KeyListFragment extends LoaderFragment // saves the mode object for multiselect, needed for reset at some point private ActionMode mActionMode = null; + private Button vSearchButton; + private ViewAnimator vSearchContainer; private String mQuery; private FloatingActionsMenu mFab; @@ -161,7 +168,9 @@ public class KeyListFragment extends LoaderFragment super.onActivityCreated(savedInstanceState); // show app name instead of "keys" from nav drawer - getActivity().setTitle(R.string.app_name); + final FragmentActivity activity = getActivity(); + + activity.setTitle(R.string.app_name); mStickyList.setOnItemClickListener(this); mStickyList.setAreHeadersSticky(true); @@ -170,7 +179,7 @@ public class KeyListFragment extends LoaderFragment // Adds an empty footer view so that the Floating Action Button won't block content // in last few rows. - View footer = new View(getActivity()); + View footer = new View(activity); int spacing = (int) android.util.TypedValue.applyDimension( android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics() @@ -194,7 +203,7 @@ public class KeyListFragment extends LoaderFragment @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - android.view.MenuInflater inflater = getActivity().getMenuInflater(); + android.view.MenuInflater inflater = activity.getMenuInflater(); inflater.inflate(R.menu.key_list_multi, menu); mActionMode = mode; return true; @@ -234,7 +243,7 @@ public class KeyListFragment extends LoaderFragment @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { + boolean checked) { if (checked) { mAdapter.setNewSelection(position, true); } else { @@ -254,8 +263,21 @@ public class KeyListFragment extends LoaderFragment // Start out with a progress indicator. setContentShown(false); + // this view is made visible if no data is available + mStickyList.setEmptyView(activity.findViewById(R.id.key_list_empty)); + + // click on search button (in empty view) starts query for search string + vSearchContainer = (ViewAnimator) activity.findViewById(R.id.search_container); + vSearchButton = (Button) activity.findViewById(R.id.search_button); + vSearchButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + startSearchForQuery(); + } + }); + // Create an empty adapter we will use to display the loaded data. - mAdapter = new KeyListAdapter(getActivity(), null, 0); + mAdapter = new KeyListAdapter(activity, null, 0); mStickyList.setAdapter(mAdapter); // Prepare the loader. Either re-connect with an existing one, @@ -263,8 +285,20 @@ public class KeyListFragment extends LoaderFragment getLoaderManager().initLoader(0, null, this); } + private void startSearchForQuery() { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + Intent searchIntent = new Intent(activity, ImportKeysActivity.class); + searchIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, mQuery); + searchIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); + startActivity(searchIntent); + } + static final String ORDER = - KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC"; + KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " COLLATE NOCASE ASC"; @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { @@ -318,9 +352,6 @@ public class KeyListFragment extends LoaderFragment mStickyList.setAdapter(mAdapter); - // this view is made visible if no data is available - mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty)); - // end action mode, if any if (mActionMode != null) { mActionMode.finish(); @@ -386,6 +417,7 @@ public class KeyListFragment extends LoaderFragment if (Constants.DEBUG) { menu.findItem(R.id.menu_key_list_debug_cons).setVisible(true); + menu.findItem(R.id.menu_key_list_debug_bench).setVisible(true); menu.findItem(R.id.menu_key_list_debug_read).setVisible(true); menu.findItem(R.id.menu_key_list_debug_write).setVisible(true); menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true); @@ -469,6 +501,10 @@ public class KeyListFragment extends LoaderFragment consolidate(); return true; + case R.id.menu_key_list_debug_bench: + benchmark(); + return true; + default: return super.onOptionsItemSelected(item); } @@ -482,17 +518,25 @@ public class KeyListFragment extends LoaderFragment @Override public boolean onQueryTextChange(String s) { Log.d(Constants.TAG, "onQueryTextChange s:" + s); - // Called when the action bar search text has changed. Update - // the search filter, and restart the loader to do a new query - // with this filter. + // Called when the action bar search text has changed. Update the + // search filter, and restart the loader to do a new query with this + // filter. // If the nav drawer is opened, onQueryTextChange("") is executed. // This hack prevents restarting the loader. - // TODO: better way to fix this? - String tmp = (mQuery == null) ? "" : mQuery; - if (!s.equals(tmp)) { + if (!s.equals(mQuery)) { mQuery = s; getLoaderManager().restartLoader(0, null, this); } + + if (s.length() > 2) { + vSearchButton.setText(getString(R.string.btn_search_for_query, mQuery)); + vSearchContainer.setDisplayedChild(1); + vSearchContainer.setVisibility(View.VISIBLE); + } else { + vSearchContainer.setDisplayedChild(0); + vSearchContainer.setVisibility(View.GONE); + } + return true; } @@ -559,8 +603,8 @@ public class KeyListFragment extends LoaderFragment mKeyserver = cloudPrefs.keyserver; } - mImportOpHelper = new CryptoOperationHelper<>(1, this, - this, R.string.progress_updating); + mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_updating); + mImportOpHelper.setProgressCancellable(true); mImportOpHelper.cryptoOperation(); } @@ -601,6 +645,43 @@ public class KeyListFragment extends LoaderFragment mConsolidateOpHelper.cryptoOperation(); } + private void benchmark() { + + CryptoOperationHelper.Callback<BenchmarkInputParcel, BenchmarkResult> callback + = new CryptoOperationHelper.Callback<BenchmarkInputParcel, BenchmarkResult>() { + + @Override + public BenchmarkInputParcel createOperationInput() { + return new BenchmarkInputParcel(); // we want to perform a full consolidate + } + + @Override + public void onCryptoOperationSuccess(BenchmarkResult result) { + result.createNotify(getActivity()).show(); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(BenchmarkResult result) { + result.createNotify(getActivity()).show(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + + CryptoOperationHelper opHelper = + new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); + + opHelper.cryptoOperation(); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index 7e9b4953c..7bd7bafcc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * Copyright (C) 2015 Kai Jiang <jiangkai@gmail.com> * @@ -27,13 +27,13 @@ import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.Toolbar; import android.view.View; -import android.widget.AdapterView; import com.mikepenz.community_material_typeface_library.CommunityMaterial; +import com.mikepenz.fontawesome_typeface_library.FontAwesome; import com.mikepenz.google_material_typeface_library.GoogleMaterial; -import com.mikepenz.iconics.typeface.FontAwesome; import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.DrawerBuilder; +import com.mikepenz.materialdrawer.model.DividerDrawerItem; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; @@ -75,25 +75,23 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac .withToolbar(mToolbar) .addDrawerItems( new PrimaryDrawerItem().withName(R.string.nav_keys).withIcon(CommunityMaterial.Icon.cmd_key) - .withIdentifier(ID_KEYS).withCheckable(false), + .withIdentifier(ID_KEYS).withSelectable(false), new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock) - .withIdentifier(ID_ENCRYPT_DECRYPT).withCheckable(false), + .withIdentifier(ID_ENCRYPT_DECRYPT).withSelectable(false), new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps) - .withIdentifier(ID_APPS).withCheckable(false), + .withIdentifier(ID_APPS).withSelectable(false), new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore) - .withIdentifier(ID_BACKUP).withCheckable(false) - ) - .addStickyDrawerItems( - // display and stick on bottom of drawer - new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withCheckable(false), - new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withCheckable(false) + .withIdentifier(ID_BACKUP).withSelectable(false), + new DividerDrawerItem(), + new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withSelectable(false), + new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false) ) .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { @Override - public boolean onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) { + public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { if (drawerItem != null) { Intent intent = null; - switch(drawerItem.getIdentifier()) { + switch (drawerItem.getIdentifier()) { case ID_KEYS: onKeysSelected(); break; @@ -182,28 +180,28 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac private void onKeysSelected() { mToolbar.setTitle(R.string.app_name); - mDrawer.setSelectionByIdentifier(ID_KEYS, false); + mDrawer.setSelection(ID_KEYS, false); Fragment frag = new KeyListFragment(); setFragment(frag, false); } private void onEnDecryptSelected() { mToolbar.setTitle(R.string.nav_encrypt_decrypt); - mDrawer.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false); + mDrawer.setSelection(ID_ENCRYPT_DECRYPT, false); Fragment frag = new EncryptDecryptFragment(); setFragment(frag, true); } private void onAppsSelected() { mToolbar.setTitle(R.string.nav_apps); - mDrawer.setSelectionByIdentifier(ID_APPS, false); + mDrawer.setSelection(ID_APPS, false); Fragment frag = new AppsListFragment(); setFragment(frag, true); } private void onBackupSelected() { mToolbar.setTitle(R.string.nav_backup); - mDrawer.setSelectionByIdentifier(ID_BACKUP, false); + mDrawer.setSelection(ID_BACKUP, false); Fragment frag = new BackupRestoreFragment(); setFragment(frag, true); } @@ -258,16 +256,16 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac // make sure the selected icon is the one shown at this point if (frag instanceof KeyListFragment) { mToolbar.setTitle(R.string.app_name); - mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_KEYS), false); + mDrawer.setSelection(mDrawer.getPosition(ID_KEYS), false); } else if (frag instanceof EncryptDecryptFragment) { mToolbar.setTitle(R.string.nav_encrypt_decrypt); - mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false); + mDrawer.setSelection(mDrawer.getPosition(ID_ENCRYPT_DECRYPT), false); } else if (frag instanceof AppsListFragment) { mToolbar.setTitle(R.string.nav_apps); - mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_APPS), false); + mDrawer.setSelection(mDrawer.getPosition(ID_APPS), false); } else if (frag instanceof BackupRestoreFragment) { mToolbar.setTitle(R.string.nav_backup); - mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false); + mDrawer.setSelection(mDrawer.getPosition(ID_BACKUP), false); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java new file mode 100644 index 000000000..b5d3948be --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java @@ -0,0 +1,125 @@ +/* + * 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.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentActivity; +import android.support.v4.content.ContextCompat; +import android.view.KeyEvent; + +import com.journeyapps.barcodescanner.CaptureManager; +import com.journeyapps.barcodescanner.CompoundBarcodeView; + +import org.sufficientlysecure.keychain.R; + +public class QrCodeCaptureActivity extends FragmentActivity { + private CaptureManager capture; + private CompoundBarcodeView barcodeScannerView; + + public static final int MY_PERMISSIONS_REQUEST_CAMERA = 42; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.qr_code_capture_activity); + + barcodeScannerView = (CompoundBarcodeView) findViewById(R.id.zxing_barcode_scanner); + barcodeScannerView.setStatusText(getString(R.string.import_qr_code_text)); + + if (savedInstanceState != null) { + init(barcodeScannerView, getIntent(), savedInstanceState); + } + + // check Android 6 permission + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { + init(barcodeScannerView, getIntent(), null); + } else { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.CAMERA}, + MY_PERMISSIONS_REQUEST_CAMERA); + } + } + + private void init(CompoundBarcodeView barcodeScannerView, Intent intent, Bundle savedInstanceState) { + capture = new CaptureManager(this, barcodeScannerView); + capture.initializeFromIntent(intent, savedInstanceState); + capture.decode(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], + @NonNull int[] grantResults) { + switch (requestCode) { + case MY_PERMISSIONS_REQUEST_CAMERA: { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted + init(barcodeScannerView, getIntent(), null); + } else { + setResult(Activity.RESULT_CANCELED); + finish(); + } + } + } + } + + @Override + protected void onResume() { + super.onResume(); + if (capture != null) { + capture.onResume(); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (capture != null) { + capture.onPause(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (capture != null) { + capture.onDestroy(); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (capture != null) { + capture.onSaveInstanceState(outState); + } + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); + } +}
\ No newline at end of file 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 8118c2fa7..f5c239558 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -54,14 +54,8 @@ import java.util.List; 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"; - public static final String ACTION_PREFS_PROXY = "org.sufficientlysecure.keychain.ui.PREFS_PROXY"; - public static final String ACTION_PREFS_GUI = "org.sufficientlysecure.keychain.ui.PREFS_GUI"; - public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; - private PreferenceScreen mKeyServerPreference = null; private static Preferences sPreferences; private ThemeChanger mThemeChanger; @@ -74,49 +68,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity { super.onCreate(savedInstanceState); setupToolbar(); - - String action = getIntent().getAction(); - - if (ACTION_PREFS_CLOUD.equals(action)) { - addPreferencesFromResource(R.xml.cloud_search_prefs); - - mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS); - mKeyServerPreference.setSummary(keyserverSummary(this)); - mKeyServerPreference - .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - Intent intent = new Intent(SettingsActivity.this, - SettingsKeyServerActivity.class); - intent.putExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS, - sPreferences.getKeyServers()); - startActivityForResult(intent, REQUEST_CODE_KEYSERVER_PREF); - return false; - } - }); - initializeSearchKeyserver( - (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER) - ); - initializeSearchKeybase( - (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE) - ); - - } else if (ACTION_PREFS_ADV.equals(action)) { - addPreferencesFromResource(R.xml.passphrase_preferences); - - initializePassphraseCacheSubs( - (CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS)); - - initializePassphraseCacheTtl( - (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); - - initializeUseNumKeypadForYubiKeyPin( - (CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN)); - - } else if (ACTION_PREFS_GUI.equals(action)) { - addPreferencesFromResource(R.xml.gui_preferences); - - initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); - } } @Override @@ -448,23 +399,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } /** - * This fragment shows gui preferences. - */ - public static class GuiPrefsFragment extends PreferenceFragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.gui_preferences); - - initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); - } - } - - /** * This fragment shows the keyserver/contacts sync preferences */ public static class SyncPrefsFragment extends PreferenceFragment { @@ -576,7 +510,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity { return PassphrasePrefsFragment.class.getName().equals(fragmentName) || CloudSearchPrefsFragment.class.getName().equals(fragmentName) || ProxyPrefsFragment.class.getName().equals(fragmentName) - || GuiPrefsFragment.class.getName().equals(fragmentName) || SyncPrefsFragment.class.getName().equals(fragmentName) || ExperimentalPrefsFragment.class.getName().equals(fragmentName) || super.isValidFragment(fragmentName); 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 cbc7b88bf..0184527b7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -38,6 +38,7 @@ import android.os.Handler; import android.provider.ContactsContract; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CollapsingToolbarLayout; +import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; @@ -869,7 +870,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mActionEncryptFile.setVisibility(View.INVISIBLE); mActionEncryptText.setVisibility(View.INVISIBLE); mActionNfc.setVisibility(View.INVISIBLE); - mFab.setVisibility(View.GONE); + hideFab(); mQrCodeLayout.setVisibility(View.GONE); } else if (mIsExpired) { if (mIsSecret) { @@ -885,7 +886,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mActionEncryptFile.setVisibility(View.INVISIBLE); mActionEncryptText.setVisibility(View.INVISIBLE); mActionNfc.setVisibility(View.INVISIBLE); - mFab.setVisibility(View.GONE); + hideFab(); mQrCodeLayout.setVisibility(View.GONE); } else if (mIsSecret) { mStatusText.setText(R.string.view_key_my_key); @@ -927,7 +928,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements } else { mActionNfc.setVisibility(View.GONE); } - mFab.setVisibility(View.VISIBLE); + showFab(); // noinspection deprecation (no getDrawable with theme at current minApi level 15!) mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); } else { @@ -944,7 +945,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements color = getResources().getColor(R.color.key_flag_green); photoTask.execute(mMasterKeyId); - mFab.setVisibility(View.GONE); + hideFab(); } else { mStatusText.setText(R.string.view_key_unverified); mStatusImage.setVisibility(View.VISIBLE); @@ -952,7 +953,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements State.UNVERIFIED, R.color.icons, true); color = getResources().getColor(R.color.key_flag_orange); - mFab.setVisibility(View.VISIBLE); + showFab(); } } @@ -982,6 +983,28 @@ public class ViewKeyActivity extends BaseNfcActivity implements } } + /** + * Helper to show Fab, from http://stackoverflow.com/a/31047038 + */ + private void showFab() { + CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) mFab.getLayoutParams(); + p.setBehavior(new FloatingActionButton.Behavior()); + p.setAnchorId(R.id.app_bar_layout); + mFab.setLayoutParams(p); + mFab.setVisibility(View.VISIBLE); + } + + /** + * Helper to hide Fab, from http://stackoverflow.com/a/31047038 + */ + private void hideFab() { + CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) mFab.getLayoutParams(); + p.setBehavior(null); //should disable default animations + p.setAnchorId(View.NO_ID); //should let you set visibility + mFab.setLayoutParams(p); + mFab.setVisibility(View.GONE); + } + @Override public void onLoaderReset(Loader<Cursor> loader) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 139512ba9..038ebd5dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -17,6 +17,14 @@ package org.sufficientlysecure.keychain.ui.adapter; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + import android.content.Context; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.util.LongSparseArray; @@ -28,28 +36,26 @@ import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; +import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState; +import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PositionAwareInputStream; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.util.ArrayList; - public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { final Context mContext; - final InputData mInputData; + final BytesLoaderState mLoaderState; ArrayList<ImportKeysListEntry> mData = new ArrayList<>(); LongSparseArray<ParcelableKeyRing> mParcelableRings = new LongSparseArray<>(); AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper; - public ImportKeysListLoader(Context context, InputData inputData) { + public ImportKeysListLoader(Context context, BytesLoaderState inputData) { super(context); this.mContext = context; - this.mInputData = inputData; + this.mLoaderState = inputData; } @Override @@ -62,12 +68,13 @@ public class ImportKeysListLoader GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null); mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult); - if (mInputData == null) { + if (mLoaderState == null) { Log.e(Constants.TAG, "Input data is null!"); return mEntryListWrapper; } - generateListOfKeyrings(mInputData); + InputData inputData = getInputData(getContext(), mLoaderState); + generateListOfKeyrings(inputData); return mEntryListWrapper; } @@ -99,12 +106,7 @@ public class ImportKeysListLoader return mParcelableRings; } - /** - * Reads all PGPKeyRing objects from input - * - * @param inputData - * @return - */ + /** Reads all PGPKeyRing objects from the bytes of an InputData object. */ private void generateListOfKeyrings(InputData inputData) { PositionAwareInputStream progressIn = new PositionAwareInputStream( inputData.getInputStream()); @@ -132,4 +134,23 @@ public class ImportKeysListLoader } } + private static InputData getInputData(Context context, BytesLoaderState loaderState) { + InputData inputData = null; + if (loaderState.mKeyBytes != null) { + inputData = new InputData(new ByteArrayInputStream(loaderState.mKeyBytes), loaderState.mKeyBytes.length); + } else if (loaderState.mDataUri != null) { + try { + InputStream inputStream = context.getContentResolver().openInputStream(loaderState.mDataUri); + long length = FileHelper.getFileSize(context, loaderState.mDataUri, -1); + + inputData = new InputData(inputStream, length); + } catch (FileNotFoundException e) { + Log.e(Constants.TAG, "FileNotFoundException!", e); + return null; + } + } + + return inputData; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java index 471a20411..7cc37b3a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java @@ -1,13 +1,8 @@ package org.sufficientlysecure.keychain.ui.adapter; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - import android.content.Context; import android.database.Cursor; -import android.support.v7.internal.widget.AdapterViewCompat; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; @@ -18,6 +13,10 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + public class KeySelectableAdapter extends KeyAdapter implements OnItemClickListener { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java index 5cf0e6e08..5566c725b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java @@ -24,6 +24,7 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; +import android.os.Build.VERSION_CODES; import android.support.v4.content.CursorLoader; import android.util.Log; import android.view.LayoutInflater; @@ -228,9 +229,11 @@ public class LinkedIdsAdapter extends UserAttributesAdapter { } public void seekAttention() { - ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000); - anim.setStartDelay(200); - anim.start(); + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000); + anim.setStartDelay(200); + anim.start(); + } } } 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 index d2877d542..7ab9c7237 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -84,6 +84,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu public static final int REQUEST_CODE_RETRY_UPLOAD = 4; private Integer mProgressMessageResource; + private boolean mCancellable = false; private FragmentActivity mActivity; private Fragment mFragment; @@ -118,6 +119,10 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu mProgressMessageResource = id; } + public void setProgressCancellable(boolean cancellable) { + mCancellable = cancellable; + } + private void initiateInputActivity(RequiredInputParcel requiredInput, CryptoInputParcel cryptoInputParcel) { @@ -311,7 +316,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu if (mProgressMessageResource != null) { saveHandler.showProgressDialog( activity.getString(mProgressMessageResource), - ProgressDialog.STYLE_HORIZONTAL, false); + ProgressDialog.STYLE_HORIZONTAL, mCancellable); } activity.startService(intent); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index 48e6c2cee..01d51af48 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -23,11 +23,13 @@ import android.database.Cursor; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -46,14 +48,14 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; import org.sufficientlysecure.keychain.util.Log; -public class EncryptKeyCompletionView extends TokenCompleteTextView +public class EncryptKeyCompletionView extends TokenCompleteTextView<KeyItem> implements LoaderCallbacks<Cursor> { public static final String ARG_QUERY = "query"; private KeyAdapter mAdapter; private LoaderManager mLoaderManager; - private String mPrefix; + private CharSequence mPrefix; public EncryptKeyCompletionView(Context context) { super(context); @@ -79,30 +81,27 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView } @Override - public void setPrefix(String p) { + public void setPrefix(CharSequence p) { // this one is private in the superclass, but we need it here mPrefix = p; super.setPrefix(p); } @Override - protected View getViewForObject(Object object) { - if (object instanceof KeyItem) { - LayoutInflater l = LayoutInflater.from(getContext()); - View view = l.inflate(R.layout.recipient_box_entry, null); - ((TextView) view.findViewById(android.R.id.text1)).setText(((KeyItem) object).getReadableName()); - return view; - } - return null; + protected View getViewForObject(KeyItem keyItem) { + LayoutInflater l = LayoutInflater.from(getContext()); + View view = l.inflate(R.layout.recipient_box_entry, null); + ((TextView) view.findViewById(android.R.id.text1)).setText(keyItem.getReadableName()); + return view; } @Override - protected Object defaultObject(String completionText) { + protected KeyItem defaultObject(String completionText) { // TODO: We could try to automagically download the key if it's unknown but a key id /*if (completionText.startsWith("0x")) { }*/ - return ""; + return null; } @Override @@ -128,7 +127,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView // These are the rows that we will retrieve. Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); - String[] projection = KeyAdapter.getProjectionWith(new String[] { + String[] projection = KeyAdapter.getProjectionWith(new String[]{ KeychainContract.KeyRings.HAS_ENCRYPT, }); @@ -136,18 +135,19 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView + KeyRings.IS_EXPIRED + " = 0 AND " + Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0"; - if (args != null && args.containsKey(ARG_QUERY)) { - String query = args.getString(ARG_QUERY); - mAdapter.setSearchQuery(query); + if (args == null || !args.containsKey(ARG_QUERY)) { + // mAdapter.setSearchQuery(null); + // return new CursorLoader(getContext(), baseUri, projection, where, null, null); + return null; + } - where += " AND " + KeyRings.USER_ID + " LIKE ?"; + String query = args.getString(ARG_QUERY); + mAdapter.setSearchQuery(query); - return new CursorLoader(getContext(), baseUri, projection, where, - new String[]{"%" + query + "%"}, null); - } + where += " AND " + KeyRings.USER_ID + " LIKE ?"; - mAdapter.setSearchQuery(null); - return new CursorLoader(getContext(), baseUri, projection, where, null, null); + return new CursorLoader(getContext(), baseUri, projection, where, + new String[]{"%" + query + "%"}, null); } @@ -169,6 +169,8 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView super.showDropDown(); } + + @Override public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { super.onFocusChanged(hasFocus, direction, previous); @@ -179,13 +181,18 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView } @Override - protected void performFiltering(CharSequence text, int start, int end, int keyCode) { + protected void performFiltering(@NonNull CharSequence text, int start, int end, int keyCode) { super.performFiltering(text, start, end, keyCode); if (start < mPrefix.length()) { start = mPrefix.length(); } + String query = text.subSequence(start, end).toString(); + if (TextUtils.isEmpty(query) || query.length() < 2) { + mLoaderManager.destroyLoader(0); + return; + } Bundle args = new Bundle(); - args.putString(ARG_QUERY, text.subSequence(start, end).toString()); + args.putString(ARG_QUERY, query); mLoaderManager.restartLoader(0, args, this); } |