Merge branch 'master' into v/decrypt-key-lookup
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (results != null && results.containsKey(uri)) {
|
||||
boolean uriHasResult = results != null && results.containsKey(uri);
|
||||
if (uriHasResult) {
|
||||
processResult(uri);
|
||||
} else {
|
||||
mPendingInputUris.add(uri);
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -447,23 +398,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
|
||||
*/
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
where += " AND " + KeyRings.USER_ID + " LIKE ?";
|
||||
|
||||
return new CursorLoader(getContext(), baseUri, projection, where,
|
||||
new String[]{"%" + query + "%"}, null);
|
||||
if (args == null || !args.containsKey(ARG_QUERY)) {
|
||||
// mAdapter.setSearchQuery(null);
|
||||
// return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
mAdapter.setSearchQuery(null);
|
||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||
String query = args.getString(ARG_QUERY);
|
||||
mAdapter.setSearchQuery(query);
|
||||
|
||||
where += " AND " + KeyRings.USER_ID + " LIKE ?";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user