Merge branch 'master' of github.com:open-keychain/open-keychain

This commit is contained in:
Dominik Schürmann
2015-11-19 14:00:17 +01:00
21 changed files with 598 additions and 256 deletions

View File

@@ -73,10 +73,14 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.InputDataResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.InputDataParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
// this import NEEDS to be above the ViewModel AND SubViewHolder one, or it won't compile! (as of 16.09.15)
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder;
@@ -90,8 +94,25 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
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 {
@@ -103,7 +124,7 @@ public class DecryptListFragment
public static final String ARG_CAN_DELETE = "can_delete";
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
private static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 12;
private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12;
private ArrayList<Uri> mInputUris;
private HashMap<Uri, InputDataResult> mInputDataResults;
@@ -201,7 +222,9 @@ public class DecryptListFragment
);
}
private void displayInputUris(ArrayList<Uri> inputUris, ArrayList<Uri> cancelledUris,
private void displayInputUris(
ArrayList<Uri> inputUris,
ArrayList<Uri> cancelledUris,
HashMap<Uri,InputDataResult> results) {
mInputUris = inputUris;
@@ -214,21 +237,19 @@ public class DecryptListFragment
for (final Uri uri : inputUris) {
mAdapter.add(uri);
if (mCancelledInputUris.contains(uri)) {
mAdapter.setCancelled(uri, new OnClickListener() {
@Override
public void onClick(View v) {
retryUri(uri);
}
});
boolean uriIsCancelled = mCancelledInputUris.contains(uri);
if (uriIsCancelled) {
mAdapter.setCancelled(uri, true);
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);
}
// check if there are any pending input uris
@@ -362,12 +383,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();
@@ -457,8 +473,9 @@ public class DecryptListFragment
// un-cancel this one
mCancelledInputUris.remove(uri);
mInputDataResults.remove(uri);
mPendingInputUris.add(uri);
mAdapter.setCancelled(uri, null);
mAdapter.resetItemData(uri);
// check if there are any pending input uris
cryptoOperation();
@@ -582,6 +599,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
@@ -593,95 +615,102 @@ public class DecryptListFragment
Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri);
if (readPermissionGranted(mCurrentInputUri)) {
PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel()
.setAllowSymmetricDecryption(true);
return new InputDataParcel(mCurrentInputUri, decryptInput);
} else {
if ( ! checkAndRequestReadPermission(activity, mCurrentInputUri)) {
return null;
}
PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel()
.setAllowSymmetricDecryption(true);
return new InputDataParcel(mCurrentInputUri, decryptInput);
}
/**
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
*
* see
* https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
* 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 readPermissionGranted(final Uri uri) {
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;
}
if (! "file".equals(uri.getScheme())) {
// 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;
}
// Build 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 ||
ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
return true;
} else {
requestPermissions(
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
mCurrentInputUri = null;
mCancelledInputUris.add(uri);
mAdapter.setCancelled(uri, new OnClickListener() {
@Override
public void onClick(View v) {
retryUri(uri);
}
});
return false;
}
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) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
// permission granted -> retry all cancelled file uris!
for (Iterator<Uri> iterator = mCancelledInputUris.iterator(); iterator.hasNext(); ) {
Uri uri = iterator.next();
if ("file".equals(uri.getScheme())) {
iterator.remove();
mPendingInputUris.add(uri);
mAdapter.setCancelled(uri, null);
}
}
// check if there are any pending input uris
cryptoOperation();
} else {
// permission denied -> cancel all pending file uris
mCurrentInputUri = null;
for (final Uri uri : mPendingInputUris) {
if ("file".equals(uri.getScheme())) {
if (! mCancelledInputUris.contains(uri)) {
mCancelledInputUris.add(uri);
}
mAdapter.setCancelled(uri, new OnClickListener() {
@Override
public void onClick(View v) {
retryUri(uri);
}
});
}
}
}
}
default: {
super.onRequestPermissionsResult(requestCode, permissions, 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
Iterator<Uri> it = mCancelledInputUris.iterator();
while (it.hasNext()) {
Uri uri = it.next();
if ( ! "file".equals(uri.getScheme())) {
continue;
}
it.remove();
mPendingInputUris.add(uri);
mAdapter.setCancelled(uri, false);
}
} else {
// permission denied -> cancel current, and all pending file uris
mCancelledInputUris.add(mCurrentInputUri);
mAdapter.setCancelled(mCurrentInputUri, true);
mCurrentInputUri = null;
Iterator<Uri> it = mPendingInputUris.iterator();
while (it.hasNext()) {
Uri uri = it.next();
if ( ! "file".equals(uri.getScheme())) {
continue;
}
it.remove();
mCancelledInputUris.add(uri);
mAdapter.setCancelled(uri, true);
}
}
// hand control flow back
cryptoOperation();
}
@Override
@@ -714,38 +743,83 @@ public class DecryptListFragment
return false;
}
private void lookupUnknownKey(final Uri inputUri, long unknownKeyId) {
final ArrayList<ParcelableKeyRing> keyList;
final String keyserver;
// search config
{
Preferences prefs = Preferences.getPreferences(getActivity());
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
keyserver = cloudPrefs.keyserver;
}
{
ParcelableKeyRing keyEntry = new ParcelableKeyRing(null,
KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry);
keyList = selectedEntries;
}
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> callback
= new CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult>() {
@Override
public ImportKeyringParcel createOperationInput() {
return new ImportKeyringParcel(keyList, keyserver);
}
@Override
public void onCryptoOperationSuccess(ImportKeyResult result) {
retryUri(inputUri);
}
@Override
public void onCryptoOperationCancelled() {
mAdapter.setProcessingKeyLookup(inputUri, false);
}
@Override
public void onCryptoOperationError(ImportKeyResult result) {
result.createNotify(getActivity()).show();
mAdapter.setProcessingKeyLookup(inputUri, false);
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
};
mAdapter.setProcessingKeyLookup(inputUri, true);
CryptoOperationHelper importOpHelper = new CryptoOperationHelper<>(2, this, callback, null);
importOpHelper.cryptoOperation();
}
private void deleteFile(Activity activity, Uri uri) {
// we can only ever delete a file once, if we got this far either it's gone or it will never work
mCanDelete = false;
if ("file".equals(uri.getScheme())) {
File file = new File(uri.getPath());
if (file.delete()) {
try {
int deleted = FileHelper.deleteFileSecurely(activity, uri);
if (deleted > 0) {
Notify.create(activity, R.string.file_delete_ok, Style.OK).show();
} else {
Notify.create(activity, R.string.file_delete_none, Style.WARN).show();
}
return;
} catch (Exception e) {
Log.e(Constants.TAG, "exception deleting file", e);
Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show();
}
if ("content".equals(uri.getScheme())) {
try {
int deleted = activity.getContentResolver().delete(uri, null, null);
if (deleted > 0) {
Notify.create(activity, R.string.file_delete_ok, Style.OK).show();
} else {
Notify.create(activity, R.string.file_delete_none, Style.WARN).show();
}
} catch (Exception e) {
Log.e(Constants.TAG, "exception deleting file", e);
Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show();
}
return;
}
Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show();
}
public class DecryptFilesAdapter extends RecyclerView.Adapter<ViewHolder> {
@@ -759,6 +833,7 @@ public class DecryptListFragment
int mProgress, mMax;
String mProgressMsg;
OnClickListener mCancelled;
boolean mProcessingKeyLookup;
ViewModel(Uri uri) {
mInputUri = uri;
@@ -767,7 +842,7 @@ public class DecryptListFragment
mCancelled = null;
}
void addResult(InputDataResult result) {
void setResult(InputDataResult result) {
mResult = result;
}
@@ -787,6 +862,10 @@ public class DecryptListFragment
mMax = max;
}
void setProcessingKeyLookup(boolean processingKeyLookup) {
mProcessingKeyLookup = processingKeyLookup;
}
// Depends on inputUri only
@Override
public boolean equals(Object o) {
@@ -797,8 +876,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
@@ -853,17 +934,13 @@ public class DecryptListFragment
}
private void bindItemCancelled(ViewHolder holder, ViewModel model) {
if (holder.vAnimator.getDisplayedChild() != 3) {
holder.vAnimator.setDisplayedChild(3);
}
holder.vAnimator.setDisplayedChild(3);
holder.vCancelledRetry.setOnClickListener(model.mCancelled);
}
private void bindItemProgress(ViewHolder holder, ViewModel model) {
if (holder.vAnimator.getDisplayedChild() != 0) {
holder.vAnimator.setDisplayedChild(0);
}
holder.vAnimator.setDisplayedChild(0);
holder.vProgress.setProgress(model.mProgress);
holder.vProgress.setMax(model.mMax);
@@ -873,11 +950,10 @@ public class DecryptListFragment
}
private void bindItemSuccess(ViewHolder holder, final ViewModel model) {
if (holder.vAnimator.getDisplayedChild() != 1) {
holder.vAnimator.setDisplayedChild(1);
}
holder.vAnimator.setDisplayedChild(1);
KeyFormattingUtils.setStatus(getResources(), holder, model.mResult.mDecryptVerifyResult);
KeyFormattingUtils.setStatus(getResources(), holder,
model.mResult.mDecryptVerifyResult, model.mProcessingKeyLookup);
int numFiles = model.mResult.getOutputUris().size();
holder.resizeFileList(numFiles, LayoutInflater.from(getActivity()));
@@ -955,6 +1031,13 @@ public class DecryptListFragment
activity.startActivity(intent);
}
});
} else {
holder.vSignatureLayout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
lookupUnknownKey(model.mInputUri, keyId);
}
});
}
}
@@ -983,9 +1066,7 @@ public class DecryptListFragment
}
private void bindItemFailure(ViewHolder holder, final ViewModel model) {
if (holder.vAnimator.getDisplayedChild() != 2) {
holder.vAnimator.setDisplayedChild(2);
}
holder.vAnimator.setDisplayedChild(2);
holder.vErrorMsg.setText(model.mResult.getLog().getLast().mType.getMsgId());
@@ -1034,21 +1115,44 @@ 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);
}
public void setProcessingKeyLookup(Uri uri, boolean processingKeyLookup) {
ViewModel newModel = new ViewModel(uri);
int pos = mDataset.indexOf(newModel);
mDataset.get(pos).setProcessingKeyLookup(processingKeyLookup);
notifyItemChanged(pos);
}
public void addResult(Uri uri, InputDataResult result) {
ViewModel model = new ViewModel(uri);
int pos = mDataset.indexOf(model);
model = mDataset.get(pos);
model.setResult(result);
notifyItemChanged(pos);
}
model.addResult(result);
public void resetItemData(Uri uri) {
ViewModel model = new ViewModel(uri);
int pos = mDataset.indexOf(model);
model = mDataset.get(pos);
model.setResult(null);
model.setCancelled(null);
model.setProcessingKeyLookup(false);
notifyItemChanged(pos);
}
@@ -1072,7 +1176,7 @@ public class DecryptListFragment
public View vSignatureLayout;
public TextView vSignatureName;
public TextView vSignatureMail;
public TextView vSignatureAction;
public ViewAnimator vSignatureAction;
public View vContextMenu;
public TextView vErrorMsg;
@@ -1115,7 +1219,7 @@ public class DecryptListFragment
vSignatureLayout = itemView.findViewById(R.id.result_signature_layout);
vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name);
vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email);
vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action);
vSignatureAction = (ViewAnimator) itemView.findViewById(R.id.result_signature_action);
vFileList = (LinearLayout) itemView.findViewById(R.id.file_list);
for (int i = 0; i < vFileList.getChildCount(); i++) {
@@ -1179,7 +1283,7 @@ public class DecryptListFragment
}
@Override
public TextView getSignatureAction() {
public ViewAnimator getSignatureAction() {
return vSignatureAction;
}

View File

@@ -72,6 +72,7 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Log;
@@ -298,7 +299,7 @@ public class KeyListFragment extends LoaderFragment
}
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) {
@@ -787,6 +788,8 @@ public class KeyListFragment extends LoaderFragment
final KeyItemViewHolder holder = (KeyItemViewHolder) view.getTag();
holder.mSlinger.setVisibility(View.VISIBLE);
ContentDescriptionHint.setup(holder.mSlingerButton);
holder.mSlingerButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

View File

@@ -82,6 +82,7 @@ import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
@@ -182,6 +183,15 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout);
mRotateSpin = AnimationUtils.loadAnimation(this, R.anim.rotate_spin);
//ContentDescriptionHint Listeners implemented
ContentDescriptionHint.setup(mActionEncryptFile);
ContentDescriptionHint.setup(mActionEncryptText);
ContentDescriptionHint.setup(mActionNfc);
ContentDescriptionHint.setup(mFab);
mRotateSpin.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

View File

@@ -0,0 +1,101 @@
package org.sufficientlysecure.keychain.ui.util;
/**
* Created by rohan on 20/9/15.
*/
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.graphics.Rect;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.widget.Toast;
public class ContentDescriptionHint {
private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48;
public static void setup(View view) {
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return showLongClickText(view, view.getContentDescription());
}
});
}
public static void setup(View view, final int textResId) {
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return showLongClickText(view, view.getContext().getString(textResId));
}
});
}
public static void setup(View view, final CharSequence text) {
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return showLongClickText(view, text);
}
});
}
public static void remove(final View view) {
view.setOnLongClickListener(null);
}
private static boolean showLongClickText(View view, CharSequence text) {
if (TextUtils.isEmpty(text)) {
return false;
}
final int[] screenPos = new int[2]; // origin is device display
final Rect displayFrame = new Rect(); // includes decorations (e.g. status bar)
view.getLocationOnScreen(screenPos);
view.getWindowVisibleDisplayFrame(displayFrame);
final Context context = view.getContext();
final int viewWidth = view.getWidth();
final int viewHeight = view.getHeight();
final int viewCenterX = screenPos[0] + viewWidth / 2;
final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
final int estimatedToastHeight = (int) (ESTIMATED_TOAST_HEIGHT_DIPS
* context.getResources().getDisplayMetrics().density);
Toast longClickText = Toast.makeText(context, text, Toast.LENGTH_SHORT);
boolean showBelow = screenPos[1] < estimatedToastHeight;
if (showBelow) {
// Show below
// Offsets are after decorations (e.g. status bar) are factored in
longClickText.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
viewCenterX - screenWidth / 2,
screenPos[1] - displayFrame.top + viewHeight);
} else {
// Show above
// Offsets are after decorations (e.g. status bar) are factored in
// NOTE: We can't use Gravity.BOTTOM because when the keyboard is up
// its height isn't factored in.
longClickText.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
viewCenterX - screenWidth / 2,
screenPos[1] - displayFrame.top - estimatedToastHeight);
}
longClickText.show();
return true;
}
}

