Merge branch 'import-refactor'
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -80,8 +81,8 @@ public class CreateKeyNameFragment extends Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
|
||||
@@ -164,8 +164,8 @@ public class CreateKeyPassphraseFragment extends Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
@@ -130,8 +131,8 @@ public class CreateKeyStartFragment extends Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -75,8 +76,8 @@ public class CreateSecurityTokenBlankFragment extends Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,8 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
@@ -36,6 +33,7 @@ import android.widget.TextView;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
@@ -48,6 +46,10 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
public class CreateSecurityTokenImportResetFragment
|
||||
extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
|
||||
@@ -194,8 +196,8 @@ public class CreateSecurityTokenImportResetFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
@@ -211,14 +213,14 @@ public class CreateSecurityTokenImportResetFragment
|
||||
}
|
||||
|
||||
public void refreshSearch() {
|
||||
mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mTokenFingerprint,
|
||||
mListFragment.loadState(new CloudLoaderState("0x" + mTokenFingerprint,
|
||||
Preferences.getPreferences(getActivity()).getCloudSearchPrefs()));
|
||||
}
|
||||
|
||||
public void importKey() {
|
||||
|
||||
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
|
||||
keyList.add(new ParcelableKeyRing(mTokenFingerprint, null));
|
||||
keyList.add(new ParcelableKeyRing(mTokenFingerprint, null, null, null));
|
||||
mKeyList = keyList;
|
||||
|
||||
mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
||||
|
||||
@@ -167,8 +167,8 @@ public class CreateSecurityTokenPinFragment extends Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
@@ -59,6 +57,8 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final int LOADER_ID_UNIFIED = 0;
|
||||
@@ -145,7 +145,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager.
|
||||
|
||||
{
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(null,
|
||||
KeyFormattingUtils.convertKeyIdToHex(unknownKeyId));
|
||||
KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null, null);
|
||||
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
|
||||
selectedEntries.add(keyEntry);
|
||||
|
||||
@@ -320,7 +320,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager.
|
||||
mSignatureEmail.setText(userIdSplit.email);
|
||||
} else {
|
||||
mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(
|
||||
getActivity(), mSignatureResult.getKeyId()));
|
||||
mSignatureResult.getKeyId()));
|
||||
}
|
||||
|
||||
// NOTE: Don't use revoked and expired fields from database, they don't show
|
||||
@@ -430,7 +430,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager.
|
||||
mSignatureEmail.setText(userIdSplit.email);
|
||||
} else {
|
||||
mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(
|
||||
getActivity(), mSignatureResult.getKeyId()));
|
||||
mSignatureResult.getKeyId()));
|
||||
}
|
||||
|
||||
switch (mSignatureResult.getResult()) {
|
||||
|
||||
@@ -68,6 +68,7 @@ import android.widget.Toast;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import com.cocosw.bottomsheet.BottomSheet;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.BuildConfig;
|
||||
@@ -98,24 +99,24 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
|
||||
/** Displays a list of decrypted inputs.
|
||||
*
|
||||
/**
|
||||
* Displays a list of decrypted inputs.
|
||||
* <p/>
|
||||
* 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.
|
||||
*
|
||||
* <p/>
|
||||
* 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.
|
||||
* from the list of mPendingInputUris.
|
||||
* - Once a mCurrentInputUri is finished processing, it should be set to null and
|
||||
* control handed back to cryptoOperation()
|
||||
* control handed back to cryptoOperation()
|
||||
* - Control flow can move through asynchronous calls, and resume in callbacks
|
||||
* like onActivityResult() or onPermissionRequestResult().
|
||||
*
|
||||
* like onActivityResult() or onPermissionRequestResult().
|
||||
*/
|
||||
public class DecryptListFragment
|
||||
extends QueueingCryptoOperationFragment<InputDataParcel,InputDataResult>
|
||||
extends QueueingCryptoOperationFragment<InputDataParcel, InputDataResult>
|
||||
implements OnMenuItemClickListener {
|
||||
|
||||
public static final String ARG_INPUT_URIS = "input_uris";
|
||||
@@ -189,7 +190,7 @@ public class DecryptListFragment
|
||||
|
||||
outState.putParcelableArrayList(ARG_INPUT_URIS, mInputUris);
|
||||
|
||||
HashMap<Uri,InputDataResult> results = new HashMap<>(mInputUris.size());
|
||||
HashMap<Uri, InputDataResult> results = new HashMap<>(mInputUris.size());
|
||||
for (Uri uri : mInputUris) {
|
||||
if (mPendingInputUris.contains(uri)) {
|
||||
continue;
|
||||
@@ -219,7 +220,7 @@ 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);
|
||||
ParcelableHashMap<Uri, InputDataResult> results = args.getParcelable(ARG_RESULTS);
|
||||
|
||||
mCanDelete = args.getBoolean(ARG_CAN_DELETE, false);
|
||||
|
||||
@@ -231,11 +232,11 @@ public class DecryptListFragment
|
||||
private void displayInputUris(
|
||||
ArrayList<Uri> inputUris,
|
||||
ArrayList<Uri> cancelledUris,
|
||||
HashMap<Uri,InputDataResult> results) {
|
||||
HashMap<Uri, InputDataResult> results) {
|
||||
|
||||
mInputUris = inputUris;
|
||||
mCurrentInputUri = null;
|
||||
mInputDataResults = results != null ? results : new HashMap<Uri,InputDataResult>(inputUris.size());
|
||||
mInputDataResults = results != null ? results : new HashMap<Uri, InputDataResult>(inputUris.size());
|
||||
mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>();
|
||||
|
||||
mPendingInputUris = new ArrayList<>();
|
||||
@@ -295,7 +296,7 @@ public class DecryptListFragment
|
||||
String filename = metadata.getFilename();
|
||||
if (TextUtils.isEmpty(filename)) {
|
||||
String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType());
|
||||
filename = "decrypted" + (ext != null ? "."+ext : "");
|
||||
filename = "decrypted" + (ext != null ? "." + ext : "");
|
||||
}
|
||||
|
||||
// requires >=kitkat
|
||||
@@ -395,7 +396,7 @@ public class DecryptListFragment
|
||||
|
||||
}
|
||||
|
||||
HashMap<Uri,Drawable> mIconCache = new HashMap<>();
|
||||
HashMap<Uri, Drawable> mIconCache = new HashMap<>();
|
||||
|
||||
private void processResult(final Uri uri) {
|
||||
|
||||
@@ -562,7 +563,7 @@ public class DecryptListFragment
|
||||
|
||||
Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
|
||||
new Parcelable[] { internalIntent });
|
||||
new Parcelable[]{internalIntent});
|
||||
|
||||
startActivity(chooserIntent);
|
||||
|
||||
@@ -606,7 +607,7 @@ public class DecryptListFragment
|
||||
.putExtra(DisplayTextActivity.EXTRA_METADATA, metadata),
|
||||
BuildConfig.APPLICATION_ID, R.string.view_internal, R.mipmap.ic_launcher);
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
|
||||
new Parcelable[] { internalIntent });
|
||||
new Parcelable[]{internalIntent});
|
||||
}
|
||||
|
||||
startActivity(chooserIntent);
|
||||
@@ -633,7 +634,7 @@ public class DecryptListFragment
|
||||
|
||||
Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri);
|
||||
|
||||
if ( ! checkAndRequestReadPermission(activity, mCurrentInputUri)) {
|
||||
if (!checkAndRequestReadPermission(activity, mCurrentInputUri)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -645,15 +646,15 @@ public class DecryptListFragment
|
||||
|
||||
/**
|
||||
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
|
||||
*
|
||||
* <p/>
|
||||
* 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.
|
||||
*
|
||||
* <p/>
|
||||
* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
|
||||
*/
|
||||
private boolean checkAndRequestReadPermission(Activity activity, final Uri uri) {
|
||||
if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -668,7 +669,7 @@ public class DecryptListFragment
|
||||
}
|
||||
|
||||
requestPermissions(
|
||||
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
REQUEST_PERMISSION_READ_EXTERNAL_STORAGE);
|
||||
|
||||
return false;
|
||||
@@ -677,8 +678,8 @@ public class DecryptListFragment
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
@NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
@NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
|
||||
if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
@@ -694,7 +695,7 @@ public class DecryptListFragment
|
||||
Iterator<Uri> it = mCancelledInputUris.iterator();
|
||||
while (it.hasNext()) {
|
||||
Uri uri = it.next();
|
||||
if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
continue;
|
||||
}
|
||||
it.remove();
|
||||
@@ -712,7 +713,7 @@ public class DecryptListFragment
|
||||
Iterator<Uri> it = mPendingInputUris.iterator();
|
||||
while (it.hasNext()) {
|
||||
Uri uri = it.next();
|
||||
if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
continue;
|
||||
}
|
||||
it.remove();
|
||||
@@ -767,7 +768,7 @@ public class DecryptListFragment
|
||||
|
||||
{
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(null,
|
||||
KeyFormattingUtils.convertKeyIdToHex(unknownKeyId));
|
||||
KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null, null);
|
||||
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
|
||||
selectedEntries.add(keyEntry);
|
||||
|
||||
@@ -975,7 +976,7 @@ public class DecryptListFragment
|
||||
String filename;
|
||||
if (metadata == null) {
|
||||
filename = getString(R.string.filename_unknown);
|
||||
} else if ( ! TextUtils.isEmpty(metadata.getFilename())) {
|
||||
} else if (!TextUtils.isEmpty(metadata.getFilename())) {
|
||||
filename = metadata.getFilename();
|
||||
} else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), Constants.MIME_TYPE_KEYS)) {
|
||||
filename = getString(R.string.filename_keys);
|
||||
@@ -1227,7 +1228,7 @@ public class DecryptListFragment
|
||||
vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text);
|
||||
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);
|
||||
vSignatureMail = (TextView) itemView.findViewById(R.id.result_signature_email);
|
||||
vSignatureAction = (ViewAnimator) itemView.findViewById(R.id.result_signature_action);
|
||||
|
||||
vFileList = (LinearLayout) itemView.findViewById(R.id.file_list);
|
||||
|
||||
@@ -125,10 +125,11 @@ public class EncryptFilesFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
if ( ! (activity instanceof EncryptActivity) ) {
|
||||
throw new AssertionError(activity + " must inherit from EncryptionActivity");
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
if ( ! (context instanceof EncryptActivity) ) {
|
||||
throw new AssertionError(context + " must inherit from EncryptionActivity");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,10 +82,11 @@ public class EncryptTextFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
if ( ! (activity instanceof EncryptActivity) ) {
|
||||
throw new AssertionError(activity + " must inherit from EncryptionActivity");
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
if ( ! (context instanceof EncryptActivity) ) {
|
||||
throw new AssertionError(context + " must inherit from EncryptionActivity");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.markdown4j.Markdown4jProcessor;
|
||||
import org.sufficientlysecure.htmltextview.HtmlLocalImageGetter;
|
||||
import org.sufficientlysecure.htmltextview.HtmlResImageGetter;
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@@ -52,7 +52,7 @@ public class HelpAboutFragment extends Fragment {
|
||||
try {
|
||||
String html = new Markdown4jProcessor().process(
|
||||
getActivity().getResources().openRawResource(R.raw.help_about));
|
||||
aboutTextView.setHtml(html, new HtmlLocalImageGetter(aboutTextView));
|
||||
aboutTextView.setHtml(html, new HtmlResImageGetter(aboutTextView));
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException", e);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import org.markdown4j.Markdown4jProcessor;
|
||||
import org.sufficientlysecure.htmltextview.HtmlLocalImageGetter;
|
||||
import org.sufficientlysecure.htmltextview.HtmlResImageGetter;
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -69,7 +69,7 @@ public class HelpMarkdownFragment extends Fragment {
|
||||
try {
|
||||
String html = new Markdown4jProcessor().process(
|
||||
getActivity().getResources().openRawResource(mHtmlFile));
|
||||
text.setHtml(html, new HtmlLocalImageGetter(text));
|
||||
text.setHtml(html, new HtmlResImageGetter(text));
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException", e);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2011 Senecaso
|
||||
* Copyright (C) 2012-2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* 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;
|
||||
@@ -24,8 +24,6 @@ import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
@@ -34,6 +32,10 @@ import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.LoaderState;
|
||||
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
@@ -42,15 +44,14 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ImportKeysActivity extends BaseActivity
|
||||
implements CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
|
||||
public class ImportKeysActivity extends BaseActivity implements ImportKeysListener {
|
||||
|
||||
public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY;
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER;
|
||||
@@ -79,13 +80,8 @@ public class ImportKeysActivity extends BaseActivity
|
||||
public static final String TAG_FRAG_LIST = "frag_list";
|
||||
public static final String TAG_FRAG_TOP = "frag_top";
|
||||
|
||||
// for CryptoOperationHelper.Callback
|
||||
private ParcelableHkpKeyserver mKeyserver;
|
||||
private ArrayList<ParcelableKeyRing> mKeyList;
|
||||
|
||||
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper;
|
||||
|
||||
private boolean mFreshIntent;
|
||||
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOpHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -95,12 +91,6 @@ public class ImportKeysActivity extends BaseActivity
|
||||
mFreshIntent = true;
|
||||
|
||||
setFullScreenDialogClose(Activity.RESULT_CANCELED, true);
|
||||
findViewById(R.id.import_import).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
importSelectedKeys();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,7 +172,7 @@ public class ImportKeysActivity extends BaseActivity
|
||||
|
||||
if (query != null && query.length() > 0) {
|
||||
// display keyserver fragment with query
|
||||
startTopCloudFragment(query, false, null);
|
||||
startTopCloudFragment(query, null);
|
||||
|
||||
// action: search immediately
|
||||
startListFragment(null, null, query, null);
|
||||
@@ -200,9 +190,6 @@ public class ImportKeysActivity extends BaseActivity
|
||||
if (isFingerprintValid(fingerprint)) {
|
||||
String query = "0x" + fingerprint;
|
||||
|
||||
// display keyserver fragment with query
|
||||
startTopCloudFragment(query, true, null);
|
||||
|
||||
// action: search immediately
|
||||
startListFragment(null, null, query, null);
|
||||
}
|
||||
@@ -220,27 +207,35 @@ public class ImportKeysActivity extends BaseActivity
|
||||
|
||||
Preferences.CloudSearchPrefs cloudSearchPrefs =
|
||||
new Preferences.CloudSearchPrefs(false, true, true, null);
|
||||
// we allow our users to edit the query if they wish
|
||||
startTopCloudFragment(fbUsername, false, cloudSearchPrefs);
|
||||
// search immediately
|
||||
startListFragment(null, null, fbUsername, cloudSearchPrefs);
|
||||
break;
|
||||
}
|
||||
case ACTION_SEARCH_KEYSERVER_FROM_URL: {
|
||||
// need to process URL to get search query and keyserver authority
|
||||
String query = dataUri.getQueryParameter("search");
|
||||
// if query not specified, we still allow users to search the keyserver in the link
|
||||
if (query == null) {
|
||||
Notify.create(this, R.string.import_url_warn_no_search_parameter, Notify.LENGTH_INDEFINITE,
|
||||
Notify.Style.WARN).show();
|
||||
}
|
||||
ParcelableHkpKeyserver keyserver = new ParcelableHkpKeyserver(dataUri.getAuthority());
|
||||
// get keyserver from URL
|
||||
ParcelableHkpKeyserver keyserver = new ParcelableHkpKeyserver(
|
||||
dataUri.getScheme() + "://" + dataUri.getAuthority());
|
||||
Preferences.CloudSearchPrefs cloudSearchPrefs = new Preferences.CloudSearchPrefs(
|
||||
true, true, true, keyserver);
|
||||
// we allow our users to edit the query if they wish
|
||||
startTopCloudFragment(query, false, cloudSearchPrefs);
|
||||
// search immediately (if query is not null)
|
||||
startListFragment(null, null, query, cloudSearchPrefs);
|
||||
true, false, false, keyserver);
|
||||
Log.d(Constants.TAG, "Using keyserver: " + keyserver);
|
||||
|
||||
// process URL to get operation and query
|
||||
String operation = dataUri.getQueryParameter("op");
|
||||
String query = dataUri.getQueryParameter("search");
|
||||
|
||||
// if query or operation not specified, we still allow users to search
|
||||
if (query == null || operation == null) {
|
||||
startTopCloudFragment(null, cloudSearchPrefs);
|
||||
startListFragment(null, null, null, cloudSearchPrefs);
|
||||
} else {
|
||||
if (operation.equalsIgnoreCase("get")) {
|
||||
// don't allow searching here, only one key!
|
||||
startListFragment(null, null, query, cloudSearchPrefs);
|
||||
} else { // for example: operation: index
|
||||
startTopCloudFragment(query, cloudSearchPrefs);
|
||||
startListFragment(null, null, query, cloudSearchPrefs);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_IMPORT_KEY_FROM_FILE:
|
||||
@@ -251,24 +246,13 @@ public class ImportKeysActivity extends BaseActivity
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
startTopCloudFragment(null, false, null);
|
||||
startTopCloudFragment(null, null);
|
||||
startListFragment(null, null, null, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
|
||||
// the only thing we need to take care of for restoring state is
|
||||
// that the top layout is shown iff it contains a fragment
|
||||
Fragment topFragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_TOP);
|
||||
boolean hasTopFragment = topFragment != null;
|
||||
findViewById(R.id.import_keys_top_layout).setVisibility(hasTopFragment ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the list of keys to be imported.
|
||||
* If the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately
|
||||
@@ -282,6 +266,7 @@ public class ImportKeysActivity extends BaseActivity
|
||||
*/
|
||||
private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery,
|
||||
Preferences.CloudSearchPrefs cloudSearchPrefs) {
|
||||
|
||||
Fragment listFragment =
|
||||
ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false,
|
||||
cloudSearchPrefs);
|
||||
@@ -291,30 +276,29 @@ public class ImportKeysActivity extends BaseActivity
|
||||
}
|
||||
|
||||
private void startTopFileFragment() {
|
||||
findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE);
|
||||
Fragment importFileFragment = ImportKeysFileFragment.newInstance();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.import_keys_top_container, importFileFragment, TAG_FRAG_TOP)
|
||||
.commit();
|
||||
FragmentManager fM = getSupportFragmentManager();
|
||||
if (fM.findFragmentByTag(TAG_FRAG_TOP) == null) {
|
||||
Fragment importFileFragment = ImportKeysFileFragment.newInstance();
|
||||
fM.beginTransaction().add(importFileFragment, TAG_FRAG_TOP).commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* loads the CloudFragment, which consists of the search bar, search button and settings icon
|
||||
* visually.
|
||||
* loads the CloudFragment, which enables the search bar
|
||||
*
|
||||
* @param query search query
|
||||
* @param disableQueryEdit if true, user will not be able to edit the search query
|
||||
* @param cloudSearchPrefs keyserver authority to use for search, if null will use keyserver
|
||||
* specified in user preferences
|
||||
*/
|
||||
private void startTopCloudFragment(String query, boolean disableQueryEdit,
|
||||
private void startTopCloudFragment(String query,
|
||||
Preferences.CloudSearchPrefs cloudSearchPrefs) {
|
||||
findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE);
|
||||
Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit,
|
||||
cloudSearchPrefs);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.import_keys_top_container, importCloudFragment, TAG_FRAG_TOP)
|
||||
.commit();
|
||||
|
||||
FragmentManager fM = getSupportFragmentManager();
|
||||
if (fM.findFragmentByTag(TAG_FRAG_TOP) == null) {
|
||||
Fragment importCloudFragment = ImportKeysSearchFragment.newInstance(query,
|
||||
cloudSearchPrefs);
|
||||
fM.beginTransaction().add(importCloudFragment, TAG_FRAG_TOP).commit();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFingerprintValid(String fingerprint) {
|
||||
@@ -327,96 +311,68 @@ public class ImportKeysActivity extends BaseActivity
|
||||
}
|
||||
}
|
||||
|
||||
public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) {
|
||||
FragmentManager fragMan = getSupportFragmentManager();
|
||||
ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST);
|
||||
keyListFragment.loadNew(loaderState);
|
||||
}
|
||||
|
||||
private void importSelectedKeys() {
|
||||
|
||||
FragmentManager fragMan = getSupportFragmentManager();
|
||||
ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST);
|
||||
|
||||
if (keyListFragment.getSelectedEntries().size() == 0) {
|
||||
Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR)
|
||||
.show((ViewGroup) findViewById(R.id.import_snackbar));
|
||||
return;
|
||||
}
|
||||
|
||||
mOperationHelper = new CryptoOperationHelper<>(
|
||||
1, this, this, R.string.progress_importing
|
||||
);
|
||||
|
||||
ImportKeysListFragment.LoaderState ls = keyListFragment.getLoaderState();
|
||||
if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
|
||||
Log.d(Constants.TAG, "importKeys started");
|
||||
|
||||
// get DATA from selected key entries
|
||||
IteratorWithSize<ParcelableKeyRing> selectedEntries = keyListFragment.getSelectedData();
|
||||
|
||||
// instead of giving the entries by Intent extra, cache them into a
|
||||
// file to prevent Java Binder problems on heavy imports
|
||||
// read FileImportCache for more info.
|
||||
try {
|
||||
// We parcel this iteratively into a file - anything we can
|
||||
// display here, we should be able to import.
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<>(this, "key_import.pcl");
|
||||
cache.writeCache(selectedEntries);
|
||||
|
||||
mKeyList = null;
|
||||
mKeyserver = null;
|
||||
mOperationHelper.cryptoOperation();
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Problem writing cache file", e);
|
||||
Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR)
|
||||
.show((ViewGroup) findViewById(R.id.import_snackbar));
|
||||
}
|
||||
} else if (ls instanceof ImportKeysListFragment.CloudLoaderState) {
|
||||
ImportKeysListFragment.CloudLoaderState sls =
|
||||
(ImportKeysListFragment.CloudLoaderState) ls;
|
||||
|
||||
// get selected key entries
|
||||
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
|
||||
{
|
||||
// change the format into ParcelableKeyRing
|
||||
ArrayList<ImportKeysListEntry> entries = keyListFragment.getSelectedEntries();
|
||||
for (ImportKeysListEntry entry : entries) {
|
||||
keys.add(new ParcelableKeyRing(entry.getFingerprintHex(),
|
||||
entry.getKeyIdHex(), entry.getKeybaseName(), entry.getFbUsername()));
|
||||
}
|
||||
}
|
||||
|
||||
mKeyList = keys;
|
||||
mKeyserver = sls.mCloudPrefs.keyserver;
|
||||
mOperationHelper.cryptoOperation();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (mOperationHelper != null &&
|
||||
mOperationHelper.handleActivityResult(requestCode, resultCode, data)) {
|
||||
if (mOpHelper != null &&
|
||||
mOpHelper.handleActivityResult(requestCode, resultCode, data)) {
|
||||
return;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how the result of this activity is returned.
|
||||
* Is overwritten in RemoteImportKeysActivity
|
||||
*/
|
||||
protected void handleResult(ImportKeyResult result) {
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
FragmentManager fM = getSupportFragmentManager();
|
||||
ImportKeysListFragment listFragment =
|
||||
(ImportKeysListFragment) fM.findFragmentByTag(TAG_FRAG_LIST);
|
||||
|
||||
if ((listFragment == null) || listFragment.onBackPressed()) {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadKeys(LoaderState loaderState) {
|
||||
FragmentManager fM = getSupportFragmentManager();
|
||||
((ImportKeysListFragment) fM.findFragmentByTag(TAG_FRAG_LIST)).loadState(loaderState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importKeys(List<ImportKeysListEntry> entries) {
|
||||
List<ParcelableKeyRing> keyRings = new ArrayList<>();
|
||||
for (ImportKeysListEntry e : entries) {
|
||||
keyRings.add(e.getParcelableKeyRing());
|
||||
}
|
||||
// instead of giving the entries by Intent extra, cache them into a
|
||||
// file to prevent Java Binder problems on heavy imports
|
||||
// read FileImportCache for more info.
|
||||
try {
|
||||
// We parcel this iteratively into a file - anything we can
|
||||
// display here, we should be able to import.
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<>(this, ImportOperation.CACHE_FILE_NAME);
|
||||
cache.writeCache(entries.size(), keyRings.iterator());
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Problem writing cache file", e);
|
||||
Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
|
||||
ImportKeyringParcel inputParcel = new ImportKeyringParcel(null, null);
|
||||
ImportKeysOperationCallback callback = new ImportKeysOperationCallback(this, inputParcel);
|
||||
mOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_importing);
|
||||
mOpHelper.cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(ImportKeyResult result) {
|
||||
String intentAction = getIntent().getAction();
|
||||
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction)
|
||||
|| ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) {
|
||||
if (ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction)
|
||||
|| ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
|
||||
setResult(RESULT_OK, intent);
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
finish();
|
||||
} else if (result.isOkNew() || result.isOkUpdated()) {
|
||||
// User has successfully imported a key, hide first time dialog
|
||||
@@ -424,38 +380,12 @@ public class ImportKeysActivity extends BaseActivity
|
||||
|
||||
// Close activities opened for importing keys and go to the list of keys
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
result.createNotify(ImportKeysActivity.this)
|
||||
.show((ViewGroup) findViewById(R.id.import_snackbar));
|
||||
result.createNotify(this).show();
|
||||
}
|
||||
}
|
||||
|
||||
// methods from CryptoOperationHelper.Callback
|
||||
|
||||
@Override
|
||||
public ImportKeyringParcel createOperationInput() {
|
||||
return new ImportKeyringParcel(mKeyList, mKeyserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(ImportKeyResult result) {
|
||||
handleResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationCancelled() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(ImportKeyResult result) {
|
||||
handleResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Consists of the search bar, search button, and search settings button
|
||||
*/
|
||||
public class ImportKeysCloudFragment extends Fragment {
|
||||
public static final String ARG_QUERY = "query";
|
||||
public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
|
||||
public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs";
|
||||
|
||||
private ImportKeysActivity mImportActivity;
|
||||
|
||||
private AutoCompleteTextView mQueryEditText;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*
|
||||
* @param query query to search for
|
||||
* @param disableQueryEdit if true, user cannot edit query
|
||||
* @param cloudSearchPrefs search parameters to use. If null will retrieve from user's
|
||||
* preferences.
|
||||
*/
|
||||
public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit,
|
||||
Preferences.CloudSearchPrefs
|
||||
cloudSearchPrefs) {
|
||||
ImportKeysCloudFragment frag = new ImportKeysCloudFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_QUERY, query);
|
||||
args.putBoolean(ARG_DISABLE_QUERY_EDIT, disableQueryEdit);
|
||||
args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.import_keys_cloud_fragment, container, false);
|
||||
|
||||
mQueryEditText = (AutoCompleteTextView) view.findViewById(R.id.cloud_import_server_query);
|
||||
|
||||
ContactHelper contactHelper = new ContactHelper(getActivity());
|
||||
List<String> namesAndEmails = contactHelper.getContactNames();
|
||||
namesAndEmails.addAll(contactHelper.getContactMails());
|
||||
mQueryEditText.setThreshold(3);
|
||||
mQueryEditText.setAdapter(
|
||||
new ArrayAdapter<>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
namesAndEmails
|
||||
)
|
||||
);
|
||||
|
||||
View searchButton = view.findViewById(R.id.cloud_import_server_search);
|
||||
searchButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
search(mQueryEditText.getText().toString());
|
||||
}
|
||||
});
|
||||
|
||||
mQueryEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||
search(mQueryEditText.getText().toString());
|
||||
|
||||
// Don't return true to let the keyboard close itself after pressing search
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
View configButton = view.findViewById(R.id.cloud_import_server_config_button);
|
||||
configButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(mImportActivity, SettingsActivity.class);
|
||||
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, SettingsActivity.CloudSearchPrefsFragment.class.getName());
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// set displayed values
|
||||
if (getArguments() != null) {
|
||||
String query = getArguments().getString(ARG_QUERY);
|
||||
if (query != null) {
|
||||
mQueryEditText.setText(query, TextView.BufferType.EDITABLE);
|
||||
|
||||
Log.d(Constants.TAG, "query: " + query);
|
||||
} else {
|
||||
// open keyboard
|
||||
mQueryEditText.requestFocus();
|
||||
toggleKeyboard(true);
|
||||
}
|
||||
|
||||
if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) {
|
||||
mQueryEditText.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
mImportActivity = (ImportKeysActivity) activity;
|
||||
}
|
||||
|
||||
private void search(String query) {
|
||||
Preferences.CloudSearchPrefs cloudSearchPrefs
|
||||
= getArguments().getParcelable(ARG_CLOUD_SEARCH_PREFS);
|
||||
|
||||
// no explicit search preferences passed
|
||||
if (cloudSearchPrefs == null) {
|
||||
cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs();
|
||||
}
|
||||
|
||||
mImportActivity.loadCallback(
|
||||
new ImportKeysListFragment.CloudLoaderState(query, cloudSearchPrefs));
|
||||
toggleKeyboard(false);
|
||||
}
|
||||
|
||||
private void toggleKeyboard(boolean show) {
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
InputMethodManager inputManager = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
// check if no view has focus
|
||||
View v = getActivity().getCurrentFocus();
|
||||
if (v == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (show) {
|
||||
inputManager.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT);
|
||||
} else {
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,46 +17,42 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.util.PermissionsUtil;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class ImportKeysFileFragment extends Fragment {
|
||||
private ImportKeysActivity mImportActivity;
|
||||
private View mBrowse;
|
||||
private View mClipboardButton;
|
||||
|
||||
private Activity mActivity;
|
||||
private ImportKeysListener mCallback;
|
||||
|
||||
private Uri mCurrentUri;
|
||||
|
||||
private static final int REQUEST_CODE_FILE = 0x00007003;
|
||||
private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
@@ -70,52 +66,60 @@ public class ImportKeysFileFragment extends Fragment {
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.import_keys_file_fragment, container, false);
|
||||
public View onCreateView(LayoutInflater i, ViewGroup c, Bundle savedInstanceState) {
|
||||
setHasOptionsMenu(true);
|
||||
return null;
|
||||
}
|
||||
|
||||
mBrowse = view.findViewById(R.id.import_keys_file_browse);
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
mCallback = (ImportKeysListener) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(context.toString()
|
||||
+ " must implement ImportKeysListener");
|
||||
}
|
||||
|
||||
mActivity = (Activity) context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.import_keys_file_fragment, menu);
|
||||
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
switch (itemId) {
|
||||
case R.id.menu_import_keys_file_open:
|
||||
// open .asc or .gpg files
|
||||
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
|
||||
// or gpg types!
|
||||
FileHelper.openDocument(ImportKeysFileFragment.this,
|
||||
Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
mClipboardButton = view.findViewById(R.id.import_clipboard_button);
|
||||
mClipboardButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
return true;
|
||||
case R.id.menu_import_keys_file_paste:
|
||||
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
|
||||
String sendText = "";
|
||||
if (clipboardText != null) {
|
||||
sendText = clipboardText.toString();
|
||||
sendText = PgpHelper.getPgpKeyContent(sendText);
|
||||
if (sendText == null) {
|
||||
Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show();
|
||||
return;
|
||||
Notify.create(mActivity, R.string.error_bad_data, Style.ERROR).show();
|
||||
} else {
|
||||
mCallback.loadKeys(new BytesLoaderState(sendText.getBytes(), null));
|
||||
}
|
||||
mImportActivity.loadCallback(new BytesLoaderState(sendText.getBytes(), null));
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
mImportActivity = (ImportKeysActivity) activity;
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,90 +129,49 @@ public class ImportKeysFileFragment extends Fragment {
|
||||
if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) {
|
||||
mCurrentUri = data.getData();
|
||||
|
||||
if (checkAndRequestReadPermission(mCurrentUri)) {
|
||||
if (PermissionsUtil.checkAndRequestReadPermission(this, mCurrentUri)) {
|
||||
startImportingKeys();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void startImportingKeys() {
|
||||
boolean isEncrypted;
|
||||
try {
|
||||
isEncrypted = FileHelper.isEncryptedFile(mImportActivity, mCurrentUri);
|
||||
isEncrypted = FileHelper.isEncryptedFile(mActivity, mCurrentUri);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error opening file", e);
|
||||
|
||||
Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show();
|
||||
Notify.create(mActivity, R.string.error_bad_data, Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEncrypted) {
|
||||
Intent intent = new Intent(mImportActivity, DecryptActivity.class);
|
||||
Intent intent = new Intent(mActivity, DecryptActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setData(mCurrentUri);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
mImportActivity.loadCallback(new BytesLoaderState(null, mCurrentUri));
|
||||
mCallback.loadKeys(new BytesLoaderState(null, mCurrentUri));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
|
||||
* <p/>
|
||||
* This method returns true on Android < 6, or if permission is already granted. It
|
||||
* requests the permission and returns false otherwise.
|
||||
* <p/>
|
||||
* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
|
||||
*/
|
||||
private boolean checkAndRequestReadPermission(final Uri uri) {
|
||||
if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
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.M) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ContextCompat.checkSelfPermission(getActivity(), 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) {
|
||||
if (PermissionsUtil.checkReadPermissionResult(mActivity, requestCode, grantResults)) {
|
||||
startImportingKeys();
|
||||
} else {
|
||||
Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show();
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
mActivity.setResult(Activity.RESULT_CANCELED);
|
||||
mActivity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,47 +18,45 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.Context;
|
||||
import android.databinding.DataBindingUtil;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.databinding.ImportKeysListFragmentBinding;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.AsyncTaskResultWrapper;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListCloudLoader;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListLoader;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.LoaderState;
|
||||
import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
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.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
||||
import org.sufficientlysecure.keychain.ui.util.PermissionsUtil;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Preferences.CloudSearchPrefs;
|
||||
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
||||
|
||||
public class ImportKeysListFragment extends ListFragment implements
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ImportKeysListFragment extends Fragment implements
|
||||
LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
||||
|
||||
private static final String ARG_DATA_URI = "uri";
|
||||
@@ -67,74 +65,25 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
public static final String ARG_NON_INTERACTIVE = "non_interactive";
|
||||
public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs";
|
||||
|
||||
private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12;
|
||||
private FragmentActivity mActivity;
|
||||
private ImportKeysListener mListener;
|
||||
|
||||
private Activity mActivity;
|
||||
private ImportKeysAdapter mAdapter;
|
||||
private ImportKeysListFragmentBinding mBinding;
|
||||
private ParcelableProxy mParcelableProxy;
|
||||
|
||||
private ImportKeysAdapter mAdapter;
|
||||
private LoaderState mLoaderState;
|
||||
|
||||
public static final int STATUS_FIRST = 0;
|
||||
public static final int STATUS_LOADING = 1;
|
||||
public static final int STATUS_LOADED = 2;
|
||||
public static final int STATUS_EMPTY = 3;
|
||||
|
||||
private static final int LOADER_ID_BYTES = 0;
|
||||
private static final int LOADER_ID_CLOUD = 1;
|
||||
|
||||
private LongSparseArray<ParcelableKeyRing> mCachedKeyData;
|
||||
private boolean mNonInteractive;
|
||||
|
||||
private boolean mShowingOrbotDialog;
|
||||
|
||||
public LoaderState getLoaderState() {
|
||||
return mLoaderState;
|
||||
}
|
||||
|
||||
public List<ImportKeysListEntry> getData() {
|
||||
return mAdapter.getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Iterator (with size) of the selected data items.
|
||||
* This iterator is sort of a tradeoff, it's slightly more complex than an
|
||||
* ArrayList would have been, but we save some memory by just returning
|
||||
* relevant elements on demand.
|
||||
*/
|
||||
public IteratorWithSize<ParcelableKeyRing> getSelectedData() {
|
||||
final ArrayList<ImportKeysListEntry> entries = getSelectedEntries();
|
||||
final Iterator<ImportKeysListEntry> it = entries.iterator();
|
||||
return new IteratorWithSize<ParcelableKeyRing>() {
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelableKeyRing next() {
|
||||
// throws NoSuchElementException if it doesn't exist, but that's not our problem
|
||||
return mCachedKeyData.get(it.next().hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
it.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ArrayList<ImportKeysListEntry> getSelectedEntries() {
|
||||
if (mAdapter != null) {
|
||||
return mAdapter.getSelectedEntries();
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Adapter not initialized, returning empty list");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified
|
||||
* by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order
|
||||
@@ -148,7 +97,8 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
* @return fragment with arguments set based on passed parameters
|
||||
*/
|
||||
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery,
|
||||
Preferences.CloudSearchPrefs cloudSearchPrefs) {
|
||||
CloudSearchPrefs cloudSearchPrefs) {
|
||||
|
||||
return newInstance(bytes, dataUri, serverQuery, false, cloudSearchPrefs);
|
||||
}
|
||||
|
||||
@@ -168,8 +118,7 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
Uri dataUri,
|
||||
String serverQuery,
|
||||
boolean nonInteractive,
|
||||
Preferences.CloudSearchPrefs cloudSearchPrefs) {
|
||||
ImportKeysListFragment frag = new ImportKeysListFragment();
|
||||
CloudSearchPrefs cloudSearchPrefs) {
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_BYTES, bytes);
|
||||
@@ -178,115 +127,68 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive);
|
||||
args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs);
|
||||
|
||||
ImportKeysListFragment frag = new ImportKeysListFragment();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
static public class LoaderState {
|
||||
}
|
||||
|
||||
static public class BytesLoaderState extends LoaderState {
|
||||
public byte[] mKeyBytes;
|
||||
public Uri mDataUri;
|
||||
|
||||
BytesLoaderState(byte[] keyBytes, Uri dataUri) {
|
||||
mKeyBytes = keyBytes;
|
||||
mDataUri = dataUri;
|
||||
}
|
||||
}
|
||||
|
||||
static public class CloudLoaderState extends LoaderState {
|
||||
Preferences.CloudSearchPrefs mCloudPrefs;
|
||||
String mServerQuery;
|
||||
|
||||
CloudLoaderState(String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) {
|
||||
mServerQuery = serverQuery;
|
||||
mCloudPrefs = cloudPrefs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
mBinding = DataBindingUtil.inflate(inflater, R.layout.import_keys_list_fragment, container, false);
|
||||
mBinding.setStatus(STATUS_FIRST);
|
||||
View view = mBinding.getRoot();
|
||||
|
||||
mActivity = getActivity();
|
||||
|
||||
// Give some text to display if there is no data.
|
||||
setEmptyText(mActivity.getString(R.string.error_nothing_import));
|
||||
|
||||
// Create an empty adapter we will use to display the loaded data.
|
||||
mAdapter = new ImportKeysAdapter(mActivity);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
Bundle args = getArguments();
|
||||
Uri dataUri = args.getParcelable(ARG_DATA_URI);
|
||||
byte[] bytes = args.getByteArray(ARG_BYTES);
|
||||
String query = args.getString(ARG_SERVER_QUERY);
|
||||
mNonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false);
|
||||
boolean nonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false);
|
||||
mBinding.basic.setNonInteractive(nonInteractive);
|
||||
|
||||
getListView().setOnTouchListener(new OnTouchListener() {
|
||||
// Create an empty adapter we will use to display the loaded data.
|
||||
mAdapter = new ImportKeysAdapter(mActivity, mListener, nonInteractive);
|
||||
mBinding.recyclerView.setAdapter(mAdapter);
|
||||
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
|
||||
|
||||
if (dataUri != null || bytes != null) {
|
||||
loadState(new BytesLoaderState(bytes, dataUri));
|
||||
} else if (query != null) {
|
||||
CloudSearchPrefs cloudSearchPrefs = args.getParcelable(ARG_CLOUD_SEARCH_PREFS);
|
||||
if (cloudSearchPrefs == null) {
|
||||
cloudSearchPrefs = Preferences.getPreferences(mActivity).getCloudSearchPrefs();
|
||||
}
|
||||
loadState(new CloudLoaderState(query, cloudSearchPrefs));
|
||||
}
|
||||
|
||||
// mBinding.basic is only used for file import
|
||||
mBinding.basic.importKeys.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (!mAdapter.isEmpty()) {
|
||||
mActivity.onTouchEvent(event);
|
||||
}
|
||||
return false;
|
||||
public void onClick(View view) {
|
||||
mListener.importKeys(mAdapter.getEntries());
|
||||
}
|
||||
});
|
||||
mBinding.basic.listKeys.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mBinding.setAdvanced(true);
|
||||
}
|
||||
});
|
||||
|
||||
getListView().setFastScrollEnabled(true);
|
||||
|
||||
if (dataUri != null || bytes != null) {
|
||||
mLoaderState = new BytesLoaderState(bytes, dataUri);
|
||||
} else if (query != null) {
|
||||
Preferences.CloudSearchPrefs cloudSearchPrefs
|
||||
= args.getParcelable(ARG_CLOUD_SEARCH_PREFS);
|
||||
if (cloudSearchPrefs == null) {
|
||||
cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs();
|
||||
}
|
||||
|
||||
mLoaderState = new CloudLoaderState(query, cloudSearchPrefs);
|
||||
}
|
||||
|
||||
if (dataUri != null && ! checkAndRequestReadPermission(dataUri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
restartLoaders();
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
|
||||
*/
|
||||
private boolean checkAndRequestReadPermission(final Uri uri) {
|
||||
if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
return true;
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
try {
|
||||
mListener = (ImportKeysListener) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(context.toString()
|
||||
+ " must implement ImportKeysListener");
|
||||
}
|
||||
|
||||
// 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.M) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ContextCompat.checkSelfPermission(getActivity(), 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
|
||||
@@ -294,140 +196,99 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
@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 -> load key
|
||||
if (PermissionsUtil.checkReadPermissionResult(mActivity, requestCode, grantResults)) {
|
||||
restartLoaders();
|
||||
} else {
|
||||
Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show();
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
mActivity.setResult(Activity.RESULT_CANCELED);
|
||||
mActivity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
super.onListItemClick(l, v, position, id);
|
||||
|
||||
if (mNonInteractive) {
|
||||
return;
|
||||
/**
|
||||
* User may want to go back to single card view if he's now in full key list
|
||||
* Check if we are in full key list and if this import operation supports basic mode
|
||||
*
|
||||
* @return true if activity's back pressed can be performed
|
||||
*/
|
||||
public boolean onBackPressed() {
|
||||
boolean advanced = mBinding.getAdvanced();
|
||||
if (advanced && mLoaderState.isBasicModeSupported()) {
|
||||
mBinding.setAdvanced(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select checkbox!
|
||||
// Update underlying data and notify adapter of change. The adapter will
|
||||
// update the view automatically.
|
||||
|
||||
ImportKeysListEntry entry = mAdapter.getItem(position);
|
||||
entry.setSelected(!entry.isSelected());
|
||||
mAdapter.notifyDataSetChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void loadNew(LoaderState loaderState) {
|
||||
public void loadState(LoaderState loaderState) {
|
||||
mLoaderState = loaderState;
|
||||
|
||||
if (mLoaderState instanceof BytesLoaderState) {
|
||||
BytesLoaderState ls = (BytesLoaderState) mLoaderState;
|
||||
|
||||
if ( ls.mDataUri != null && ! checkAndRequestReadPermission(ls.mDataUri)) {
|
||||
if (ls.mDataUri != null &&
|
||||
!PermissionsUtil.checkAndRequestReadPermission(this, ls.mDataUri)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mBinding.setAdvanced(!mLoaderState.isBasicModeSupported());
|
||||
|
||||
restartLoaders();
|
||||
}
|
||||
|
||||
public void destroyLoader() {
|
||||
if (getLoaderManager().getLoader(LOADER_ID_BYTES) != null) {
|
||||
getLoaderManager().destroyLoader(LOADER_ID_BYTES);
|
||||
}
|
||||
if (getLoaderManager().getLoader(LOADER_ID_CLOUD) != null) {
|
||||
getLoaderManager().destroyLoader(LOADER_ID_CLOUD);
|
||||
}
|
||||
if (getView() != null) {
|
||||
setListShown(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void restartLoaders() {
|
||||
LoaderManager loaderManager = getLoaderManager();
|
||||
|
||||
if (mLoaderState instanceof BytesLoaderState) {
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this);
|
||||
loaderManager.restartLoader(LOADER_ID_BYTES, null, this);
|
||||
} else if (mLoaderState instanceof CloudLoaderState) {
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
getLoaderManager().restartLoader(LOADER_ID_CLOUD, null, this);
|
||||
loaderManager.restartLoader(LOADER_ID_CLOUD, null, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>>
|
||||
onCreateLoader(int id, Bundle args) {
|
||||
public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> onCreateLoader(
|
||||
int id, Bundle args) {
|
||||
|
||||
Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader = null;
|
||||
switch (id) {
|
||||
case LOADER_ID_BYTES: {
|
||||
return new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState);
|
||||
loader = new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState);
|
||||
break;
|
||||
}
|
||||
case LOADER_ID_CLOUD: {
|
||||
CloudLoaderState ls = (CloudLoaderState) mLoaderState;
|
||||
return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs,
|
||||
loader = new ImportKeysListCloudLoader(mActivity, (CloudLoaderState) mLoaderState,
|
||||
mParcelableProxy);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (loader != null) {
|
||||
mBinding.setStatus(STATUS_LOADING);
|
||||
}
|
||||
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader,
|
||||
AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
public void onLoadFinished(
|
||||
Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader,
|
||||
AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
|
||||
|
||||
Log.d(Constants.TAG, "data: " + data.getResult());
|
||||
|
||||
// swap in the real data!
|
||||
mAdapter.setData(data.getResult());
|
||||
mAdapter.notifyDataSetChanged();
|
||||
int size = mAdapter.getItemCount();
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
|
||||
// free old cached key data
|
||||
mCachedKeyData = null;
|
||||
mBinding.setNumber(size);
|
||||
mBinding.setStatus(size > 0 ? STATUS_LOADED : STATUS_EMPTY);
|
||||
|
||||
GetKeyResult getKeyResult = (GetKeyResult) data.getOperationResult();
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_BYTES:
|
||||
|
||||
if (getKeyResult.success()) {
|
||||
// No error
|
||||
mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings();
|
||||
} else {
|
||||
getKeyResult.createNotify(getActivity()).show();
|
||||
if (!getKeyResult.success()) {
|
||||
getKeyResult.createNotify(mActivity).show();
|
||||
}
|
||||
break;
|
||||
|
||||
case LOADER_ID_CLOUD:
|
||||
|
||||
if (getKeyResult.success()) {
|
||||
// No error
|
||||
} else if (getKeyResult.isPending()) {
|
||||
if (getKeyResult.isPending()) {
|
||||
if (getKeyResult.getRequiredInputParcel().mType ==
|
||||
RequiredInputParcel.RequiredInputType.ENABLE_ORBOT) {
|
||||
if (mShowingOrbotDialog) {
|
||||
@@ -461,8 +322,7 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
}
|
||||
};
|
||||
|
||||
if (OrbotHelper.putOrbotInRequiredState(dialogActions,
|
||||
getActivity())) {
|
||||
if (OrbotHelper.putOrbotInRequiredState(dialogActions, mActivity)) {
|
||||
// looks like we didn't have to show the
|
||||
// dialog after all
|
||||
mShowingOrbotDialog = false;
|
||||
@@ -473,30 +333,18 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
new Handler().post(showOrbotDialog);
|
||||
mShowingOrbotDialog = true;
|
||||
}
|
||||
} else {
|
||||
getKeyResult.createNotify(getActivity()).show();
|
||||
} else if (!getKeyResult.success()) {
|
||||
getKeyResult.createNotify(mActivity).show();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader) {
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_BYTES:
|
||||
// Clear the data in the adapter.
|
||||
mAdapter.clear();
|
||||
break;
|
||||
case LOADER_ID_CLOUD:
|
||||
// Clear the data in the adapter.
|
||||
mAdapter.clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
public void onLoaderReset(
|
||||
Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader) {
|
||||
|
||||
mAdapter.clearData();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ public class ImportKeysProxyActivity extends FragmentActivity
|
||||
}
|
||||
|
||||
public void importKeys(String fingerprint) {
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null);
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null, null);
|
||||
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
|
||||
selectedEntries.add(keyEntry);
|
||||
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.MatrixCursor;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.provider.BaseColumns;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v4.view.MenuItemCompat.OnActionExpandListener;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.support.v4.widget.SimpleCursorAdapter;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Preferences.CloudSearchPrefs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static android.support.v7.widget.SearchView.OnQueryTextListener;
|
||||
import static android.support.v7.widget.SearchView.OnSuggestionListener;
|
||||
|
||||
/**
|
||||
* Consists of the search bar, search button, and search settings button
|
||||
*/
|
||||
public class ImportKeysSearchFragment extends Fragment {
|
||||
|
||||
public static final String ARG_QUERY = "query";
|
||||
public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs";
|
||||
|
||||
private static final String CURSOR_SUGGESTION = "suggestion";
|
||||
|
||||
private Activity mActivity;
|
||||
private ImportKeysListener mCallback;
|
||||
|
||||
private List<String> mNamesAndEmails;
|
||||
private SimpleCursorAdapter mSearchAdapter;
|
||||
|
||||
private List<String> mCurrentSuggestions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*
|
||||
* @param query query to search for
|
||||
* @param cloudSearchPrefs search parameters to use. If null will retrieve from user's
|
||||
* preferences.
|
||||
*/
|
||||
public static ImportKeysSearchFragment newInstance(String query,
|
||||
CloudSearchPrefs cloudSearchPrefs) {
|
||||
|
||||
ImportKeysSearchFragment frag = new ImportKeysSearchFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_QUERY, query);
|
||||
args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater i, ViewGroup c, Bundle savedInstanceState) {
|
||||
ContactHelper contactHelper = new ContactHelper(mActivity);
|
||||
mNamesAndEmails = contactHelper.getContactNames();
|
||||
mNamesAndEmails.addAll(contactHelper.getContactMails());
|
||||
|
||||
mSearchAdapter = new SimpleCursorAdapter(mActivity,
|
||||
R.layout.import_keys_cloud_suggestions_item, null, new String[]{CURSOR_SUGGESTION},
|
||||
new int[]{android.R.id.text1}, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
// no view, just search view
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
try {
|
||||
mCallback = (ImportKeysListener) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(context.toString()
|
||||
+ " must implement ImportKeysListener");
|
||||
}
|
||||
|
||||
mActivity = (Activity) context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.import_keys_cloud_fragment, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.menu_import_keys_cloud_search);
|
||||
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
searchView.setSuggestionsAdapter(mSearchAdapter);
|
||||
|
||||
searchView.setOnSuggestionListener(new OnSuggestionListener() {
|
||||
@Override
|
||||
public boolean onSuggestionSelect(int position) {
|
||||
searchView.setQuery(mCurrentSuggestions.get(position), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSuggestionClick(int position) {
|
||||
searchView.setQuery(mCurrentSuggestions.get(position), true);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setOnQueryTextListener(new OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
searchView.clearFocus();
|
||||
search(searchView.getQuery().toString().trim());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
updateAdapter(newText);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
MenuItemCompat.setOnActionExpandListener(searchItem, new OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
mActivity.finish();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
searchItem.expandActionView();
|
||||
|
||||
String query = getArguments().getString(ARG_QUERY);
|
||||
if (query != null) {
|
||||
searchView.setQuery(query, false);
|
||||
}
|
||||
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
private void updateAdapter(String query) {
|
||||
mCurrentSuggestions.clear();
|
||||
MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, CURSOR_SUGGESTION});
|
||||
for (int i = 0; i < mNamesAndEmails.size(); i++) {
|
||||
String s = mNamesAndEmails.get(i);
|
||||
if (s.toLowerCase().startsWith(query.toLowerCase())) {
|
||||
mCurrentSuggestions.add(s);
|
||||
c.addRow(new Object[]{i, s});
|
||||
}
|
||||
|
||||
}
|
||||
mSearchAdapter.changeCursor(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
switch (itemId) {
|
||||
case R.id.menu_import_keys_cloud_settings:
|
||||
Intent intent = new Intent(mActivity, SettingsActivity.class);
|
||||
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
|
||||
SettingsActivity.CloudSearchPrefsFragment.class.getName());
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void search(String query) {
|
||||
CloudSearchPrefs cloudSearchPrefs
|
||||
= getArguments().getParcelable(ARG_CLOUD_SEARCH_PREFS);
|
||||
|
||||
// no explicit search preferences passed
|
||||
if (cloudSearchPrefs == null) {
|
||||
cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs();
|
||||
}
|
||||
|
||||
mCallback.loadKeys(new CloudLoaderState(query, cloudSearchPrefs));
|
||||
toggleKeyboard(false);
|
||||
}
|
||||
|
||||
private void toggleKeyboard(boolean show) {
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
InputMethodManager inputManager = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
// check if no view has focus
|
||||
View v = getActivity().getCurrentFocus();
|
||||
if (v == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (show) {
|
||||
inputManager.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT);
|
||||
} else {
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -54,6 +54,7 @@ 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;
|
||||
@@ -78,6 +79,7 @@ import org.sufficientlysecure.keychain.util.FabContainer;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
||||
|
||||
@@ -245,7 +247,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 {
|
||||
@@ -334,7 +336,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
headerCursor.addRow(row);
|
||||
|
||||
Cursor dataCursor = data;
|
||||
data = new MergeCursor(new Cursor[] {
|
||||
data = new MergeCursor(new Cursor[]{
|
||||
headerCursor, dataCursor
|
||||
});
|
||||
}
|
||||
@@ -576,7 +578,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
while (cursor.moveToNext()) {
|
||||
byte[] blob = cursor.getBlob(0);//fingerprint column is 0
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null);
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null, null);
|
||||
keyList.add(keyEntry);
|
||||
}
|
||||
mKeyList = keyList;
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.widget.NumberPicker;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
@@ -142,7 +143,7 @@ public class SafeSlingerActivity extends BaseActivity
|
||||
// We parcel this iteratively into a file - anything we can
|
||||
// display here, we should be able to import.
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<>(this, "key_import.pcl");
|
||||
new ParcelableFileCache<>(this, ImportOperation.CACHE_FILE_NAME);
|
||||
cache.writeCache(it.size(), it.iterator());
|
||||
|
||||
mOperationHelper =
|
||||
|
||||
@@ -19,11 +19,6 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
@@ -89,10 +84,10 @@ import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint;
|
||||
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;
|
||||
@@ -104,6 +99,11 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
LoaderManager.LoaderCallbacks<Cursor>,
|
||||
@@ -115,7 +115,9 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE})
|
||||
private @interface RequestType {}
|
||||
private @interface RequestType {
|
||||
}
|
||||
|
||||
static final int REQUEST_QR_FINGERPRINT = 1;
|
||||
static final int REQUEST_BACKUP = 2;
|
||||
static final int REQUEST_CERTIFY = 3;
|
||||
@@ -565,7 +567,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
|
||||
private void startBackupActivity() {
|
||||
Intent intent = new Intent(this, BackupActivity.class);
|
||||
intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[] { mMasterKeyId });
|
||||
intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[]{mMasterKeyId});
|
||||
intent.putExtra(BackupActivity.EXTRA_SECRET, true);
|
||||
startActivity(intent);
|
||||
}
|
||||
@@ -722,7 +724,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
manager.beginTransaction()
|
||||
.addToBackStack("security_token")
|
||||
.replace(R.id.view_key_fragment, frag)
|
||||
// if this is called while the activity wasn't resumed, just forget it happened
|
||||
// if this is called while the activity wasn't resumed, just forget it happened
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
});
|
||||
@@ -1105,7 +1107,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
|
||||
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null);
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null, null);
|
||||
ArrayList<ParcelableKeyRing> entries = new ArrayList<>();
|
||||
entries.add(keyEntry);
|
||||
mKeyList = entries;
|
||||
|
||||
@@ -225,7 +225,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements
|
||||
|
||||
// get key id from MASTER_KEY_ID
|
||||
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
getSupportActionBar().setSubtitle(KeyFormattingUtils.beautifyKeyIdWithPrefix(this, masterKeyId));
|
||||
String formattedKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(masterKeyId);
|
||||
getSupportActionBar().setSubtitle(formattedKeyId);
|
||||
|
||||
mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
|
||||
|
||||
@@ -237,7 +237,8 @@ public class ViewKeyAdvCertsFragment extends LoaderFragment implements
|
||||
TextView wSignerName = (TextView) view.findViewById(R.id.signerName);
|
||||
TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus);
|
||||
|
||||
String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(getActivity(), cursor.getLong(mIndexSignerKeyId));
|
||||
String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(
|
||||
cursor.getLong(mIndexSignerKeyId));
|
||||
OpenPgpUtils.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexSignerUserId));
|
||||
if (userId.name != null) {
|
||||
wSignerName.setText(userId.name);
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
|
||||
/**
|
||||
* The AsyncTaskResultWrapper is used to wrap a result from a AsyncTask (for example: Loader).
|
||||
* You can pass the result and an exception in it if an error occurred.
|
||||
* Concept found at:
|
||||
* https://stackoverflow.com/questions/19593577/how-to-handle-errors-in-custom-asynctaskloader
|
||||
*
|
||||
* @param <T> - Typ of the result which is wrapped
|
||||
*/
|
||||
public class AsyncTaskResultWrapper<T> {
|
||||
|
||||
private final T mResult;
|
||||
private final OperationResult mOperationResult;
|
||||
|
||||
public AsyncTaskResultWrapper(T result, OperationResult operationResult) {
|
||||
this.mResult = result;
|
||||
this.mOperationResult = operationResult;
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return mResult;
|
||||
}
|
||||
|
||||
public OperationResult getOperationResult() {
|
||||
return mOperationResult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,261 +17,295 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.content.Intent;
|
||||
import android.databinding.DataBindingUtil;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.databinding.ImportKeysListItemBinding;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableHkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback;
|
||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysResultListener;
|
||||
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Highlighter;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
||||
protected LayoutInflater mInflater;
|
||||
protected Activity mActivity;
|
||||
public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.ViewHolder> implements ImportKeysResultListener {
|
||||
|
||||
protected List<ImportKeysListEntry> mData;
|
||||
private FragmentActivity mActivity;
|
||||
private ImportKeysResultListener mListener;
|
||||
private boolean mNonInteractive;
|
||||
|
||||
static class ViewHolder {
|
||||
public TextView mainUserId;
|
||||
public TextView mainUserIdRest;
|
||||
public TextView keyId;
|
||||
public TextView fingerprint;
|
||||
public TextView algorithm;
|
||||
public ImageView status;
|
||||
public View userIdsDivider;
|
||||
public LinearLayout userIdsList;
|
||||
public CheckBox checkBox;
|
||||
}
|
||||
private List<ImportKeysListEntry> mData;
|
||||
private KeyState[] mKeyStates;
|
||||
private int mCurrent;
|
||||
|
||||
private ProviderHelper mProviderHelper;
|
||||
|
||||
public ImportKeysAdapter(FragmentActivity activity, ImportKeysListener listener,
|
||||
boolean nonInteractive) {
|
||||
|
||||
public ImportKeysAdapter(Activity activity) {
|
||||
super(activity, -1);
|
||||
mActivity = activity;
|
||||
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mListener = listener;
|
||||
mNonInteractive = nonInteractive;
|
||||
|
||||
mProviderHelper = new ProviderHelper(activity);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public void setData(List<ImportKeysListEntry> data) {
|
||||
mData = data;
|
||||
|
||||
clear();
|
||||
if (data != null) {
|
||||
this.mData = data;
|
||||
mKeyStates = new KeyState[data.size()];
|
||||
for (int i = 0; i < mKeyStates.length; i++) {
|
||||
mKeyStates[i] = new KeyState();
|
||||
|
||||
// add data to extended ArrayAdapter
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
addAll(data);
|
||||
} else {
|
||||
for (ImportKeysListEntry entry : data) {
|
||||
add(entry);
|
||||
ImportKeysListEntry entry = mData.get(i);
|
||||
long keyId = KeyFormattingUtils.convertKeyIdHexToKeyId(entry.getKeyIdHex());
|
||||
try {
|
||||
KeyRing keyRing;
|
||||
if (entry.isSecretKey()) {
|
||||
keyRing = mProviderHelper.getCanonicalizedSecretKeyRing(keyId);
|
||||
} else {
|
||||
keyRing = mProviderHelper.getCachedPublicKeyRing(keyId);
|
||||
}
|
||||
mKeyStates[i].mAlreadyPresent = true;
|
||||
mKeyStates[i].mVerified = keyRing.getVerified() > 0;
|
||||
} catch (ProviderHelper.NotFoundException | PgpKeyNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// If there is only one key, get it automatically
|
||||
if (mData.size() == 1) {
|
||||
mCurrent = 0;
|
||||
getKeyWithProgress(0, mData.get(0), true);
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public List<ImportKeysListEntry> getData() {
|
||||
return mData;
|
||||
public void clearData() {
|
||||
mData = null;
|
||||
mKeyStates = null;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/** This method returns a list of all selected entries, with public keys sorted
|
||||
/**
|
||||
* This method returns a list of all selected entries, with public keys sorted
|
||||
* before secret keys, see ImportOperation for specifics.
|
||||
*
|
||||
* @see ImportOperation
|
||||
*/
|
||||
public ArrayList<ImportKeysListEntry> getSelectedEntries() {
|
||||
public List<ImportKeysListEntry> getEntries() {
|
||||
ArrayList<ImportKeysListEntry> result = new ArrayList<>();
|
||||
ArrayList<ImportKeysListEntry> secrets = new ArrayList<>();
|
||||
if (mData == null) {
|
||||
return result;
|
||||
}
|
||||
for (ImportKeysListEntry entry : mData) {
|
||||
if (entry.isSelected()) {
|
||||
// add this entry to either the secret or the public list
|
||||
(entry.isSecretKey() ? secrets : result).add(entry);
|
||||
}
|
||||
// add this entry to either the secret or the public list
|
||||
(entry.isSecretKey() ? secrets : result).add(entry);
|
||||
}
|
||||
// add secret keys at the end of the list
|
||||
result.addAll(secrets);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public ImportKeysListItemBinding b;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
super(view);
|
||||
b = DataBindingUtil.bind(view);
|
||||
b.setNonInteractive(mNonInteractive);
|
||||
}
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
ImportKeysListEntry entry = mData.get(position);
|
||||
Highlighter highlighter = new Highlighter(mActivity, entry.getQuery());
|
||||
ViewHolder holder;
|
||||
if (convertView == null) {
|
||||
holder = new ViewHolder();
|
||||
convertView = mInflater.inflate(R.layout.import_keys_list_item, null);
|
||||
holder.mainUserId = (TextView) convertView.findViewById(R.id.import_item_user_id);
|
||||
holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.import_item_user_id_email);
|
||||
holder.keyId = (TextView) convertView.findViewById(R.id.import_item_key_id);
|
||||
holder.fingerprint = (TextView) convertView.findViewById(R.id.import_item_fingerprint);
|
||||
holder.algorithm = (TextView) convertView.findViewById(R.id.import_item_algorithm);
|
||||
holder.status = (ImageView) convertView.findViewById(R.id.import_item_status);
|
||||
holder.userIdsDivider = convertView.findViewById(R.id.import_item_status_divider);
|
||||
holder.userIdsList = (LinearLayout) convertView.findViewById(R.id.import_item_user_ids_list);
|
||||
holder.checkBox = (CheckBox) convertView.findViewById(R.id.import_item_selected);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
LayoutInflater inflater = LayoutInflater.from(mActivity);
|
||||
return new ViewHolder(inflater.inflate(R.layout.import_keys_list_item, parent, false));
|
||||
}
|
||||
|
||||
// main user id
|
||||
String userId = entry.getUserIds().get(0);
|
||||
OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId);
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, final int position) {
|
||||
final ImportKeysListItemBinding b = holder.b;
|
||||
final ImportKeysListEntry entry = mData.get(position);
|
||||
b.setEntry(entry);
|
||||
|
||||
// name
|
||||
if (userIdSplit.name != null) {
|
||||
// show red user id if it is a secret key
|
||||
if (entry.isSecretKey()) {
|
||||
holder.mainUserId.setText(mActivity.getString(R.string.secret_key)
|
||||
+ " " + userIdSplit.name);
|
||||
} else {
|
||||
holder.mainUserId.setText(highlighter.highlight(userIdSplit.name));
|
||||
}
|
||||
} else {
|
||||
holder.mainUserId.setText(R.string.user_id_no_name);
|
||||
}
|
||||
final KeyState keyState = mKeyStates[position];
|
||||
|
||||
// email
|
||||
if (userIdSplit.email != null) {
|
||||
holder.mainUserIdRest.setVisibility(View.VISIBLE);
|
||||
holder.mainUserIdRest.setText(highlighter.highlight(userIdSplit.email));
|
||||
} else {
|
||||
holder.mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
b.card.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (!keyState.mDownloaded) {
|
||||
mCurrent = position;
|
||||
|
||||
holder.keyId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(getContext(), entry.getKeyIdHex()));
|
||||
|
||||
// don't show full fingerprint on key import
|
||||
holder.fingerprint.setVisibility(View.GONE);
|
||||
|
||||
if (entry.getAlgorithm() != null) {
|
||||
holder.algorithm.setText(entry.getAlgorithm());
|
||||
holder.algorithm.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.algorithm.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (entry.isRevoked()) {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.key_flag_gray);
|
||||
} else if (entry.isExpired()) {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.key_flag_gray);
|
||||
}
|
||||
|
||||
if (entry.isRevoked() || entry.isExpired()) {
|
||||
holder.status.setVisibility(View.VISIBLE);
|
||||
|
||||
// no more space for algorithm display
|
||||
holder.algorithm.setVisibility(View.GONE);
|
||||
|
||||
holder.mainUserId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
|
||||
holder.mainUserIdRest.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
|
||||
holder.keyId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
|
||||
} else {
|
||||
holder.status.setVisibility(View.GONE);
|
||||
holder.algorithm.setVisibility(View.VISIBLE);
|
||||
|
||||
if (entry.isSecretKey()) {
|
||||
holder.mainUserId.setTextColor(Color.RED);
|
||||
} else {
|
||||
holder.mainUserId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
|
||||
}
|
||||
|
||||
holder.mainUserIdRest.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
|
||||
holder.keyId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
|
||||
}
|
||||
|
||||
if (entry.getUserIds().size() == 1) {
|
||||
holder.userIdsList.setVisibility(View.GONE);
|
||||
holder.userIdsDivider.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.userIdsList.setVisibility(View.VISIBLE);
|
||||
holder.userIdsDivider.setVisibility(View.VISIBLE);
|
||||
|
||||
// destroyLoader view from holder
|
||||
holder.userIdsList.removeAllViews();
|
||||
|
||||
// we want conventional gpg UserIDs first, then Keybase ”proofs”
|
||||
HashMap<String, HashSet<String>> mergedUserIds = entry.getMergedUserIds();
|
||||
ArrayList<Map.Entry<String, HashSet<String>>> sortedIds = new ArrayList<Map.Entry<String, HashSet<String>>>(mergedUserIds.entrySet());
|
||||
java.util.Collections.sort(sortedIds, new java.util.Comparator<Map.Entry<String, HashSet<String>>>() {
|
||||
@Override
|
||||
public int compare(Map.Entry<String, HashSet<String>> entry1, Map.Entry<String, HashSet<String>> entry2) {
|
||||
|
||||
// sort keybase UserIds after non-Keybase
|
||||
boolean e1IsKeybase = entry1.getKey().contains(":");
|
||||
boolean e2IsKeybase = entry2.getKey().contains(":");
|
||||
if (e1IsKeybase != e2IsKeybase) {
|
||||
return (e1IsKeybase) ? 1 : -1;
|
||||
}
|
||||
return entry1.getKey().compareTo(entry2.getKey());
|
||||
}
|
||||
});
|
||||
|
||||
for (Map.Entry<String, HashSet<String>> pair : sortedIds) {
|
||||
String cUserId = pair.getKey();
|
||||
HashSet<String> cEmails = pair.getValue();
|
||||
|
||||
TextView uidView = (TextView) mInflater.inflate(
|
||||
R.layout.import_keys_list_entry_user_id, null);
|
||||
uidView.setText(highlighter.highlight(cUserId));
|
||||
uidView.setPadding(0, 0, FormattingUtils.dpToPx(getContext(), 8), 0);
|
||||
|
||||
if (entry.isRevoked() || entry.isExpired()) {
|
||||
uidView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
|
||||
getKeyWithProgress(position, entry, true);
|
||||
} else {
|
||||
uidView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText));
|
||||
}
|
||||
|
||||
holder.userIdsList.addView(uidView);
|
||||
|
||||
for (String email : cEmails) {
|
||||
TextView emailView = (TextView) mInflater.inflate(
|
||||
R.layout.import_keys_list_entry_user_id, null);
|
||||
emailView.setPadding(
|
||||
FormattingUtils.dpToPx(getContext(), 16), 0,
|
||||
FormattingUtils.dpToPx(getContext(), 8), 0);
|
||||
emailView.setText(highlighter.highlight(email));
|
||||
|
||||
if (entry.isRevoked() || entry.isExpired()) {
|
||||
emailView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray));
|
||||
} else {
|
||||
emailView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText));
|
||||
}
|
||||
|
||||
holder.userIdsList.addView(emailView);
|
||||
changeShowed(position, !keyState.mShowed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
b.extra.importKey.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
getKeyWithProgress(position, entry, false);
|
||||
}
|
||||
});
|
||||
|
||||
b.extra.showKey.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
long keyId = KeyFormattingUtils.convertKeyIdHexToKeyId(entry.getKeyIdHex());
|
||||
Intent intent = new Intent(mActivity, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(keyId));
|
||||
mActivity.startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
b.extraContainer.setVisibility(keyState.mShowed ? View.VISIBLE : View.GONE);
|
||||
|
||||
b.progress.setVisibility(keyState.mProgress ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mData != null ? mData.size() : 0;
|
||||
}
|
||||
|
||||
private void getKeyWithProgress(int position, ImportKeysListEntry entry, boolean skipSave) {
|
||||
changeProgress(position, true);
|
||||
getKey(entry, skipSave);
|
||||
}
|
||||
|
||||
private void getKey(ImportKeysListEntry entry, boolean skipSave) {
|
||||
ImportKeyringParcel inputParcel = prepareKeyOperation(entry, skipSave);
|
||||
ImportKeysResultListener listener = skipSave ? this : mListener;
|
||||
ImportKeysOperationCallback cb = new ImportKeysOperationCallback(listener, inputParcel);
|
||||
CryptoOperationHelper opHelper = new CryptoOperationHelper<>(1, mActivity, cb, null);
|
||||
opHelper.cryptoOperation();
|
||||
}
|
||||
|
||||
private ImportKeyringParcel prepareKeyOperation(ImportKeysListEntry entry, boolean skipSave) {
|
||||
ArrayList<ParcelableKeyRing> keysList = null;
|
||||
ParcelableHkpKeyserver keyserver = null;
|
||||
|
||||
ParcelableKeyRing keyRing = entry.getParcelableKeyRing();
|
||||
if (keyRing.mBytes != null) {
|
||||
// instead of giving the entries by Intent extra, cache them into a
|
||||
// file to prevent Java Binder problems on heavy imports
|
||||
// read FileImportCache for more info.
|
||||
try {
|
||||
// We parcel this iteratively into a file - anything we can
|
||||
// display here, we should be able to import.
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<>(mActivity, ImportOperation.CACHE_FILE_NAME);
|
||||
cache.writeCache(keyRing);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Problem writing cache file", e);
|
||||
Notify.create(mActivity, "Problem writing cache file!", Notify.Style.ERROR).show();
|
||||
}
|
||||
} else {
|
||||
keysList = new ArrayList<>();
|
||||
keysList.add(keyRing);
|
||||
keyserver = entry.getKeyserver();
|
||||
}
|
||||
|
||||
holder.checkBox.setChecked(entry.isSelected());
|
||||
return new ImportKeyringParcel(keysList, keyserver, skipSave);
|
||||
}
|
||||
|
||||
return convertView;
|
||||
@Override
|
||||
public void handleResult(ImportKeyResult result) {
|
||||
boolean resultStatus = result.success();
|
||||
Log.e(Constants.TAG, "getKey result: " + resultStatus);
|
||||
if (resultStatus) {
|
||||
ArrayList<CanonicalizedKeyRing> canKeyRings = result.mCanonicalizedKeyRings;
|
||||
if (canKeyRings.size() == 1) {
|
||||
CanonicalizedKeyRing keyRing = canKeyRings.get(0);
|
||||
Log.e(Constants.TAG, "Key ID: " + keyRing.getMasterKeyId() +
|
||||
"| isRev: " + keyRing.isRevoked() + "| isExp: " + keyRing.isExpired());
|
||||
|
||||
ImportKeysListEntry entry = mData.get(mCurrent);
|
||||
entry.setUpdated(result.isOkUpdated());
|
||||
|
||||
mergeEntryWithKey(entry, keyRing);
|
||||
|
||||
mKeyStates[mCurrent].mDownloaded = true;
|
||||
changeShowed(mCurrent, true);
|
||||
} else {
|
||||
throw new RuntimeException("getKey retrieved more than one key ("
|
||||
+ canKeyRings.size() + ")");
|
||||
}
|
||||
} else {
|
||||
result.createNotify(mActivity).show();
|
||||
}
|
||||
|
||||
changeProgress(mCurrent, false);
|
||||
}
|
||||
|
||||
private void mergeEntryWithKey(ImportKeysListEntry entry, CanonicalizedKeyRing keyRing) {
|
||||
entry.setRevoked(keyRing.isRevoked());
|
||||
entry.setExpired(keyRing.isExpired());
|
||||
|
||||
Date expectedDate = entry.getDate();
|
||||
Date creationDate = keyRing.getCreationDate();
|
||||
if (expectedDate == null) {
|
||||
entry.setDate(creationDate);
|
||||
} else if (!expectedDate.equals(creationDate)) {
|
||||
throw new AssertionError("Creation date doesn't match the expected one");
|
||||
}
|
||||
entry.setKeyId(keyRing.getMasterKeyId());
|
||||
|
||||
ArrayList<String> realUserIdsPlusKeybase = keyRing.getUnorderedUserIds();
|
||||
realUserIdsPlusKeybase.addAll(entry.getKeybaseUserIds());
|
||||
entry.setUserIds(realUserIdsPlusKeybase);
|
||||
}
|
||||
|
||||
private class KeyState {
|
||||
boolean mAlreadyPresent = false;
|
||||
boolean mVerified = false;
|
||||
|
||||
boolean mProgress = false;
|
||||
boolean mDownloaded = false;
|
||||
boolean mShowed = false;
|
||||
}
|
||||
|
||||
private void changeShowed(int position, boolean showed) {
|
||||
KeyState keyState = mKeyStates[position];
|
||||
keyState.mShowed = showed;
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
|
||||
private void changeProgress(int position, boolean progress) {
|
||||
KeyState keyState = mKeyStates[position];
|
||||
keyState.mProgress = progress;
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.keyimport.CloudSearch;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ImportKeysListCloudLoader
|
||||
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
||||
Context mContext;
|
||||
|
||||
|
||||
Preferences.CloudSearchPrefs mCloudPrefs;
|
||||
String mServerQuery;
|
||||
private ParcelableProxy mParcelableProxy;
|
||||
|
||||
private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<>();
|
||||
private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
|
||||
|
||||
/**
|
||||
* Searches a keyserver as specified in cloudPrefs, using an explicit proxy if passed
|
||||
*
|
||||
* @param serverQuery string to search on servers for. If is a fingerprint,
|
||||
* will enforce fingerprint check
|
||||
* @param cloudPrefs contains keyserver to search on, whether to search on the keyserver,
|
||||
* and whether to search keybase.io
|
||||
* @param parcelableProxy explicit proxy to use. If null, will retrieve from preferences
|
||||
*/
|
||||
public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs,
|
||||
@Nullable ParcelableProxy parcelableProxy) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mServerQuery = serverQuery;
|
||||
mCloudPrefs = cloudPrefs;
|
||||
mParcelableProxy = parcelableProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, null);
|
||||
|
||||
if (mServerQuery == null) {
|
||||
Log.e(Constants.TAG, "mServerQuery is null!");
|
||||
return mEntryListWrapper;
|
||||
}
|
||||
|
||||
if (mServerQuery.startsWith("0x") && mServerQuery.length() == 42) {
|
||||
Log.d(Constants.TAG, "This search is based on a unique fingerprint. Enforce a fingerprint check!");
|
||||
queryServer(true);
|
||||
} else {
|
||||
queryServer(false);
|
||||
}
|
||||
|
||||
return mEntryListWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
// Ensure the loader is stopped
|
||||
onStopLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query keyserver
|
||||
*/
|
||||
private void queryServer(boolean enforceFingerprint) {
|
||||
ParcelableProxy proxy;
|
||||
|
||||
if (mParcelableProxy == null) {
|
||||
// no explicit proxy specified, fetch from preferences
|
||||
if (OrbotHelper.isOrbotInRequiredState(mContext)) {
|
||||
proxy = Preferences.getPreferences(mContext).getParcelableProxy();
|
||||
} else {
|
||||
// user needs to enable/install orbot
|
||||
mEntryList.clear();
|
||||
GetKeyResult pendingResult = new GetKeyResult(null,
|
||||
RequiredInputParcel.createOrbotRequiredOperation(),
|
||||
new CryptoInputParcel());
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, pendingResult);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
proxy = mParcelableProxy;
|
||||
}
|
||||
|
||||
try {
|
||||
ArrayList<ImportKeysListEntry> searchResult = CloudSearch.search(
|
||||
mServerQuery,
|
||||
mCloudPrefs,
|
||||
proxy
|
||||
);
|
||||
|
||||
mEntryList.clear();
|
||||
// add result to data
|
||||
if (enforceFingerprint) {
|
||||
String fingerprint = mServerQuery.substring(2);
|
||||
Log.d(Constants.TAG, "fingerprint: " + fingerprint);
|
||||
// query must return only one result!
|
||||
if (searchResult.size() == 1) {
|
||||
ImportKeysListEntry uniqueEntry = searchResult.get(0);
|
||||
/*
|
||||
* set fingerprint explicitly after query
|
||||
* to enforce a check when the key is imported by KeychainService
|
||||
*/
|
||||
uniqueEntry.setFingerprintHex(fingerprint);
|
||||
uniqueEntry.setSelected(true);
|
||||
mEntryList.add(uniqueEntry);
|
||||
}
|
||||
} else {
|
||||
mEntryList.addAll(searchResult);
|
||||
}
|
||||
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null);
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, getKeyResult);
|
||||
} catch (Keyserver.CloudSearchFailureException e) {
|
||||
// convert exception to result parcel
|
||||
int error = GetKeyResult.RESULT_ERROR;
|
||||
OperationResult.LogType logType = null;
|
||||
if (e instanceof Keyserver.QueryFailedException) {
|
||||
error = GetKeyResult.RESULT_ERROR_QUERY_FAILED;
|
||||
logType = OperationResult.LogType.MSG_GET_QUERY_FAILED;
|
||||
} else if (e instanceof Keyserver.TooManyResponsesException) {
|
||||
error = GetKeyResult.RESULT_ERROR_TOO_MANY_RESPONSES;
|
||||
logType = OperationResult.LogType.MSG_GET_TOO_MANY_RESPONSES;
|
||||
} else if (e instanceof Keyserver.QueryTooShortException) {
|
||||
error = GetKeyResult.RESULT_ERROR_QUERY_TOO_SHORT;
|
||||
logType = OperationResult.LogType.MSG_GET_QUERY_TOO_SHORT;
|
||||
} else if (e instanceof Keyserver.QueryTooShortOrTooManyResponsesException) {
|
||||
error = GetKeyResult.RESULT_ERROR_TOO_SHORT_OR_TOO_MANY_RESPONSES;
|
||||
logType = OperationResult.LogType.MSG_GET_QUERY_TOO_SHORT_OR_TOO_MANY_RESPONSES;
|
||||
}
|
||||
OperationResult.OperationLog log = new OperationResult.OperationLog();
|
||||
log.add(logType, 0);
|
||||
GetKeyResult getKeyResult = new GetKeyResult(error, log);
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, getKeyResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.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.annotation.NonNull;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.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;
|
||||
|
||||
public class ImportKeysListLoader
|
||||
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
||||
|
||||
final Context mContext;
|
||||
final BytesLoaderState mLoaderState;
|
||||
|
||||
ArrayList<ImportKeysListEntry> mData = new ArrayList<>();
|
||||
LongSparseArray<ParcelableKeyRing> mParcelableRings = new LongSparseArray<>();
|
||||
AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
|
||||
|
||||
public ImportKeysListLoader(Context context, BytesLoaderState inputData) {
|
||||
super(context);
|
||||
this.mContext = context;
|
||||
this.mLoaderState = inputData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
|
||||
// This has already been loaded! nvm any further, just return
|
||||
if (mEntryListWrapper != null) {
|
||||
return mEntryListWrapper;
|
||||
}
|
||||
|
||||
{
|
||||
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null);
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
|
||||
}
|
||||
|
||||
if (mLoaderState == null) {
|
||||
Log.e(Constants.TAG, "Input data is null!");
|
||||
return mEntryListWrapper;
|
||||
}
|
||||
|
||||
try {
|
||||
InputData inputData = getInputData(getContext(), mLoaderState);
|
||||
generateListOfKeyrings(inputData);
|
||||
} catch (FileNotFoundException e) {
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_GET_FILE_NOT_FOUND, 0);
|
||||
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_FILE_NOT_FOUND, log);
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
|
||||
}
|
||||
|
||||
return mEntryListWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
// Ensure the loader is stopped
|
||||
onStopLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
super.forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
super.cancelLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
public LongSparseArray<ParcelableKeyRing> getParcelableRings() {
|
||||
return mParcelableRings;
|
||||
}
|
||||
|
||||
/** Reads all PGPKeyRing objects from the bytes of an InputData object. */
|
||||
private void generateListOfKeyrings(InputData inputData) {
|
||||
PositionAwareInputStream progressIn = new PositionAwareInputStream(
|
||||
inputData.getInputStream());
|
||||
|
||||
// need to have access to the bufferedInput, so we can reuse it for the possible
|
||||
// PGPObject chunks after the first one, e.g. files with several consecutive ASCII
|
||||
// armor blocks
|
||||
BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
|
||||
try {
|
||||
// parse all keyrings
|
||||
IteratorWithIOThrow<UncachedKeyRing> it = UncachedKeyRing.fromStream(bufferedInput);
|
||||
while (it.hasNext()) {
|
||||
UncachedKeyRing ring = it.next();
|
||||
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), ring);
|
||||
mData.add(item);
|
||||
mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(ring.getEncoded()));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e);
|
||||
OperationResult.OperationLog log = new OperationResult.OperationLog();
|
||||
log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0);
|
||||
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log);
|
||||
mData.clear();
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static InputData getInputData(Context context, BytesLoaderState loaderState) throws FileNotFoundException {
|
||||
InputData inputData;
|
||||
if (loaderState.mKeyBytes != null) {
|
||||
inputData = new InputData(new ByteArrayInputStream(loaderState.mKeyBytes), loaderState.mKeyBytes.length);
|
||||
} else if (loaderState.mDataUri != null) {
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(loaderState.mDataUri);
|
||||
long length = FileHelper.getFileSize(context, loaderState.mDataUri, -1);
|
||||
|
||||
inputData = new InputData(inputStream, length);
|
||||
} else {
|
||||
throw new AssertionError("Loader state must contain bytes or a data URI. This is a bug!");
|
||||
}
|
||||
|
||||
return inputData;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.bindings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.databinding.BindingAdapter;
|
||||
import android.graphics.Color;
|
||||
import android.text.format.DateFormat;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.Highlighter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class ImportKeysBindings {
|
||||
|
||||
@BindingAdapter({"keyUserId", "keySecret", "keyRevokedOrExpired", "query"})
|
||||
public static void setUserId(TextView textView, CharSequence userId, boolean secret,
|
||||
boolean revokedOrExpired, String query) {
|
||||
|
||||
Context context = textView.getContext();
|
||||
Resources resources = context.getResources();
|
||||
|
||||
if (userId == null) {
|
||||
userId = resources.getString(R.string.user_id_no_name);
|
||||
}
|
||||
|
||||
if (secret) {
|
||||
userId = resources.getString(R.string.secret_key) + " " + userId;
|
||||
} else {
|
||||
Highlighter highlighter = ImportKeysBindingsUtils.getHighlighter(context, query);
|
||||
userId = highlighter.highlight(userId);
|
||||
}
|
||||
textView.setText(userId);
|
||||
textView.setTextColor(ImportKeysBindingsUtils.getColor(context, revokedOrExpired));
|
||||
|
||||
if (secret) {
|
||||
textView.setTextColor(Color.RED);
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter({"keyUserEmail", "keyRevokedOrExpired", "query"})
|
||||
public static void setUserEmail(TextView textView, CharSequence userEmail,
|
||||
boolean revokedOrExpired, String query) {
|
||||
|
||||
Context context = textView.getContext();
|
||||
|
||||
if (userEmail == null) {
|
||||
userEmail = "";
|
||||
}
|
||||
|
||||
Highlighter highlighter = ImportKeysBindingsUtils.getHighlighter(context, query);
|
||||
textView.setText(highlighter.highlight(userEmail));
|
||||
textView.setTextColor(ImportKeysBindingsUtils.getColor(context, revokedOrExpired));
|
||||
}
|
||||
|
||||
@BindingAdapter({"keyCreation", "keyRevokedOrExpired"})
|
||||
public static void setCreation(TextView textView, Date creationDate, boolean revokedOrExpired) {
|
||||
Context context = textView.getContext();
|
||||
|
||||
String text = "";
|
||||
if (creationDate != null) {
|
||||
text = DateFormat.getDateFormat(context).format(creationDate);
|
||||
}
|
||||
|
||||
textView.setText(text);
|
||||
textView.setTextColor(ImportKeysBindingsUtils.getColor(context, revokedOrExpired));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.bindings;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Highlighter;
|
||||
import org.sufficientlysecure.keychain.util.LruCache;
|
||||
|
||||
public class ImportKeysBindingsUtils {
|
||||
|
||||
private static LruCache<String, Highlighter> highlighterCache = new LruCache<>(1);
|
||||
|
||||
public static Highlighter getHighlighter(Context context, String query) {
|
||||
Highlighter highlighter = highlighterCache.get(query);
|
||||
if (highlighter == null) {
|
||||
highlighter = new Highlighter(context, query);
|
||||
highlighterCache.put(query, highlighter);
|
||||
}
|
||||
|
||||
return highlighter;
|
||||
}
|
||||
|
||||
public static int getColor(Context context, boolean revokedOrExpired) {
|
||||
if (revokedOrExpired) {
|
||||
return context.getResources().getColor(R.color.key_flag_gray);
|
||||
} else {
|
||||
return FormattingUtils.getColorFromAttr(context, R.attr.colorText);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.bindings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.databinding.BindingAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Highlighter;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
public class ImportKeysExtraBindings {
|
||||
|
||||
@BindingAdapter({"keyRevoked", "keyExpired"})
|
||||
public static void setStatus(ImageView imageView, boolean revoked, boolean expired) {
|
||||
Context context = imageView.getContext();
|
||||
|
||||
if (revoked) {
|
||||
KeyFormattingUtils.setStatusImage(context, imageView, null,
|
||||
KeyFormattingUtils.State.REVOKED, R.color.key_flag_gray);
|
||||
} else if (expired) {
|
||||
KeyFormattingUtils.setStatusImage(context, imageView, null,
|
||||
KeyFormattingUtils.State.EXPIRED, R.color.key_flag_gray);
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter({"keyId"})
|
||||
public static void setKeyId(TextView textView, String keyId) {
|
||||
Context context = textView.getContext();
|
||||
String text;
|
||||
if (keyId != null) {
|
||||
text = KeyFormattingUtils.beautifyKeyId(keyId);
|
||||
} else {
|
||||
Resources resources = context.getResources();
|
||||
text = resources.getString(R.string.unknown);
|
||||
}
|
||||
textView.setText(text);
|
||||
}
|
||||
|
||||
@BindingAdapter({"keyUserIds", "query"})
|
||||
public static void setUserIds(LinearLayout linearLayout, ArrayList userIds, String query) {
|
||||
|
||||
linearLayout.removeAllViews();
|
||||
|
||||
if (userIds != null) {
|
||||
Context context = linearLayout.getContext();
|
||||
Highlighter highlighter = ImportKeysBindingsUtils.getHighlighter(context, query);
|
||||
|
||||
ArrayList<Map.Entry<String, HashSet<String>>> uIds = userIds;
|
||||
for (Map.Entry<String, HashSet<String>> pair : uIds) {
|
||||
String name = pair.getKey();
|
||||
HashSet<String> emails = pair.getValue();
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
|
||||
TextView uidView = (TextView) inflater.inflate(
|
||||
R.layout.import_keys_list_entry_user_id, null);
|
||||
uidView.setText(highlighter.highlight(name));
|
||||
uidView.setPadding(0, 0, FormattingUtils.dpToPx(context, 8), 0);
|
||||
uidView.setTextColor(FormattingUtils.getColorFromAttr(context, R.attr.colorText));
|
||||
linearLayout.addView(uidView);
|
||||
|
||||
for (String email : emails) {
|
||||
TextView emailView = (TextView) inflater.inflate(
|
||||
R.layout.import_keys_list_entry_user_id, null);
|
||||
emailView.setPadding(
|
||||
FormattingUtils.dpToPx(context, 16), 0,
|
||||
FormattingUtils.dpToPx(context, 8), 0);
|
||||
emailView.setText(highlighter.highlight(email));
|
||||
emailView.setTextColor(FormattingUtils.getColorFromAttr(context, R.attr.colorText));
|
||||
linearLayout.addView(emailView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public class Highlighter {
|
||||
mQuery = query;
|
||||
}
|
||||
|
||||
public Spannable highlight(String text) {
|
||||
public Spannable highlight(CharSequence text) {
|
||||
Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
|
||||
|
||||
if (mQuery == null) {
|
||||
|
||||
@@ -30,13 +30,13 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
@@ -99,7 +100,7 @@ public class KeyFormattingUtils {
|
||||
|
||||
default: {
|
||||
if (context != null) {
|
||||
algorithmStr = context.getResources().getString(R.string.unknown_algorithm);
|
||||
algorithmStr = context.getResources().getString(R.string.unknown);
|
||||
} else {
|
||||
algorithmStr = "unknown";
|
||||
}
|
||||
@@ -154,7 +155,7 @@ public class KeyFormattingUtils {
|
||||
|
||||
default: {
|
||||
if (context != null) {
|
||||
algorithmStr = context.getResources().getString(R.string.unknown_algorithm);
|
||||
algorithmStr = context.getResources().getString(R.string.unknown);
|
||||
} else {
|
||||
algorithmStr = "unknown";
|
||||
}
|
||||
@@ -189,7 +190,7 @@ public class KeyFormattingUtils {
|
||||
*/
|
||||
}
|
||||
if (context != null) {
|
||||
return context.getResources().getString(R.string.unknown_algorithm);
|
||||
return context.getResources().getString(R.string.unknown);
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
@@ -208,7 +209,7 @@ public class KeyFormattingUtils {
|
||||
return name;
|
||||
}
|
||||
if (context != null) {
|
||||
return context.getResources().getString(R.string.unknown_algorithm);
|
||||
return context.getResources().getString(R.string.unknown);
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
@@ -272,6 +273,10 @@ public class KeyFormattingUtils {
|
||||
return hexString;
|
||||
}
|
||||
|
||||
public static long convertKeyIdHexToKeyId(String hex) {
|
||||
return new BigInteger(hex.substring(2), 16).longValue();
|
||||
}
|
||||
|
||||
public static long convertFingerprintToKeyId(byte[] fingerprint) {
|
||||
return ByteBuffer.wrap(fingerprint, 12, 8).getLong();
|
||||
}
|
||||
@@ -312,12 +317,12 @@ public class KeyFormattingUtils {
|
||||
return beautifyKeyId(convertKeyIdToHex(keyId));
|
||||
}
|
||||
|
||||
public static String beautifyKeyIdWithPrefix(Context context, String idHex) {
|
||||
public static String beautifyKeyIdWithPrefix(String idHex) {
|
||||
return "Key ID: " + beautifyKeyId(idHex);
|
||||
}
|
||||
|
||||
public static String beautifyKeyIdWithPrefix(Context context, long keyId) {
|
||||
return beautifyKeyIdWithPrefix(context, convertKeyIdToHex(keyId));
|
||||
public static String beautifyKeyIdWithPrefix(long keyId) {
|
||||
return beautifyKeyIdWithPrefix(convertKeyIdToHex(keyId));
|
||||
}
|
||||
|
||||
public static SpannableStringBuilder colorizeFingerprint(String fingerprint) {
|
||||
@@ -434,14 +439,19 @@ public class KeyFormattingUtils {
|
||||
|
||||
public interface StatusHolder {
|
||||
ImageView getEncryptionStatusIcon();
|
||||
|
||||
TextView getEncryptionStatusText();
|
||||
|
||||
ImageView getSignatureStatusIcon();
|
||||
|
||||
TextView getSignatureStatusText();
|
||||
|
||||
View getSignatureLayout();
|
||||
|
||||
TextView getSignatureUserName();
|
||||
|
||||
TextView getSignatureUserEmail();
|
||||
|
||||
ViewAnimator getSignatureAction();
|
||||
|
||||
boolean hasEncrypt();
|
||||
@@ -450,7 +460,7 @@ public class KeyFormattingUtils {
|
||||
|
||||
@SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated
|
||||
public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result,
|
||||
boolean processingkeyLookup) {
|
||||
boolean processingkeyLookup) {
|
||||
|
||||
if (holder.hasEncrypt()) {
|
||||
OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult();
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.sufficientlysecure.keychain.ui.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
|
||||
public class PermissionsUtil {
|
||||
|
||||
private static final int PERMISSION_READ_EXTERNAL_STORAGE = 1;
|
||||
|
||||
/**
|
||||
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
|
||||
* <p/>
|
||||
* This method returns true on Android < 6, or if permission is already granted. It
|
||||
* requests the permission and returns false otherwise.
|
||||
* <p/>
|
||||
* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
|
||||
*/
|
||||
@SuppressLint("NewApi") // Api level is checked in checkReadPermission
|
||||
public static boolean checkAndRequestReadPermission(Activity activity, Uri uri) {
|
||||
boolean result = checkReadPermission(activity, uri);
|
||||
if (!result) {
|
||||
activity.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
PERMISSION_READ_EXTERNAL_STORAGE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean checkAndRequestReadPermission(Fragment fragment, Uri uri) {
|
||||
boolean result = checkReadPermission(fragment.getContext(), uri);
|
||||
if (!result) {
|
||||
fragment.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
PERMISSION_READ_EXTERNAL_STORAGE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean checkReadPermission(Context context, Uri uri) {
|
||||
if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
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.M) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean checkReadPermissionResult(Context context,
|
||||
int requestCode,
|
||||
int[] grantResults) {
|
||||
|
||||
if (requestCode != PERMISSION_READ_EXTERNAL_STORAGE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean permissionWasGranted = grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
if (permissionWasGranted) {
|
||||
return true;
|
||||
} else {
|
||||
Toast.makeText(context, R.string.error_denied_storage_permission, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user