View File

@@ -28,6 +28,7 @@ import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewAnimator;
import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpSignatureResult;
@@ -440,14 +441,15 @@ public class KeyFormattingUtils {
View getSignatureLayout();
TextView getSignatureUserName();
TextView getSignatureUserEmail();
TextView getSignatureAction();
ViewAnimator getSignatureAction();
boolean hasEncrypt();
}
@SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated
public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result) {
public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result,
boolean processingkeyLookup) {
if (holder.hasEncrypt()) {
OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult();
@@ -488,7 +490,7 @@ public class KeyFormattingUtils {
OpenPgpSignatureResult signatureResult = result.getSignatureResult();
int sigText, sigIcon, sigColor;
int sigActionText, sigActionIcon;
int sigActionDisplayedChild;
switch (signatureResult.getResult()) {
@@ -500,8 +502,7 @@ public class KeyFormattingUtils {
sigColor = R.color.key_flag_gray;
// won't be used, but makes compiler happy
sigActionText = 0;
sigActionIcon = 0;
sigActionDisplayedChild = -1;
break;
}
@@ -510,8 +511,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_verified_cutout_24dp;
sigColor = R.color.key_flag_green;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
sigActionDisplayedChild = 0;
break;
}
@@ -520,8 +520,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_unverified_cutout_24dp;
sigColor = R.color.key_flag_orange;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
sigActionDisplayedChild = 0;
break;
}
@@ -530,8 +529,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_revoked_cutout_24dp;
sigColor = R.color.key_flag_red;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
sigActionDisplayedChild = 0;
break;
}
@@ -540,8 +538,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_expired_cutout_24dp;
sigColor = R.color.key_flag_red;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
sigActionDisplayedChild = 0;
break;
}
@@ -550,8 +547,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_unknown_cutout_24dp;
sigColor = R.color.key_flag_red;
sigActionText = R.string.decrypt_result_action_Lookup;
sigActionIcon = R.drawable.ic_file_download_grey_24dp;
sigActionDisplayedChild = 1;
break;
}
@@ -560,8 +556,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
sigColor = R.color.key_flag_red;
sigActionText = R.string.decrypt_result_action_show;
sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
sigActionDisplayedChild = 0;
break;
}
@@ -572,13 +567,17 @@ public class KeyFormattingUtils {
sigColor = R.color.key_flag_red;
// won't be used, but makes compiler happy
sigActionText = 0;
sigActionIcon = 0;
sigActionDisplayedChild = -1;
break;
}
}
// possibly switch out "Lookup" button for progress bar
if (sigActionDisplayedChild == 1 && processingkeyLookup) {
sigActionDisplayedChild = 2;
}
int sigColorRes = resources.getColor(sigColor);
holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN);
holder.getSignatureStatusIcon().setImageDrawable(resources.getDrawable(sigIcon));
@@ -591,9 +590,7 @@ public class KeyFormattingUtils {
holder.getSignatureLayout().setVisibility(View.VISIBLE);
holder.getSignatureAction().setText(sigActionText);
holder.getSignatureAction().setCompoundDrawablesWithIntrinsicBounds(
0, 0, sigActionIcon, 0);
holder.getSignatureAction().setDisplayedChild(sigActionDisplayedChild);
String userId = result.getSignatureResult().getPrimaryUserId();
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);